参考沐神
7.6. 残差网络(ResNet) — 动手学深度学习 2.0.0-beta0 documentationhttps://zh-v2.d2l.ai/chapter_convolutional-modern/resnet.html
ResNet结构
????????这次就浅记一下,不深入讲解了。
?????????创新点是引入残差块,残差块有很好的性质。先看下面内容再展开残差块。
????????简单说一下上图,对于输入X和标签y,我们希望学习一个好的函数F来尽可能正确预测给定输入X对应的y。可能我们最开始的函数为F1,效果狠辣鸡。我们一顿操作改成F2,效果也不好,又改了好几次,变成了F6,可能效果反而更差了,在上图左半边我们可以很清楚的看到,F1模型的规模很小,但其实它比复杂的F6离最优点f'更接近。上面说的这种调整策略就是非嵌套函数类的调整,显然通过这种非嵌套函数类调整策略并不合适。
? ? ? ? 所以有大聪明提出了右边的嵌套函数类,它可以使我们在原来简单模型F1基础上不偏移的一点点向外扩张变复杂。在上图右边可以明显看出这种策略稳定性更高,可以很好的稳定的逼近最优点。
? ? ? ? 实现嵌套函数类的方式就是使用残差块!来浅看一下残差块和原来的普通块的区别吧~
?????????ResNet残差块有两种形式,具体用哪种形式根据use_1x1conv设置。use_1x1conv的作用就是调整通道数,因为1x1的卷积核不改变输入张量的形状,所以我们多搞几个1x1的卷积核就可以控制输出通道。
代码实现
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project :深度学习入门
@File :ResNet.py
@Author :little_spice
@Date :2022/5/8 21:19
"""
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
# 定义残差块
"""
残差块??先有2个有相同输出通道数的3 × 3卷积层。每个卷积
层后接?个批量规范化层和ReLU激活函数。然后我们通过跨层数据通路,跳过这2个卷积运算,将输?直接
加在最后的ReLU激活函数前。这样的设计要求2个卷积层的输出与输?形状?样,从?使它们可以相加。如
果想改变通道数,就需要引??个额外的1 × 1卷积层来将输?变换成需要的形状后再做相加运算。
"""
class Residual(nn.Module):
# 构造函数
def __init__(self,input_channels,num_channels,use_1x1conv=False,strides=1):
# 调用父类也就是nn.Moudle的构造函数
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):
# F.relu()一般在forward中用,nn.ReLU()在定义网络结构的时候用。前者是函数调用,后者是模块调用。
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)
# 不使用1x1卷积层
blk = Residual(3,3)
X = torch.rand(4,3,6,6)
Y = blk(X)
# print(Y)
# 使用1x1卷积层,同时可以在增加输出通道数的同时,减半输出的?和宽。
blk = Residual(3,6,use_1x1conv=True,strides=2)
# print(blk(X).shape)
# 定义ResNet模型
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))
"""
定义ResNet的模块
ResNet使?4个由残差块组成的模块,每个模块使?若?个同样输出通道数的残差块。第?个模块的通道数同输?通道数?致。
由于之前已经使?了步幅为2的最?汇聚层,所以?须减??和宽。之后的每个模块在第?个残差块?将上?个模块的通道数翻倍,
并将?和宽减半。
对一个模块做了特殊处理。
"""
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个残差块。
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中加?全局平均汇聚层,以及全连接层输出。
net = nn.Sequential(b1, b2, b3, b4, b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(), nn.Linear(512, 10))
"""
在训练ResNet之前,让我们观察?下ResNet中不同模块的输?形状是如何变化的。在之前所有架构中,分辨
率降低,通道数量增加,直到全局平均汇聚层聚集所有特征。
"""
X = torch.rand(size=(1,1,224,224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
# 在Fashion-MNIST数据集上训练模型
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())
?结果我就不展示了,感觉一跑我电脑巨卡,gtx1050确实顶不住了QAQ!
|