本系列文章配套代码获取有以下两种途径:
-
通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj
提取码:mnsj
-
前往GitHub获取:
https://github.com/returu/PyTorch
ResNet34:
-
输入层:
-
卷积层:
-
残差块层:
ResNet34 的残差块层由4个Layer组成,每个Layer包含多个残差块。每个Layer的输出通道数不同,且从Layer 2开始,每个Layer的第一个残差块会将特征图的尺寸减半(通过步长为2的卷积操作)。
-
Layer 1:包含3个残差块,每个残差块的输出通道数为64。
-
Layer 2:包含4个残差块,每个残差块的输出通道数为128。
-
Layer 3:包含6个残差块,每个残差块的输出通道数为256。
-
Layer 4:包含3个残差块,每个残差块的输出通道数为512。
-
平均池化层:
-
全连接层:
-
输出层:
每个残差块包含以下结构:
-
两个3×3的卷积层,每层后跟一个批量归一化(Batch Normalization)层和ReLU激活函数。 -
残差块的输入会通过一个跨层直连(如果需要的话,通过一个1×1的卷积层进行通道数的调整)与第二个卷积层的输出相加,形成残差连接。
在ResNet34中,当需要改变通道数或步长时,会使用1×1卷积作为短接路径的一部分,以匹配维度,使得残差块的输入和输出可以直接相加。
实现代码:
在PyTorch中,可以使用torchvision.models模块中的预定义模型来快速搭建ResNet34。
models.resnet34()
但如果想从头开始手动搭建ResNet34以更深入地理解其结构,以下是一个简化的示例代码,展示了如何定义基本的ResNet34网络结构:
-
定义残差块(ResidualBlock):
# 定义残差块
# 实现子module:Residual Block
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3 ,stride, 1 ,bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_channels, out_channels,3, 1 ,1 ,bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
# 如果输入和输出的维度或步长不一致,则通过一个快捷路径(shortcut)来调整维度,以确保能够进行残差连接
self.downsample = downsample
def forward(self, x):
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
residual = x if self.downsample is None else self.downsample(x)
out += residual
out = self.relu(out)
return out
-
定义ResNet34网络:
定义一个基本的ResNet34网络,包括初始的卷积层、批量归一化层、ReLU激活函数、最大池化层,以及由ResidualBlock组成的四个残差层。每个ResidualBlock都包含两个卷积层,并使用跨层直连(shortcut)来解决梯度消失问题。网络的最后是一个全局平均池化层和一个全连接层,用于分类任务。
在ResNet34类的make_layer 方法用于创建由多个残差块组成的层。它首先检查是否需要下采样,并据此创建下采样层。然后,它循环创建指定数量的残差块,并将它们添加到一个序列模型中。
# 实现主module:ResNet
class ResNet(nn.Module):
def __init__(self, block_model, layers, num_classes=1000):
super().__init__()
self.in_channels = 64
# 前几层图像转换
# 初始的卷积层、批量归一化层、ReLU激活函数、最大池化层
self.pre = nn.Sequential(
nn.Conv2d(3 , 64 , kernel_size=7 , stride=2 , padding=3 , bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
# 由BasicBlock组成的四个残差层
# 重复的layers,分别有多个Residual Block
self.layer1 = self.make_layer(block_model, 64, layers[0])
self.layer2 = self.make_layer(block_model, 128, layers[1], 2)
self.layer3 = self.make_layer(block_model, 256, layers[2], 2)
self.layer4 = self.make_layer(block_model, 512, layers[3], 2)
# 网络的最后是一个全局平均池化层和一个全连接层,用于分类任务
self.avg_pool = nn.AvgPool2d(7)
self.fc = nn.Linear(512, num_classes)
# _make_layer方法用于搭建由多个BasicBlock组成的残差层。
# 这个方法首先检查是否需要降采样(即改变特征图的尺寸或通道数),如果需要,则创建一个降采样层(downsample)。
# 然后,它循环创建指定数量的BasicBlock,并将它们添加到一个序列模型中。
def make_layer(self, block_model, out_channels, block_num, stride=1):
# 判断是否需要下采样
# 如果 stride 不为1或者输入通道数 self.in_channels 与输出通道数 out_channels 不一致,则需要下采样
# 下采样通过一个1x1的卷积层和一个批量归一化层实现,目的是将输入数据的通道数和尺寸调整到与主路径的输出相匹配
downsample = None
if (stride != 1) or (self.in_channels != out_channels):
downsample = nn.Sequential(
nn.Conv2d(self.in_channels, out_channels, 1,stride=stride, bias=False),
nn.BatchNorm2d(out_channels))
# 初始化一个空列表来保存残差块
layers = []
# 添加第一个残差块,这里可能需要下采样
layers.append(block_model(self.in_channels, out_channels, stride, downsample))
# 更新输入通道数,因为第一个残差块可能已经改变了它
self.in_channels = out_channels
# 循环添加剩余的残差块,这些残差块不需要下采样
for i in range(1, block_num):
layers.append(block_model(out_channels, out_channels))
# 将列表中的残差块转换为一个顺序模型(Sequential)并返回
return nn.Sequential(*layers)
def forward(self, x):
out = self.pre(x)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.avg_pool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
-
实例化模型:
# 实例化模型
model = ResNet(ResidualBlock, [3, 4, 6, 3])
更多内容可以前往官网查看:
https://pytorch.org/
本篇文章来源于微信公众号: 码农设计师