精华内容
下载资源
问答
  • unity c#移动脚本 Unity:使用C#脚本移动游戏对象 (Unity: Making the Game Object move using C# Script) So now that you know what Rigidbodies are and how to attach them, let's go ahead and put them to ...

    unity c#移动脚本

    So now that you know what Rigidbodies are and how to attach them, let's go ahead and put them to actual use. One of the best ways, that we can think of, to explain the working of Rigidbodies is by making the player move using the arrow keys.

    因此,既然您知道什么是刚体以及如何固定它们,就让我们继续进行实际使用。 我们可以想到的最好的方法之一是,通过使用箭头键使玩家移动来解释刚体的工作。

    To do so, open up the Movement script that we made earlier. In case you deleted it or if you're working on a clean, new project, simply create a new script and name it Movement. Open it up in your IDE, and you'll see the familiar Start() and Update() methods that we have explained earlier.

    为此,请打开我们之前制作的“ 运动”脚本。 如果您删除了它,或者正在处理一个干净的新项目,只需创建一个新脚本并将其命名为Movement即可 。 在您的IDE中打开它,您将看到我们之前介绍的熟悉的Start()Update()方法。

    Now, let's have a look at each line of code in detail and try to understand what's going on here. Don't worry if you can't grasp all of it at once, you'll learn as we progress forward.

    现在,让我们详细查看每一行代码,并尝试了解这里发生的情况。 如果您无法一次掌握所有内容,请放心,我们会不断进步,您将学习。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Movement : MonoBehaviour {
    
        public RigidBody2D starBody,
        public float speed;
        
        void start()
        {
            // use this function to initialize
            // anything
        }
        
        // Update is called once per frame
        void update()
        {
            starBody.velocity() = new Vector2(Input.GetAxisRaw("Horizontal")*speed, Input.GetAxisRaw("Vertical")*2);
        }
    
    }

    Line → public Rigidbody2D starBody: Simply declares an instance of the Rigidbody2D, named starBody. Make note of the public access modifier, We will be coming to that soon.

    线→ 公共Rigidbody2D starBody:简单声明的实例Rigidbody2D ,命名starBody 。 记下public访问修饰符,我们很快就会谈到。

    Line → public float speed: Simple declaration of a floating point value named speed. Once again, make a note of the public access modifier used here.

    Line→ public float speed :简单声明一个名为speed的浮点值。 再次记下此处使用的public访问修饰符。

    Line → void Start(): Note that this method is completely empty. We haven't even assigned any value to speed and starBody. You'd expect to get NullReferenceExceptions or UnassignedReferenceExceptions thrown everywhere and general chaos, if we didn't give those variables something to work with, right? And yet, this method remains mysteriously empty.

    Line→ void Start() :请注意,此方法完全为空。 我们甚至没有给speedstarBody分配任何值。 如果我们不给那些变量提供一些有用的东西,那么您会期望到处抛出NullReferenceExceptionsUnassignedReferenceExceptions以及普遍混乱。 然而,这种方法仍然神秘地空着。

    Line → ([Inside Update method] starBody.velocity = new Vector2(Input.GetAxisRaw ...): This is where the main action is going on around your game character. Let's dig into this one line, shall we? The first few words are starBody.velocity = means, we are assigning a velocity to the Rigidbody currently referenced by starBody. While new Vector2(...) means that a Rigidbody's velocity is defined as a Vector2, where the input parameters are the velocity along the X-axis and Y-axis respectively.

    行→ ([[内部更新方法] starBody.velocity = new Vector2(Input.GetAxisRaw ...))这是围绕您的游戏角色进行的主要动作 。让我们深入探讨这一行,对吧?单词是starBody.velocity =意味着,我们正在为starBody当前引用的Rigidbody分配速度。而new Vector2(...)意味着将Rigidbody的速度定义为Vector2 ,其中输入参数是沿X的速度轴和Y轴。

    Now, for the actual magic in this code: Input.GetAxisRaw("Horizontal") and Input.GetAxisRaw("Vertical") What are these things? Well, the Input class is provided with MonoDevelop for handling the input. It has definitions for what you'd call Input Axes defined as Horizontal and Vertical.

    现在,对于此代码中的实际魔术: Input.GetAxisRaw("Horizontal")Input.GetAxisRaw("Vertical")这些是什么? 好吧,MonoDevelop提供了Input类来处理输入。 它具有定义为“ 水平”和“ 垂直”的输入轴的定义。

    What's basically going on is that the game is detecting input corresponding to the Horizontal and Vertical input axes, and setting them to -1, 0 or 1 depending on the button you press. So, when you press the Right arrow key, it means you're setting the value of the Horizontal axis to 1. As soon as you let go of that arrow key, the value on that axis jumps back to 0. Now, in game language it simply means,

    什么是主要事情是,游戏被检测对应于水平垂直轴输入输入,并且将它们设置为-101取决于你按下按钮。 因此,当您按向右箭头键时 ,意味着您将水平轴的值设置为1 。 放开该箭头键后,该轴上的值就会跳回到0。现在,在游戏语言中,这仅意味着,

    move the character by 1 unit horizontally per frame when the Right arrow key is pressed. 按下向右箭头键时,每帧将字符水平移动1个单位。

    Keeping it at 1 means the object will travel rather slowly. Hence, we multiply that value with the speed float, thus saying,

    保持为1表示对象将缓慢移动。 因此,我们将该值乘以速度浮点,这样说:

    move the character by 1 times the speed, and since we know that anything times 1 is the same value, we're effectively saying move the character horizontally by this speed when the axis buttons are pressed. 将字符以1倍的速度移动,并且由于我们知道1的任何值都是相同的值,因此我们实际上是在按轴按钮时以该速度水平移动字符

    NOTE: By default, the axis buttons on a standard keyboard are the Up and Down arrows, as well as W and S for the Vertical axis, and Left and Right arrows, as well as A and D for the Horizontal axis.

    注意:默认情况下,标准键盘上的轴按钮是向上向下箭头,垂直轴是WS ,水平轴是箭头和箭头,以及AD。

    Moving on, what's going in that one line is basically the following sentence:

    继续,那一行的内容基本上是下面的句子:

    Give this rigidbody a velocity in the game world, where the horizontal velocity is (Horizontal Axis * speed) and the vertical velocity is (Vertical Axis * speed) 在游戏世界中给此刚体一个速度,其中水平速度为(水平轴*速度),垂直速度为(垂直轴*速度)

    Now, to finish, save this script and head on back to Unity. To attach the script to an object, simply drag and drop it over that object, or go to Add Component → Scripts.

    现在,要完成操作,请保存此脚本并回到Unity。 要将脚本附加到对象,只需将其拖放到该对象上,或转到添加组件→脚本

    Making Player Move

    Now, remember how we asked you to make note of the public access modifiers when we were writing our scripts? Have a look at the script's component.

    现在,还记得我们在编写脚本时是如何要求您记录public访问修饰符的吗? 看一下脚本的组件。

    Making Player Move

    You'll notice that along with the script, Unity automatically generated 2 fields for inputting both a Rigidbody2D and a speed float. That's the magic of Unity. Values and class instances declared publically can be seen in the Editor where you can make adjustments without having to constantly have to look in your code all the time.

    您会注意到,与脚本一起,Unity自动生成了两个用于输入Rigidbody2D和速度浮动的字段。 那就是团结的魔力。 公开声明的Valuesclass实例可以在编辑器中看到,您可以在其中进行调整,而不必始终无休止地查看代码。

    To set the Rigidbody2D for the first field, click on the small circle with a dot in it right next to the field. This will open up a list of available Rigidbodies in the scene which you can use. For the time being, since we only have one Rigidbody in the entire scene, click on the one that appears in the list.

    要为第一个字段设置Rigidbody2D,请在该字段旁边的小圆上单击一个圆点。 这将打开您可以使用的场景中的可用刚体列表。 目前,由于整个场景中只有一个刚体,因此请单击列表中出现的那个。

    Making Player Move

    What did you just accomplished by doing this? Remember how we didn't assign anything to the starBody variable back when we were writing the script? This is where we assign it. We can do assigning of components and values with a simple UI instead of having to write it in the code.

    通过这样做您刚刚完成了什么? 还记得我们在编写脚本时如何不给starBody变量分配任何东西吗? 这是我们分配它的地方。 我们可以使用简单的UI分配组件和值,而不必在代码中编写。

    Next, set a speed value in the next field. Don't set it too high, or your character might fly off before you can see where he even went. This is a problem because our Camera is stationary, so positioning it back into view will become a task much easier said, than done. We would suggest setting the speed to around 5.

    接下来,在下一个字段中设置速度值。 请勿将其设置得太高,否则您的角色可能会飞起来,然后才能看到他的去向。 这是一个问题,因为我们的相机是固定的,因此将其重新定位到视野中将比说起来容易得多。 我们建议将速度设置为5左右。

    This is what your script component should look like once you finish:

    完成后,这就是脚本组件的外观:

    Making Player Move

    Now click on play button and let's see what happens. If everything went right, you'll notice that you can now move your character with the arrow keys. Awesome!

    现在单击播放按钮,让我们看看会发生什么。 如果一切正常,您会注意到您现在可以使用箭头键移动角色。 太棒了!

    翻译自: https://www.studytonight.com/game-development-in-2D/making-player-move

    unity c#移动脚本

    展开全文
  • 题:你要改变对象在屏幕的显示顺序,向前或向后移动他们解决办法:使用DisplayObectContainer类中的setChildIndex( ) 方法改变选定项的位置getChildIndex( ) 与getChildAt( )方法用于获取项目在显示对象列表中的位置...
    题:
    
    你要改变对象在屏幕的显示顺序,向前或向后移动他们
    解决办法:
    使用DisplayObectContainer类中的setChildIndex( ) 方法改变选定项的位置

    getChildIndex( ) 与getChildAt( )方法用于获取项目在显示对象列表中的位置
    讨论:


    setChildIndex( )用于重置容器对象中子对象的显示顺序
    它有两个参数,一个是被移动的子对象,一个被移动子对象新位置的索引值
    注意这个所以值必须是有效的,不能为负数或者大于numchildren
    下面的例子创建了3个不同颜色的圈,蓝色在最上边,setChildIndex( ) 方法被用于将蓝色圈置于其它连个圈的下边
    即改变它在容器中索引值由2变为0,同时其它子对象的位置也发生对应的改变,红色圈的索引值变为1,绿色圈的索引值变为2
    package {
      import flash.display.*;
      public class SetChildIndexExample extends Sprite {
        public function SetChildIndexExample(  ) {
          //在不同位置,创建三个不同颜色的圈
          var red:Shape = createCircle( 0xFF0000, 10 );
          red.x = 10;
          red.y = 20;
          var green:Shape = createCircle( 0x00FF00, 10 );
          green.x = 15;
          green.y = 25;
          var blue:Shape = createCircle( 0x0000FF, 10 );
          blue.x = 20;
          blue.y = 20;
          
          // 将三个圈加入显示对象列表,红色圈的索引值是0, 绿色为 1, 蓝色为 2
          addChild( red );
          addChild( green );
          addChild( blue );
          //移动蓝色圈,让其在另外两个圈的下边
          setChildIndex( blue, 0 );
        }
       
        //创建圈的函数
        public function createCircle( color:uint, radius:Number ):Shape {
          var shape:Shape = new Shape(  );
          shape.graphics.beginFill( color );
          shape.graphics.drawCircle( 0, 0, radius );
          shape.graphics.endFill(  );
          return shape;
        }
      }
    }


    另外就是假设如果你不知道红色圈的位置,但是也想让蓝色圈在红色圈的下边
    你可以用


    getChildIndex( ) 或getChildAt( )配合setChildIndex()来使用

    例如:
    setChildIndex(blue,getChildIndex(red)); 
     
    展开全文
  • 中文标题【机器人移动】 这个题目是 Kayak 发布的代码挑战题目。 我认为题目本身描述的不是十分清楚,方法需要返回结果,但是结果没有说明是机器人最后的坐标位置,还是最后的坐标位置距离原点的距离。同时,...

    中文标题【机器人移动】

    这个题目是 Kayak 发布的代码挑战题目。

    我认为题目本身描述的不是十分清楚,方法需要返回结果,但是结果没有说明是机器人最后的坐标位置,还是最后的坐标位置距离原点的距离。同时,机器人的初始化方向等都没有十分明确的定义。

    根据测试数据,机器人应该是从下往上(初始化的方向)。因为如果初始化的方向不确定的话,最后的坐标值可能会不一致。

    我这里假设程序应该返回的是机器人的最后坐标位置,并且初始化的位置为 (0,0),方向为从下往上。

    英文描述

    A robot lands on Mars, which happens to be a cartesian grid; assuming that we hand the robot these instructions, such as LFFFRFFFRRFFF, where "L" is a "turn 90 degrees left", "R" is a "turn 90 degrees right", and "F" is "go forward one space。

    please write control code for the robot such that it ends up at the appropriate-and-correct destination, and include unit tests.

    Here is an example output with command "FF":

    [0, 2]

    中文描述

    这里我不按照原文一字一字的翻译,但是尽量按照题目的要求把题目解释清楚。

    这里题目的要求是,假设一个机器人降落到火星上了,我们现在需要给机器人发布指令。指令包括有 L,R,F 3 个。

    L 表示的意思是机器人向左转 90 度,R 的意思表示的是机器人向右转 90 度,F 表示的是机器人向前移动一个坐标单位。

    题目的表达并是是十分明确也清晰,比如说 LFFFRFFFRRFFF 应该返回是什么没有说清楚。假设 指令 FF ,返回的结果是 [0, 2],我默认的是程序需要返回机器人最后的坐标位置,0 表示的是 X 坐标,2 表示的是 Y 坐标。

    思路和点评

    这个问题的思路,首先你需要明白几个点。如果需要进行坐标计算的话,请注意 L 和 F 是不会改变当前机器人的坐标位置的。

    只有 F 的操作才会改变机器人的位置。考虑设置一个坐标系,那么这里需要存储 2 个信息,第一个信息为 F 移动时候机器人的位置,另外就是 L 和 F 对机器人方向的控制了。

    所以你需要在程序中初始化一个二维数组,这个数组用于存储 F 操作时候的坐标变化。

    同时你还需要存储一个 dir 变量,通常这个变量为每一次 L 和 R 操作的时候方向的变化。

    F 存储的路径数组为:int[][] move = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };

    通过这个数组,你就明白为什么我在这个 WIKI 页面前面说的,初始化方向很重要,请参考下面的图(因为不太好用计算机画图,我们用手画了一个图)。

    在这个图中比较明确的说明了,我们定义的初始化方向为从下往上,Dir 等于 0 。在 Dir 等于 0 的时候坐标数组为 int[][] move = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } }; 按照顺时针的方向。

    在图中,Dir 有 4 个反向,按照顺时针方向,分别为上,右,下,左,那么方向对应的值就分别为 0,1,2,3 。

    当方向为 L 的时候,需要将方向值减 1 ,当方向为 R 的时候,需要将值 +1。

    这里有个问题为循环,比如说,方向值为 RR,,dir 的值应该为 2。

    如果方向为 RRRRRR,那么值应该也为 2。所以在算法中我们使用了  dir = (dir + 4) % 4;, 对方向进行取 余数。你可以看到 当你旋转 RRRRRR 后,dir 的值还是为 2。

    针一次转向 + 移动的操作,不管你转向多少次,调整的方方向无非就是调整 X 或者 Y 的坐标系,在下一次移动的时候应该是 + 还是 -

    所以到这里方法就相对简单了。一次移动的时候,都会改变 X 和 Y 坐标的值,前提是你是希望怎么加减而已。

    源代码

    源代码和有关代码的更新请访问 GitHub:

    https://github.com/cwiki-us/codebank-algorithm/blob/master/src/main/java/com/ossez/codebank/interview/KayakRobotMovement.java

    测试类请参考:

    https://github.com/cwiki-us/codebank-algorithm/blob/master/src/test/java/com/ossez/codebank/interview/tests/KayakTest.java

    代码思路请参考:

    package com.ossez.codebank.interview;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * https://www.cwiki.us/display/ITCLASSIFICATION/Robot+Movement
     * 
     * @author YuCheng
     *
     */
    public class KayakRobotMovement {
    
      private final static Logger logger = LoggerFactory.getLogger(KayakRobotMovement.class);
    
      /**
       * Get coordinates for Robot Movement
       * 
       * @param data
       * @return
       */
      public static String getCoordinates(String data) {
        logger.debug("BEGIN");
    
        String retStr = "";
    
        int x = 0;
        int y = 0;
        int[][] move = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
        int dir = 0;
    
        for (char ch : data.toCharArray()) {
          if (ch == 'F') {
            x += move[dir][0];
            y += move[dir][1];
          } else if (ch == 'L') {
            dir--;
          } else if (ch == 'R') {
            dir++;
          }
          dir = (dir + 4) % 4;
        }
        retStr = x + "," + y;
    
        return retStr;
      }
    }
    

     

     

    测试结果

    上面程序的测试结果如下:

    2018/12/25 15:08:50 DEBUG [com.ossez.codebank.interview.tests.KayakTest] - LFFF - [0,2]
    2018/12/25 15:08:50 DEBUG [com.ossez.codebank.interview.tests.KayakTest] - LFFFRFFFRRFFF - [-3,0]

    https://www.cwiki.us/display/ITCLASSIFICATION/Robot+Movement

    展开全文
  • 卷积神经网络入门详解

    万次阅读 多人点赞 2017-11-08 15:52:14
      本文主要内容为 CS231n 课程的学习笔记,主要参考 学习视频 和对应的 课程笔记翻译 ,感谢各位前辈对于深度学习的辛苦付出。在这里我主要记录下自己觉得重要的内容以及一些相关的想法,希望能与大家多多交流~ ...

      本文主要内容为 CS231n 课程的学习笔记,主要参考 学习视频 和对应的 课程笔记翻译 ,感谢各位前辈对于深度学习的辛苦付出。在这里我主要记录下自己觉得重要的内容以及一些相关的想法,希望能与大家多多交流~

    0. 回顾之前所写过的博客

      在之前的博客《十四、卷积神经网络(1):介绍卷积神经网络》《十五、卷积神经网络(2):卷积神经网络的结构》中只是介绍性的给出了一些关于卷积神经网络的知识。这主要因为《Neural Networks and Deep Learning》这本书中的大部分章节在介绍神经网络,而仅在最后一个部分介绍了卷积神经网络,只介绍了卷积神经网络中的相关概念,如“感受野”、“卷积层”、“池化层”、“权值共享”等等,而且也只介绍了一种较为简单的卷积神经网络结构。而且在这上述两节内容中的图像也是单通道的图像,并没有考虑多通道图像。

      作为补充和完善,在本文中将对卷积神经网络中的多个问题具体展开讲解。

    1. 卷积神经网络结构概述

      如果用全连接神经网络处理大尺寸图像具有三个明显的缺点:

      (1)将图像展开为向量会丢失空间信息;

      (2)参数过多效率低下,训练困难;

      (3)大量的参数也很快会导致网络过拟合。

    而使用卷积神经网络可以很好地解决上面的三个问题。

      与全连接神经网络不同,卷积神经网络的各层中的神经元是3维排列的:宽度、高度和深度。其中的宽度和高度是很好理解的,因为本身卷积就是一个二维模板,但是在卷积神经网络中的深度指的是激活数据体的第三个维度,而不是整个网络的深度,整个网络的深度指的是网络的层数。

      举个例子来理解什么是宽度,高度和深度,假如使用 CIFAR-10 中的图像是作为卷积神经网络的输入,该输入数据体的维度是32x32x3(宽度,高度和深度)。**卷积神经网络中,层中的神经元将只与前一层中的一小块区域连接,而不是采取全连接方式。**对于用来分类CIFAR-10中的图像的卷积网络,其最后的输出层的维度是1x1x10,因为在卷积神经网络结构的最后部分将会把全尺寸的图像压缩为包含分类评分的一个向量,向量是在深度方向排列的。下面是例子:

    图 1. 全连接神经网络与卷积神经网络的对比

    图1中左侧是一个3层的神经网络;右侧是一个卷积神经网络,将它的神经元在成3个维度(宽、高和深度)进行排列。卷积神经网络的每一层都将3D的输入数据变化为神经元3D的激活数据并输出。在图1的右侧,红色的输入层代表输入图像,所以它的宽度和高度就是图像的宽度和高度,它的深度是3(代表了红、绿、蓝3种颜色通道),与红色相邻的蓝色部分是经过卷积和池化之后的激活值(也可以看做是神经元) ,后面是接着的卷积池化层。

    2. 构建卷积神经网络的各种层

      卷积神经网络主要由这几类层构成:输入层、卷积层,ReLU层、池化(Pooling)层和全连接层(全连接层和常规神经网络中的一样)。通过将这些层叠加起来,就可以构建一个完整的卷积神经网络。在实际应用中往往将卷积层与ReLU层共同称之为卷积层,所以卷积层经过卷积操作也是要经过激活函数的。具体说来,卷积层和全连接层(CONV/FC)对输入执行变换操作的时候,不仅会用到激活函数,还会用到很多参数,即神经元的权值w和偏差b;而ReLU层和池化层则是进行一个固定不变的函数操作。卷积层和全连接层中的参数会随着梯度下降被训练,这样卷积神经网络计算出的分类评分就能和训练集中的每个图像的标签吻合了。

    2.1 卷积层

      卷积层是构建卷积神经网络的核心层,它产生了网络中大部分的计算量。注意是计算量而不是参数量

    2.1.1 卷积层作用

      1. 滤波器的作用或者说是卷积的作用。卷积层的参数是有一些可学习的滤波器集合构成的。每个滤波器在空间上(宽度和高度)都比较小,但是深度和输入数据一致(这一点很重要,后面会具体介绍)。直观地来说,网络会让滤波器学习到当它看到某些类型的视觉特征时就激活,具体的视觉特征可能是某些方位上的边界,或者在第一层上某些颜色的斑点,甚至可以是网络更高层上的蜂巢状或者车轮状图案。

      2. 可以被看做是神经元的一个输出。神经元只观察输入数据中的一小部分,并且和空间上左右两边的所有神经元共享参数(因为这些数字都是使用同一个滤波器得到的结果)。

      3. 降低参数的数量。这个由于卷积具有“权值共享”这样的特性,可以降低参数数量,达到降低计算开销,防止由于参数过多而造成过拟合。

    2.1.2 感受野(重点理解)

      在处理图像这样的高维度输入时,让每个神经元都与前一层中的所有神经元进行全连接是不现实的。相反,我们让每个神经元只与输入数据的一个局部区域连接。该连接的空间大小叫做神经元的感受野(receptive field),它的尺寸是一个超参数(其实就是滤波器的空间尺寸)。在深度方向上,这个连接的大小总是和输入量的深度相等。需要再次强调的是,我们对待空间维度(宽和高)与深度维度是不同的:连接在空间(宽高)上是局部的,但是在深度上总是和输入数据的深度一致,这一点会在下面举例具体说明。

    图 2. 举例说明感受野的连接及尺寸说明

      在图 2 中展现的卷积神经网络的一部分,其中的红色为输入数据,假设输入数据体尺寸为[32x32x3](比如CIFAR-10的RGB图像),如果感受野(或滤波器尺寸)是5x5,那么卷积层中的每个神经元会有输入数据体中[5x5x3]区域的权重,共5x5x3=75个权重(还要加一个偏差参数)。注意这个连接在深度维度上的大小必须为3,和输入数据体的深度一致。其中还有一点需要注意,对应一个感受野有75个权重,这75个权重是通过学习进行更新的,所以很大程度上这些权值之间是不相等(也就是对于某一个特定的卷积核,在它输入数据体的每一个深度切片上的权重都是独特的,不是将 N × N × 1 N \times N \times 1 N×N×1 重复输入数据题的深度那么多次就可以的)。在卷积神经网络中,某一个特定的卷积核,可以根据输入数据题体的维度,拆解为一个一个传统意义上的 N × N × 1 N \times N \times 1 N×N×1 的卷积模板,输入数据题的深度切片与和自己对应的卷积模板做完卷积之后,再将各个深度的结果加起来,再加上偏置(注意是一个偏置,无论输入输入数据是多少层,一个卷积核就对应一个偏置)就获得了这个卷基层卷积之后的结果。

    2.1.3 神经元的空间排列

      感受野讲解了卷积层中每个神经元与输入数据体之间的连接方式,但是尚未讨论输出数据体中神经元的数量,以及它们的排列方式。3个超参数控制着输出数据体的尺寸:深度(depth),步长(stride)和零填充(zero-padding)。

      (1) 输出数据体的深度:它是一个超参数,和使用的滤波器的数量一致,而每个滤波器在输入数据中寻找一些不同的东西,即图像的某些特征。如图2 所示,将沿着深度方向排列、感受野相同的神经元集合称为深度列(depth column),也有人使用纤维(fibre)来称呼它们。

      (2) 在滑动滤波器的时候,必须指定步长。当步长为1,滤波器每次移动1个像素;当步长为2,滤波器滑动时每次移动2个像素,当然步长也可以是不常用的3,或者更大的数字,但这些在实际中很少使用 ,这个操作会让输出数据体在空间上变小。

      (3) 有时候将输入数据体用 0 在边缘处进行填充是很方便的。这个零填充(zero-padding)的尺寸是一个超参数。零填充有一个良好性质,即可以控制输出数据体的空间尺寸与输入数据体的空间尺寸相同,使得输入和输出的宽高都相等。但是要注意,只能保证宽和高相等,并不能保证深度,刚刚讲过,深度与滤波器(或者称卷积核)的数量有关。

      输出数据体在空间上的尺寸 W 2 × H 2 × D 2 W_2 \times H_2 \times D_2 W2×H2×D2 可以通过输入数据体尺寸 W 1 × H 1 × D 1 W_1 \times H_1 \times D_1 W1×H1×D1,卷积层中神经元的感受野尺寸(F),步长(S),滤波器数量(K)和零填充的数量(P)计算输出出来。

    W 2 = ( W 1 − F + 2 P ) / S + 1 H 2 = ( H 1 − F + 2 P ) / S + 1 D 2 = K \begin{matrix} W_2=(W_1-F+2P)/S+1 \\ H_2=(H_1-F+2P)/S+1 \\ D_2=K \\ \end{matrix} W2=(W1F+2P)/S+1H2=(H1F+2P)/S+1D2=K

      一般说来,当步长S=1时,零填充的值是P=(F-1)/2,这样就能保证输入和输出数据体有相同的空间尺寸。

      步长的限制:注意这些空间排列的超参数之间是相互限制的。举例说来,当输入尺寸W=10,零填充 P=0,滤波器尺寸 F=3,此时步长 S=2 是行不通,因为 (W-F+2P)/S+1=(10-3+0)/2+1=4.5,结果不是整数,也就是说神经元不能整齐对称地滑过输入数据体。因此,这些超参数的设定就被认为是无效的,一个卷积神经网络库可能会报出一个错误,通过修改零填充值、修改输入数据体尺寸,或者其他什么措施来让设置合理。在后面的卷积神经网络结构小节中,读者可以看到合理地设置网络的尺寸让所有的维度都能正常工作,是相当让人头痛的事;而使用零填充和遵守其他一些设计策略将会有效解决这个问题。

    2.1.4 权值共享

      在卷积层中权值共享被用来控制参数的数量。假如在一个卷积核中,每一个感受野采用的都是不同的权重值(卷积核的值不同),那么这样的网络中参数数量将是十分巨大的。

      权值共享是基于这样的一个合理的假设:如果一个特征在计算某个空间位置 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 的时候有用,那么它在计算另一个不同位置 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 的时候也有用。基于这个假设,可以显著地减少参数数量。举个例子,我们将深度维度上一个单独的2维切片看做深度切片(depth slice),对于一个经过卷积后尺寸为 [55x55x96] 的数据体就有96个深度切片,每个尺寸为[55x55],其中在每个深度切片上的结果都使用同样的权重和偏差获得的。对于 [55x55x96] 的这个数据体,我们可以知道他经过的卷基层有96个卷积核,也就是对应 96 个不同的权重集了,一个权重集对应一个深度切片,如果卷积核的大小是 11x11的,图像是RGB 3 通道的,那么就共有96x11x11x3=34,848个不同的权重,总共有34,944个参数(因为要+96个偏差),并且在每个深度切片中的55x55 的结果使用的都是同样的参数(即权值共享)。

      在反向传播的时候,都要计算每个神经元对它的权重的梯度,但是需要把同一个深度切片上的所有神经元对权重的梯度累加,这样就得到了对共享权重的梯度。这样,每个切片只更新一个权重集。这样做的原因可以通过下面这张图进行解释

    图 3. 将卷积层用全连接层的形式表示

    如上图所示,左侧的神经元是将每一个感受野展开为一列之后串联起来(就是展开排成一列,同一层神经元之间不连接)。右侧的 Deep1i 是深度为1的神经元的第 i 个, Deep2i 是深度为2的神经元的第 i 个,同一个深度的神经元的权值都是相同的,黄色的都是相同的(上面4个与下面4个的参数相同),蓝色都是相同的。所以现在回过头来看上面说的卷积神经网络的反向传播公式对梯度进行累加求和也是基于这点考虑(同一深度的不同神经元共用一组参数,所以累加);而每个切片只更新一个权重集的原因也是这样的,因为从图3 中可以看到,不同深度的神经元不会公用相同的权重,所以只能更新一个权重集。

      注意,如果在一个深度切片中的所有权重都使用同一个权重向量,那么卷积层的前向传播在每个深度切片中可以看做是在计算神经元权重和输入数据体的卷积(这就是“卷积层”名字由来)。这也是为什么总是将这些权重集合称为滤波器(filter)(或卷积核(kernel)),因为它们和输入进行了卷积。

      注意,有时候参数共享假设可能没有意义,特别是当卷积神经网络的输入图像是一些明确的中心结构时候。这时候我们就应该期望在图片的不同位置学习到完全不同的特征(而一个卷积核滑动地与图像做卷积都是在学习相同的特征)。一个具体的例子就是输入图像是人脸,人脸一般都处于图片中心,而我们期望在不同的位置学习到不同的特征,比如眼睛特征或者头发特征可能(也应该)会在图片的不同位置被学习。在这个例子中,通常就放松参数共享的限制,将层称为局部连接层(Locally-Connected Layer)。

    2.1.5 卷积层的超参数及选择

      由于参数共享,每个滤波器包含 F ⋅ F ⋅ D 1 F \cdot F\cdot D_1 FFD1 个权重(字符的具体含义在2.1.3中有介绍),卷积层一共有$ F\cdot F\cdot D_1\cdot K$个权重和 K K K 个偏置。在输出数据体中,第d个深度切片(空间尺寸是 W 2 × H 2 W_2\times H_2 W2×H2),用第d个滤波器和输入数据进行有效卷积运算的结果(使用步长S),最后在加上第d个偏差。

      对这些超参数,常见的设置是 F = 3 , S = 1 , P = 1 F=3,S=1,P=1 F=3S=1P=1。同时设置这些超参数也有一些约定俗成的惯例和经验,可以在下面的“卷积神经网络结构”中查看。

    2.1.6 卷积层演示

      因为3D数据难以可视化,所以所有的数据(输入数据体是蓝色,权重数据体是红色,输出数据体是绿色)都采取将深度切片按照列的方式排列展现。输入数据体的尺寸是 W 1 = 5 , H 1 = 5 , D 1 = 3 W_1=5,H_1=5,D_1=3 W1=5,H1=5,D1=3,卷积层参数 K = 2 , F = 3 , S = 2 , P = 1 K=2,F=3,S=2,P=1 K=2,F=3,S=2,P=1。就是说,有2个滤波器,滤波器的尺寸是 3 ⋅ 3 3\cdot 3 33,它们的步长是2。因此,输出数据体的空间尺寸是 ( 5 − 3 + 2 ) / 2 + 1 = 3 (5-3+2)/2+1=3 (53+2)/2+1=3。注意输入数据体使用了零填充 P = 1 P=1 P=1,所以输入数据体外边缘一圈都是0。下面的例子在绿色的输出激活数据上循环演示,展示了其中每个元素都是先通过蓝色的输入数据和红色的滤波器逐元素相乘,然后求其总和,最后加上偏差得来。

    无法正常显示,请参考http://cs231n.github.io/convolutional-networks/ 的动图
    图 4. 卷积层演示过程

    2.1.7 用矩阵乘法实现卷积

      卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。卷积层的常用实现方式就是利用这一点,将卷积层的前向传播变成一个巨大的矩阵乘法。

      (1) 输入图像的局部区域被 i m 2 c o im2co im2co l操作拉伸为列。比如输入是[227x227x3],要与尺寸为11x11x3的滤波器以步长为4进行卷积,就依次取输入中的[11x11x3]数据块,然后将其拉伸为长度为11x11x3=363的列向量。重复进行这一过程,因为步长为4,所以经过卷积后的宽和高均为(227-11)/4+1=55,共有55x55=3,025个个神经元。因为每一个神经元实际上都是对应有 363 的列向量构成的感受野,即一共要从输入上取出 3025 个 363 维的列向量。所以经过im2col操作得到的输出矩阵 X c o l X_col Xcol 的尺寸是[363x3025],其中每列是拉伸的感受野。注意因为感受野之间有重叠,所以输入数据体中的数字在不同的列中可能有重复。

      (2) 卷积层的权重也同样被拉伸成行。举例,如果有96个尺寸为[11x11x3]的滤波器,就生成一个矩阵 W r o w W_row Wrow,尺寸为[96x363]。

      (3) 现在卷积的结果和进行一个大矩阵乘法 n p . d o t ( W r o w , X c o l ) np.dot(W_row, X_col) np.dot(Wrow,Xcol) 是等价的了,能得到每个滤波器和每个感受野间的点积。在我们的例子中,这个操作的输出是[96x3025],给出了每个滤波器在每个位置的点积输出。注意其中的 n p . d o t np.dot np.dot 计算的是矩阵乘法而不是点积。

      (4) 结果最后必须被重新变为合理的输出尺寸[55x55x96]。

      这个方法的缺点就是占用内存太多,因为在输入数据体中的某些值在 X c o l X_col Xcol中被复制了多次;优点在于矩阵乘法有非常多的高效底层实现方式(比如常用的BLAS API)。还有,同样的im2col思路可以用在池化操作中。反向传播:卷积操作的反向传播(同时对于数据和权重)还是一个卷积(但是和空间上翻转的滤波器),所以依然可以使用矩阵相乘来计算卷积的反向传播。

      为什么卷积的反向传播还是卷积,这个可以参考另一篇文章 《CNN中卷积层与转置卷积层的关系(转置卷积又称反卷积、分数步长卷积)》 2.2 卷积的前向传播和反向传播 中的内容。

    2.1.8 其他形式的卷积操作

      1x1卷积:一些论文中使用了1x1的卷积,这个方法最早是在论文Network in Network中出现。人们刚开始看见这个1x1卷积的时候比较困惑,尤其是那些具有信号处理专业背景的人。因为信号是2维的,所以1x1卷积就没有意义。但是,在卷积神经网络中不是这样,因为这里是对3个维度进行操作,滤波器和输入数据体的深度是一样的。比如,如果输入是[32x32x3],那么1x1卷积就是在高效地进行3维点积(因为输入深度是3个通道);另外的一种想法是将这种卷积的结果看作是全连接层的一种实现方式,详见本文2.4.2 部分。

      扩张卷积:最近一个研究(Fisher Yu和Vladlen Koltun的论文)给卷积层引入了一个新的叫扩张(dilation)的超参数。到目前为止,我们只讨论了卷积层滤波器是连续的情况。但是,让滤波器中元素之间有间隙也是可以的,这就叫做扩张。如图5 为进行1扩张。

    图 4. 扩张卷积的例子及扩张前后的叠加效果

    在某些设置中,扩张卷积与正常卷积结合起来非常有用,因为在很少的层数内更快地汇集输入图片的大尺度特征。比如,如果上下重叠2个3x3的卷积层,那么第二个卷积层的神经元的感受野是输入数据体中5x5的区域(可以成这些神经元的有效感受野是5x5,如图5 所示)。如果我们对卷积进行扩张,那么这个有效感受野就会迅速增长。

    2.2 池化层

      通常在连续的卷积层之间会周期性地插入一个池化层。它的作用是逐渐降低数据体的空间尺寸,这样的话就能减少网络中参数的数量,使得计算资源耗费变少,也能有效控制过拟合。汇聚层使用 MAX 操作,对输入数据体的每一个深度切片独立进行操作,改变它的空间尺寸。最常见的形式是汇聚层使用尺寸2x2的滤波器,以步长为2来对每个深度切片进行降采样,将其中75%的激活信息都丢掉。每个MAX操作是从4个数字中取最大值(也就是在深度切片中某个2x2的区域),深度保持不变。

      汇聚层的一些公式:输入数据体尺寸 W 1 ⋅ H 1 ⋅ D 1 W_1\cdot H_1\cdot D_1 W1H1D1,有两个超参数:空间大小 F F F和步长 S S S;输出数据体的尺寸 W 2 ⋅ H 2 ⋅ D 2 W_2\cdot H_2\cdot D_2 W2H2D2,其中
    W 2 = ( W 1 − F ) / S + 1 H 2 = ( H 1 − F ) / S + 1 D 2 = D 1 \begin{matrix} W_2=(W_1-F)/S+1 \\ H_2=(H_1-F)/S+1 \\ D_2=D1 \\ \end{matrix} W2=(W1F)/S+1H2=(H1F)/S+1D2=D1
    这里面与之前的卷积的尺寸计算的区别主要在于两点,首先在池化的过程中基本不会进行另补充;其次池化前后深度不变。

      在实践中,最大池化层通常只有两种形式:一种是 F = 3 , S = 2 F=3,S=2 F=3,S=2,也叫重叠汇聚(overlapping pooling),另一个更常用的是 F = 2 , S = 2 F=2,S=2 F=2,S=2。对更大感受野进行池化需要的池化尺寸也更大,而且往往对网络有破坏性。
      普通池化(General Pooling):除了最大池化,池化单元还可以使用其他的函数,比如平均池化(average pooling)或L-2范式池化(L2-norm pooling)。平均池化历史上比较常用,但是现在已经很少使用了。因为实践证明,最大池化的效果比平均池化要好。

      反向传播:回顾一下反向传播的内容,其中max(x,y)函数的反向传播可以简单理解为将梯度只沿最大的数回传。因此,在向前传播经过汇聚层的时候,通常会把池中最大元素的索引记录下来(有时这个也叫作道岔(switches)),这样在反向传播的时候梯度的时候就很高效。

      下面将简单介绍平均池化和最大池化反向传播的计算过程。首先是平均池化,在前向传播的过程中,是计算窗口中的均值,很容易理解;在反向传播的过程中,是将要反向传播的梯度组分在平均分在四个部分传回去,具体如下所示

    forward: [1 3; 2 2] -> [2]
    backward: [2] -> [0.5 0.5; 0.5 0.5]
    

    对于最大池化,就如上面所说的,会把池中最大元素的索引记录下来,在反向传播的时候只对这样的位置进行反向传播,忽略其他的位置

    forward: [1 3; 2 2] -> 3
    backward: [3] -> [0 3; 0 0]
    

      不使用汇聚层:很多人不喜欢汇聚操作,认为可以不使用它。比如在Striving for Simplicity: The All Convolutional Net一文中,提出使用一种只有重复的卷积层组成的结构,抛弃汇聚层。通过在卷积层中使用更大的步长来降低数据体的尺寸。有发现认为,在训练一个良好的生成模型时,弃用汇聚层也是很重要的。比如变化自编码器(VAEs:variational autoencoders)和生成性对抗网络(GANs:generative adversarial networks)。现在看起来,未来的卷积网络结构中,可能会很少使用甚至不使用汇聚层

    2.3 归一化层

      在卷积神经网络的结构中,提出了很多不同类型的归一化层,有时候是为了实现在生物大脑中观测到的抑制机制。但是这些层渐渐都不再流行,因为实践证明它们的效果即使存在,也是极其有限的

    2.4 全连接层

      这个和常规神经网络中的一样,它们的激活可以先用矩阵乘法,再加上偏差。

    2.4.1 将卷积层转化成全连接层

      对于任意一个卷积层,都存在一个能实现和它一样的前向传播函数的全连接层。该全连接层的权重是一个巨大的矩阵,除了某些特定块(感受野),其余部分都是零;而在非 0 部分中,大部分元素都是相等的(权值共享),具体可以参考图3。如果把全连接层转化成卷积层,以输出层的 Deep11 为例,与它有关的输入神经元只有上面四个,所以在权重矩阵中与它相乘的元素,除了它所对应的4个,剩下的均为0,这也就解释了为什么权重矩阵中有为零的部分;另外要把“将全连接层转化成卷积层”和“用矩阵乘法实现卷积”区别开,这两者是不同的,后者本身还是在计算卷积,只不过将其展开为矩阵相乘的形式,并不是"将全连接层转化成卷积层",所以除非权重中本身有零,否则用矩阵乘法实现卷积的过程中不会出现值为0的权重。

    2.4.2 将全连接层转化成卷积层

      任何全连接层都可以被转化为卷积层。比如,一个K=4096的全连接层,输入数据体的尺寸是 7 × 7 × 512 7\times 7\times 512 7×7×512,这个全连接层可以被等效地看做一个 F = 7 , P = 0 , S = 1 , K = 4096 F=7,P=0,S=1,K=4096 F=7,P=0,S=1,K=4096的卷积层。换句话说,就是将滤波器的尺寸设置为和输入数据体的尺寸设为一致的。因为只有一个单独的深度列覆盖并滑过输入数据体,所以输出将变成 1 × 1 × 4096 1\times 1\times 4096 1×1×4096,这个结果就和使用初始的那个全连接层一样了。这个实际上也很好理解,因为,对于其中的一个卷积滤波器,这个滤波器的的深度为512,也就是说,虽然这个卷积滤波器的输出只有1个,但是它的权重有 7 × 7 × 512 7\times 7\times 512 7×7×512,相当于卷积滤波器的输出为一个神经元,这个神经元与上一层的所有神经元相连接,而这样与前一层所有神经元相连接的神经元一共有4096个,这不就是一个全连接网络嘛~

      在上述的两种变换中,将全连接层转化为卷积层在实际运用中更加有用。假设一个卷积神经网络的输入是224x224x3的图像,一系列的卷积层和汇聚层将图像数据变为尺寸为7x7x512的激活数据体(在AlexNet中就是这样,通过使用5个汇聚层来对输入数据进行空间上的降采样,每次尺寸下降一半,所以最终空间尺寸为224/2/2/2/2/2=7)。从这里可以看到,AlexNet使用了两个尺寸为4096的全连接层,最后一个有1000个神经元的全连接层用于计算分类评分。我们可以将这3个全连接转化为3个卷积层:

      (1) 针对第一个连接区域是[7x7x512]的全连接层,令其滤波器尺寸为F=7,这样输出数据体就为[1x1x4096]了。

      (2) 针对第二个全连接层,令其滤波器尺寸为F=1,这样输出数据体为[1x1x4096]。

      (3) 对最后一个全连接层也做类似的,令其F=1,最终输出为[1x1x1000]。

      这样做的目的是让卷积网络在一张更大的输入图片上滑动,得到多个输出,这样的转化可以让我们在单个向前传播的过程中完成上述的操作。

      举个例子,如果我们想让224x224尺寸的浮窗,以步长为32在384x384的图片上滑动,把每个经停的位置都带入卷积网络,最后得到6x6个位置的类别得分。上述的把全连接层转换成卷积层的做法会更简便。如果224x224的输入图片经过卷积层和汇聚层之后得到了[7x7x512]的数组,那么,384x384的大图片直接经过同样的卷积层和汇聚层之后会得到[12x12x512]的数组(因为途径5个汇聚层,尺寸变为384/2/2/2/2/2 = 12)。然后再经过上面由3个全连接层转化得到的3个卷积层,最终得到[6x6x1000]的输出(因为(12 - 7)/1 + 1 = 6)。这个结果正是浮窗在原图经停的6x6个位置的得分!

      面对384x384的图像,让(含全连接层)的初始卷积神经网络以32像素的步长独立对图像中的224x224块进行多次评价,其效果和使用把全连接层变换为卷积层后的卷积神经网络进行一次前向传播是一样的。相较于使用被转化前的原始卷积神经网络对所有36个位置进行迭代计算,使用转化后的卷积神经网络进行一次前向传播计算要高效得多,因为36次计算都在共享计算资源。

      这里有几个问题,首先为什么是以32为步长,如果我以64为步长呢?再或者如果我们想用步长小于32(如16)的浮窗怎么办

      首先回答其中的第一个问题。这个是因为其中一个有五个汇聚层,因为 2 5 = 32 2^5=32 25=32,也就是在原始图像上的宽或者高增加 32 32 32 个像素,经过这些卷积和汇聚后,将变为一个像素。现在进行举例说明,虽然例子并没有32那么大的尺寸,但是意义都是一样的。假设较小的输入图像尺寸为 4×4,进行一次卷积核为 F = 3 , S = 1 , P = 1 F=3, S=1, P=1 F=3,S=1,P=1的卷积后,再进行一次 2 × 2 2\times 2 2×2 的 maxpooling;然后在进行一次这样的卷积,再进行一次池化。假设较大的原始图像的尺寸为 8×8,以步长为4进行滑窗操作,对于每个窗内进行同样的卷积池化操作,如图5所示

    图 5. 以步长为4在原始图像上滑动取出4×4窗口再计算卷积的结果

    对较小的原始图像(图5左图红框)进行卷积得到的结果是图5右图红色框内的结果,通过划窗取样再进行卷积运算获得的结果为图5中右侧四种颜色加在一起的样子。所以以步长为4在8x8的图片上滑动,把每个经停的位置都带入卷积网络,最后得到2x2个位置的卷积结果,但是如果直接使用卷积核 F = 3 , S = 1 , P = 1 F=3, S=1, P=1 F=3,S=1,P=1进行两次卷积池化的话,得到的结果的大小显然也是2×2的。

      所以从获得结果来看,这两者是相同的,但是不同点在哪呢?如图6所示,是在整个图像上进行卷积运算和以步长为4在8x8的图片上滑动所经停的第一个位置,这两种方法使用相同的卷积核进行计算的对比图。

    这里写图片描述
    图6. 使用整张图像和一部分图像计算某一点处的卷积

    如图6所示,左图代表使用整张图像时计算a点处的卷积,右图代表使用滑动的方法第一次经停图像上a点的卷积,两张图中的a点是同一个a点。虽然同一个卷积模板进行计算,但是在计算卷积的过程是不同的!因为在右图中a的右侧及右下侧是0,而在左图中是原始的像素值,所以计算的卷积一定是不同的。但是要怎么理解这样的差别呢?这要从补零的意义讲起,补零是因为如果不补零的话,图像经过卷积之后的尺寸会小于原始的尺寸,补零可以保证图像的尺寸不变,所以归根结底补零实际上是一种图像填充的方法。左图中a的右边及右下角的像素是原始图像的像素,相当于在计算a点的时候,不是用0进行的补充,而是原始像素值进行补充,这样的不仅可以保持卷积前后图像的大小不变,而且可以这种图像填充方法得到的结果显然要比填0更接近与原始图像,保留的信息更多。

      小结

      (1) 用一整图像进行卷积和在较大的图像上通过滑动窗提取出一个个子图象进行卷积得到的效果是相同的。

      (2) 可以这样做的主要原因在于将最后的全连接层改写成了卷积层。

      (3) 在一整张图像做卷积的效率要远远高于在图像上滑动的效率,因为前者只需要一次前向传播,而后者需要多次

      (4) 用整张图像计算与滑动窗口的方法对比,所补充的零更少(如上所讲,不用零而是用在其旁边的像素代替),提取的信息损失的更少。

      即,用整张图像直接计算卷积不仅仅在效率上高于使用滑动窗口的方法,而且更多的保留了图像的细节,完胜!

      还可以得到另一个结论在较大的图像上以步长为 2 L 2^L 2L进行滑动然后再计算的卷积结果,其效果与 以步长为 1 在大图像直接卷积得到的结果上 移动 所获得的结果 是等价的。如图5中的大红色框对应小红色框,大黄色框对应小黄色框。所以当步长为64时,将相当于以步长为2在大图的卷积结果上移动。

      对于第二个问题,如果我非要以16为步长呢?是可以的,只是这个时候所获得结果不再是如图5的那种滑动的计算方式了。还是举例说明,在上一个例子中,我们将步长32改为4,所以这里将步长16改为2进行分析。假如说原始的输入图像为一个 4×4 的图像,现在将使用一个比原来大的图像,是一个8×8的图像,使用卷积核为 4×4 大小,步长为4,则在图像进行卷积运算的如图6左侧的4个部分(红黄绿蓝),而图6 右侧的是步长为2时与原始相比增加的部分。将图 6中两个部分相加就可以得到步长为2它时所有进行卷积运算的部分了。

    这里写图片描述
    图6 .步长为4时原始图像进行卷积的部分及将步长改为2时比原来多出的部分

      获得步长为2的时进行卷积的区域。首先像之前一样对原始图像做以4为步长的卷积,这时进行卷积的部分就是图6中左侧的部分;其次将原始图片沿宽度方向平移2个像素之后,依旧进行步长为4的卷积,这个时候进行卷积的部分为图6中的红色部分和绿色部分;然后沿高度方向平移2个像素之后,按步长为4进行卷积,这个时候进行卷积的部分为图6中的蓝色部分和黄色部分;最后沿高度方向和宽度方向同时移动2个像素,按步长为4进行卷积,这个时候进行卷积的部分为图6中的紫色部分。将这些部分加在一起就是进行卷积运算你得所有区域了。

      这个结果明显是无法通过像图5中的那样滑动得到了,这样的方法所需要进行卷积的区域要远远大于以4为步长时所需要就进行卷积运算的区域;后续的卷积都是在这一卷积的结果上进行的,所以后面的都会发生改变。

      综上,步长为32的正整数倍只是保证 直接在最后的卷积结果上进行滑动和滑窗效果相等 的下限值。

    3. 卷积神经网络的结构

      卷积神经网络通常是由三种层构成:卷积层,汇聚层(除非特别说明,一般就是最大值汇聚)和全连接层(简称FC)。ReLU激活函数也应该算是是一层,它逐元素地进行激活函数操作,常常将它与卷积层看作是同一层。

    3.1 层的排列规律

      卷积神经网络最常见的形式就是将一些卷积层和ReLU层放在一起,其后紧跟汇聚层,然后重复如此直到图像在空间上被缩小到一个足够小的尺寸,在某个地方过渡成成全连接层也较为常见。最后的全连接层得到输出,比如分类评分等。换句话说,最常见的卷积神经网络结构如下:
    I N P U T → [ C O N V → R E L U ] ∗ N → [ P O O L ? ] ∗ M → [ F C → R E L U ] ∗ K → F C INPUT \rightarrow [CONV \rightarrow RELU]*N \rightarrow [POOL?]*M \rightarrow [FC \rightarrow RELU]*K \rightarrow FC INPUT[CONVRELU]N[POOL?]M[FCRELU]KFC

    其中*指的是重复次数,POOL?指的是一个可选的汇聚层。其中N >=0,通常N<=3,M>=0,K>=0,通常K<3。例如,下面是一些常见的网络结构规律:

    • INPUT -> FC ,实现一个线性分类器,此处N = M = K = 0。

    • INPUT -> CONV -> RELU -> FC,单层的卷积神经网络

    • *INPUT -> [CONV -> RELU -> POOL]2 -> FC -> RELU -> FC,此处在每个汇聚层之间有一个卷积层,这种网络就是简单的多层的卷积神经网络。

    • **INPUT -> [CONV -> RELU -> CONV -> RELU -> POOL]3 -> [FC -> RELU]2 -> FC ,此处每个汇聚层前有两个卷积层,这个思路适用于更大更深的网络(比如说这个思路就和VGG比较像),因为在执行具有破坏性的汇聚操作前,多重的卷积层可以从输入数据中学习到更多的复杂特征。

      最新进展:传统的将层按照线性进行排列的方法已经受到了挑战,挑战来自谷歌的Inception结构和微软亚洲研究院的残差网络(Residual Net)结构。这两个网络的特征更加复杂,连接结构也不同。

    3.2 卷积层的大小选择

      几个小滤波器卷积层的组合比一个大滤波器卷积层好。假设你一层一层地重叠了3个3x3的卷积层(层与层之间有非线性激活函数)。在这个排列下,第一个卷积层中的每个神经元都对输入数据体有一个3x3的视野。第二个卷积层上的神经元对第一个卷积层有一个3x3的视野,也就是对输入数据体有5x5的视野。同样,在第三个卷积层上的神经元对第二个卷积层有3x3的视野,也就是对输入数据体有7x7的视野。假设不采用这3个3x3的卷积层,而是使用一个单独的有7x7的感受野的卷积层,那么所有神经元的感受野也是7x7,但是这样有一些缺点。首先,多个卷积层与非线性的激活层交替的结构,比单一卷积层的结构更能提取出深层的更好的特征。其次,假设所有的数据有C个通道,那么单独的7x7卷积层将会包含 C × ( 7 × 7 × C ) = 49 C 2 C\times (7\times 7\times C)=49C^2 C×(7×7×C)=49C2个参数,而3个3x3的卷积层的组合仅有 3 × ( C × ( 3 × 3 × C ) ) = 27 C 2 3\times (C\times (3\times 3\times C))=27C^2 3×(C×(3×3×C))=27C2个参数。直观说来,最好选择带有小滤波器的卷积层组合,而不是用一个带有大的滤波器的卷积层。前者可以表达出输入数据中更多个强力特征,使用的参数也更少。唯一的不足是,在进行反向传播时,中间的卷积层可能会导致占用更多的内存。所以他减少的是参数量,而非计算量。

    3.3 层的尺寸设置规律

    • 输入层 ,应该能被2整除很多次。常用数字包括32(比如CIFAR-10),64,96(比如STL-10)或224(比如ImageNet卷积神经网络),384和512。

    • 卷积层 ,应该使用小尺寸滤波器(比如3x3或最多5x5),使用步长S=1。还有一点非常重要,就是对输入数据进行零填充,这样卷积层就不会改变输入数据在空间维度上的尺寸。比如,当F=3,那就使用P=1来保持输入尺寸。当F=5,P=2,一般对于任意F,当P=(F-1)/2的时候能保持输入尺寸。如果必须使用更大的滤波器尺寸(比如7x7之类),通常只用在第一个面对原始图像的卷积层上。

    • 汇聚层 ,负责对输入数据的空间维度进行降采样。最常用的设置是用用2x2感受野(即F=2)的最大值汇聚,步长为2(S=2)。注意这一操作将会把输入数据中75%的激活数据丢弃(因为对宽度和高度都进行了2的降采样)。另一个不那么常用的设置是使用3x3的感受野,步长为2。最大值汇聚的感受野尺寸很少有超过3的,因为汇聚操作过于激烈,易造成数据信息丢失,这通常会导致算法性能变差。

      上文中展示的两种设置(卷积层F=3,P=1,汇聚层F=2,P=2)是很好的,因为所有的卷积层都能保持其输入数据的空间尺寸,汇聚层只负责对数据体从空间维度进行降采样。如果使用的步长大于1并且不对卷积层的输入数据使用零填充,那么就必须非常仔细地监督输入数据体通过整个卷积神经网络结构的过程,确认所有的步长和滤波器都尺寸互相吻合,卷积神经网络的结构美妙对称地联系在一起。

      为何使用零填充?使用零填充除了前面提到的可以让卷积层的输出数据保持和输入数据在空间维度的不变,还可以提高算法性能。如果卷积层值进行卷积而不进行零填充,那么数据体的尺寸就会略微减小,那么图像边缘的信息就会过快地损失掉。

      因为内存限制所做的妥协:在某些案例(尤其是早期的卷积神经网络结构)中,基于前面的各种规则,内存的使用量迅速飙升。例如,使用64个尺寸为3x3的滤波器对224x224x3的图像进行卷积,零填充为1,得到的激活数据体尺寸是[224x224x64]。这个数量就是一千万的激活数据,或者就是72MB的内存(每张图就是这么多,激活函数和梯度都是)。因为GPU通常因为内存导致性能瓶颈,所以做出一些妥协是必须的。在实践中,人们倾向于在网络的第一个卷积层做出妥协。例如,可以妥协可能是在第一个卷积层使用步长为2,尺寸为7x7的滤波器(比如在ZFnet中)。在AlexNet中,滤波器的尺寸的11x11,步长为4。

    4. 案例学习

      下面是卷积神经网络领域中比较有名的几种结构:

    • LeNet ,第一个成功的卷积神经网络应用,是Yann LeCun在上世纪90年代实现的。当然,最著名还是被应用在识别数字和邮政编码等的LeNet结构。

    • AlexNet ,AlexNet卷积神经网络在计算机视觉领域中受到欢迎,它由Alex Krizhevsky,Ilya Sutskever和Geoff Hinton实现。AlexNet在2012年的ImageNet ILSVRC 竞赛中夺冠,性能远远超出第二名(16%的top5错误率,第二名是26%的top5错误率)。这个网络的结构和LeNet非常类似,但是更深更大,并且使用了层叠的卷积层来获取特征(之前通常是只用一个卷积层并且在其后马上跟着一个汇聚层)。

    • ZF Net ,Matthew Zeiler和Rob Fergus发明的网络在ILSVRC 2013比赛中夺冠,它被称为 ZFNet(Zeiler & Fergus Net的简称)。它通过修改结构中的超参数来实现对AlexNet的改良,具体说来就是增加了中间卷积层的尺寸让第一层的步长和滤波器尺寸更小

    • GoogLeNet ,ILSVRC 2014的胜利者是谷歌的Szeged等实现的卷积神经网络。它主要的贡献就是实现了一个奠基模块,它能够显著地减少网络中参数的数量(AlexNet中有60M,该网络中只有4M)。还有,这个论文中没有使用卷积神经网络顶部使用全连接层,而是使用了一个平均汇聚,把大量不是很重要的参数都去除掉了。GooLeNet还有几种改进的版本,最新的一个是Inception-v4

    • VGGNet ,ILSVRC 2014的第二名是Karen Simonyan和 Andrew Zisserman实现的卷积神经网络,现在称其为VGGNet。它主要的贡献是展示出网络的深度是算法优良性能的关键部分。他们最好的网络包含了16个卷积/全连接层。网络的结构非常一致,从头到尾全部使用的是3x3的卷积和2x2的汇聚。他们的预训练模型是可以在网络上获得并在Caffe中使用的。VGGNet不好的一点是它耗费更多计算资源,并且使用了更多的参数,导致更多的内存占用(140M)。其中绝大多数的参数都是来自于第一个全连接层。后来发现这些全连接层即使被去除,对于性能也没有什么影响,这样就显著降低了参数数量。

    • ResNet ,残差网络(Residual Network)是ILSVRC2015的胜利者,由何恺明等实现。它使用了特殊的跳跃链接,大量使用了批量归一化(batch normalization)。这个结构同样在最后没有使用全连接层。读者可以查看何恺明的的演讲,以及一些使用Torch重现网络的实验。ResNet当前最好的卷积神经网络模型(2016年五月)。何开明等最近的工作是对原始结构做一些优化,可以看论文Identity Mappings in Deep Residual Networks,2016年3月发表。

    4.1 VGGNet的细节

      我们进一步对VGGNet的细节进行分析学习。整个VGGNet中的卷积层都是以步长为1进行3x3的卷积,使用了1的零填充,汇聚层都是以步长为2进行了2x2的最大值汇聚。可以写出处理过程中每一步数据体尺寸的变化,然后对数据尺寸和整体权重的数量进行查看:

    INPUT: [224x224x3]        memory:  224*224*3=150K   weights: 0
    CONV3-64: [224x224x64]  memory:  224*224*64=3.2M   weights: (3*3*3)*64 = 1,728
    CONV3-64: [224x224x64]  memory:  224*224*64=3.2M   weights: (3*3*64)*64 = 36,864
    POOL2: [112x112x64]  memory:  112*112*64=800K   weights: 0
    CONV3-128: [112x112x128]  memory:  112*112*128=1.6M   weights: (3*3*64)*128 = 73,728
    CONV3-128: [112x112x128]  memory:  112*112*128=1.6M   weights: (3*3*128)*128 = 147,456
    POOL2: [56x56x128]  memory:  56*56*128=400K   weights: 0
    CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*128)*256 = 294,912
    CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*256)*256 = 589,824
    CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*256)*256 = 589,824
    POOL2: [28x28x256]  memory:  28*28*256=200K   weights: 0
    CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*256)*512 = 1,179,648
    CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*512)*512 = 2,359,296
    CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*512)*512 = 2,359,296
    POOL2: [14x14x512]  memory:  14*14*512=100K   weights: 0
    CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
    CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
    CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
    POOL2: [7x7x512]  memory:  7*7*512=25K  weights: 0
    FC: [1x1x4096]  memory:  4096  weights: 7*7*512*4096 = 102,760,448
    FC: [1x1x4096]  memory:  4096  weights: 4096*4096 = 16,777,216
    FC: [1x1x1000]  memory:  1000 weights: 4096*1000 = 4,096,000
    
    TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
    TOTAL params: 138M parameters
    

      注意,大部分的内存和计算时间都被前面的卷积层占用,大部分的参数都用在后面的全连接层,这在卷积神经网络中是比较常见的。在这个例子中,全部参数有140M,但第一个全连接层就包含了100M的参数

    5. 计算上的考量

      在构建卷积神经网络结构时,最大的瓶颈是内存瓶颈,所以如何降低内存消耗量是一个值得思考的问题。三种内存占用来源:

    • 1 来自中间数据体尺寸:卷积神经网络中的每一层中都有激活数据体的原始数值,以及损失函数对它们的梯度(和激活数据体尺寸一致)。通常,大部分激活数据都是在网络中靠前的层中(比如第一个卷积层)。在训练时,这些数据需要放在内存中,因为反向传播的时候还会用到。但是在测试时可以聪明点:让网络在测试运行时候每层都只存储当前的激活数据,然后丢弃前面层的激活数据,这样就能减少巨大的激活数据量。这实际上是底层问题,在编写框架的过程中,设计者会进行这方面的考虑。

    • 2 来自参数尺寸:即整个网络的参数的数量,在反向传播时它们的梯度值,以及使用momentum、Adagrad或RMSProp等方法进行最优化时的每一步计算缓存。因此,存储参数向量的内存通常需要在参数向量的容量基础上乘以3或者更多。

    • 3 卷积神经网络实现还有各种零散的内存占用,比如成批的训练数据,扩充的数据等等。

      一旦对于所有这些数值的数量有了一个大略估计(包含激活数据,梯度和各种杂项),数量应该转化为以GB为计量单位。把这个值乘以4,得到原始的字节数(因为每个浮点数占用4个字节,如果是双精度浮点数那就是占用8个字节),然后多次除以1024分别得到占用内存的KB,MB,最后是GB计量。如果你的网络工作得不好,一个常用的方法是降低批尺寸(batch size),因为绝大多数的内存都是被激活数据消耗掉了。

    展开全文
  • 问题: 你要改变对象在屏幕的显示顺序,向前或向后移动他们 解决办法: 使用DisplayObectContainer类中的setChildIndex( ) 方法改变选定项的位置 getChildIndex( ) 与getChildAt( )方法用于获取项目在显示对象列表中...
  • 虚幻引擎(2)-角色移动

    千次阅读 2020-03-24 15:12:55
    使用的虚幻引擎版本:4.23.1 实现的功能:角色移动
  • 翻译下面这段比较完整的: 为了移动你的机器人(真实的或者模拟的),使用鼠标点击并按住红点,然后按您希望机器人移动的方向来移动红点。 注意不要将其移得太远,因为机器人的速度受距原点的距离控制。 如果您...
  • 老孟导读:这是老孟翻译的精品文章,此篇文章共有 3.6K的赞。加入老孟Flutter交流群(wx:laomengit),精彩文章不容错误。 原文地址:...
  • 通过刚体组件控制物体的移动

    千次阅读 2017-10-25 17:29:09
    以下翻译主要引用自:http://www.ceeger.com/Components/class-Rigidbody.html,详情点击该网址。Rigidbodies enable your GameObjects to act under the control of physics. The Rigidbody can receive forces and...
  • 压铸过程中ABB程序的主程序和子程序的相关翻译 ! the waiting position before entering into die casting machine MoveJ pWaitToDCM,v2000,z10,toGripper;(进入压铸机第一个点) MoveJ pWaitDie, v2500, z50, ...
  • Qt之检测鼠标移动

    千次阅读 2020-04-06 14:57:35
    翻译过来就是: 如果禁用鼠标跟踪(默认),widget 只在鼠标移动时至少按下一个鼠标按钮时才能接收鼠标移动事件。 如果启用了鼠标跟踪,即使没有按下按钮,小部件也会接收鼠标移动事件。 因此需要调用 ...
  • CRNN论文翻译——中英文对照

    万次阅读 2017-08-29 16:14:06
    文章作者:Tyan ...声明:作者翻译论文仅为学习,如有侵权请联系作者删除博文,谢谢! 翻译论文汇总:https://github.com/SnailTyan/deep-learning-papers-translation An End-to-End Trainable Neural N...
  • 【开始时间】2018.09.25 ...【论文翻译】GoogleNet网络论文中英对照翻译--(Going deeper with convolutions) 【中文译名】 更深的卷积 【论文链接】https://arxiv.org/abs/1409.4842  ...
  • 向前移动100像素 bk(100) 向后移动100像素 lt(45) 向左转45度 rt(90) 向右转90度 penup() 提起笔,移动时不留痕迹 pendown() 再次放下笔进行绘图 setshape(“火箭大”) 更改光标。 可用(有或...
  • CRNN论文翻译——中文版

    万次阅读 热门讨论 2017-08-29 16:16:33
    文章作者:Tyan ...nbsp;|&nbsp; CSDN &...翻译论文汇总:https://github.com/SnailTyan/deep-learning-papers-translation An End-to-End Trainable Neural Network for Image-based Sequence R...
  • 向前(下)移动一屏 C-v scroll-down-command 向后(上)移动一屏 M-v recenter-top-bottom 重绘屏幕,将光标移至屏中央 C-l backward-paragraph 移动到下一个段落开头 M-{ ...
  • 这是一篇有关《统计学习基础》,原书名The Elements of Statistical Learning的学习笔记,该书学习难度较高,有很棒的学者将其翻译成中文并放在自己的个人网站上,翻译质量非常高,本博客中有关翻译的内容都是出自该...
  • 本页列举了基于《UAAG2.0 Reference:用户代理无障碍指南2.0(UAAG2.0)解释、案例与资源》的移动案例。其中包括准则、成功标准,并旨在为移动案例提供上下文。相关背景请参阅UAAG概述。这些案例展
  • [ROS Navigation Tuning Guide]翻译

    千次阅读 2017-09-11 16:34:05
    ROS导航堆栈对于移动机器人从一个地方移动到另一个地方是强大的可靠。 导航堆栈的工作是通过处理来自测距、传感器和环境图的数据来产生让机器人执行的安全路径。 最大限度地提高此导航堆栈的性能需要对参数进行一些...
  • 脑干翻译 先决条件 海湾合作委员会 脑 如何使用 首先编译核心: gcc core.c -o core 然后输入文件名作为参数: ./core cool_brainfuck_filename 语言 Brainfuck程序具有一个称为“指针”的隐式字节指针,该指针...
  • 使用python实现sma 什么是移动平均线(MAs)? (What are Moving Averages(MAs) ?) A moving average, also called as rolling average or running average is a used to analyze the time-series data by calculating...
  • 移动通信协议

    千次阅读 2013-03-31 16:54:46
    作为移动通信软件工程师您应该了解最基本的移动通信协议,更严格的来讲不是了解,而应该是精通,面临3G时代的到来,国家需要复合型移动通信人才,对于开发人员也提出了新的更高的标准要求:即拥有传统软件的开发经验...
  • 1401:机器翻译

    2019-08-08 11:50:49
    1401:机器翻译 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 5838 通过数: 2609 【题目描述】 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。 这个翻译软件的原理很简单,它只是从头到尾...
  • The Google File System 论文翻译

    千次阅读 2019-05-03 22:00:40
    如果应用程序对性能非常关注,通常的做法是把小规模的随机读取操作合并、排序,之后按顺序批量读取,这样就避免了在文件中前后来回的移动读取位置。 系统的工作负载还包括许多大规模的、顺序的、追加方式的写操作。...
  • 你可以通过 goBack() 或 goForward()向前或向后访问已访问过的站点。 例如,下面的代码实现了通过 Activity来利用设备的后退按钮来向后导航: @Override   public   boolean  onKeyDown( int  keyCode,...
  • 译者注:本文翻译自斯坦福CS231n课程笔记ConvNet notes,由课程教师Andrej Karpathy授权进行翻译。本篇教程由杜客和猴子翻译完成,堃堃和李艺颖进行校对修改。原文如下内容列表:结构概述用来构建卷积神经网络的各种...
  • 本页列举了基于《UAAG2.0 Reference:用户代理无障碍指南2.0(UAAG2.0)解释、案例与资源》的移动案例。其中包括准则、成功标准,并旨在为移动案例提供上下文。
  • RCNN 论文翻译

    千次阅读 2019-03-18 20:05:24
    福岛的“新识别器”[19],一种生物启发式的等级和移动不变模式承认,是对这一过程的早期尝试。然而,新识别器缺乏监督培训算法。建立在Rumelhart等人的基础上。 [33],LeCun et人。 [26]表明通过反向传播的随机梯度...
  • 翻译】ExtJS vs AngularJS

    万次阅读 热门讨论 2014-06-19 15:17:22
    ExtJS和AngularJS是两个行业内领先的富界面开发框架。TechFerry有机会使用Ext JS和Angular ...本文围绕利弊、架构、测试、移动能力、性能、生成和部署等方面对Ext JS和Angular JS进行了超过30个以上的要点进行了比较。
  • multicollinearity 多重共线性 tolerance 容忍度 variance inflation factor 方差扩大因子/VIF forward selection 向前选择 backward elimination 向后剔除 stepwise regression 逐步回归 best subset 最优子集 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,323
精华内容 4,929
关键字:

向前移动翻译