zoukankan      html  css  js  c++  java
  • [深度学习]动手学深度学习笔记-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

  • 相关阅读:
    Python 学习笔记 11.模块(Module)
    Python 学习笔记 8.引用(Reference)
    Python 学习笔记 9.函数(Function)
    Python 学习笔记 6.List和Tuple
    Python 学习笔记 4.if 表达式
    Python 学习笔记 2.自省
    Python 学习笔记 3.简单类型
    Python 学习笔记 7.Dictionary
    Python 学习笔记 5.对象驻留
    Python 学习笔记 10.类(Class)
  • 原文地址:https://www.cnblogs.com/Jack-Tim-TYJ/p/12831950.html
Copyright © 2011-2022 走看看