ResNet残差网络
论文原文:Deep Residual Learning for Image Recognition
学习参考:
ResNet(残差网络)详解
详解残差网络
一文读懂残差网络ResNet
残差网络(ResNet)
提出原因——解决网络退化(degradation)现象
随着网络层数的增多,训练集loss逐渐下降,然后趋于饱和,当你再增加网络深度的话,训练集loss反而会增大。
此处的退化与过拟合不同(过拟合中训练loss是一直减小),在网络退化时浅层网络能够达到比深层网络更好的训练效果。
退化现象的出现是由于前向传输的过程中,随着层数的加深,特征图(Feature Map)包含的图像信息会逐层减少,而ResNet的直接映射的加入,保证了
l
l
l 层的网络一定比
l
+
1
l+1
l+1 层包含更多的图像信息。
感觉有些像 LSTM 中的遗忘门(更好地捕获序列中的长距离依赖关系),能够一遍一遍强化原始信息使其不至于丢失。
残差和误差的区别
误差:观测值和真实值之间的差距
残差:预测值和观测值之间的差距
万能近似定理(Universal Approximation Theorem):一个前馈神经网络(feedforward neural network)如果具有线性输出层,同时至少存在一层具有任何一种“挤压”性质的激活函数(例如logistic sigmoid激活函数)的隐藏层,那么只要给予这个网络足够数量的隐藏单元,它就可以以任意的精度来近似任何从一个有限维空间到另一个有限维空间的波莱尔可测函数(Borel Measurable Function)。
- 残差网络假定理想预测——将卷积层训练成恒等映射(identity function) f ( x ) = x f(x)=x f(x)=x 则新生成模型和原模型将同样有效(即通过卷积操作后输入信息特征不会减弱)。将理想预测记为: 卷积后结果+某一个值 。
- 同时,由于新模型可能得出更优的解来拟合训练数据集,因此添加层似乎更容易降低训练误差。
这个某一个值有多种计算方法,此处我们采用原始输入 x x x 进行计算。
残差块
x x x 为第 l l l 层的结果(此处一直取 x x x); W W W 为卷积层操作; F ( x l , W l ) F(x_l, W_l) F(xl,Wl) 是残差部分,一般由两个或者三个卷积操作(以及激活和归一化)构成。
x l + 1 = x l + F ( x l , W l ) x_{l+1} = x_l + F(x_l, W_l) xl+1=xl+F(xl,Wl)
可得L层表示公式为:
x
L
=
x
l
+
∑
i
=
l
L
−
1
F
(
x
i
,
W
i
)
x_L = x_l + \sum_{i=l}^{L-1}F(x_i, W_i)
xL=xl+i=l∑L−1F(xi,Wi)
x
L
=
x
0
+
∑
l
=
0
L
−
1
F
(
x
l
,
W
l
)
x_L = x_0 + \sum_{l=0}^{L-1}F(x_l, W_l)
xL=x0+l=0∑L−1F(xl,Wl)
残差网络还有一个好处是不会出现梯度消失的问题,梯度表示为
∂
ε
∂
x
l
=
∂
ε
∂
x
L
∂
x
L
∂
x
l
=
∂
ε
∂
x
L
(
1
+
∂
∂
x
l
∑
i
=
l
L
−
1
F
(
x
i
,
w
i
)
)
\frac{\partial{\varepsilon}}{\partial_{x_l}} = \frac{\partial{\varepsilon}}{\partial_{x_L}}\frac{\partial{x_L}}{\partial_{x_l}} = \frac{\partial{\varepsilon}}{\partial_{x_L}}(1 + \frac{\partial}{\partial_{x_l}}\sum_{i=l}^{L-1}F(x_i, w_i))
∂xl∂ε=∂xL∂ε∂xl∂xL=∂xL∂ε(1+∂xl∂∑i=lL−1F(xi,wi))。因为
x
l
x_l
xl 的导数一直为1,而
∑
i
=
l
L
−
1
F
(
x
i
,
w
i
)
\sum_{i=l}^{L-1}F(x_i, w_i)
∑i=lL−1F(xi,wi) 不可能一直为-1
经过卷积计算后
x
l
x_l
xl 为第
x
l
+
l
x_{l+l}
xl+l 的通道数量可能并不一致,可以使用 1×1卷积 进行调整。
1×1卷积
1×1卷积 用于升维或者降维
下面是两个 1×1卷积 的例子
# 1×1卷积可用于控制通道数量和输出大小(缩小)
blk = Residual(3,3) # 输入通道3、通道数3的残差块计算,定义在下面
X = torch.rand(4, 3, 6, 6)
Y = blk(X)
Y.shape
# 输出为:torch.Size([4, 3, 6, 6])
# 1×1卷积层增加输出通道数的同时,减半输出的高和宽
blk = Residual(3,6, use_1x1conv=True, strides=2)
blk(X).shape
# 输出为:torch.Size([4, 6, 3, 3])
这时候我们可以把 1×1卷积 就记作
h
(
x
l
)
h(x_l)
h(xl),残差块表示为:
x
l
+
1
=
h
(
x
l
)
+
F
(
x
l
,
W
l
)
x_{l+1} = h(x_l) + F(x_l, W_l)
xl+1=h(xl)+F(xl,Wl)
残差块代码如下:
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Residual(nn.Module):
# 输入通道 通道数 1×1卷积 步长
def __init__(self, input_channels, num_channels,
use_1x1conv=False, strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, num_channels,
kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.Conv2d(num_channels, num_channels,
kernel_size=3, padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_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(X)
Y += X
return F.relu(Y)
ResNet模型实现
模块定义:
# 模块定义
def resnet_block(input_channels, num_channels, num_residuals,
first_block=False):
blk = []
for i in range(num_residuals):
# 第一个残差块且不是整个网络的第一个块
if i == 0 and not first_block:
blk.append(Residual(input_channels, num_channels,
use_1x1conv=True, strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk
一个卷积块和四个残差块:
# 第一层 卷积层 → 批归一化 → 最大池化
b1 = 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))
# 第二~五层 残差块层
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))
整体网络:
# 在一~五层后添加平均池化和全连接层
net = nn.Sequential(b1, b2, b3, b4, b5,
nn.AdaptiveAvgPool2d((1,1)), # 自适应平均池化层输出大小为 (1, 1) 的特征图
nn.Flatten(), nn.Linear(512, 10))
每个模块有4个卷积层(不包括恒等映射的1×1卷积层)。加上第一个7×7卷积层和最后一个全连接层,共有18层。 因此,这种模型通常被称为ResNet-18。
每一层的形状显示:
X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
# 输出为:
#Sequential output shape: torch.Size([1, 64, 56, 56])
#Sequential output shape: torch.Size([1, 64, 56, 56])
#Sequential output shape: torch.Size([1, 128, 28, 28])
#Sequential output shape: torch.Size([1, 256, 14, 14])
#Sequential output shape: torch.Size([1, 512, 7, 7])
#AdaptiveAvgPool2d output shape: torch.Size([1, 512, 1, 1])
#Flatten output shape: torch.Size([1, 512])
#Linear output shape: torch.Size([1, 10])
在Fashion-MNIST数据集上训练ResNet:
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
# 输出为:
loss 0.012, train acc 0.997, test acc 0.893
5032.7 examples/sec on cuda:0