一、理论基础
resnet网络结构的理论基础看这里,这篇博文讲得简洁而清晰:深度学习卷积神经网络CNN之ResNet模型网络详解说明(超详细理论篇)-****博客
ResNet网络是参考了VGG19网络,并在此基础上进行修改,通过短路机制加入了残差单元。
残差块的基本结构:
上图中,网络分为左右两支,左网络是主干分支,右网络是残差网络
ResNet的结构与VGG网络的比较:
二、实现过程
新建resnet.py:
1、定义一个基本的块,类似于图1的结构,包含主干网络和残差网络:
class BasicBlock(nn.Module): # 定义一个基本块,由主干和残差分支组成
def __init__(self, in_channels, out_channels, stride=1):
super(BasicBlock, self).__init__()
self.layer = nn.Sequential( # 左网络(也就是主干分支)
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1), # 这三行代码来自标准的VGGNet网络
# 第一次卷积有可能进行下采样,所以stride=stride
nn.BatchNorm2d(out_channels),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1), # 第二次卷积不进行下采样,这样每个基本快就只进行一次下采样
nn.BatchNorm2d(out_channels)
)
if in_channels != out_channels: # 如果输入通道数和输出通道数不同,说明输入和输出的维度不同,需要添加一个下采样层
self.shortcut = nn.Sequential( # 右网络(也就是残差分支)
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1),
nn.BatchNorm2d(out_channels),
)
else:
self.shortcut = nn.Sequential() # 如果输入和输出的维度相同,就不需要添加下采样层
def forward(self, x): # 定义前向传播
out = self.layer(x) # 执行左网络
out += self.shortcut(x) # 执行右网络
out = F.relu(out) # 激活函数
return out
2、定义ResNet网络结构
class ResNet(nn.Module): # 定义ResNet网络
def make_layer(self, block, out_channels, stride, num_blocks): # 生成网络层
# 参数:
# block:用于构建网络层的基本模块
# out_channels:输出通道数,表示该层的特征图数量。
# stride:步长,用于下采样操作。
# num_blocks:该层中要生成的block数量。
layers = [] # 定义一个列表,用来保存网络层
for i in range(num_blocks): # 生成num_blocks个block
if i == 0: # 如果是第一个block,需要下采样
in_stride = stride
else:
in_stride = 1 # 其他block不需要下采样
layers.append(block(self.in_channels, out_channels, in_stride)) # 将block添加到列表中
self.in_channels = out_channels # 更新输入通道数
return nn.Sequential(*layers) # 将列表中的网络层串联组合成一个网络层
def __init__(self, block, num_blocks, num_classes=10):
super(ResNet, self).__init__()
self.in_channels = 32 # 定义输入通道数
self.conv1 = nn.Sequential( # 定义第1个卷积层,这个层不用残差,就是标准的VGGNet网络
nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(32),
nn.ReLU()
)
self.layer_1 = self.make_layer(BasicBlock, 64, 2, 2) # 生成第1个网络层
self.layer_2 = self.make_layer(BasicBlock, 128, 2, 2) # 生成第2个网络层
self.layer_3 = self.make_layer(BasicBlock, 256, 2, 2) # 生成第2个网络层
self.layer_4 = self.make_layer(BasicBlock, 512, 2, 2) # 生成第2个网络层
self.fc = nn.Linear(512, 10) # 全连接层,输入为512,输出为10
def forward(self, x):
out = self.conv1(x) # 执行第1个卷积层
out = self.layer_1(out) # 执行第1个网络层
out = self.layer_2(out) # 执行第2个网络层
out = self.layer_3(out) # 执行第3个网络层
out = self.layer_4(out) # 执行第4个网络层
out = F.avg_pool2d(out, 2) # 最大池化,池化核大小为2
out = out.view(out.size(0), -1) # 将输出展平
out = self.fc(out) # 全连接层
return out
3、resnet.py脚本的完整内容:
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicBlock(nn.Module): # 定义一个基本模块,由主干和残差分支组成
def __init__(self, in_channels, out_channels, stride=1):
super(BasicBlock, self).__init__()
self.layer = nn.Sequential( # 左网络(也就是主干分支)
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1), # 这三行代码来自标准的VGGNet网络
# 第一次卷积有可能进行下采样,所以stride=stride
nn.BatchNorm2d(out_channels),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1), # 第二次卷积不进行下采样,这样每个基本快就只进行一次下采样
nn.BatchNorm2d(out_channels)
)
if in_channels != out_channels: # 如果输入通道数和输出通道数不同,说明输入和输出的维度不同,需要添加一个下采样层
self.shortcut = nn.Sequential( # 右网络(也就是残差分支)
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1),
# 为了保证左右网络的维度相同,使用了同一个stride
nn.BatchNorm2d(out_channels),
)
else:
self.shortcut = nn.Sequential() # 如果输入和输出的维度相同,就不需要添加下采样层
def forward(self, x): # 定义前向传播
out = self.layer(x) # 执行左网络
out += self.shortcut(x) # 加上右网络
out = F.relu(out) # 激活函数
return out
class ResNet(nn.Module): # 定义ResNet网络
def make_layer(self, block, out_channels, stride, num_blocks): # 生成网络层
# 参数:
# block:用于构建网络层的基本模块
# out_channels:输出通道数,表示该层的特征图数量。
# stride:步长,用于下采样操作。
# num_blocks:该层中要生成的block数量。
layers = [] # 定义一个列表,用来保存网络层
for i in range(num_blocks): # 生成num_blocks个block
if i == 0: # 如果是第一个block,需要下采样
in_stride = stride
else:
in_stride = 1 # 其他block不需要下采样
layers.append(block(self.in_channels, out_channels, in_stride)) # 将block添加到列表中
self.in_channels = out_channels # 更新输入通道数
return nn.Sequential(*layers) # 将列表中的网络层串联组合成一个网络层
def __init__(self, block=BasicBlock):
super(ResNet, self).__init__()
self.in_channels = 32 # 定义输入通道数
self.conv1 = nn.Sequential( # 定义第1个卷积层,这个层不用残差,就是标准的VGGNet网络
nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(32),
nn.ReLU()
)
self.layer_1 = self.make_layer(block, 64, 2, 2) # 生成第1个网络层
self.layer_2 = self.make_layer(block, 128, 2, 2) # 生成第2个网络层
self.layer_3 = self.make_layer(block, 256, 2, 2) # 生成第2个网络层
self.layer_4 = self.make_layer(block, 512, 2, 2) # 生成第2个网络层
self.fc = nn.Linear(512, 10) # 全连接层,输入为512,输出为10
def forward(self, x):
out = self.conv1(x) # 执行第1个卷积层
out = self.layer_1(out) # 执行第1个网络层
out = self.layer_2(out) # 执行第2个网络层
out = self.layer_3(out) # 执行第3个网络层
out = self.layer_4(out) # 执行第4个网络层
out = F.avg_pool2d(out, 2) # 最大池化,池化核大小为2
out = out.view(out.size(0), -1) # 将输出展平
out = self.fc(out) # 全连接层
return out
def resnet():
return ResNet(BasicBlock) # 生成ResNet网络
4、训练
将之前的train.py脚本中的
net = VGGNet().to(device),改为:
net = resnet().to(device),即可运行开始训练: