date: 2018/04/08

update: 2020/03/20

SAKURA - いきものがかり:每次听这首歌都想起绘梨衣…以及被江南老贼写烂尾的《龙族》


在深度学习任务中,随着层数的增加,因为反向传播的链式求导规则,梯度容易出现指数形式地衰减或增长,从而导致梯度消失(非常小,训练缓慢)或梯度爆炸(非常大,训练不稳定)现象的发生。

相比CNN,RNN更容易出现梯度消失和梯度爆炸问题,这一点在《梯度消失与梯度爆炸 - 为什么RNN通常不用ReLU? | Hey~YaHei!》中有一些简单的讨论。


参考:

  1. Hands-On Machine Learning with Scikit-Learn and TensorFlow(2017)》Chap11
  2. 卷积神经网络——深度学习实践手册(2017.05)
  3. 详解深度学习中的梯度消失、爆炸原因及其解决方法 | 知乎, DoubleV


缓解梯度消失爆炸的常用技术(本文只讨论前五项):

  1. 缓解爆炸】合理的随机初始化策略(Xavier Initialization、He Initialization等)
  2. 缓解消失】使用非饱和函数作为激活函数(如ReLU)
  3. 缓解消失爆炸】批归一化(Batch Normalization, BN)
  4. 缓解爆炸】梯度裁剪(Gradient Clipping)
  5. 缓解消失爆炸】复用预训练层
  6. 缓解爆炸权重正则化(Weights Regularization)
  7. 缓解消失残差结构
  8. 缓解消失】LSTM


随机初始化

参考:《深度学习500问 - Ch03深度学习基础 - 3.8权重偏差初始化

模型的训练需要对参数进行初始化,然后用反向传播算法和梯度下降法更新参数,如何初始化参数是有讲究的。通常会随机初始化为一些相对比较小的数值,防止参数过大导致梯度爆炸;但也不能太小,否则梯度太小,收敛就太慢。

考虑一些简单的初始化方式,

image.png


前向传播:


反向传播:

其中,是第层的输出误差,

如果使用的是sigmoid激活函数


  1. 权重均初始化为零:梯度均为0,是无法进行训练的

    


  1. 参数均初始化为同一个值:初始值相同,梯度也相同,最终训练后所有参数也相同,训练是无效的


直观上讲,卷积层包含许多滤波器,它们用来提取输入特征图上的特征。通过随机初始化,让每个滤波器最初都随机关注不同的特征,有助于训练出倾向于提取不同特征的滤波器,避免冗余滤波器的出现。而随机初始化,多数采用的是均匀分布或是高斯分布的初始化,各个初始化方式不同之处在于均匀分布的范围、高斯分布的标准差的确定方式不同,事实上,随着BN层的普遍使用,随机初始化的形式已经变得不再那么重要。


Xavier Initialization(Glorot Initialization)

论文:《Understanding the difficulty of training deep feedforward neural networks(2010)

作者Xavier建议:使每一层的输入输出的方差相等,而且正反向传播的梯度也相等


并针对sigmoid激活函数(logistic激活函数)提出一种初始化方式:


  1. 各权重用均值为0的正态分布随机数进行初始化,并且标准差根据输入、输出的维度确定——

    

参考:torch.nn.init.xavier_normal_

  1. 用[-r, r]的均匀分布随机数进行初始化——

    

参考:torch.nn.init.xavier_uniform_

(在卷积层中,指的是输入、输出特征图的通道数量)


He Initialization(Kaiming Initialization)

论文:《Delving Deep into Rectifiers:Surpassing Human-Level Performance on ImageNet Classification(2015)

    
    

(在卷积层中,指的是输入、输出特征图的通道数量)


数据敏感的参数初始化

是一种根据自身任务数据集量身定制的参数初始化方式;

论文:《Data-dependent Initializations of Convolutional Neural Networks(2016)

代码:philkr/magic_init | github


激活函数

卷积、全连接、BN都是典型的线性操作,而真实世界的模型往往是高度非线性的,激活函数通常采用的都是非线性函数,主要目的就是为了给深度学习模型引入非线性特征,从而更好的模拟、拟合出非线性的真实情况。采用非饱和的激活函数,可以防止梯度在层层反传的过程中逐渐减小,导致梯度消失。


激活函数通常会跟全连接层、卷积层紧密配合使用,但也不总是如此,比如MobileNetv2、ShuffleNet等就取消一些非线性激活来避免信息损失,相关论述可以参考《MobileNet全家桶 - 通道收缩时使用非线性激活带来信息丢失 | Hey~YaHei!》。


