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是512Mymodel.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 nnclass 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 xNet = 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 nnclass 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 xNet = 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]  # 定义每一个卷积模块有多少个BasicBlockself.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 xmodel = 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
==========================================================================================

利用Pytorch搭建简单的图像分类模型(之二)---搭建网络相关推荐

  1. 【小白学习PyTorch教程】六、基于CIFAR-10 数据集,使用PyTorch 从头开始​​构建图像分类模型...

    「@Author:Runsen」 图像识别本质上是一种计算机视觉技术,它赋予计算机"眼睛",让计算机通过图像和视频"看"和理解世界. 在开始阅读本文之前,建议先 ...

  2. 【小白学习PyTorch教程】六、基于CIFAR-10 数据集,使用PyTorch 从头开始​​构建图像分类模型

    @Author:Runsen 图像识别本质上是一种计算机视觉技术,它赋予计算机"眼睛",让计算机通过图像和视频"看"和理解世界. 在开始阅读本文之前,建议先了解 ...

  3. Pytorch最简单的图像分类——K折交叉验证处理小型鸟类数据集分类2.0版本ing

    https://blog.csdn.net/hb_learing/article/details/110411532 https://blog.csdn.net/Pl_Sun/article/deta ...

  4. 基于PyTorch的卷积神经网络图像分类——猫狗大战(二):使用Pytorch定义网络模型

    文章目录 1. 需要用到的库 2. 模型定义 3. 测试 基于上一篇文章 https://blog.csdn.net/linghu8812/article/details/100044971,这次介绍 ...

  5. 【深度学习】——利用pytorch搭建一个完整的深度学习项目(构建模型、加载数据集、参数配置、训练、模型保存、预测)

    目录 一.深度学习项目的基本构成 二.实战(猫狗分类) 1.数据集下载 2.dataset.py文件 3.model.py 4.config.py 5.predict.py 一.深度学习项目的基本构成 ...

  6. 3dmax su 简单_【建模技巧】如何用3DMAX制作简单的绣球模型

    今天我给大家讲的是如何制作一个绣球的模型.说到绣球,大家会想到在中国古代,姑娘到了婚嫁之时,就预定于某一天,让求婚者集中在绣楼之下,姑娘抛出一个绣球,谁抢到这个绣球,谁就可以成为这个姑娘的夫君.而今天 ...

  7. Ubuntu 搭建简单的Web服务器

    Ubuntu 搭建简单的Web服务器 一.搭建工具 二.搭建步骤 2.1Apche工具安装 2.2打开火狐浏览器 三.实现文件浏览功能 一.搭建工具   在这个实验上我用的是apche搭建的web服务 ...

  8. matlab里的仿真模型块,搭建simulink模型(如何利用MATLAB/SIMULINK搭建简单的仿真模型)...

    如何利用MATLAB/SIMULINK搭建简单的仿真模型 安装完MATLAB软件后,在电脑桌面点击MATLAB快捷方式 打开MATLAB后,点击Simulink Library按钮 之后会进入Simu ...

  9. 一个完整的pytorch预训练实现图像分类,模型融合

    向AI转型的程序员都关注了这个号???????????? 机器学习AI算法工程   公众号:datayx 利用pytorch实现图像分类,其中包含的densenet,resnext,mobilenet ...

最新文章

  1. 阿里不让多表join?我偏要!
  2. OpenCASCADE:Modeling Data之拓扑
  3. MySQL划重点-查询-条件
  4. 一道内存分配的面试题
  5. 【深度学习】深入理解Batch Normalization批归一化
  6. python发布服务,Python服务发现:在本地n上发布服务
  7. OSPF——DR及BDR详解
  8. nginx的gzip压缩
  9. SQL(1)—增删改查
  10. windows10商店应用离线安装方法
  11. Jenkins下载安装
  12. 图形界面上的任意形状图形按钮
  13. 图文电视related
  14. 利用keytool工具生成数字证书
  15. 软件测试简历上的职业技能怎么写,测试工程师岗位个人简历个人技能范文
  16. ios html5 audio mp3,H5 audio 微信端 在IOS上不能播放音乐
  17. [译] 为什么设计师讨厌政治(如何解决)
  18. Vue Dhtmlxgantt甘特图/横道图 baselines 含(计划、实际时间对比)树形实例实现及部分扩展
  19. 小康qq小助手 免费
  20. 克服“测试怠惰”的习惯

热门文章

  1. python中true_python中的true是什么
  2. 函数周期表丨INTERSECT
  3. mysql intersect用法格式_我们如何模拟MySQL INTERSECT查询?
  4. 全新iPhone发布会确定时间了!从Loog上可以看出有新配色
  5. Zookeeper-3.4.5安装步骤及异常处理
  6. JAVA-------计算机基础与准备阶段
  7. shell编程经典案例,建议收藏
  8. iP查询工具,免费的IP地址查询
  9. 雪碧图 css 使用方式与 Js使用方式
  10. Tech Talk · 云技术有话聊 | 关键基础部件如何保障高可靠?