【resNet】resNet 综述

【resNet】resNet 综述

  • [resNet v1] Deep Residual Learning for Image Recognition (2015-12)
  • [resNet v2] Identity Mappings in Deep Residual Networks (2016-03)
  • [wide resNet] Wide Residual Networks (2016-05)
  • [resNeXt] Aggregated Residual Transformations for Deep Neural Networks (2016-11)
  • Accurate, Large Minibatch SGD:Training ImageNet in 1 Hour (2017-02)
  • Residual Networks Behave Like Ensembles of Relatively Shallow Networks
  • [DenseNet]
  • 比较
  • [DiracNets] Training Very Deep Neural Networks Without Skip-Connections

[resNet v1] Deep Residual Learning for Image Recognition (2015-12)

https://arxiv.org/abs/1512.03385

ResNet 最重要的贡献就是short cut,通过增加恒等映射,解决模型退化的问题。
为什么可以解决模型退化问题呢?
首先什么是模型退化问题,也就是加深简单的模型,不能直接提升模型的性能。
我们在训练模型的时候,使用BP算法,进行反向传播,反向传播的导数经层层传递,虽然过程中可以增加BN层避免梯度消失(更何况ResNet出现的时候,BN仍然不普及),导数在模型浅层本来就很少,同时计算机的计算精度float32/float32有限,更新浅层参数的梯度不够精确,导致模型无法有效训练。
【resNet】resNet 综述_第1张图片
为什么加上了shortcut,模型就不退化呢?
主要观点总结如下:

  1. 避免梯度弥散
    梯度弥散,和梯度爆炸(vanishing/exploding gradients)在原文中就提到,可以使用规范初始化(normalized initialization)及 中间规范层(intermediate normalization layers)解决这个问题。
    但是这样模型肯定可以训,但是训的好不好就不一定了,因为中间规范层其实就是一个拉分布的过程,每一次模型的输入不一样,它的分布当然就不一样,每一次训练把tensor没有理由的拉到同一分布,势必损失了信息,而之后的BN则是优化了这一过程。
    所以resnet的出发点,仍然是去解决梯度弥散和爆炸的问题。加上了shortcut 其实就解决了梯度弥散的问题。
    前 向 传 播 z = x + f ( x ) a = r e l u ( x ) 反 向 传 播 ∂ a ∂ x = α a α z ⋅ α z α x = 1 + α z α f ⋅ α f α x 前向传播 \\ z=x+f(x) \\ a=relu(x)\\ 反向传播\\ \frac{\partial a}{\partial x}=\frac{\alpha a}{\alpha z} \cdot \frac{\alpha z}{\alpha x}\\ =1+ \frac{\alpha z}{\alpha f} \cdot \frac{\alpha f}{\alpha x} z=x+f(x)a=relu(x)xa=αzαaαxαz=1+αfαzαxαf
    这个1 是一直存在的,避免了梯度弥散的问题。

  2. 特征冗余
    认为在正向卷积时,通过感受野与张量的相互制约,逐渐提取高级语义特征。
    每一次卷积,就是一次提取语义特征的过程。可以想象语义特征的提取需要依赖与各个层级的特征。低级语义特征是检测边缘,中级语义特征检测形状,及背景,我们在做分类问题,这些都是需要考虑的。而传统卷积网络高级语义特征生成层只接受前一层的信息,信息必然丢失严重,就像是传话游戏,中间有失误的话,误差就会不断变大。增加了shortcut之后,深层网络至少接受了 1 2 n \frac{1}{2n} 2n1的浅层网络信息,一定程度上保留了更多的原始信息。这个 n n n代表前 n n n层的信息,假设信息是1+1这样的形式。
    后面的densenet发展了这个优势。

  3. ensambling
    应该在下面的论文会说。

  4. luck node
    luck node 源于ICLR 2019 best paper 《THE LOTTERY TICKET HYPOTHESIS: FINDING SPARSE, TRAINABLE NEURAL NETWORKS》。

    https://arxiv.org/abs/1803.03635

    这篇论文最大的贡献就是提出了新的思路去理解神经网络。
    简单概述就是一句话,神经网络不仅仅是在学习参数,更重要的是在学习一种结构。
    这篇论文通过剪枝实验证明了神经网络中存在子网络,剪枝出的子网络重新初始化,训练更快,预测更准,泛化性更好。
    整个神经网络中按照不同的训练集训练存在某一些结点,成为 luck node 对整个网络的新能贡献最大, luck node仅占整个网络的1/10。
    作者认为训练神经网络就像买彩票,买越多张彩票,中奖的几率就越高。所以往往大的网络效果更好,实际上是大网络中的子网络更优。
    那么用luck node 去解释resnet,加上了shortcut结构,增加了前后层之间的联系,更加容易寻找到luck node,同时由于shortcut 的存在,最优子网络的结构更加丰富,对模型性能的提升就更加明显。

【resNet】resNet 综述_第2张图片
resnet的实现上有两个重要的点:

  1. 残差块必须包含shortcut结构以及至少两层网络,例如两个卷积层以及一个激活层。
    少于两层,简单推导就可以证明残擦块退化为一层网络。
    满足这个条件后,保证shortcut,卷积的变化,激活函数的变化,至少不会降低模型的性能,能不能提高就看实验结果了。如后面的论文。
  2. tensor的size 与 channel 成反比。

