之——ResNet
目录
杂谈
一味地增加模型复杂度显然是不可取的,就算运算资源足够,也可能有参数空间的偏移,依靠单纯的大和深来解决问题过于野蛮暴力。
ResNet(Residual Network)是深度学习中一种非常流行的卷积神经网络(CNN)架构,它引入了残差连接(Residual Connection)的概念,以解决深度神经网络中的梯度消失和梯度爆炸等问题。以下是ResNet的介绍和主要特点:
1. 残差连接: ResNet的核心思想是通过残差连接引入跳跃连接,使得模型能够学习残差函数。在传统的卷积神经网络中,每个层都是学习映射输入到输出的函数,而在ResNet中,每个层学习的是输入和输出之间的残差(差异)的函数。这种残差连接的引入使得网络更容易训练,允许训练非常深的网络,因为梯度可以更轻松地传播。
2. 深度: ResNet以其非常深的网络结构而闻名,通常包含数十、甚至数百个卷积层。这种深度有助于提取复杂的特征,从而提高了网络的性能。
3. 网络块: ResNet的网络块通常由多个卷积层组成,其中包括正常的卷积层、批量归一化层和激活函数。每个网络块的最后都包含了残差连接,将输入和输出相加。
4. 堆叠块: ResNet中的网络块可以堆叠在一起,形成深层网络。这种堆叠可以通过增加网络块的数量来增加网络的深度,而不需要担心梯度消失的问题。
5. 预训练: 由于ResNet的深度,通常会采用迁移学习的方式,使用在大规模图像数据上预训练的模型,然后在特定任务上进行微调。这种预训练的模型通常可以提供出色的性能。
6. 应用广泛: ResNet在各种计算机视觉任务中表现出色,包括图像分类、目标检测、语义分割等。它曾经在ImageNet图像分类竞赛中取得了重大突破,将错误率降低到了极低的水平。
7. 参数效率: 尽管ResNet非常深,但由于残差连接的引入,它的参数效率相对较高,因为大部分层都学习到了残差。
总之,ResNet的残差连接和深层结构使其成为深度学习中的一个重要里程碑,对于解决深度神经网络中的梯度问题和提高性能非常有帮助。这个架构的思想也启发了许多后续的卷积神经网络设计。
正文
1.问题:深度不是绝对的优化方法
模型的深度并不能保证模型的优秀,更复杂的模型不一定能拿到更好的参数,就是说卷错方向了:
resnet核心思想就是就算增加网络也不要使得和之前的差距太大,所以用添层的输入和添加层的输出来一起影响最后输出,而对于残差块所需要学习的自然成了最优模型f(x)-x,所以命名为残差块,中文名残差网络。这样使得就算这一层模块没有任何作用我也还保留着这一层得到的东西,这样就使得模型至少不会变差。
让我们聚焦于神经网络局部:如图所示,假设我们的原始输入为x,而希望学出的理想映射为f(x)(作为上方激活函数的输入)。左图虚线框中的部分需要直接拟合出该映射f(x),而右图虚线框中的部分则需要拟合出残差映射f(x)−x。 残差映射在现实中往往更容易优化。 ,残差映射也易于捕捉恒等映射的细微波动。
先学习一个容易学习的基准x的一些非线性变化(下图蓝色的,由x自身不断经过非线性变化得来),再学习每一个板块的细微残差映射(下图绿色的) 。
2.实现
ResNet沿用了VGG完整的3×3卷积层设计。 残差块里首先有2个有相同输出通道数的3×3卷积层。 每个卷积层后接一个批量规范化层和ReLU激活函数。 然后我们通过跨层数据通路,跳过这2个卷积运算,将输入直接加在最后的ReLU激活函数前。 这样的设计要求2个卷积层的输出与输入形状一样,从而使它们可以相加。 如果想跨层改变通道数,就需要在跨层数据通路中引入一个额外的1×1卷积层来将输入变换成需要的通道数后再与有通道数变化的卷积通路做相加运算。 残差块的实现如下:
2.1 残差快
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Residual(nn.Module):
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:
#残差通路为了保持通道数与图像大小和主要通路的一致使用了1*1卷积
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)
blk = Residual(3,3)
X = torch.rand(4, 3, 6, 6)
Y = blk(X)
Y.shape #torch.Size([4, 3, 6, 6]) 通道数和图片大小不变
blk = Residual(3,6, use_1x1conv=True, strides=2)
Y = blk(X)
Y.shape #torch.Size([4, 6, 3, 3]) 通道数变,图像大小减半
2.2 stages
GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。
图片进入,两次2stride。
#第一层,缩小再缩小
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))
第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为2的最大汇聚层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。注意,我们对第一个模块做了特别处理。
#之后有规律得连接残差快,一个有stride的接上一个没有的为一组,但第一个组第一个块不需要再缩小
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
第一个resnet_blk,通道数不变,高宽不变,之后的则在每一个块中的第一个残差快设置1*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)),
nn.Flatten(), nn.Linear(512, 10))
X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
训练:
lr, num_epochs, batch_size = 0.005, 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())
小结
-
学习嵌套函数(nested function)是训练神经网络的理想情况。在深层神经网络中,学习另一层作为恒等映射(identity function)较容易(尽管这是一个极端情况)。
-
残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零。
-
利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播。
-
残差网络(ResNet)对随后的深层神经网络设计产生了深远影响。
-
batchsize过大是会造成样本重复,所以不好收敛。
-
lr策略有固定、固定下降、cos等