CNN

卷积神经网络

卷积层尺寸的计算原理

  • 输入矩阵格式:四个维度,依次为:样本数、图像高度、图像宽度、图像通道数
  • 输出矩阵格式:与输出矩阵的维度顺序和含义相同,但是后三个维度(图像高度、图像宽度、图像通道数)的尺寸发生变化。
  • 权重矩阵(卷积核)格式:同样是四个维度,但维度的含义与上面两者都不同,为:卷积核高度、卷积核宽度、输入通道数、输出通道数(卷积核个数)
  • 关系

    • 卷集核的通道数由输入矩阵决定。
    • 输出矩阵的通道数由卷集核的数量决定。
    • 输出矩阵的高度和宽度计算如下:

tensorflow中卷积层的实现

1
2
3
4
5
6
7
8
def conv_layer(x, out_channel, k_size, stride, padding):
in_channel = x.shape[3].value
w = tf.Variable(tf.truncated_normal([k_size, k_size, in_channel, out_channel], mean=0, stddev=stddev))
b = tf.Variable(tf.zeros(out_channel))
y = tf.nn.conv2d(x, filter=w, strides=[1, stride, stride, 1], padding=padding)
y = tf.nn.bias_add(y, b)
y = tf.nn.relu(y)
return x

关于padding有两种方式,“SAME”和“VALID”,表示如下:

对比普通神经网络的优点

  1. 减少参数
    如果一张100*100*3的图片输入普通的神经网络,假设第一层有1000个神经元,那么单这一层就有30000*1000个weight,太多了。(1)因为查看某种pattern只需要看局部就可以,例如:检查是否有鸟嘴,只需要看一部分就可以。(2)同样的pattern和位置关系不大,左上角的鸟嘴和中间的鸟嘴都是一样的,不需要多个detector检测。(3)subsampling对图像类别影响很小。
    使用卷积和第一,减少了参数的数量,使每一个神经元只和上一层的部分神经元连接。第二,每一层的神经元share了weight,做反向梯度传播的时候把share weight的梯度做平均进行梯度下降。

  2. 可解释
    对于每一个feature map,把每个元素相加,得到这个feature map的激活度。现在固定网络参数,使用梯度上升更新x,使得当前的feature map最大,那么就能显示出产生这个feature map的kernel所学习到的东西。

VGG19

残差网络

从理论上,增加网络的层数,使网络更加复杂能够提取更加复杂的特征。但是通过实验发现,当网络层数增加时,网络准确度出现饱和甚至下降。56层的网络效果比20层的网络差。这是因为深层网络存在梯度消失或者梯度爆炸的问题。使得深度网络难以训练。

残差学习

从极端情况考虑,在浅层的网络上加入恒等变换层,可以得到和浅层网络相同的效果。因此可以在这个基础上加入残差,降低学习复杂度。从直观上看残差学习需要学习的内容少,因为残差一般会比较小,学习难度小点。

残差单元可以表示为如下的形式,其中$\delta$表示激活函数,在ResNet中是relu,$h(x)$表示恒等变换,$F(x)$是残差函数,表示学习到的残差。一般每个残差单元有多层的结构,表示如下:
利用链式规则进行求梯度如下:

其中1代表了短路机制,可以让梯度更容的反向传播。

ResNet网络结构

ResNet参考了VGG19网络,在其基础上进行修改加入了残差单元。变化主要体现在,ResNet直接使用stride=2的卷积做下采样,并且用global average pool代替了最后的全连接层。

DenseNet

BatchNorm

梯度消失和梯度爆炸

关于深度神经网络出现梯度消失和梯度爆炸的问题,要从BP算法上看。一个简单的神经网络绘制成下图所示。

神经网络公式表示如下:

其中$f_i, W_i, b_i$分别代表第$i$层的输出,系数和偏置项。使用$f_0$表示输入x。$\sigma$代表sigmod函数。

BP算法基于梯度下降策略,以目标的负梯度方向对参数进行调整。参数更新策略为$w=w+\Delta w$,其中$\Delta w=-\alpha \frac{\partial loss}{\partial w}$

以$b_2$的反向梯度传播为例,

其中每一层的反向梯度都是对激活函数的求导之后乘上$W_i$。

梯度消失问题

如果每一层的激活函数求导的值都小于1,那么经过很多层之后,浅层的梯度就接近于0。此时浅层得不到有效的训练,因此增加了层数未必效果就好。一般w初始化在0-1之间,所以总的乘积小于1。