【resNet】resNet 综述_第3张图片
简单解释一下 pytorch 的实现。
BasicBlock
18 和 34 层的resnet使用BasicBlock作为残差块。
每一个残差块包含两个卷积层。

class BasicBlock(nn.Module):
        # 可以看到上面的表格resnet是按照四个层顺序组成的。
        # 层与层之间有若干个残差块,
        # 在层内传输的时候,输入输出一致,stride为1, downsample = None;
        # 在层与层之间,通道数增加4倍,张量高宽缩小1倍,面积缩小4倍。这个时候,如果需要shortcut就需要对输入的张量下采样一次,保证通道数和张量高宽一致。
        # downsample 使用1x1 卷积实现。
    expansion = 1 #表示使用bottle结构,expansion表示输出扩张的倍数。

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None):
        super(BasicBlock, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        if groups != 1 or base_width != 64:
            raise ValueError('BasicBlock only supports groups=1 and base_width=64')
        if dilation > 1:
            raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
        # Both self.conv1 and self.downsample layers downsample the input when stride != 1
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = norm_layer(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = norm_layer(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        identity = 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:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

Bottleneck
50 ,101,152 层的resnet使用Bottleneck作为残差块。

class Bottleneck(nn.Module):
        # 和BasicBlock一样,resnet是按照四个层顺序组成的,层与层之间有若干个残差块。
        # 在层内传输的时候,输出的chanel是输入的4倍,所以每一个残擦块的第一层1x1卷积都要改变通道数。
        # 在层与层之间,通道数增加4倍,张量高宽缩小1倍,面积缩小4倍。这个时候,如果需要shortcut就需要对输入的张量下采样一次,保证通道数和张量高宽一致。
        # downsample 使用1x1 卷积实现。
    expansion = 4  #表示使用bottle结构,expansion表示输出扩张的倍数。

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None):
        super(Bottleneck, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        width = int(planes * (base_width / 64.)) * groups
        # Both self.conv2 and self.downsample layers downsample the input when stride != 1
        self.conv1 = conv1x1(inplanes, width)
        self.bn1 = norm_layer(width)
        self.conv2 = conv3x3(width, width, stride, groups, dilation)
        self.bn2 = norm_layer(width)
        self.conv3 = conv1x1(width, planes * self.expansion)
        self.bn3 = norm_layer(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

resnet
把不重要的判断,以及初始化删掉了。

class ResNet(nn.Module):
    
    def __init__(self, block, layers, num_classes=1000):
        super(ResNet, self).__init__()
        norm_layer = nn.BatchNorm2
        self.inplanes = 64
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = norm_layer(self.inplanes)
        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.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)
        
    def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
    # 重要的就是这个_make_layer函数
    # 主要就是去按照表格去生成每一层的残差块。
    # 每一层的第一个残擦块单独处理,主要不同就是增加了downsample。
    # self.inplanes = planes * block.expansion 按照残差块的不同expansion决定了残擦块的输入channel的变化。
        norm_layer = self._norm_layer
        downsample = None
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )
        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, groups=1,
                            base_width = 64, dilation = False, norm_layer = norm_layer))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups,
                                base_width=64, dilation=False,norm_layer = norm_layer))
        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 = torch.flatten(x, 1)
        x = self.fc(x)

        return x

pytorch实现源码
https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py

参考
【resnet 最好讲解】https://blog.csdn.net/chenyuping333/article/details/82344334
【Resnet overview】https://towardsdatascience.com/an-overview-of-resnet-and-its-variants-5281e2f56035
【Resnet 理解】https://blog.csdn.net/lanran2/article/details/79057994
【Resnet 吴恩达课程】https://blog.csdn.net/qq_29893385/article/details/81207203
【Resnet 碎碎念】https://blog.csdn.net/u014296502/article/details/80438616
【Resnet emsable解释】https://blog.csdn.net/Buyi_Shizi/article/details/53336192
【Resnet 个人理解】https://blog.csdn.net/nini_coded/article/details/79582902
【Resnet 的解释和有趣的点】https://blog.csdn.net/qq_21190081/article/details/75933329
【luck node】https://zhuanlan.zhihu.com/p/65161889
【模型压缩】https://accepteddoge.com/cnblogs/mldl/network-compression

[resNet v2] Identity Mappings in Deep Residual Networks (2016-03)

https://arxiv.org/abs/1603.05027

参考
【resnet 最好讲解】https://blog.csdn.net/chenyuping333/article/details/82344334
【resNet v2 翻译】https://blog.csdn.net/wspba/article/details/60750007

[wide resNet] Wide Residual Networks (2016-05)

https://arxiv.org/abs/1605.07146

[resNeXt] Aggregated Residual Transformations for Deep Neural Networks (2016-11)

https://arxiv.org/abs/1611.05431

Accurate, Large Minibatch SGD:Training ImageNet in 1 Hour (2017-02)

https://arxiv.org/abs/1706.02677

Residual Networks Behave Like Ensembles of Relatively Shallow Networks

http://papers.nips.cc/paper/6556-residual-networks-behave-like-ensembles-of-relatively-shallow-networks.pdf

[DenseNet]

比较

[DiracNets] Training Very Deep Neural Networks Without Skip-Connections

https://arxiv.org/abs/1706.00388

https://www.cnblogs.com/bonelee/p/9029934.html
https://www.cnblogs.com/liaohuiqiang/p/9606901.html
https://blog.csdn.net/weixin_42398658/article/details/84639391

你可能感兴趣的:(深度学习,算法)