各类激活函数基本都可以在 torch.nn#non-linear-activations-weighted-sum-nonlinearity 中找到。


sigmoid

此处为语雀文档,点击链接查看:https://www.yuque.com/yahei/hey-yahei/target_function#pYaLW


tanh

参考:《在神经网络中,激活函数sigmoid和tanh除了阈值取值外有什么不同吗?| 知乎

除了sigmoid,双曲正切tanh函数也经常用作激活函数,它们之间是线性相关的:

   

image.png          image.png


一般来说,tanh收敛速度要快于sigmoid,

  1. sigmoid的值域为,均为正数
    考虑《梯度消失与梯度下降 - 随机初始化 | Hey~YaHei!》推导出的反向传播公式会发现,由于相同,且符号必定相同。那么每次迭代,这一组权重的更新方向会比较单一,不利于训练。
    (简单地考虑只有两个参数的情形,参数的更新方向只能是第一或第三象限,优化路径会变得比较曲折,课程cs231中将其称之为zig zag)

image.png

而tanh的值域为,既有正数又有负数,就不会出现类似的问题

  1. sigmoid的导数值域为,而tanh的导数值域为,更新速度会快一些


ReLU

参考:


线性整流函数(Rectified Linear Unit, ReLU),也称修正线性单元,可以看作是softplus的一个hard版本:

image.png



为什么RNN通常不用ReLU?

参考:《RNN 中为什么要采用 tanh,而不是 ReLU 作为激活函数? - 何之源的回答 | 知乎

在CNN上,ReLU几乎已经取代掉sigmoid和tanh,可在RNN中饱和的sigmoid和tanh依旧随处可见——

  1. 在RNN中,不饱和激活容易带来数值快速膨胀

用数学符号表示RNN,

为方便讨论,这里简化问题,令,那么RNN的过程可以展开为

如果中有某个数值大于1,而且使用不饱和的ReLU作为连乘后数值必定快速膨胀;

而饱和的sigmoid和tanh在这里可以约束数值的范围,避免这种膨胀现象的发生。

这个现象在CNN中不容易出现,原因是在CNN中连乘的并不是相同的,而CNN权重往往具有很强的稀疏性。

  1. 在RNN中,ReLU并不能解决梯度传递问题

依旧令,考虑反向传播过程,展开后有

只考虑第三项,在ReLU作用下有

但会发现,反向传播中依旧出现了的连乘,这依旧很容易出现梯度消失和梯度爆炸(具体理由同1);

  1. 也存在一些在RNN成功使用ReLU的例子
    比如《A Simple Way to Initialize Recurrent Networks of Rectified Linear Units (2015)》提出的IRNN


批归一化

论文:《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift(2015)

参考:


归一化&正则化&标准化

参考文章:《机器学习里的黑色艺术:normalization, standardization, regularization | 知乎, 刘锐

归一化(Normalization):数据预处理,将数据限定在特定范围内,消除量纲对建模的影响;

标准化(Standardization):数据预处理,使数据符合标准正态分布;

正则化(Regularization):在损失函数中添加惩罚项,增加建模模糊性,将建模关注点转移到整体趋势上;


具体操作

归一化的方式有很多种:

  1. 最大最小值归一化
  2. 对数归一化
  3. 反正切归一化
  4. 零平均归一化


批归一化是在每层的激活函数之前添加一个BN操作,使得特征图数据归一化为均值为0,标准差为1的分布(不一定是正态分布);并且在每一层中使用两个新的参数来调整输出的范围,这是因为归一化过程有可能会抑制神经网络的非线性表达能力,通过学习一个线性映射关系使得模型的每一层能够学习到适宜的表示范围,可以补偿这一部分非线性表达能力


对于某个mini-batch的输入,归一化的具体过程如下:

其中,

分别是该mini-batch的平均数和方差;

是一个很小的数(通常取0.001),用于避免 导致分母为0的情况;

分别是每一层中的两个新的可学习参数,调整后的 通过线性变化后得到归一化的 输出;

初始化时,通常令,但在残差单元中将最后一个BN初始化为会是更好的选择,具体参见 高效训练 - 零gamma初始化


在深度学习模型中,多数常规层在训练和预测过程中都表现出相同的行为,比如全连接层、卷积层、激活层等等。而BN层在训练阶段和预测阶段的行为是不完全一致的,这主要体现在均值方差上,


