Contents

  • 1 Intorduction
  • 2 残差块
  • 3 ResNet模型
  • 4 获取数据和训练模型


1 Intorduction

ResNet在2015年的ImageNet图像识别挑战赛夺魁。

由于存在梯度消失和梯度爆炸问题,深度很深的神经网络是很难训练的。解决方法之一是人为地让神经网络某些层跳过下一层神经元的连接,隔层相连,弱化每层之间的强联系,即跳跃连接(skip connection)。用它可以构建能够训练深度网络的ResNets,这种神经网络被称为Residual Networks(ResNets)。Residual Networks由许多隔层相连的Residual block组成。

2 残差块

ResNet沿用了VGG全3×3卷积层的设计。残差块里首先有2个有相同输出通道数的3×3卷积层。每个卷积层后接一个批量归一化层和ReLU激活函数。然后我们将输入跳过这两个卷积运算后直接加在最后的ReLU激活函数前。这样的设计要求两个卷积层的输出与输入形状一样,从而可以相加。如果想改变通道数,就需要引入一个额外的1×1卷积层来将输入变换成需要的形状后再做相加运算。

神经网络残差计算 神经网络残差块_神经网络残差计算

# 残差块的实现如下,它可以设定输出通道数、是否使用额外的1×1卷积层来修改通道数以及卷积层的步幅
import time
import torch
from torch import nn, optim
import torch.nn.functional as F

import sys
sys.path.append("..") 
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

class Residual(nn.Module):
    def __init__(self, in_channels, out_channels, use_1x1conv = False, stride = 1):
        super(Residual, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels,kernel_size = 3, padding = 1, stride=stride)
        self.conv2 = nn.Conv2d(out_channels, out_channels,kernel_size = 3, padding = 1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(in_channels, out_channels,kernel_size = 1, stride=stride)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)
    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
                X = self.conv3(Y)
        return F.relu(Y+X)

其中卷积操作、批量归一化BN、激活函数ReLU操作顺序可参考下图:

神经网络残差计算 神经网络残差块_卷积_02

3 ResNet模型

net = nn.Sequential(
        nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
        nn.BatchNorm2d(64), 
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# ResNet使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。
def resnet_block( in_channels, out_channels, num_resduals, first__block = False):
    assert in_channels==out_channels
    blk=[]
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=2))
        else:
            blk.append(Residual(out_channels, out_channels))
    return nn.Sequential(*blk)

net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))
net.add_module("resnet_block2", resnet_block(64 128, 2))
net.add_module("resnet_block3", resnet_block(128, 256, 2))
net.add_module("resnet_block4", resnet_block(256, 512, 2))
# 加上全局平均池化层后接上全连接输出层
net.add_module("global_avg_pool", d2l.GlobalAvgPool2d()) # GlobalAvgPool2d的输出: (Batch, 512, 1, 1)
net.add_module("fc", nn.Sequential(d2l.FlattenLayer(), nn.Linear(512, 10)))

每个模块里有4个卷积层(不计算1×1卷积层),加上最开始的卷积层和最后的全连接层,共计18层。这个模型通常也被称为ResNet-18。

以上的模型参数是ResNet-18中的模型参数,也可按照下图修改其参数:

神经网络残差计算 神经网络残差块_深度学习_03

4 获取数据和训练模型

# 硬件限制,不跑结果了……
batch_size = 256
# 如出现“out of memory”的报错信息,可减小batch_size或resize
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)

lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)