机器学习——残差网络
函数类
残差块
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 | import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l class Residual(nn.Module): #@save 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) |
下面我们来查看输入和输出形状一致的情况。
1 2 3 4 | blk = Residual( 3 , 3 ) X = torch.rand( 4 , 3 , 6 , 6 ) Y = blk(X) Y.shape |
torch.Size([4, 3, 6, 6])
我们也可以在增加输出通道数的同时,减半输出的高和宽。
1 2 | blk = Residual( 3 , 6 , use_1x1conv = True , strides = 2 ) blk(X).shape |
torch.Size([4, 6, 3, 3])
ResNet模型
ResNet的前两层跟之前介绍的GoogLeNet中的一样: 在输出通道数为64、步幅为2的卷积层后,接步幅为2的最大汇聚层。 不同之处在于ResNet每个卷积层后增加了批量规范化层。
1 2 3 | 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 )) |
GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为2的最大汇聚层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。
下面我们来实现这个模块。注意,我们对第一个模块做了特别处理。
1 2 3 4 5 6 7 8 9 10 | 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加入所有残差块,这里每个模块使用2个残差块。
1 2 3 4 | 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 )) |
最后,与GoogLeNet一样,在ResNet中加入全局平均汇聚层,以及全连接层输出。
1 2 3 | net = nn.Sequential(b1, b2, b3, b4, b5, nn.AdaptiveAvgPool2d(( 1 , 1 )), nn.Flatten(), nn.Linear( 512 , 10 )) |
在训练ResNet之前,让我们观察一下ResNet中不同模块的输入形状是如何变化的。 在之前所有架构中,分辨率降低,通道数量增加,直到全局平均汇聚层聚集所有特征。
1 2 3 4 | 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])
虽然ResNet的主体架构跟GoogLeNet类似,但ResNet架构更简单,修改也更方便。这些因素都导致了ResNet迅速被广泛使用。
总结
-
学习嵌套函数(nested function)是训练神经网络的理想情况。在深层神经网络中,学习另一层作为恒等映射(identity function)较容易(尽管这是一个极端情况)。
-
残差映射可以更容易地学习同一函数(指的是当 H(x)=x 时,残差映射 F(x) 为零),例如将权重层中的参数近似为零。
-
利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播。
-
残差网络(ResNet)对随后的深层神经网络设计产生了深远影响。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)