一般计算机视觉的问题:
图片分类Image Classification
目标识别Object detection
图片风格迁移Neural Style Transfer
使用传统神经网络处理机器视觉的一个主要问题是输入层维度很大。例如一张64x64x3的图片,神经网络输入层的维度为12288。如果图片尺寸较大,例如一张1000x1000x3的图片,神经网络输入层的维度将达到3百万,使得网络权重W非常庞大。这样会造成两个后果,一是神经网络结构复杂,数据量相对不够,容易出现过拟合;二是所需内存、计算量较大。解决这一问题的方法就是使用卷积神经网络(CNN)。
使用边缘检测作为入门样例,如何在一张图片中进行边缘检测。
给了这样一张图片,让电脑去搞清楚照片里有什么物体,你可能做的第一件事是检测图片中的垂直边缘。比如说,在这张图片中的栏杆就对应垂直线,与此同时,这些行人的轮廓线某种程度上也是垂线,这些线是垂直边缘检测器的输出。同样,你可能也想检测水平边缘,比如说这些栏杆就是很明显的水平线,它们也能被检测到。
看一个例子,这是一个6×6的灰度图像, 它是6×6×1的矩阵,而不是6×6×3的,因为没有RGB三通道。为了检测图像中的垂直边缘,你可以构造一个3×3矩阵。在卷积神经网络的术语中,它被称为过滤器。我要构造一个3×3的过滤器,像这样 [ 1 0 − 1 1 0 − 1 1 0 − 1 ] \begin{bmatrix}1 & 0 & -1\\1 & 0 & -1\\ 1 & 0 & -1\end{bmatrix} ⎣⎡111000−1−1−1⎦⎤。在论文它有时候会被称为核。对这个6×6的图像进行卷积运算,卷积运算用*来表示,用3×3的过滤器对其进行卷积。
关于符号表示: 有一些问题,在数学中“ ∗ * ∗”就是卷积的标准标志,但是在Python中,这个标识常常被用来表示乘法或者元素乘法。所以这个“ ∗ * ∗”有多层含义,它是一个重载符号。
这个卷积运算的输出将会是一个4×4的矩阵,你可以将它看成一个4×4的图像。下面来说明是如何计算得到这个4×4矩阵的。为了计算第一个元素,在4×4左上角的那个元素,使用3×3的过滤器,将其覆盖在输入图像,如下图所示。然后进行元素乘法(element-wise products)运算,所以 [ 3 × 1 0 × 0 1 × ( 1 ) 1 × 1 5 × 0 8 × ( − 1 ) 2 × 1 7 × 0 2 × ( − 1 ) ] = [ 3 0 − 1 1 0 − 8 2 0 − 2 ] \begin{bmatrix} 3 \times 1 & 0 \times 0 & 1 \times \left(1 \right) \\ 1 \times 1 & 5 \times 0 & 8 \times \left( - 1 \right) \\ 2 \times1 & 7 \times 0 & 2 \times \left( - 1 \right) \ \end{bmatrix} = \begin{bmatrix}3 & 0 & - 1 \\ 1 & 0 & - 8 \\ 2 & 0 & - 2 \\ \end{bmatrix} ⎣⎡3×11×12×10×05×07×01×(1)8×(−1)2×(−1) ⎦⎤=⎣⎡312000−1−8−2⎦⎤,然后将该矩阵每个元素相加得到最左上角的元素,即 3 + 1 + 2 + 0 + 0 + 0 + ( − 1 ) + ( − 8 ) + ( − 2 ) = − 5 3+1+2+0+0 +0+(-1)+(-8) +(-2)=-5 3+1+2+0+0+0+(−1)+(−8)+(−2)=−5。
接下来,为了弄明白第二个元素是什么,你要把蓝色的方块,向右移动一步:
继续做同样的元素乘法,然后加起来,所以是 $0×1+5×1+7×1+1×0+8×0+2×0+2×(-1)+ 9×(-1)+5×(-1)=-4 $。
因此6×6矩阵和3×3矩阵进行卷积运算得到4×4矩阵。这些图片和过滤器是不同维度的矩阵,但左边矩阵容易被理解为一张图片,中间的这个被理解为过滤器,右边的图片我们可以理解为另一张图片。这个就是垂直边缘检测器,下一页中你就会明白。
在往下讲之前,多说一句,如果你要使用编程语言实现这个运算,不同的编程语言有不同的函数,而不是用“ ∗ * ∗”来表示卷积。所以在编程练习中,你会使用一个叫conv_forward的函数。如果在tensorflow下,这个函数叫tf.conv2d。在其他深度学习框架中,在后面的课程中,你将会看到Keras这个框架,在这个框架下用Conv2D实现卷积运算。所有的编程框架都有一些函数来实现卷积运算。
为什么这个可以做垂直边缘检测呢?来看另外一个例子。
这是一个简单的6×6图像,左边的一半是10,右边一般是0。如果你把它当成一个图片,左边那部分看起来是白色的,像素值10是比较亮的像素值,右边像素值比较暗,使用灰色来表示0。图片里,有一个特别明显的垂直边缘在图像中间,这条垂直线是从黑到白的过渡线.
所以,当用一个3×3过滤器进行卷积运算的时候,这个3×3的过滤器可视化为在左边有明亮的像素,然后有一个过渡,0在中间,然后右边是深色的。卷积运算后,你得到的是右边的矩阵。 10 × 1 + 10 × 1 + 10 × 1 + 10 × 0 + 10 × 0 + 10 × 0 + 10 × ( − 1 ) + 10 × ( − 1 ) + 10 × ( − 1 ) = 0 10×1+10×1+10×1+10×0+10×0+10×0+10×(-1)+10×(-1)+10×(-1)=0 10×1+10×1+10×1+10×0+10×0+10×0+10×(−1)+10×(−1)+10×(−1)=0
相反这个30是由这个
10 × 1 + 10 × 1 + 10 × 1 + 10 × 0 + 10 × 0 + 10 × 0 + 0 × ( − 1 ) + 0 × ( − 1 ) + 0 × ( − 1 ) = 30 10×1+10×1+10×1+10×0+10×0+10×0+0×(-1)+0×(-1)+ 0×(-1)=30 10×1+10×1+10×1+10×0+10×0+10×0+0×(−1)+0×(−1)+0×(−1)=30。
在这个例子中,在输出图像中间的亮处,表示在图像中间有一个特别明显的垂直边缘。从垂直边缘检测中可以得到的启发是,因为我们使用3×3的过滤器,所以垂直边缘是一个3×3的区域,左边是明亮的像素,中间的并不需要考虑,右边是深色像素。在这个6×6图像的中间部分,明亮的像素在左边,深色的像素在右边,就被视为一个垂直边缘,卷积运算提供了一个方便的方法来发现图像中的垂直边缘。
这张6×6的图片,左边较亮,而右边较暗,将它与垂直边缘检测滤波器进行卷积,检测结果就显示在了右边这幅图的中间部分。
现在这幅图有什么变化呢?它的颜色被翻转了,变成了左边比较暗,而右边比较亮。现在亮度为10的点跑到了右边,为0的点则跑到了左边。如果你用它与相同的过滤器进行卷积,最后得到的图中间会是-30,而不是30。如果你将矩阵转换为图片,就会是该矩阵下面图片的样子。现在中间的过渡部分被翻转了,之前的30翻转成了-30,表明是由暗向亮过渡,而不是由亮向暗过渡。
如果你不在乎这两者的区别,你可以取出矩阵的绝对值。但这个特定的过滤器确实可以为我们区分这两种明暗变化的区别。
再来看看更多的边缘检测的例子,右边这个过滤器,它能让你检测出水平的边缘。
我们现在所使用的都是相对很小的图片,仅有6×6。但假如这个一个非常大的1000×1000的类似这样棋盘风格的大图,就不会出现这些亮度为10的过渡带了,因为图片尺寸很大,这些中间值就会变得非常小。
总而言之,通过使用不同的过滤器,可以找出垂直的或是水平的边缘。
除了上面提到的这种简单的Vertical、Horizontal滤波器之外,还有其它常用的filters,例如Sobel filter和Scharr filter。这两种滤波器的特点是增加图片中心区域的权重。
在深度学习中,如果我们想检测图片的各种边缘特征,而不仅限于垂直边缘和水平边缘,那么filter的数值一般需要通过模型训练得到,类似于标准神经网络中的权重W一样由梯度下降算法反复迭代求得。CNN的主要目的就是计算出这些filter的数值。确定得到了这些filter后,CNN浅层网络也就实现了对图片所有边缘特征的检测。
我们在之前视频中看到,如果你用一个3×3的过滤器卷积一个6×6的图像,你最后会得到一个4×4的输出,也就是一个4×4矩阵。这背后的数学解释是,如果我们有一个 n × n n×n n×n的图像,用 f × f f×f f×f的过滤器做卷积,那么输出的维度就是 ( n − f + 1 ) × ( n − f + 1 ) (n-f+1)×(n-f+1) (n−f+1)×(n−f+1)。
这样的话会有两个缺点:
每次做卷积操作,图像就会缩小。
丢掉了图像边缘位置的许多信息。
为了解决这些问题,可以在卷积操作之前填充这幅图像。在这个案例中,你可以沿着图像边缘再填充一层像素。6×6的图像就被你填充成了一个8×8的图像。如果你用3×3的图像对这个8×8的图像卷积,你得到的输出就是6×6的图像,一个尺寸和原始图像6×6的图像。
习惯上用0去填充,如果 p p p是填充的数量(在这个案例中, p = 1 p=1 p=1),因为我们在周围都填充了一个像素点,输出也就变成了 ( n + 2 p − f + 1 ) × ( n + 2 p − f + 1 ) (n+2p-f+1)×(n+2p-f+1) (n+2p−f+1)×(n+2p−f+1),所以就变成了 ( 6 + 2 × 1 − 3 + 1 ) × ( 6 + 2 × 1 − 3 + 1 ) = 6 × 6 (6+2×1-3+1)×(6+2×1-3+1)=6×6 (6+2×1−3+1)×(6+2×1−3+1)=6×6,和输入的图像一样大。这个涂绿的像素点(左边矩阵)影响了输出中的这些格子(右边矩阵)。这样一来,丢失信息或者更准确来说角落或图像边缘的信息发挥的作用较小的这一缺点就被削弱了。
如果你想的话,也可以填充两个像素点,也就是说在这里填充一层。实际上你还可以填充更多像素。我这里画的这种情况,填充后 p = 2 p=2 p=2。
选择填充多少像素:通常有两个选择,分别叫做Valid卷积和Same卷积。
Valid卷积意味着不填充。
Same卷积:那意味你填充后,输出大小和输入大小是一样的。
习惯上,计算机视觉中, f f f通常是奇数,有两个原因。
其中一个可能是,如果 f f f是一个偶数,那么你只能使用一些不对称填充。只有 f f f是奇数的情况下,Same卷积才会有自然的填充,我们可以以同样的数量填充四周,而不是左边填充多一点,右边填充少一点,这样不对称的填充。
第二个原因是当你有一个奇数维过滤器,比如3×3或者5×5的,它就有一个中心点。有时在计算机视觉里,如果有一个中心像素点会更方便,便于指出过滤器的位置。
如果你想用3×3的过滤器卷积这个7×7的图像,把步幅设置成了2,最后结果为91。
之前我们移动蓝框的步长是1,现在移动的步长是2,我们让过滤器跳过2个步长,注意一下左上角,这个点移动到其后两格的点,跳过了一个位置。然后你还是将每个元素相乘并求和,你将会得到的结果是100。
现在我们继续,将蓝色框移动两个步长,你将会得到83的结果。当你移动到下一行的时候,你也是使用步长2而不是步长1,所以我们将蓝色框移动到这里:
所以在这个例子中,我们用3×3的矩阵卷积一个7×7的矩阵,得到一个3×3的输出。输入和输出的维度是由下面的公式决定的:
用一个 f × f f×f f×f的过滤器卷积一个 n × n n×n n×n的图像,padding为 p p p,步幅为 s s s,在这个例子中 s = 2 s=2 s=2,得到一个输出 n + 2 p − f s + 1 × n + 2 p − f s + 1 \frac{n+2p - f}{s} + 1 \times \frac{n+2p - f}{s} + 1 sn+2p−f+1×sn+2p−f+1
在我们的这个例子里, n = 7 n=7 n=7, p = 0 p=0 p=0, f = 3 f=3 f=3, s = 2 s=2 s=2, 7 + 0 − 3 2 + 1 = 3 \ \frac{7 + 0 - 3}{2} + 1 =3 27+0−3+1=3,即3×3的输出。
如果商不是一个整数怎么办?在这种情况下,我们向下取整。 ⌊ ⌋ ⌊ ⌋ ⌊⌋这是向下取整的符号,这也叫做对 z z z进行地板除(floor)。
这个原则实现的方式是,你只在蓝框完全包括在图像或填充完的图像内部时,才对它进行运算。如果有任意一个蓝框移动到了外面,那你就不要进行相乘操作。你的3×3的过滤器必须完全处于图像中或者填充之后的图像区域内才输出相应结果。
总结一下维度情况,如果你有一个 n × n n×n n×n的矩阵或者 n × n n×n n×n的图像,与一个 f × f f×f f×f的矩阵卷积,或者说 f × f f×f f×f的过滤器。Padding是 p p p,步幅为 s s s没输出尺寸就是这样:
可以选择所有的数使结果是整数是挺不错的,尽管一些时候,你不必这样做,只要向下取整也就可以了。你也可以自己选择一些 n n n, f f f, p p p和 s s s的值来验证这个输出尺寸的公式是对的。
其实,目前为止我们介绍的CNN卷积实际上计算的是相关系数,而不是数学意义上的卷积。但是,为了简化计算,我们一般把CNN中的这种“相关系数”就称作卷积运算。之所以可以这么等效,是因为滤波器算子一般是水平或垂直对称的,180度旋转影响不大;而且最终滤波器算子需要通过CNN网络梯度下降算法计算得到,旋转部分可以看作是包含在CNN模型算法中。总的来说,忽略旋转运算可以大大提高CNN网络运算速度,而且不影响模型性能。
卷积运算服从结合律。
现在看看如何执行卷积不仅仅在二维图像上,而是三维立体上。
假如说想检测RGB彩色图像的特征。彩色图像如果是6×6×3,这里的3指的是三个颜色通道,你可以把它想象成三个6×6图像的堆叠。为了检测图像的边缘或者其他的特征,跟一个三维的过滤器,它的维度是3×3×3,这样这个过滤器也有三层,对应红绿、蓝三个通道。
给这些起个名字(原图像),这里的第一个6代表图像高度,第二个6代表宽度,这个3代表通道的数目。同样你的过滤器也有一个高,宽和通道数,并且图像的通道数必须和过滤器的通道数匹配。这个的输出会是一个4×4的图像,注意是4×4×1,最后一个数不是3了。
我们研究下这背后的细节,首先先换一张好看的图片。这个是6×6×3的图像,这个是3×3×3的过滤器,最后一个数字通道数必须和过滤器中的通道数相匹配。为了简化这个3×3×3过滤器的图像,我们不把它画成3个矩阵的堆叠,而画成这样,一个三维的立方体。
为了计算这个卷积操作的输出,你要做的就是把这个3×3×3的过滤器先放到最左上角的位置,这个3×3×3的过滤器有27个数,27个参数就是3的立方。依次取这27个数,然后乘以相应的红绿蓝通道中的数字。先取红色通道的前9个数字,然后是绿色通道,然后再是蓝色通道,乘以左边黄色立方体覆盖的对应的27个数,然后把这些数都加起来,就得到了输出的第一个数字。
如果要计算下一个输出,你把这个立方体滑动一个单位,再与这27个数相乘,把它们都加起来,就得到了下一个输出,以此类推。
举个例子,这个过滤器是3×3×3的,如果你想检测图像红色通道的边缘,那么你可以将第一个过滤器设为 [ 1 0 − 1 1 0 − 1 1 0 − 1 ] \begin{bmatrix}1 & 0 & - 1 \\ 1 & 0 & - 1 \\ 1 & 0 & - 1 \\ \end{bmatrix} ⎣⎡111000−1−1−1⎦⎤,和之前一样,而绿色通道全为0, [ 0 0 0 0 0 0 0 0 0 ] \begin{bmatrix} 0& 0 & 0 \\ 0 &0 & 0 \\ 0 & 0 & 0 \\\end{bmatrix} ⎣⎡000000000⎦⎤,蓝色也全为0。如果你把这三个堆叠在一起形成一个3×3×3的过滤器,那么这就是一个检测垂直边界的过滤器,但只对红色通道有用。
或者如果你不关心垂直边界在哪个颜色通道里,那么你可以用一个这样的过滤器, [ 1 0 − 1 1 0 − 1 1 0 − 1 ] \begin{bmatrix}1 & 0 & - 1 \\ 1 & 0 & - 1 \\ 1 & 0 & - 1 \\ \end{bmatrix} ⎣⎡111000−1−1−1⎦⎤, [ 1 0 − 1 1 0 − 1 1 0 − 1 ] \begin{bmatrix}1 & 0 & - 1 \\ 1 & 0 & - 1 \\ 1 & 0 & - 1 \\ \end{bmatrix} ⎣⎡111000−1−1−1⎦⎤, [ 1 0 − 1 1 0 − 1 1 0 − 1 ] \begin{bmatrix}1 & 0 & - 1 \\ 1 & 0 & - 1 \\ 1 & 0 & - 1 \\\end{bmatrix} ⎣⎡111000−1−1−1⎦⎤,所有三个通道都是这样。所以通过设置第二个过滤器参数,你就有了一个边界检测器,3×3×3的边界检测器,用来检测任意颜色通道里的边界。
按照计算机视觉的惯例,当你的输入有特定的高宽和通道数时,你的过滤器可以有不同的高,不同的宽,但是必须一样的通道数。理论上,我们的过滤器只关注红色通道,或者只关注绿色或者蓝色通道也是可行的。
再注意一下这个卷积立方体,一个6×6×6的输入图像卷积上一个3×3×3的过滤器,得到一个4×4的二维输出。
现在你已经了解了如何对立方体卷积,还有最后一个概念,对建立卷积神经网络至关重要。就是,如果我们不仅仅想要检测垂直边缘怎么办?如果我们同时检测垂直边缘和水平边缘,还有45°倾斜的边缘,还有70°倾斜的边缘怎么做?换句话说,如果你想同时用多个过滤器怎么办?
为了进行多个卷积运算,实现更多边缘检测,可以增加更多的滤波器组。例如设置第一个滤波器组实现垂直边缘检测,第二个滤波器组实现水平边缘检测。这样,不同滤波器组卷积得到不同的输出,个数由滤波器组决定。
若输入图片的尺寸为 n ∗ n ∗ n c n*n*n_c n∗n∗nc,filter尺寸为 f ∗ f ∗ n c f*f*n_c f∗f∗nc ,则卷积后的图片尺寸为 ( n − f + 1 ) ∗ ( n − f + 1 ) ∗ n c ′ (n-f+1) *(n-f+1) *n_c' (n−f+1)∗(n−f+1)∗nc′ 。其中,$n_c $ 为图片通道数目, n c ′ n_c' nc′为滤波器组个数。
假设使用第一个过滤器进行卷积,得到第一个4×4矩阵。使用第二个过滤器进行卷积得到另外一个4×4矩阵。
最终各自形成一个卷积神经网络层,然后增加偏差,它是一个实数,通过Python的广播机制。然后应用非线性函数ReLU,输出结果是一个4×4矩阵。
对于第二个4×4矩阵,我们加上不同的偏差,它也是一个实数,16个数字都加上同一个实数,然后应用非线性激活函数ReLU,最终得到另一个4×4矩阵。然后重复我们之前的步骤,把这两个矩阵堆叠起来,最终得到一个4×4×2的矩阵。我们通过计算,从6×6×3的输入推导出一个4×4×2矩阵,它是卷积神经网络的一层,把它映射到标准神经网络中四个卷积层中的某一层或者一个非卷积神经网络中。
注意前向传播中一个操作是 z [ 1 ] = W [ 1 ] a [ 0 ] + b [ 1 ] z^{[1]} = W^{[1]}a^{[0]} + b^{[1]} z[1]=W[1]a[0]+b[1],其中 a [ 0 ] = x a^{[0]} =x a[0]=x,执行非线性函数 a [ 1 ] = g ( z [ 1 ] ) a^{[1]} = g(z^{[1]}) a[1]=g(z[1])。这里的输入是 a [ 0 ] a^{\left\lbrack 0\right\rbrack} a[0],也就是 x x x,这些过滤器用变量 W [ 1 ] W^{[1]} W[1]表示。在卷积过程中,我们对这27个数进行操作,其实是27×2,因为我们用了两个过滤器,我们取这些数做乘法。实际执行了一个线性函数,得到一个4×4的矩阵。卷积操作的输出结果是一个4×4的矩阵,它的作用类似于 W [ 1 ] a [ 0 ] W^{[1]}a^{[0]} W[1]a[0],也就是这两个4×4矩阵的输出结果,然后加上偏差。
这一部分(图中蓝色边框标记的部分)就是应用激活函数ReLU之前的值,它的作用类似于 z [ 1 ] z^{[1]} z[1],最后应用非线性函数,得到的这个4×4×2矩阵,成为神经网络的下一层,也就是激活层。
这就是 a [ 0 ] a^{[0]} a[0]到 a [ 1 ] a^{[1]} a[1]的演变过程: 运用线性函数再加上偏差,然后应用激活函数ReLU。这样就通过神经网络的一层把一个6×6×3的维度 a [ 0 ] a^{[0]} a[0]演化为一个4×4×2维度的 a [ 1 ] a^{[1]} a[1],这就是卷积神经网络的一层。
示例中我们有两个过滤器,也就是有两个特征,因此我们才最终得到一个4×4×2的输出。但如果我们用了10个过滤器,而不是2个,我们最后会得到一个4×4×10维度的输出图像.
最后我们总结一下用于描述卷积神经网络中的一层(以 l l l层为例),也就是卷积层的各种标记。
这一层是卷积层,用 f [ l ] f^{[l]} f[l]表示过滤器大小,我们说过过滤器大小为 f × f f×f f×f,上标 [ l ] \lbrack l\rbrack [l]表示 l l l层中过滤器大小为 f × f f×f f×f。通常情况下,上标 [ l ] \lbrack l\rbrack [l]用来标记 l l l层。用 p [ l ] p^{[l]} p[l]来标记padding的数量,padding数量也可指定为一个valid卷积,即无padding。或是same卷积,即选定padding,如此一来,输出和输入图片的高度和宽度就相同了。用 s [ l ] s^{[l]} s[l]标记步幅。
这一层的输入会是某个维度的数据,表示为 n × n × n c n \times n \times n_{c} n×n×nc, n c n_{c} nc某层上的颜色通道数。
增加上标 [ l − 1 ] \lbrack l -1\rbrack [l−1],即 n [ l − 1 ] × n [ l − 1 ] × n c [ l − 1 ] n^{\left\lbrack l - 1 \right\rbrack} \times n^{\left\lbrack l -1 \right\rbrack} \times n_{c}^{\left\lbrack l - 1\right\rbrack} n[l−1]×n[l−1]×nc[l−1],因为它是上一层的激活值。
此例中,所用图片的高度和宽度都一样,但它们也有可能不同,所以分别用上下标 H H H和 W W W来标记,即 n H [ l − 1 ] × n W [ l − 1 ] × n c [ l − 1 ] n_{H}^{\left\lbrack l - 1 \right\rbrack} \times n_{W}^{\left\lbrack l - 1 \right\rbrack} \times n_{c}^{\left\lbrack l - 1\right\rbrack} nH[l−1]×nW[l−1]×nc[l−1]。那么在第 l l l层,图片大小为 n H [ l − 1 ] × n W [ l − 1 ] × n c [ l − 1 ] n_{H}^{\left\lbrack l - 1 \right\rbrack} \times n_{W}^{\left\lbrack l - 1 \right\rbrack} \times n_{c}^{\left\lbrack l - 1\right\rbrack} nH[l−1]×nW[l−1]×nc[l−1], l l l层的输入就是上一层的输出,因此上标要用 [ l − 1 ] \lbrack l - 1\rbrack [l−1]。神经网络这一层中会有输出,它本身会输出图像。其大小为 n H [ l ] × n W [ l ] × n c [ l ] n_{H}^{[l]} \times n_{W}^{[l]} \times n_{c}^{[l]} nH[l]×nW[l]×nc[l],这就是输出图像的大小。
前面我们提到过,这个公式给出了输出图片的大小,至少给出了高度和宽度, ⌊ n + 2 p − f s + 1 ⌋ \lfloor\frac{n+2p - f}{s} + 1\rfloor ⌊sn+2p−f+1⌋(注意:( n + 2 p − f s + 1 ) \frac{n + 2p - f}{s} +1) sn+2p−f+1)直接用这个运算结果,也可以向下取整)。在这个新表达式中, l l l层输出图像的高度,即 n H [ l ] = ⌊ n H [ l − 1 ] + 2 p [ l ] − f [ l ] s [ l ] + 1 ⌋ n_{H}^{[l]} = \lfloor\frac{n_{H}^{\left\lbrack l - 1 \right\rbrack} +2p^{[l]} - f^{[l]}}{s^{[l]}} +1\rfloor nH[l]=⌊s[l]nH[l−1]+2p[l]−f[l]+1⌋,同样我们可以计算出图像的宽度,用 W W W替换参数 H H H,即 n W [ l ] = ⌊ n W [ l − 1 ] + 2 p [ l ] − f [ l ] s [ l ] + 1 ⌋ n_{W}^{[l]} = \lfloor\frac{n_{W}^{\left\lbrack l - 1 \right\rbrack} +2p^{[l]} - f^{[l]}}{s^{[l]}} +1\rfloor nW[l]=⌊s[l]nW[l−1]+2p[l]−f[l]+1⌋,公式一样,只要变化高度和宽度的参数我们便能计算输出图像的高度或宽度。这就是由 n H [ l − 1 ] n_{H}^{\left\lbrack l - 1 \right\rbrack} nH[l−1]推导 n H [ l ] n_{H}^{[l]} nH[l]以及 n W [ l − 1 ] n_{W}^{\left\lbrack l - 1\right\rbrack} nW[l−1]推导 n W [ l ] n_{W}^{[l]} nW[l]的过程。
那么通道数量又是什么?这些数字从哪儿来的?输出通道数量就是输入通道数量,所以过滤器维度等于 f [ l ] × f [ l ] × n c [ l − 1 ] f^{[l]} \times f^{[l]} \times n_{c}^{\left\lbrack l - 1 \right\rbrack} f[l]×f[l]×nc[l−1]。
应用偏差和非线性函数之后,这一层的输出等于它的激活值 a [ l ] a^{[l]} a[l],也就是这个维度(输出维度)。 a [ l ] a^{[l]} a[l]是一个三维体,即 n H [ l ] × n W [ l ] × n c [ l ] n_{H}^{[l]} \times n_{W}^{[l]} \times n_{c}^{[l]} nH[l]×nW[l]×nc[l]。当你执行批量梯度下降或小批量梯度下降时,如果有 m m m个例子,就是有 m m m个激活值的集合,那么输出 A [ l ] = m × n H [ l ] × n W [ l ] × n c [ l ] A^{[l]} = m \times n_{H}^{[l]} \times n_{W}^{[l]} \times n_{c}^{[l]} A[l]=m×nH[l]×nW[l]×nc[l]。如果采用批量梯度下降,变量的排列顺序如下,首先是索引和训练示例,然后是其它三个变量。
该如何确定权重参数,即参数W呢?过滤器的维度已知,为 f [ l ] × f [ l ] × n c [ l − 1 ] f^{[l]} \times f^{[l]} \times n_{c}^{[l - 1]} f[l]×f[l]×nc[l−1],这只是一个过滤器的维度,有多少个过滤器,这( n c [ l ] n_{c}^{[l]} nc[l])是过滤器的数量,权重也就是所有过滤器的集合再乘以过滤器的总数量,即 f [ l ] × f [ l ] × n c [ l − 1 ] × n c [ l ] f^{[l]} \times f^{[l]} \times n_{c}^{[l - 1]} \times n_{c}^{[l]} f[l]×f[l]×nc[l−1]×nc[l],损失数量L就是 l l l层中过滤器的个数。
最后我们看看偏差参数,每个过滤器都有一个偏差参数,它是一个实数。偏差包含了这些变量,它是该维度上的一个向量。为了方便,偏差在代码中表示为一个1×1×1× n c [ l ] n_{c}^{[l]} nc[l]的四维向量或四维张量。
除了卷积层,卷积网络也经常使用池化层来缩减模型的大小,提高计算速度,同时提高所提取特征的鲁棒性。
先举一个池化层的例子,然后我们再讨论池化层的必要性。假如输入是一个4×4矩阵,用到的池化类型是最大池化(max pooling)。执行最大池化的树池是一个2×2矩阵。
执行过程非常简单,把4×4的输入拆分成不同的区域,我把这个区域用不同颜色来标记。对于2×2的输出,输出的每个元素都是其对应颜色区域中的最大元素值。
因为我们使用的过滤器为2×2,最后输出是9。然后向右移动2个步幅,计算出最大值2。然后是第二行,向下移动2步得到最大值6。最后向右移动3步,得到最大值3。这是一个2×2矩阵,即 f = 2 f=2 f=2,步幅是2,即 s = 2 s=2 s=2。
最大化运算的实际作用就是,如果在过滤器中提取到某个特征,那么保留其最大值。如果没有提取到这个特征,可能在右上象限中不存在这个特征,那么其中的最大值也还是很小,这就是最大池化的直观理解。
人们使用最大池化的主要原因是此方法在很多实验中效果都很好。
其中一个有意思的特点就是,它有一组超参数,但并没有参数需要学习。
另外还有一种类型的池化,平均池化,这种运算顾名思义,选取的不是每个过滤器的最大值,而是平均值。
目前来说,最大池化比平均池化更常用。
总结:
池化的超级参数包括过滤器大小 f f f和步幅 s s s,常用的参数值为 f = 2 f=2 f=2, s = 2 s=2 s=2,应用频率非常高,其效果相当于高度和宽度缩减一半。
最大池化时,往往很少用到超参数padding. 输入通道与输出通道个数相同,因为我们对每个通道都做了池化。
池化过程中没有需要学习的参数。执行反向传播时,反向传播没有参数适用于最大池化。只有这些设置过的超参数,可能是手动设置的,也可能是通过交叉验证设置的。
LeNet-5针对灰度图片训练的,可以识别图中的手写数字, 图片的大小只有32×32×1。
LeNet-5的结构使用6个5×5的过滤器,步幅为1,padding为0,输出结果为28×28×6。
池化操作:平均池化,过滤器的宽度为2,步幅为2,图像的尺寸,高度和宽度都缩小了2倍,输出结果是一个14×14×6的图像。
卷积层:用一组16个5×5的过滤器,新的输出结果有16个通道。没有padding,每进行一次卷积,图像的高度和宽度都会缩小,所以这个图像从14到14缩小到了10×10。然后又是池化层,高度和宽度再缩小一半,输出一个5×5×16的图像。将所有数字相乘,乘积是400。
全连接层:在全连接层中,有400个节点,每个节点有120个神经元,这里已经有了一个全连接层。但有时还会从这400个节点中抽取一部分节点构建另一个全连接层,就像这样,有2个全连接层。
最后一步就是利用这84个特征得到最后的输出,我们还可以在这里再加一个节点用来预测 y ^ \hat{y} y^的值, y ^ \hat{y} y^有10个可能的值,对应识别0-9这10个数字。在现在的版本中则使用softmax函数输出十种分类结果,而在当时,LeNet-5网络在输出层使用了另外一种。
不管怎样,如果我们从左往右看,随着网络越来越深,图像的高度和宽度在缩小,从最初的32×32缩小到28×28,再到14×14、10×10,最后只有5×5。与此同时,随着网络层次的加深,通道数量一直在增加,从1增加到6个,再到16个。
这个神经网络中还有一种模式至今仍然经常用到,就是一个或多个卷积层后面跟着一个池化层,然后又是若干个卷积层再接一个池化层,然后是全连接层,最后是输出。
AlexNet是在LeNet的基础上加深了网络的结构,学习更丰富更高维的图像特征。AlexNet的特点:
AlexNet首先用一张227×227×3的图片作为输入:
第一层使用96个11×11的过滤器,步幅为4,尺寸缩小到55×55,然后用一个3×3的过滤器构建最大池化层, f = 3 f=3 f=3,步幅 s s s为2,卷积层尺寸缩小为27×27×96。
接着再执行一个5×5的卷积,padding之后,输出是27×27×276。然后再次进行最大池化,尺寸缩小到13×13。
再执行一次same卷积,相同的padding,得到的结果是13×13×384,384个过滤器。再做一次same卷积。再做一次同样的操作,最后再进行一次最大池化,尺寸缩小到6×6×256。6×6×256等于9216,将其展开为9216个单元,然后是一些全连接层。最后使用softmax函数输出识别的结果,看它究竟是1000个可能的对象中的哪一个。
这种神经网络与LeNet有很多相似之处,不过AlexNet要大得多。正如前面讲到的LeNet或LeNet-5大约有6万个参数,而AlexNet包含约6000万个参数。当用于训练图像和数据集时,AlexNet能够处理非常相似的基本构造模块,这些模块往往包含着大量的隐藏单元或数据,这一点AlexNet表现出色。
AlexNet比LeNet表现更为出色的另一个原因是它使用了ReLu激活函数。
也叫作VGG-16网络。VGG-16网络没有那么多超参数,这是一种只需要专注于构建卷积层的简单网络。
卷积层:CONV=3*3 filters, s = 1, padding = same convolution。
池化层:MAX_POOL = 2*2 , s = 2。
如此进行几轮操作后,将最后得到的7×7×512的特征图进行全连接操作,得到4096个单元,然后进行softmax激活,输出从1000个对象中识别的结果。
VGG-16的16,指在这个网络中包含13层卷积层+3层全连接层。确实是个很大的网络,但VGG-16的结构并不复杂,而且这种网络结构很规整,都是几个卷积层后面跟着可以压缩图像大小的池化层,池化层缩小图像的高度和宽度。
同时,卷积层的过滤器数量变化存在一定的规律,由64翻倍变成128,再到256和512。作者可能认为512已经足够大了,所以后面的层就不再翻倍了。无论如何,每一步都进行翻倍,或者说在每一组卷积层进行过滤器翻倍操作,正是设计此种网络结构的另一个简单原则。这种相对一致的网络结构对研究者很有吸引力.
主要缺点是需要训练的特征数量非常巨大。
随着网络加深,图像的宽度和高度都在以一定的规律不断减小,每次池化后刚好缩小一半,信道数目不断增加一倍。
非常非常深的神经网络是很难训练的,因为存在梯度消失和梯度爆炸问题。这节课我们学习跳跃连接(Skip connection),它可以从某一层网络层获取激活,然后迅速反馈给另外一层,甚至是神经网络的更深层。我们可以利用跳跃连接构建能够训练深度网络的ResNets,有时深度能够超过100层。
ResNets是由残差块(Residual block)构建的。
这是一个两层神经网络,在 L L L层进行激活,得到 a [ l + 1 ] a^{\left\lbrack l + 1 \right\rbrack} a[l+1],再次进行激活,两层之后得到 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} a[l+2]。计算过程是从 a [ l ] a^{[l]} a[l]开始,首先进行线性激活,根据这个公式: z [ l + 1 ] = W [ l + 1 ] a [ l ] + b [ l + 1 ] z^{\left\lbrack l + 1 \right\rbrack} = W^{\left\lbrack l + 1 \right\rbrack}a^{[l]} + b^{\left\lbrack l + 1 \right\rbrack} z[l+1]=W[l+1]a[l]+b[l+1],通过 a [ l ] a^{[l]} a[l]算出 z [ l + 1 ] z^{\left\lbrack l + 1 \right\rbrack} z[l+1],即 a [ l ] a^{[l]} a[l]乘以权重矩阵,再加上偏差因子。然后通过ReLU非线性激活函数得到 a [ l + 1 ] a^{\left\lbrack l + 1 \right\rbrack} a[l+1], a [ l + 1 ] = g ( z [ l + 1 ] ) a^{\left\lbrack l + 1 \right\rbrack} =g(z^{\left\lbrack l + 1 \right\rbrack}) a[l+1]=g(z[l+1])计算得出。接着再次进行线性激活,依据等式 z [ l + 2 ] = W [ 2 + 1 ] a [ l + 1 ] + b [ l + 2 ] z^{\left\lbrack l + 2 \right\rbrack} = W^{\left\lbrack 2 + 1 \right\rbrack}a^{\left\lbrack l + 1 \right\rbrack} + b^{\left\lbrack l + 2 \right\rbrack} z[l+2]=W[2+1]a[l+1]+b[l+2],最后根据这个等式再次进行ReLu非线性激活,即 a [ l + 2 ] = g ( z [ l + 2 ] ) a^{\left\lbrack l + 2 \right\rbrack} = g(z^{\left\lbrack l + 2 \right\rbrack}) a[l+2]=g(z[l+2]),这里的 g g g是指ReLU非线性函数,得到的结果就是 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} a[l+2]。换句话说,信息流从 a [ l ] a^{\left\lbrack l \right\rbrack} a[l]到 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} a[l+2]需要经过以上所有步骤,即这组网络层的主路径。
在残差网络中有一点变化,我们将 a [ l ] a^{[l]} a[l]直接向后,拷贝到神经网络的深层,在ReLU非线性激活函数前加上 a [ l ] a^{[l]} a[l],这是一条捷径。 a [ l ] a^{[l]} a[l]的信息直接到达神经网络的深层,不再沿着主路径传递,这就意味着最后这个等式( a [ l + 2 ] = g ( z [ l + 2 ] ) a^{\left\lbrack l + 2 \right\rbrack} = g(z^{\left\lbrack l + 2 \right\rbrack}) a[l+2]=g(z[l+2]))去掉了,取而代之的是另一个ReLU非线性函数,仍然对 z [ l + 2 ] z^{\left\lbrack l + 2 \right\rbrack} z[l+2]进行 g g g函数处理,但这次要加上 a [ l ] a^{[l]} a[l],即: a [ l + 2 ] = g ( z [ l + 2 ] + a [ l ] ) \ a^{\left\lbrack l + 2 \right\rbrack} = g\left(z^{\left\lbrack l + 2 \right\rbrack} + a^{[l]}\right) a[l+2]=g(z[l+2]+a[l]),也就是加上的这个 a [ l ] a^{[l]} a[l]产生了一个残差块。
有了ResNets,即使网络再深,训练的表现却不错。
假设有一个大型神经网络,假如你想增加这个神经网络的深度,那么用Big NN表示,输出为 a [ l ] a^{\left\lbrack l\right\rbrack} a[l]。再给这个网络额外添加两层,最后输出为 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} a[l+2],可以把这两层看作一个ResNets块,即具有捷径连接的残差块。假设我们在整个网络中使用ReLU激活函数。
a [ l + 2 ] = g ( z [ l + 2 ] + a [ l ] ) a^{\left\lbrack l + 2\right\rbrack} = g(z^{\left\lbrack l + 2 \right\rbrack} + a^{\left\lbrack l\right\rbrack}) a[l+2]=g(z[l+2]+a[l]),展开这个表达式 a [ l + 2 ] = g ( W [ l + 2 ] a [ l + 1 ] + b [ l + 2 ] + a [ l ] ) a^{\left\lbrack l + 2 \right\rbrack} = g(W^{\left\lbrack l + 2 \right\rbrack}a^{\left\lbrack l + 1 \right\rbrack} + b^{\left\lbrack l + 2 \right\rbrack} + a^{\left\lbrack l\right\rbrack}) a[l+2]=g(W[l+2]a[l+1]+b[l+2]+a[l])这里的 W W W是关键项,如果 W [ l + 2 ] = 0 W^{\left\lbrack l + 2 \right\rbrack} = 0 W[l+2]=0,为方便起见,假设 b [ l + 2 ] = 0 b^{\left\lbrack l + 2 \right\rbrack} = 0 b[l+2]=0,这几项就没有了,最后 a [ l + 2 ] = g ( a [ l ] ) = a [ l ] a^{\left\lbrack l + 2 \right\rbrack} = \ g\left( a^{[l]} \right) = a^{\left\lbrack l\right\rbrack} a[l+2]= g(a[l])=a[l],因为我们假定使用ReLU激活函数,并且所有激活值都是非负的, g ( a [ l ] ) g\left(a^{[l]} \right) g(a[l])是应用于非负数的ReLU函数,所以 a [ l + 2 ] = a [ l ] a^{[l+2]} =a^{[l]} a[l+2]=a[l]。
结果表明,残差块学习这个恒等式函数并不难,跳跃连接使我们很容易得出 a [ l + 2 ] = a [ l ] a^{\left\lbrack l + 2 \right\rbrack} = a^{\left\lbrack l\right\rbrack} a[l+2]=a[l]。
这意味着,即使给神经网络增加了这两层,它的效率也并不逊色于更简单的神经网络。所以给大型神经网络增加两层,不论是把残差块添加到神经网络的中间还是末端位置,都不会影响网络的表现。
当然,我们的目标还要提升它的效率。想象一下,如果这些隐藏层单元学到一些有用信息,那么它可能比学习恒等函数表现得更好。
残差网络起作用的主要原因就是这些残差块学习恒等函数非常容易,你能确定网络性能不会受到影响,很多时候甚至可以提高效率,或者说至少不会降低网络的效率,因此创建类似残差网络可以提升网络性能。
如果输入和输出有不同维度,比如输入的维度是128, a [ l + 2 ] a^{\left\lbrack l + 2\right\rbrack} a[l+2]的维度是256,再增加一个矩阵,这里标记为 W s W_{s} Ws, W s W_{s} Ws是一个256×128维度的矩阵,所以 W s a [ l ] W_{s}a^{\left\lbrack l\right\rbrack} Wsa[l]的维度是256,这个新增项是256维度的向量。你不需要对 W s W_{s} Ws做任何操作,它是网络通过学习得到的矩阵或参数,它是一个固定矩阵,padding值为0,用0填充 a [ l ] a^{[l]} a[l],其维度为256,所以者几个表达式都可以。
最后,我们来看看ResNets的图片识别。这些图片是我从何凯明等人论文中截取的,这是一个普通网络,我们给它输入一张图片,它有多个卷积层,最后输出了一个Softmax。
如何把它转化为ResNets呢?只需要添加跳跃连接。这里我们只讨论几个细节,这个网络有很多层3×3卷积,而且它们大多都是same卷积,这就是添加等维特征向量的原因。所以这些都是卷积层,而不是全连接层,因为它们是same卷积,维度得以保留,这也解释了添加项(维度相同所以能够相加)。
ResNets类似于其它很多网络,也会有很多卷积层,其中偶尔会有池化层或类池化层的层。不论这些层是什么类型,正如我们在上一张幻灯片看到的,你都需要调整矩阵 W s W_{s} Ws的维度。普通网络和ResNets网络常用的结构是:卷积层-卷积层-卷积层-池化层-卷积层-卷积层-卷积层-池化层……依此重复。直到最后,有一个通过softmax进行预测的全连接层。
假设当前输入张量维度为6×6×32,卷积核维度为1×1×32,取输入张量的某一个位置(如图黄色区域)与卷积核进行运算。
实际上可以看到,如果把1×1×32卷积核看成是32个权重W,输入张量运算的1×1×32部分为输入x,那么每一个卷积操作相当于一个Wx过程,多个卷积核就是多个神经元,相当于一个全连接网络。
综上,可以将1×1卷积过程看成是将输入张量分为一个个输入为1×1×32的x,他们共享卷积核变量(对应全连接网络的权重)W的全连接网络。
一般来说,如果过滤器不止一个,而是多个,就好像有多个输入单元,其输入内容为一个切片上所有数字,输出结果是6×6过滤器数量。
所以1×1卷积可以从根本上理解为对这32个不同的位置都应用一个全连接层,全连接层的作用是输入32个数字(过滤器数量标记为 n C [ l + 1 ] n_{C}^{\left\lbrack l + 1\right\rbrack} nC[l+1],在这36个单元上重复此过程),输出结果是6×6×#filters(过滤器数量),以便在输入层上实施一个非平凡(non-trivial)计算。
这种方法通常称为1×1卷积,有时也被称为Network in Network。
1×1卷积核作用:
放缩nc的大小
通过控制卷积核的数量达到通道数大小的放缩。池化层只能改变高度和宽度,无法改变通道数。
增加非线性
如上所述,1×1卷积核的卷积过程相当于全连接层的计算过程,并且还加入了非线性激活函数,从而可以增加网络的非线性,使得网络可以表达更加复杂的特征。
减少参数
可以通过引入1×1确保效果的同时减少计算量。
这是28×28×192维度的输入层,Inception网络或Inception层的作用就是代替人工来确定卷积层中的过滤器类型,或者确定是否需要创建卷积层或池化层。
如果使用1×1卷积,输出结果会是28×28×#(某个值),假设输出为28×28×64,并且这里只有一个层。
如果使用3×3的过滤器,那么输出是28×28×128。然后我们把第二个值堆积到第一个值上,为了匹配维度,我们应用same卷积,输出维度依然是28×28。
或许用5×5过滤器或许会更好,输出变成28×28×32,我们再次使用same卷积,保持维度不变。
或许不想要卷积层,那就用池化操作,得到一些不同的输出结果,我们把它也堆积起来,这里的池化输出是28×28×32。为了匹配所有维度,我们需要对最大池化使用padding,它是一种特殊的池化形式,因为如果输入的高度和宽度为28×28,则输出的相应维度也是28×28。然后再进行池化,padding不变,步幅为1。
Inception模块累加了所有数字,模块的输入为28×28×192,输出为28×28×256。这就是Inception网络的核心内容。基本思想是Inception网络不需要人为决定使用哪个过滤器或者是否需要池化,而是由网络自行确定这些参数,你可以给网络添加这些参数的所有可能值,然后把这些输出连接起来,让网络自己学习它需要什么样的参数,采用哪些过滤器组合。
Inception层有一个问题,就是计算成本。
有另外一种架构,其输入为28×28×192,输出为28×28×32。对于输入层,使用1×1卷积把输入值从192个通道减少到16个通道。然后对这个较小层运行5×5卷积,得到最终输出。请注意,输入和输出的维度依然相同,输入是28×28×192,输出是28×28×32,和上一页的相同。但我们要做的就是把左边这个大的输入层压缩成这个较小的的中间层,它只有16个通道,而不是192个。
有时候这被称为瓶颈层。
瓶颈层也是网络中最小的部分,我们先缩小网络表示,然后再扩大它。
接下来我们看看这个计算成本,应用1×1卷积,过滤器个数为16,每个过滤器大小为1×1×192,这两个维度相匹配(输入通道数与过滤器通道数),28×28×16这个层的计算成本是,输出28×28×192中每个元素都做192次乘法,用1×1×192来表示,相乘结果约等于240万。
那第二个卷积层呢?240万只是第一个卷积层的计算成本,第二个卷积层的计算成本又是多少呢?这是它的输出,28×28×32,对每个输出值应用一个5×5×16维度的过滤器,计算结果为1000万。
所以所需要乘法运算的总次数是这两层的计算成本之和,也就是1204万,与上一张幻灯片中的值做比较,计算成本从1.2亿下降到了原来的十分之一,即1204万。所需要的加法运算与乘法运算的次数近似相等,所以我只统计了乘法运算的次数。
总结一下,如果你在构建神经网络层的时候,不想决定池化层是使用1×1,3×3还是5×5的过滤器,那么Inception模块就是最好的选择。我们可以应用各种类型的过滤器,只需要把输出连接起来。关于计算成本问题,使用1×1卷积来构建瓶颈层,从而大大降低计算成本。
如果你要做一个计算机视觉的应用,相比于从头训练权重,或者说从随机初始化权重开始,如果你下载别人已经训练好网络结构的权重,你通常能够进展的相当快,用这个作为预训练,然后转换到你感兴趣的任务上。
举个例子,假如说你要建立一个猫咪检测器,用来检测你自己的宠物猫。假如你的两只猫叫Tigger和Misty,还有一种情况是,两者都不是。所以你现在有一个三分类问题我们忽略两只猫同时出现在一张图片里的情况。现在你可能没有Tigger或者Misty的大量的图片,所以你的训练集会很小,你该怎么办呢?
我建议你从网上下载一些神经网络开源的实现,不仅把代码下载下来,也把权重下载下来。有许多训练好的网络,你都可以下载。举个例子,ImageNet数据集,它有1000个不同的类别,因此这个网络会有一个Softmax单元,它可以输出1000个可能类别之一。
你可以去掉这个Softmax层,创建你自己的Softmax单元。就网络而言,冻结网络中所有层的参数,你只需要训练和你的Softmax层有关的参数。这个Softmax层有三种可能的输出,Tigger、Misty或者都不是。
通过使用其他人预训练的权重,你很可能得到很好的性能,即使只有一个小的数据集。幸运的是,大多数深度学习框架都支持这种操作,事实上,取决于用的框架,它也许会有trainableParameter=0这样的参数,对于这些前面的层,你可能会设置这个参数。为了不训练这些权重,有时也会有freeze=1这样的参数。
另一个技巧,由于前面的层都冻结了,相当于一个固定的函数,不需要改变。因为你不需要改变它,也不训练它,取输入图像 X X X,然后把它映射到这层(softmax的前一层)的激活函数。所以这个能加速训练的技巧就是,如果我们先计算这一层(紫色箭头标记),计算特征或者激活值,然后把它们存到硬盘里。你所做的就是用这个固定的函数,在这个神经网络的前半部分(softmax层之前的所有层视为一个固定映射),取任意输入图像 X X X,然后计算它的某个特征向量,这样你训练的就是一个很浅的softmax模型,用这个特征向量来做预测。对你的计算有用的一步就是对你的训练集中所有样本的这一层的激活值进行预计算,然后存储到硬盘里,然后在此之上训练softmax分类器。所以,存储到硬盘或者说预计算方法的优点就是,你不需要每次遍历训练集再重新计算这个激活值了。
因此如果你的任务只有一个很小的数据集,你可以这样做。要有一个更大的训练集怎么办呢?根据经验,如果你有一个更大的标定的数据集,也许你有大量的Tigger和Misty的照片,还有两者都不是的,这种情况,你应该冻结更少的层,比如只把这些层冻结,然后训练后面的层。如果你的输出层的类别不同,那么你需要构建自己的输出单元,Tigger、Misty或者两者都不是三个类别。有很多方式可以实现,你可以取后面几层的权重,用作初始化,然后从这里开始梯度下降。
或者你可以直接去掉这几层,换成你自己的隐藏单元和你自己的softmax输出层,这些方法值得一试。但是有一个规律,如果你有越来越多的数据,你需要冻结的层数越少,你能够训练的层数就越多。这个理念就是,如果你有一个更大的数据集,也许有足够多的数据,那么不要单单训练一个softmax单元,而是考虑训练中等大小的网络,包含你最终要用的网络的后面几层。
如果你有越多的标定的数据,你可以训练越多的层。极端情况下,你可以用下载的权重只作为初始化,用它们来代替随机初始化,接着你可以用梯度下降训练,更新网络所有层的所有权重。
大部分的计算机视觉任务使用很多的数据,所以数据扩充是经常使用的一种技巧来提高计算机视觉系统的表现。
最简单的数据扩充方法就是垂直镜像对称,假如,训练集中有这张图片,然后将其翻转得到右边的图像。对大多数计算机视觉任务,左边的图片是猫,然后镜像对称仍然是猫,如果镜像操作保留了图像中想识别的物体的前提下,这是个很实用的数据扩充技巧。
另一个经常使用的技巧是随机裁剪。随机裁剪并不是一个完美的数据扩充的方法,如果你随机裁剪的那一部分(红色方框标记部分,编号4),这部分看起来不像猫。但在实践中,这个方法还是很实用的,随机裁剪构成了很大一部分的真实图片。
理论上,你也可以使用旋转,剪切(shearing:此处并非裁剪的含义,图像仅水平或垂直坐标发生变化)图像,可以对图像进行这样的扭曲变形,引入很多形式的局部弯曲等等。当然使用这些方法并没有坏处,尽管在实践中,因为太复杂了所以使用的很少。
第二种经常使用的方法是彩色转换,有这样一张图片,然后给R、G和B三个通道上加上不同的失真值。
在这个例子中(编号1),要给红色、蓝色通道加值,给绿色通道减值。红色和蓝色会产生紫色,使整张图片看起来偏紫,这样训练集中就有失真的图片。为了演示效果,我对图片的颜色进行改变比较夸张。在实践中,对R、G和B的变化是基于某些分布的,这样的改变也可能很小。
这么做的目的就是使用不同的R、G和B的值,使用这些值来改变颜色。在第二个例子中(编号2),我们少用了一点红色,更多的绿色和蓝色色调,这就使得图片偏黄一点。
在这(编号3)使用了更多的蓝色,仅仅多了点红色。在实践中,R、G和B的值是根据某种概率分布来决定的。介绍这些,颜色失真或者是颜色变换方法,这样会使得你的学习算法对照片的颜色更改更具鲁棒性。
数据扩充,与训练深度神经网络的其他部分类似,在数据扩充过程中也有一些超参数,比如说颜色变化了多少,以及随机裁剪的时候使用的参数。与计算机视觉其他部分类似,一个好的开始可能是使用别人的开源实现,了解他们如何实现数据扩充。当然如果你想获得更多的不变特性,而其他人的开源实现并没有实现这个,你也可以去调整这些参数。