• [深度学习]动手学深度学习笔记-5


    Task2——梯度消失、梯度爆炸
    在这里插入图片描述

    5.1 梯度消失与梯度爆炸的概念

    深度神经网络训练的时候,采用的是反向传播方式,该方式使用链式求导,计算每层梯度的时候会涉及一些连乘操作,因此如果网络过深。

    1. 那么如果连乘的因子大部分小于1,最后乘积的结果可能趋于0,也就是梯度消失,后面的网络层的参数不发生变化.
    2. 那么如果连乘的因子大部分大于1,最后乘积可能趋于无穷,这就是梯度爆炸

    5.2 梯度消失与梯度爆炸的后果

    1. 梯度消失会导致我们的神经网络中前面层的网络权重无法得到更新,也就停止了学习。
    2. 梯度爆炸会使得学习不稳定, 参数变化太大导致无法获取最优参数。
    3. 在深度多层感知机网络中,梯度爆炸会导致网络不稳定,最好的结果是无法从训练数据中学习,最坏的结果是由于权重值为NaN而无法更新权重。
    4. 在循环神经网络(RNN)中,梯度爆炸会导致网络不稳定,使得网络无法从训练数据中得到很好的学习,最好的结果是网络不能在长输入数据序列上学习。

    5.3 梯度消失与梯度爆炸的原因

    我们在这以一个例子来说明:
    在这里插入图片描述
    如上图,是一个每层只有一个神经元的神经网络,且每一层的激活函数为sigmoid,则有:yi=σ(zi)=σ(wixi+bi)y_{i}=sigmaleft(z_{i} ight)=sigmaleft(w_{i} x_{i}+b_{i} ight)
    σsigma是sigmoid函数。
    根据反向传播算法有:δCδb1=δCδy4δy4δz4δz4δx4δx4δz3δz3δx3δx3δz2δz2δx2δx2δz1δz1δb1frac{delta C}{delta b_{1}}=frac{delta C}{delta y_{4}} frac{delta y_{4}}{delta z_{4}} frac{delta z_{4}}{delta x_{4}} frac{delta x_{4}}{delta z_{3}} frac{delta z_{3}}{delta x_{3}} frac{delta x_{3}}{delta z_{2}} frac{delta z_{2}}{delta x_{2}} frac{delta x_{2}}{delta z_{1}} frac{delta z_{1}}{delta b_{1}}
    =δCδy4(σ(z4)w4)(σ(z3)w3)(σ(z2)w2)(σ(z1))=frac{delta C}{delta y_{4}}left(sigma^{prime}left(z_{4} ight) w_{4} ight) quadleft(sigma^{prime}left(z_{3} ight) w_{3} ight) quadleft(sigma^{prime}left(z_{2} ight) w_{2} ight) quadleft(sigma^{prime}left(z_{1} ight) ight)
    而sigmoid函数的导数公式为:
    S(x)=ex(1+ex)2=S(x)(1S(x))S^{prime}(x)=frac{e^{-x}}{left(1+e^{-x} ight)^{2}}=S(x)(1-S(x))sigmoid函数及其它的图形曲线为:

    sigmoid函数图像如上图

    在这里插入图片描述

    sigmoid函数导数图像如上图

    由上可见,sigmoid函数的导数σ(x)sigma^{prime}(x)的最大值为14frac{1}{4} ,通常我们会将权重初始值 w|w| 初始化为为小于1的随机值,因此我们可以得到σ(z4)w4<14left|sigma^{prime}left(z_{4} ight) w_{4} ight|<frac{1}{4},随着层数的增多,那么求导结果δCδb1frac{delta C}{delta b_{1}}越小,这也就导致了梯度消失问题。

    那么如果我们设置初始权重 w|w|较大,那么会有σ(z4)w4>1left|sigma^{prime}left(z_{4} ight) w_{4} ight|>1 ,造成梯度太大(也就是下降的步伐太大),这也是造成梯度爆炸的原因。

    总之,无论是梯度消失还是梯度爆炸,都是源于网络结构太深,造成网络权重不稳定,从本质上来讲是因为梯度反向传播中的连乘效应。

    5.4 解决方法

    5.4.1 换用Relu、LeakyRelu、Elu等激活函数

    1. ReLu:让激活函数的导数为1

    2. LeakyReLu:包含了ReLu的几乎所有有点,同时解决了ReLu中0区间带来的影响

    3. ELU:和LeakyReLu一样,都是为了解决0区间问题,相对于来,elu计算更耗时一些

    5.4.2 Batch Normalization

    Batchnorm是深度学习发展以来提出的最重要的成果之一了,目前已经被广泛的应用到了各大网络中,具有加速网络收敛速度,提升训练稳定性的效果,Batchnorm本质上是解决反向传播过程中的梯度问题。batchnorm全名是batch normalization,简称BN,即批规范化,通过规范化操作将输出信号x规范化保证网络的稳定性。
    具体的batchnorm原理非常复杂,在这里不做详细展开,此部分大概讲一下batchnorm解决梯度的问题上。具体来说就是反向传播中,经过每一层的梯度会乘以该层的权重,举个简单例子:
    正向传播中f2=f1(wTx+b)f_{2}=f_{1}left(w^{T} * x+b ight),那么反向传播中,f2x=f2f1xfrac{partial f_{2}}{partial x}=frac{partial f_{2}}{partial f_{1}} x,反向传播式子中有xx的存在,所以xx的大小影响了梯度的消失和爆炸,batchnorm就是通过对每一层的输出规范为均值和方差一致的方法,消除了xx带来的放大缩小的影响,进而解决梯度消失和爆炸的问题,或者可以理解为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结构

    参见本人的第6篇学习笔记

    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

  • 相关阅读:
    我们工作为了什么
    为什么去国企(HP中华区总裁孙振耀退休感言)
    android中的所有activity间动画跳转
    [转]Eclipse进行可视化的GUI开发3大GUI插件
    用Monkey测试android程序
    大学之后拉开差距的原因
    dataset 和 datareader 区别
    曾经运行该线程的应用程序域已卸载。
    guid.tostring() 格式化指南
    vs 使用技巧
  • 原文地址:https://www.cnblogs.com/Jack-Tim-TYJ/p/12831950.html
Copyright © 2020-2023  润新知