另外从直觉上理解,loss对于w的梯度,代表w有微小改变时,loss的变化量。在经过sigmod函数的时候,尽管w变化很大,sigmod的输出也会变化很小,特别是经过多层sigmod函数之后,变化就更小了。

Nielsen在《Neural Networks and Deep Learning》中的实验结果如下图,可以看出越浅的层训练的速度越慢,越深的层训练速度越快。第一层的速度比第四层·慢了100倍。

第二,不同的激活函数对于梯度消失的影响不同。例如sigmod函数,如下图所示,$\sigma(x)=\frac{1}{1+e^{-x}}$的导数为$\sigma^{\prime}=\sigma*(1-\sigma)$。而sigmod函数的取值范围是(0, 1),因此sigmod函数导数的取值最大为0.25。当神经网络层数增加之后,梯度消失的很快。

同理,tanh作为激活函数$tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}$,如下图所示,比sigmod好一些,但是同样小于1。$tanh^{\prime}(x)=1-tanh^2(x)$

梯度爆炸问题

如果每一层激活函数求导的值都大于1,那么经过很多层之后,浅层的梯度就是个很大的数字,浅层也同样的不到有效的训练。

解决方法

  1. 预训练加微调,在深度信念网络(DBN)中,每层依次训练,训练完成之后对整个网络进行微调,得到全局最优,目前用的不是很多了。

  2. 针对梯度爆炸问题,可以使用梯度裁剪的方法,将梯度限制在某个范围内。或者对权重进行l1和l2正则化。

  3. 使用relu,leakrelu,elu等激活函数。$relu(x)=max(x, 0)$,如下图所示。在$x>0$的部分导数等于1,因此在深度神经网络中不容出现梯度消失和梯度爆炸的问题。relu的优点有:(1)解决了梯度消失、爆炸的问题;(2)计算方便,计算速度快;(3)加速了网络的训练。缺点有:(1)由于负数部分恒为0,会导致一些神经元无法激活(可通过设置小学习率部分解决);(2)输出不是以0为中心的。更进一步,使用Maxout激活函数,relu是maxout的特殊情况。

  4. 使用batchnorm的方式解决。在反向传播中,$\frac{\partial f_2}{\partial x} = \frac{\partial f_2}{\partial f_1}w$,反向传播中有w存在,w的大小影响了梯度消失和梯度爆炸。BN的存在通过对每一层的输出规范为均值和方差一致的方法,消除了w带来的放大缩小的影响进,而解决梯度消失和爆炸的问题,或者可以理解为BN将输出从饱和区拉倒了非饱和区。

  1. 使用残差网络解决

  2. LSTM

Maxout

Maxout是由网络学出来的激活函数,relu是maxout的特殊情况。Maxout将计算得到的神经元分组,在同一组中取出最大的值,类似于maxpooling的操作。在Maxout反向梯度传播的时候,由于max函数无法求导,不能直接传递。但是经过max之后的分段函数,在每一段内都是一个线性函数,因此反向梯度只沿着选出的值进行传递。不同的分组会得到不同的激活函数,学出来的激活函数如下所示:

每一次maxout相当于只训练部分的网络,如下图所示,没有被选出的部分,没有被训练。但是数据很多,网络最终回得到完整的训练。

梯度下降

Adagrad

RMSprop

Momentum

Adam

训练技巧

Early Stopping

Regularization

在loss function上加上权重的二范式,这样使得权重更加接近于0,防止过拟合。

之前的梯度更新公式为

现在变成了

一般的$\eta$就是learning rateing会是一个很小的值,$\lambda$也是一个很小的值。这样每次更新权重的时候给权重乘了一个小于1的值,让权重更加接近于0。因此,每次weight都会小一点,使用l2norm的方法也叫weight decay

如果使用l1norm,梯度更新如下:

相当于每一次让w向0靠近$\eta\lambda$。

对于较大的权重,使用l2能够让其快速的向0接近,l1让其向0接近的速度很慢。对于较小的权重,l2只能缓慢下降。因此最终l2有很多接近0的权重,l1有很多是0的权重,比较sparse,同时有很多很大的权重。

Dropout

训练每个样本(mini-batch)的时候,对神经元以一定概率进行采样,采样留下的神经元所连接的权重被更新,其他神经元的权重不被更新。Dropout会使得training的准确率下降,如果training本身效果就不好,那么不要加dropout。在test的时候不使用dropout。在test的时候所有的weight要乘keepprob,因为训练的时候是在keepprob这么多个神经元下训练的,而在测试的时候使用了所有的神经元,那么每个神经元的权重应该是之前的keepprob倍。dropout可以看作是一种集成学习的方法。