效果

优点:

  1. 可以很好的缓解梯度爆炸和消失的问题,甚至饱和激活函数如sigmoid和tanh都可以在深度网络中正常使用;
  2. 网络对权重初始化不再那么敏感。而且随机初始化可以在训练的开始显著减少梯度爆炸和消失的问题,却不能保证训练过程中不再出现;
  3. 可以使用更大的学习率,提高训练速度;
  4. 由于每个Batch统计的均值和方差不同,相当于在训练过程中引入噪声,因此具有一定的正则化的效果,可以减少dropout等其他正则化技术的使用


缺点:

  1. BN操作增加了模型的计算量
    但是多数情况下该缺点是可以忽略不计的,因为在训练完成后,BN层可以合并到前一层的卷积层或全连接层,在预测阶段不产生任何额外开销,具体参见《MobileNet-SSD网络解析 - BN层合并 | Hey~YaHei!》;但也存在一些特殊的情况,使得BN层不方便融入的前一层,比如
    1. 二值量化移位量化等网络,在卷积、全连接层的计算细节上发生了变化,甚至有时候BN操作都发生了变化,加上这类网络往往依赖于重训练,融合BN操作起来是比较麻烦的
    2. ResNet-v2将BN和ReLU提到了卷积之前,此时BN成了残差路径上的第一个层
    3. 有的网络认为BN放在ReLU之后效果更好,此时也是无法合并运算的
  1. 当Batch Size太小(8)时,BN的效果明显下降,甚至有负面影响
    论文《Group Normalization (ECCV2018)》用ResNet50在ImageNet上做了实验

image.png  image.png

  1. 像素级细粒度图像任务表现不佳
    很可能是因为在一个Batch中包含若干互相无关的图片,在它们的特征图上统计均值和方差来进行归一化,弱化了单个样例本身特有的一些细节信息;
  2. 在RNN等动态网络上统计困难,且效果不佳
    RNN等动态网络的输入输出序列是不定长的,难以确保有效的统计,即使存在一些专门为RNN改进的BN,但效果都不尽人意
  3. 训练阶段和预测阶段的统计量不一致
    训练阶段是Batch上的实际统计结果,预测阶段用的则是训练时的平滑平均值,因此在预测阶段的统计量与实际情况并不完全一致,可能会存在一些潜在的问题


其他形式的归一化

image.png

都是减均值后除以方差的归一化形式,区别在均值和方差的统计粒度不同。

其中,都是k维向量,是一个标量,通常取


Rescale不变性

Rescale不变性指的是某些参数经过缩放(也即Rescale)之后,对模型结构的输出不产生影响。如果神经网络具备Rescale不变性,这意味着参数值太大、太小对神经元的输出都不会有影响,这无疑对缓解梯度消失与梯度爆炸是有很大帮助的。

而BN恰好能提供这种不变性,具体来说包含权重向量Rescale不变性、权重矩阵Rescale不变性和数据Rescale不变性。


考虑MLP或CNN的乘法运算,

  1. ,则

其均值和方差分别为

显然,经过批归一化后

故称BN具有权重向量Rescale不变性,此处权重向量指的是参与计算的所有权重值共享同一个

  1. 权重矩阵由若干权重向量构成,如果具有权重向量Rescale不变性,那么也必定具有权重矩阵Rescale不变性,此处不需要单独证明;
  2. ,则

经过相同的推导过程,可知BN亦具有数据Rescale不变性。


与Batch Normalization类似,其他Normalizaion操作也具有一些Rescale不变性:


权重向量Rescale不变性

权重矩阵Rescale不变性

数据Rescale不变性

Batch Normalization

Layer Normalization

Instance Normalization

Group Normalization


争论

参考:


事实上,BN的提出更多的是产生这样一个猜想,经过实现发现确实有效,又经过广泛的使用、实验,大家发现确实是个好东西,具体如何工作的也有不少人提出自己的解释和猜想,但至今没有一个广泛认可的解释。


提出者曾认为BN是降低了内部协变量偏移(Internal Covariate Shift, ICS)从而加速和稳定了训练过程。

ICS指的是在深层网络的训练过程中,由于网络中参数变化而引起内部结点数据分布发生变化的现象。简单来说,就是浅层参数更新之后会影响深层网络的输入分布,而训练过程往往是同时更新所有参数的,这就使得深层参数不得不在迭代过程中不停地调整自己以适应输入分布的变化。


