Task2——梯度消失、梯度爆炸
5.1 梯度消失与梯度爆炸的概念
深度神经网络训练的时候,采用的是反向传播方式,该方式使用链式求导,计算每层梯度的时候会涉及一些连乘操作,因此如果网络过深。
- 那么如果连乘的因子大部分小于1,最后乘积的结果可能趋于0,也就是梯度消失,后面的网络层的参数不发生变化.
- 那么如果连乘的因子大部分大于1,最后乘积可能趋于无穷,这就是梯度爆炸。
5.2 梯度消失与梯度爆炸的后果
- 梯度消失会导致我们的神经网络中前面层的网络权重无法得到更新,也就停止了学习。
- 梯度爆炸会使得学习不稳定, 参数变化太大导致无法获取最优参数。
- 在深度多层感知机网络中,梯度爆炸会导致网络不稳定,最好的结果是无法从训练数据中学习,最坏的结果是由于权重值为NaN而无法更新权重。
- 在循环神经网络(RNN)中,梯度爆炸会导致网络不稳定,使得网络无法从训练数据中得到很好的学习,最好的结果是网络不能在长输入数据序列上学习。
5.3 梯度消失与梯度爆炸的原因
我们在这以一个例子来说明:
如上图,是一个每层只有一个神经元的神经网络,且每一层的激活函数为sigmoid,则有:
是sigmoid函数。
根据反向传播算法有:
而sigmoid函数的导数公式为:
sigmoid函数及其它的图形曲线为:
sigmoid函数图像如上图
sigmoid函数导数图像如上图
由上可见,sigmoid函数的导数的最大值为 ,通常我们会将权重初始值 初始化为为小于1的随机值,因此我们可以得到,随着层数的增多,那么求导结果越小,这也就导致了梯度消失问题。
那么如果我们设置初始权重 较大,那么会有 ,造成梯度太大(也就是下降的步伐太大),这也是造成梯度爆炸的原因。
总之,无论是梯度消失还是梯度爆炸,都是源于网络结构太深,造成网络权重不稳定,从本质上来讲是因为梯度反向传播中的连乘效应。
5.4 解决方法
5.4.1 换用Relu、LeakyRelu、Elu等激活函数
-
ReLu:让激活函数的导数为1
-
LeakyReLu:包含了ReLu的几乎所有有点,同时解决了ReLu中0区间带来的影响
-
ELU:和LeakyReLu一样,都是为了解决0区间问题,相对于来,elu计算更耗时一些
5.4.2 Batch Normalization
Batchnorm是深度学习发展以来提出的最重要的成果之一了,目前已经被广泛的应用到了各大网络中,具有加速网络收敛速度,提升训练稳定性的效果,Batchnorm本质上是解决反向传播过程中的梯度问题。batchnorm全名是batch normalization,简称BN,即批规范化,通过规范化操作将输出信号x规范化保证网络的稳定性。
具体的batchnorm原理非常复杂,在这里不做详细展开,此部分大概讲一下batchnorm解决梯度的问题上。具体来说就是反向传播中,经过每一层的梯度会乘以该层的权重,举个简单例子:
正向传播中,那么反向传播中,,反向传播式子中有的存在,所以的大小影响了梯度的消失和爆炸,batchnorm就是通过对每一层的输出规范为均值和方差一致的方法,消除了带来的放大缩小的影响,进而解决梯度消失和爆炸的问题,或者可以理解为BN将输出从饱和区拉倒了非饱和区。
5.4.3 ResNet残差结构
具体理解见此文
总的来说,Resnet的核心思想就是更改了网络结构的学习目的,原本学习的是直接通过卷积得到的图像特征H(X),现在学习的是图像与特征的残差H(X)-X,这样更改的原因是因为残差学习相比原始特征的直接学习更加容易。以下进行一个简单的证明。
ResNet代码实现:
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
def conv3x3(in_planes, out_planes, stride=1):
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000):
self.inplanes = 64
super(ResNet, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
self.avgpool = nn.AvgPool2d(7, stride=1)
self.fc = nn.Linear(512 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
def _make_layer(self, block, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
def resnet18(pretrained=False, **kwargs):
trained (bool): If True, returns a model pre-trained on ImageNet
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
if pretrained:
model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
return model
5.4.4 LSTM结构
5.4.5 预训练加finetunning
此方法来自Hinton在06年发表的论文上,其基本思想是每次训练一层隐藏层节点,将上一层隐藏层的输出作为输入,而本层的输出作为下一层的输入,这就是逐层预训练。
训练完成后,再对整个网络进行“微调(fine-tunning)”。
此方法相当于是找全局最优,然后整合起来寻找全局最优,但是现在基本都是直接拿imagenet的预训练模型直接进行finetunning。
5.4.6 梯度剪切、正则
这个方案主要是针对梯度爆炸提出的,其思想是设值一个剪切阈值,如果更新梯度时,梯度超过了这个阈值,那么就将其强制限制在这个范围之内。这样可以防止梯度爆炸。
另一种防止梯度爆炸的手段是采用权重正则化,正则化主要是通过对网络权重做正则来限制过拟合,但是根据正则项在损失函数中的形式:
可以看出,如果发生梯度爆炸,那么权值的范数就会变的非常大,反过来,通过限制正则化项的大小,也可以在一定程度上限制梯度爆炸的发生。
end
[参考文档]
[1] https://www.jianshu.com/p/3f35e555d5ba
[2] https://www.cnblogs.com/pigbreeder/p/8051442.html
[3] https://www.cnblogs.com/pinking/p/9418280.html
[4] https://www.jianshu.com/p/ec0967460d08
[5] https://zhuanlan.zhihu.com/p/38537439