Pytorch搭建网络模型-ResNet
一、ResNet的两个结构
首先来看一下ResNet和一般卷积网络结构上的差异:
图中上面一部分就是ResNet34的网络结构图,下面可以理解为一个含有34层卷积层的CNN,它们的差异就在于是否存在箭头。而这个箭头就是ResNet的特殊结构之一:shortcut(Residual的组成部分);另一个结构就是Batch Normalization(BN,批量归一化)。
1. Residual(残差结构)
Residual结构使用了一种shortcut的连接方式,也可理解为捷径。让特征矩阵隔层相加,所谓相加是特征矩阵相同位置上的数字进行相加。ResNet的残差结构分为两种,如图:
- BasicBlock(左侧的残差结构)
BasicBlock是浅层ResNet的基础模块,如ResNet18、ResNet18。 - Bottleneck(右侧的残差结构)
Bottleneck是深层ResNet的基础模块,如ResNet50、ResNet101、ResNet152。其中第一层的1×1的卷积核的作用是对特征矩阵进行降维操作,将特征矩阵的深度由256降为64;
第三层的1×1的卷积核是对特征矩阵进行升维操作,将特征矩阵的深度由64升成256。这么做的原因就在于可以减少模型的参数:
如果采用BasicBlock,参数的个数应该是:256×256×3×3×2=1179648;
采用Bottleneck,参数的个数是:1×1×256×64+3×3×64×64+1×1×256×64=69632。
因此,Bottleneck又被称为ResNet的瓶颈结构、沙漏结构,图像的尺寸由256 -> 64 -> 256 就像一个瓶颈、沙漏,如图:
仔细观察开头给出的ResNet34的网络结构图,可以发现有一些shortcut是实线,而有一些是虚线,它们的区别就在于是否需要对shortcut进行维度变换。这里以BasicBlock为例:
上图右侧给出了详细的虚线残差结构,注意下每个卷积层的步距stride,以及捷径分支上的卷积核的个数(与主分支上的卷积核个数相同),最终的目的就是保证卷积层的输入和输出尺寸要完全一样。
假设输入尺寸为X,卷积操作为F(),那么BasicBlock的输出:Y = F(X) + X,因为要让两个特征F(X)和X进行加法操作,因此两个特征的尺寸必须一致。
2. Batch Normalization(批量归一化)
Batch Normalization是指批标准化处理,将一批数据的feature map满足均值为0,方差为1的分布规律。
下图展示了一个batch size为2(两张图片)的Batch Normalization的计算过程,假设feature1、feature2分别是由image1、image2经过一系列卷积池化后得到的特征矩阵,feature的channel为2,那么代表该batch的所有feature的channel1的数据,同理代表该batch的所有feature的channel2的数据。然后分别计算和的均值与方差,得到我们的和两个向量。然后在根据标准差计算公式分别计算每个channel的值(公式中的是一个很小的常量,防止分母为零的情况)。在我们训练网络的过程中,我们是通过一个batch一个batch的数据进行训练的,但是我们在预测过程中通常都是输入一张图片进行预测,此时batch size为1,如果在通过上述方法计算均值和方差就没有意义了。所以我们在训练过程中要去不断的计算每个batch的均值和方差,并使用移动平均(moving average)的方法记录统计的均值和方差,在训练完后我们可以近似认为所统计的均值和方差就等于整个训练集的均值和方差。然后在我们验证以及预测过程中,就使用统计得到的均值和方差进行标准化处理。
在图像预处理过程中通常会对图像进行标准化处理,这样能够加速网络的收敛,如下图所示,对于Conv1来说输入的就是满足某一分布的特征矩阵,但对于Conv2而言输入的feature map就不一定满足某一分布规律了(注意这里所说满足某一分布规律并不是指某一个feature map的数据要满足分布规律,理论上是指整个训练样本集所对应feature map的数据要满足分布规律),因此在ResNet中在每一个卷积层后面偶读加入了Batch Normalization,而Batch Normalization的目的就是使我们的feature map满足均值为0,方差为1的分布规律。
二、几种ResNet的网络结构表
上图中从左至右依次表述了:ResNet18、ResNet34、ResNet50、ResNet101、ResNet152的模型结构,原文的标注中已说明,conv3_x, conv4_x, conv5_x所对应的一系列残差结构的第一层残差结构都是虚线残差结构。图中的[]×2就代表用了两块BasicBlock或者Bottleneck。
三、利用Pytorch来搭建ResNet网络
了解了ResNet网络结构后,就可以开始利用Pytorch来编写ResNet模型了。
利用pytoch来搭建类似ResNet有两种方式,一种是直接调用torchvision.models
中Pytorch官方已经打包好的模型(无敌的torchvision),非常简洁和方便,但是缺点就是灵活性不够强,不太方便自己设计想要的结构;第二种就是通过继承nn.Module
类来根据自身需求去定义自己的网络结构(如果是学习的话,建议使用这一种方式,虽然比较复杂,但是真正编出来以后,会有很大的收获)。
1.利用torchvision.models
直接加载所需要的模型
Pytorch对于深度学习真的特别友好,torchvision.models
中包含了计算机视觉中各种任务的经典模型,包括图像分类、目标检测、语义分割等任务中的各种模型都有。具体包含了哪些模型可以去Pytorch的官网查看https://pytorch.org/vision/stable/models.html,这里就以分类任务中的ResNet为例。
import torchvision.models as models
import torch.nn as nn
'''ResNet34模型的搭建(ResNet18一样)'''
# resnet34 = models.resnet34(pretrained=True) # pretrained为True的话就是需要加载预训练模型的权重,不过一般不这么用,就算要加载权重也不会这么用(容易报错)
Mymodel = models.resnet34(pretrained=False) # pretrained为False的话就是只导入模型的结构,不加载预训练的权重
Mymodel = models.resnet34() # 由于pretrained的默认值就是False,所以一般不加载预训练权重的话直接写这一句
print(Mymodel.fc) # 由于Pytorch上分类任务的ResNet是基于Imagenet数据集设计的,因此最后输出的是1000类
Mymodel.fc = nn.Linear(512, 5) # 所以唯一需要修改的地方就是最后一层全连接层的参数,要将输出类别数量改成自己数据集的类别数量,这里以5分类为例
print(Mymodel.fc)
model_dict = Mymodel.state_dict()
print('--------------------------------') # 手动分隔符(手动狗头)
'''ResNet50模型的搭建(ResNet101、ResNet152一样)'''
Mymodel = models.resnet50()
print(Mymodel.fc) # 与ResNet34区别在于,ResNet50的最后一层全连接层的输入通道是2048,而ResNet34是512
Mymodel.fc = nn.Linear(2048, 5) # 注意输入通道数就行
print(Mymodel.fc)
Linear(in_features=512, out_features=1000, bias=True)
Linear(in_features=512, out_features=5, bias=True)
--------------------------------
Linear(in_features=2048, out_features=1000, bias=True)
Linear(in_features=2048, out_features=5, bias=True)
利用torchvision.models
,几句话就搭好了模型(顶级啊!!!)。当然这种方法的缺点就是太依赖于torchvision.models
了,如果自己要用的模型没有包含在内,则无法采用这种方法。
2.利用nn.Module
来构建自己的模型
torch.nn
是专门为神经网络设计的模块化接口. nn构建于autograd之上,可以用来定义和运行神经网络。nn.Module
是nn中十分重要的类,包含网络各层的定义及forward方法。
在搭建ResNet之前,先搭建一个简单的神经网络来熟悉一下怎么用nn.Module
来搭建自己的模型,并且需要注意些什么。
import torch.nn as nn
class Mymodel(nn.Module):
'''定义类的初始化函数'''
def __init__(self, inchannel, outchannel): # self后面就是我们传入的参数,通常是输入输出通道数量等变量
super(Mymodel, self).__init__() # 继承父类nn.Module的初始化方法
'''定义网络的每一层'''
self.conv1 = nn.Conv2d(in_channels=inchannel, out_channels=6, kernel_size=3, stride=1, padding=1, bias=False) # 一个2d卷积操作
self.bn1 = nn.BatchNorm2d(6) # 这里要注意,括号内的参数要与上一层的卷积层输出通道数相同
self.relu1 = nn.ReLU() # 加入ReLu激活函数
self.conv2 = nn.Conv2d(in_channels=6, out_channels=9, kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(9)
self.relu2 = nn.ReLU()
self.fc = nn.Linear(9, outchannel, bias=True) # 全连接层的bias通常设为True
'''定义网络的前向传播路径'''
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu1(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.relu2(x)
x = self.fc(x)
return x
Net = Mymodel(3, 5)
print(Net)
Mymodel(
(conv1): Conv2d(3, 6, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(6, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu1): ReLU()
(conv2): Conv2d(6, 9, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(9, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu2): ReLU()
(fc): Linear(in_features=9, out_features=5, bias=True)
)
上块代码就是利用nn.Module
搭建了一个具有两层卷积层的神经网络,在利用nn.Module
搭建自己的模型时,必须要有def __init__(self)
和def forward(self)
,前者是用来定义网络中每一层的操作,后者是设定网络前向传播的路径。在自己设计网络的时候,最关键的点就在于要理清楚每一层之间通道的变换关系,上一层的输出通道数与下一层的输入通道数一定要相等,否则数据就无法正常在网络中前向传播。
做一个小拓展:随着网络层数的增多,为了让网络代码看起来更加清晰,通常还会引入nn.Sequential
让你的代码结构变得更加清晰,而且在编写def forward(self)
的时候没那么痛苦(手动狗头),具体代码如下:
import torch.nn as nn
class Mymodel(nn.Module):
'''定义类的初始化函数'''
def __init__(self, inchannel, outchannel): # self后面就是我们传入的参数,通常是输入输出通道数量等变量
super(Mymodel, self).__init__() # 继承父类nn.Module的初始化方法
'''将每一次卷积操作模块化'''
self.conv_block1 = nn.Sequential(
nn.Conv2d(in_channels=inchannel, out_channels=6, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(6),
nn.ReLU())
self.conv_block2 = nn.Sequential(
nn.Conv2d(in_channels=6, out_channels=9, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(9),
nn.ReLU())
self.fc = nn.Linear(9, outchannel, bias=True)
'''定义网络的前向传播路径'''
def forward(self, x):
x = self.conv_block1(x)
x = self.conv_block2(x)
x = self.fc(x)
return x
Net = Mymodel(3, 5)
print(Net)
Mymodel(
(conv_block1): Sequential(
(0): Conv2d(3, 6, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(1): BatchNorm2d(6, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(conv_block2): Sequential(
(0): Conv2d(6, 9, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(1): BatchNorm2d(9, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(fc): Linear(in_features=9, out_features=5, bias=True)
)
上面两块代码所搭建的网络模型是一模一样的,只是在代码结构上后者明显要更清晰、更简洁,方面代码后续的学习与维护。引入nn.Sequential
让网络的代码模块化了,随着网络层数的增加,将整个网络模块化是很有必要的。nn.Sequential
相当于一个list,里面按顺序存放着网络每一层的操作,因此在编辑def forward(self)
的时候直接写一行模块名称就可以了,即x = self.conv_block1(x)
。
此外,由于nn.Sequential
是以一个list形式呈现的,因此在后续如果遇到几层结构完全一样的操作的话,还可以利用for循环将一样结构的层添加到nn.Sequential
模块中去,省心省事,后续在ResNet模型的搭建中就可以看到这一用途。
下面就以ResNet34为例,利用Pytorch搭建模型。
import torch.nn as nn
import torch
'''定义ResNet34的BasicBlock'''
class BasicBlock(nn.Module):
'''定义类的初始化函数'''
def __init__(self, inchannel, outchannel, stride=1, downsample=None):
super(BasicBlock, self).__init__()
'''定义所要用的操作'''
self.conv1 = nn.Conv2d(in_channels=inchannel, out_channels=outchannel,
kernel_size=3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(outchannel) # ResNet中每一次卷积后都会有一个BatchNorm层,即每一次卷积后都对数据做一次批量归一化
self.relu = nn.ReLU(inplace=True) # 这里的(inplace=True)就是类似x = x+1,做一个覆盖操作,就不会浪费内存
self.conv2 = nn.Conv2d(in_channels=outchannel, out_channels=outchannel,
kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(outchannel)
self.donwsample = downsample
'''定义前向传播路径'''
def forward(self, x):
shortcut = x # 保存输入特征,以便后续的shortcut操作
'''正常的前向传播路径'''
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
'''引入残差结构'''
if self.donwsample is not None:
shortcut = self.donwsample(x) # 这里就是上面所提到的虚线和实线的shortcut,输入输出尺寸如果一样就跳过这一步,如果不一样则需要下采样使其尺寸统一
out += shortcut # 输入输出相加的残差结构
out = self.relu(out)
return out
'''定义ResNet34网络类'''
class ResNet34(nn.Module):
def __init__(self, num_class=1000):
super(ResNet34, self).__init__()
self.num_BasicBlock = [3, 4, 6, 3] # 定义每一个卷积模块有多少个BasicBlock
self.in_convx_channels = 64 # 定义convx的输入通道数,初始值为64
'''定义ResNet34网络的第一块卷积结构'''
self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) # 最大池化
'''定义ResNet34网络的残差块卷积结构'''
self.conv2 = self.make_convx(BasicBlock, 64, self.num_BasicBlock[0], stride=1)
self.conv3 = self.make_convx(BasicBlock, 128, self.num_BasicBlock[1], stride=2)
self.conv4 = self.make_convx(BasicBlock, 256, self.num_BasicBlock[2], stride=2)
self.conv5 = self.make_convx(BasicBlock, 512, self.num_BasicBlock[3], stride=2)
self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1)) # 全局平均池化
self.fc = nn.Linear(512, num_class)
'''初始化模型权重'''
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
'''这个函数就是用来定义每一块convx的'''
def make_convx(self, BasicBlock, channels, num_BasicBlock, stride=1):
"""
BasicBlock: 导入上述定义的BasicBlock块
channels: 导入每一个BasicBlock中特征的通道数(由于每一块BasicBlock中的通道数都相同, 所以这里只导入了一个通道数)
num_BasicBlock: 每一块BasicBlock有多少层BasicBlock
"""
downsample = None # 设置downsample的默认值为False,因为除了conv3_1,conv4_1,conv5_1第一层需要下采样之外,其他的BasicBlock都不需要下采样
if stride != 1: # 通过步长来决定是否需要下采样
downsample = nn.Sequential(
# 这里要注意, in_channels=self.in_convx_channels每一块BasicBlock的第一层的输入通道数要与上一块BasicBlock最后一层的输出通道数匹配
nn.Conv2d(in_channels=self.in_convx_channels, out_channels=channels, kernel_size=1, stride=stride, padding=0), # 一个1×1的卷积
nn.BatchNorm2d(channels)
)
convx =[]
convx.append(
BasicBlock(inchannel=self.in_convx_channels, outchannel=channels, stride=stride, downsample=downsample) # 每个convx的第一层单独列出来
)
'''利用for循环将结构相同的BasicBlock层加入到convx块中去'''
for _ in range(1, num_BasicBlock):
convx.append(
BasicBlock(inchannel=channels, outchannel=channels, stride=1, downsample=None) # 后续的层由于结构一样,因此可以通过for循环来加入
)
self.in_convx_channels = channels # 将这一块BasicBlock最后一层的输出通道数保存下来,提供给下一块BasicBlock作为输入通道数
return nn.Sequential(*convx) # 这里就用到了nn.Sequential,大大简化了我们的代码量
def forward(self,x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
model = ResNet34(num_class=5) # 将模型实例化后就可以直接使用
print(model)
ResNet34(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(conv2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(2): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(conv3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(donwsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2))
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(2): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(3): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(conv4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(donwsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2))
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(2): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(3): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(4): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(5): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(conv5): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(donwsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2))
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(2): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=5, bias=True)
)
上面就完成了ResNet34的搭建,自己用代码实现模型的时候注意以下几点就好:
- 将模型模块化,结构相似的层可以归为一个模块,让模型层次化,这样编写起来思路会清晰很多
- 理清楚模型中层与层之间数据尺寸的变换,要注意好衔接,不仅仅是通道上的,还有数据本身的大小
- 注意前向通道的编写,顺序一定不能弄错
编写好网络以后,除了用print(model)
来查看网络的大概结构外,单纯的print(model)
,只能得出基础构件的信息,既不能显示出每一层的shape,也不能显示对应参数量的大小,因此通常使用torchinfo
来可视化模型。torchinfo提供了更加详细的信息,包括模块信息(每一层的类型、输出shape和参数量)、模型整体的参数量、模型大小、一次前向或者反向传播需要的内存大小等。
完整项目在我上传的资源里面,需要的可以自取
from torchinfo import summary
model = ResNet34(num_class=5)
summary(model, (1, 3, 224, 224)) # (1, 3, 224, 224)中的1:batchsize;3:输入图片的通道数channel;224:输入图片的尺寸
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
ResNet34 [1, 5] --
├─Conv2d: 1-1 [1, 64, 112, 112] 9,408
├─BatchNorm2d: 1-2 [1, 64, 112, 112] 128
├─ReLU: 1-3 [1, 64, 112, 112] --
├─MaxPool2d: 1-4 [1, 64, 56, 56] --
├─Sequential: 1-5 [1, 64, 56, 56] --
│ └─BasicBlock: 2-1 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-1 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-2 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-3 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-4 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-5 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-6 [1, 64, 56, 56] --
│ └─BasicBlock: 2-2 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-7 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-8 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-9 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-10 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-11 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-12 [1, 64, 56, 56] --
│ └─BasicBlock: 2-3 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-13 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-14 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-15 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-16 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-17 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-18 [1, 64, 56, 56] --
├─Sequential: 1-6 [1, 128, 28, 28] --
│ └─BasicBlock: 2-4 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-19 [1, 128, 28, 28] 73,728
│ │ └─BatchNorm2d: 3-20 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-21 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-22 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-23 [1, 128, 28, 28] 256
│ │ └─Sequential: 3-24 [1, 128, 28, 28] 8,576
│ │ └─ReLU: 3-25 [1, 128, 28, 28] --
│ └─BasicBlock: 2-5 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-26 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-27 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-28 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-29 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-30 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-31 [1, 128, 28, 28] --
│ └─BasicBlock: 2-6 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-32 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-33 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-34 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-35 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-36 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-37 [1, 128, 28, 28] --
│ └─BasicBlock: 2-7 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-38 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-39 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-40 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-41 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-42 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-43 [1, 128, 28, 28] --
├─Sequential: 1-7 [1, 256, 14, 14] --
│ └─BasicBlock: 2-8 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-44 [1, 256, 14, 14] 294,912
│ │ └─BatchNorm2d: 3-45 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-46 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-47 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-48 [1, 256, 14, 14] 512
│ │ └─Sequential: 3-49 [1, 256, 14, 14] 33,536
│ │ └─ReLU: 3-50 [1, 256, 14, 14] --
│ └─BasicBlock: 2-9 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-51 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-52 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-53 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-54 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-55 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-56 [1, 256, 14, 14] --
│ └─BasicBlock: 2-10 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-57 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-58 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-59 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-60 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-61 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-62 [1, 256, 14, 14] --
│ └─BasicBlock: 2-11 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-63 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-64 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-65 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-66 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-67 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-68 [1, 256, 14, 14] --
│ └─BasicBlock: 2-12 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-69 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-70 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-71 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-72 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-73 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-74 [1, 256, 14, 14] --
│ └─BasicBlock: 2-13 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-75 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-76 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-77 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-78 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-79 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-80 [1, 256, 14, 14] --
├─Sequential: 1-8 [1, 512, 7, 7] --
│ └─BasicBlock: 2-14 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-81 [1, 512, 7, 7] 1,179,648
│ │ └─BatchNorm2d: 3-82 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-83 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-84 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-85 [1, 512, 7, 7] 1,024
│ │ └─Sequential: 3-86 [1, 512, 7, 7] 132,608
│ │ └─ReLU: 3-87 [1, 512, 7, 7] --
│ └─BasicBlock: 2-15 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-88 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-89 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-90 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-91 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-92 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-93 [1, 512, 7, 7] --
│ └─BasicBlock: 2-16 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-94 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-95 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-96 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-97 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-98 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-99 [1, 512, 7, 7] --
├─AdaptiveAvgPool2d: 1-9 [1, 512, 1, 1] --
├─Linear: 1-10 [1, 5] 2,565
==========================================================================================
Total params: 21,288,133
Trainable params: 21,288,133
Non-trainable params: 0
Total mult-adds (G): 3.66
==========================================================================================
Input size (MB): 0.60
Forward/backward pass size (MB): 59.81
Params size (MB): 85.15
Estimated Total Size (MB): 145.56
==========================================================================================