本质

拟合出有关恒等映射的残差映射f(x)-x并将加权运算的权重和偏差参数学成0,那么f(x)即为恒等映射。

残差块

⾸先有2个有相同输出通道数的3 * 3卷积层。每个卷积层后接⼀个批量归⼀化层和ReLU激活函数。然后我们将输⼊跳过这两个卷积运算后直接加在最后的ReLU激活函数前。这样的设计要求两个卷积层的输出与输⼊形状⼀样,从⽽可以相加。如果想改变通道数,就需要引⼊⼀个额外的1 * 1卷积层来将输⼊变换成需要的形状后再做相加运算。

残差网络

首先是输出通道数为64、步幅为2的7 * 7卷积层后接一个批量归⼀化层再接一个步幅为2的3 * 3的最⼤池化层。

之后是4个由残差块组成的模块,每个模块使⽤若⼲个同样输出通道数的残差块(本文中为两个)。第⼀个模块的通道数同输⼊通道数⼀致。

最后加⼊全局平均池化层后接上全连接层输出。

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

获取数据和训练模型

在Fashion-MNIST数据集上训练ResNet。

以下为完整代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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')

def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
if first_block:
assert in_channels == out_channels
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(d2l.Residual(in_channels, out_channels, use_1x1conv=True, stride=2))
else:
blk.append(d2l.Residual(out_channels, out_channels))
return nn.Sequential(*blk)

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))
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())
net.add_module("fc",nn.Sequential(d2l.FlattenLayer(),nn.Linear(512,10)))

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)

结果