残差网络结构及其实现

一、残差网络基本结构

(1) 传统的深度学习遇到的困难
  1. 梯度消失问题:随着网络深度的增加,网络会出现梯度消失/梯度爆炸的情况,阻碍收敛的过程
  2. 退化问题:精确率出现饱和的情况是层数的增加引起的,并不是过拟合,表明更深的网络并未出现更高的训练错误率。
  3. 深度学习提高网络精度常用初始化权值、局部感受野、权值共享等技术,但是层数较深时,依然面对反向传播时梯度消失这类困难。
(2) 深度残差网络思路
  1. 引入skip connect 打破了网络的对称性,减轻了神经网络的退化
  2. 认为可以混合利用不同层级的特征进一步抽取深层特征。
  3. 类似的工作在文章《Highway》和《Training Very Deep Networks》
  4. 深度残差的目标:更深层次的网络应该也是利于优化的,层数越多准确率越高,训练方法与传统的深度网络相比不会有很大变化
(3)残差结构(左图)和残差瓶颈结构(右图)

残差瓶颈结构:1 * 1 卷积 ——> 3 * 3卷积 ——> 1 * 1 卷积
在这里插入图片描述
首端和末端的1x1卷积用来削减和恢复维度,相比于原本结构,只有中间3x3成为瓶颈部分。
开始的1 * 1 卷积主要目的是为了减少参数的数量,从而减少计算量,且在降维之后可以更加有效、直观地进行数据的训练和特征提取。这里用32 个1*1 的卷积核进行降维,准确的说是32组1 * 1 * 64大小的卷积核,那么与原始图片卷积后生成32 * 56 * 56大小的特征图,降低了图片的厚度(通道),保持图片的宽高不变,所以参数量也降低了。最后再用1 * 1 卷积恢复维度。
Alt注意:

  1. H(x) = F(x) + x , F(x) 就表示残差,x表示输入。F(x)和x维度要一致。
  2. 残差在数理统计中是指实际观察值与估计值(拟合值)之间的差。“残差”蕴含了有关模型基本假设的重要信息。如果回归模型正确的话, 我们可以将残差看作误差的观测值。它应符合模型的假设条件,且具有误差的一些性质。利用残差所提供的信息,来考察模型假设的合理性及数据的可靠性称为残差分析。
(4)三种网络结构进行对比

Alt
说明:

在残差网络虚线处feature map的维度增加了,作者提出了两种选择:

  1. 直接通过zero padding 来增加维度(channel)。
  2. 乘以W矩阵投影到新的空间。是用1x1卷积实现的,直接改变1x1卷积的filters数目。这种会增加参数。

二、残差网络实现

(1) 简单残差结构的实现

# 这个例子可以用来了解残差块的基本原理
import torch
import torch.nn as nn
import numpy as np


class ResNet(nn.Module):
    def __init__(self):
        super(ResNet, self).__init__()
        self.conv = nn.Conv2d(3, 6, 3, 1, padding=1)

    def forward(self, x):
        # 这里的维度要一致
        return x.repeat(1, 2, 1, 1) + self.conv(x)

if __name__ == '__main__':
    net = ResNet()
    x = torch.randn(1, 3, 6, 6)
    print(net(x).shape)

(2) 完整残差结构(残差块)实现

"""
3 * 3 卷积 ————> 3 * 3 卷积
"""
import torch.nn as nn
import torch.nn.functional as F


class BasicBlock(nn.Module):
    expansion = 1
   
    def __init__(self, in_feature, out_feature, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_feature, out_feature, kernel_size=3, stride=stride, padding=1)
        self.bn1 = nn.BatchNorm2d(out_feature)
        self.conv2 = nn.Conv2d(out_feature, out_feature, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(out_feature)
        self.shortcut = nn.Sequential()
        """
        经过处理后的x要与x的维度相同(尺寸和深度)
        如果不相同,需要添加卷积+BN来变换为同一维度
        """
        if stride != 1 or in_feature != self.expansion * in_feature:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_feature, self.expansion*in_feature, kernel_size=1, stride=stride),
                nn.BatchNorm2d(self.expansion * in_feature)
            )
        
    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out

(3) 残差瓶颈结构实现

"""
1*1 卷积 ————> 3 * 3 卷积 ————>  1 * 1 卷积
"""
import torch.nn as nn
import torch.nn.functional as F


class Bottleneck(nn.Module):
    # 前面1x1和3x3卷积的filter个数相等,最后1x1卷积是其expansion倍
    expansion = 4
    def __init__(self, in_feature, out_feature, stride=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_feature, out_feature, kernel_size=1, stride=stride)
        self.bn1 = nn.BatchNorm2d(out_feature)
        self.conv2 = nn.Conv2d(out_feature, out_feature, kernel_size=3, padding=1, stride=stride)
        self.bn2 = nn.BatchNorm2d(out_feature)
        self.conv3 = nn.Conv2d(out_feature, self.expansion*out_feature, kernel_size=1, padding=1, stride=stride)
        self.bn3 = nn.BatchNorm2d(self.expansion*out_feature)
        self.shortcut = nn.Sequential()
        if stride != 1 or in_feature != self.expansion*out_feature:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_feature, self.expansion*out_feature, kernel_size=1, stride=stride),
                nn.BatchNorm2d(self.expansion*out_feature)
            )
    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章