该观点后来被多次质疑,比如

  1. How Does Batch Normalization Help Optimization? (No, It Is Not About Internal Covariate Shift) (2018)》就指出BN不仅没有降低ICS反而还增加了ICS,真正原因是因为BN使得损失平面更加平滑,容易收敛;
  2. GAN之父Ian Goodfellow也曾猜想,深度学习实际有很多高阶的跨层交互,但因为算力受限,很少使用牛顿法等二阶以上的高阶优化。在梯度下降算法中,我们通常只用到了一阶优化,而忽略了高阶跨层交互带来的影响,每次更新一个层的权重后就会对后续层产生不利影响,而BN降低了这类影响——这一观点也能佐证最后一个全连接层加不加BN效果都差不多这一现象。


梯度裁剪

论文:《On the difficulty of training recurrent neural networks(2012)

直接限制梯度的上限,简单粗暴,但却有效,这在RNN、强化学习等训练过程不稳定的任务中运用的比较多。


复用预训练层

迁移学习(Transfer Learning)在深度学习中相当常见,如果已经训练好了一个网络(比如可以识别猫),需要训练一个新的类似任务的网络(比如识别狗),可以直接使用已有网络的一部分浅层权重,重新初始化深层网络的权重来进行训练。


通常,深度学习模型浅层会提取一些底层的特征(比如颜色、点线),深层会提取一些抽象的特征(比如一些简单图案、耳朵、鼻子等局部特征),最后输出的是最抽象也是最终的分类结果(一只猫/狗)。直观上理解,复用浅层特征就是复用浅层的特征提取能力,比如已经训练了一个可以识别猫的网络,那么它的浅层就会提取一些毛发之类的底层特征,恰好狗是有类似特征的,那么在训练狗的识别模型时复用一部分猫识别模型的浅层权重,相当于迁移了一部分基础能力过来,然后进行进一步的训练,来微调、融合这部分能力,训练出最终的目标——识别狗子的模型。


  1. 加速训练过程
  2. 有利于小样本任务的训练
  3. 要求任务类型是相似的,具有类似浅层特征的,否则可能会有负面效果;任务越接近,可以复用的浅层越多
  4. 通常会在初期训练过程中固定复用层的权重,只训练随机初始化的权重;当训练比较稳定之后再同时微调(称为finetune)整个模型的权重
  5. 复用预训练层时,通常会使用比从头训练时更小的学习率(比如小一个数量级)


通常我们讲的是针对同一个模型结构的预训练层复用,那么如何将一个网络的预训练层应用到另一个不同模型结构的网络上呢?关于这一方面的知识迁移也是有一些研究的,比如Net2Net


常见模型库

模型库通常称为model zoo


无监督预训练

但实际操作中并不是总有类似任务的现成模型可以复用,实际的训练任务很可能只有少量标注好的数据和大量未标注的数据,这时候有以下选择:

  1. 继续标注数据
  2. 如果标注繁琐或者成本过高,可以尝试无监督预训练的方式
    论文:《Why Does Unsupervised Pre-training Help Deep Learning?(2009)
    使用无监督训练,从底层开始逐层训练各个隐藏层,每次只训练一个层而固定其他层的权重;
    无监督训练可以使用Restricted BoltzmannMachines (RBMs)、自编码器(autoencoders)等,目前应该是自编码器用的更多些;
    最后用监督训练的方式,finetune较高的layers:

11.2unsupervised_pretrain.png

  1. 也可以尝试用部分原始数据和目标数据协同训练,如《Borrowing Treasures from the Wealthy: Deep Transfer Learning through Selective Joint Fine-tuning(2017)
  2. 寻找一个训练数据易收集且易标注的类似任务,先训练出一个模型来复用给这个任务
  3. 在辅助任务上进行预训练,比如:


训练出现loss=NaN

(这部分内容暂时不知道放哪,先借住一下吧~)

可能原因:

  1. 出现梯度爆炸,尝试梯度裁剪
  2. 指数运算结果数值过大,出现INF,而INF/INF=NaN
  3. 除数出现0(比如就有这样的风险,可以通过防止下溢出的softmax来解决)
  4. 对数出现0(比如,当下溢出时就有这样风险。可以考虑给取一个下限进行clip;出现这种情况有可能数据集有很多脏数据,需要清洗数据)
  5. 学习率过高,通常是出现在训练初期,调低学习率即可