目录
一、实验介绍
二、实验环境
1. 配置虚拟环境
2. 库版本介绍
三、实验内容
0. 导入必要的工具包
1. Residual(残差连接)
__init__(初始化)
forward(前向传播)
2. resnet_block(残差网络块)
3. ResNet(网络模型)
__init__(初始化)
forward(前向传播)
4. 代码整合
本实验实现了实现深度残差神经网络ResNet。
残差网络(ResNet)是一种深度神经网络架构,用于解决深层网络训练过程中的梯度消失和梯度爆炸问题。通过引入残差连接(residual connection)来构建网络层与层之间的跳跃连接,使得网络可以更好地优化深层结构。
残差网络的一个重要应用是在图像识别任务中,特别是在深度卷积神经网络(CNN)中。通过使用残差模块,可以构建非常深的网络,例如ResNet,其在ILSVRC 2015图像分类挑战赛中取得了非常出色的成绩。
在ResNet中,每个残差块由一个或多个卷积层组成,其中包含了跳跃连接。跳跃连接将输入直接添加到残差块的输出中,从而使得网络可以学习残差函数,即残差块只需学习将输入的变化部分映射到输出,而不需要学习完整的映射关系。这种设计有助于减轻梯度消失问题,使得网络可以更深地进行训练。
本系列实验使用了PyTorch深度学习框架,相关操作如下:
conda create -n DL python=3.7
conda activate DL
pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
conda install matplotlib
conda install scikit-learn
软件包 | 本实验版本 | 目前最新版 |
matplotlib | 3.5.3 | 3.8.0 |
numpy | 1.21.6 | 1.26.0 |
python | 3.7.16 | |
scikit-learn | 0.22.1 | 1.3.0 |
torch | 1.8.1+cu102 | 2.0.1 |
torchaudio | 0.8.1 | 2.0.2 |
torchvision | 0.9.1+cu102 | 0.15.2 |
from torch import nn
import torch.nn.functional as F
class Residual(nn.Module):
def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
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
# 批量归一化层,将会在第7章讲到
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_channels)
def forward(self, X):
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)
生成由多个残差块组成的残差网络块。
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
input_channels
:输入通道数,即每个残差块的输入的通道数。num_channels
:每个残差块中卷积层的输出通道数,也是每个残差块内部卷积层的通道数。num_residuals
:残差块的数量。first_block
:一个布尔值,表示是否为整个 ResNet 中的第一个残差块。blk
,用于存储构建的残差块。num_residuals
次,每次迭代都构建一个残差块并将其添加到 blk
列表中。
first_block
为 False。
blk
列表中。这是为了在整个 ResNet 中的第一个残差块中进行下采样。first_block
为 True,则创建一个普通的残差块,并将其添加到 blk
列表中。blk
3. ResNet(
网络模型)
ResNet 网络模型,包含了多个残差块,用于实现图像分类任务。
class ResNet(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
self.b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
self.b3 = nn.Sequential(*resnet_block(64, 128, 2))
self.b4 = nn.Sequential(*resnet_block(128, 256, 2))
self.b5 = nn.Sequential(*resnet_block(256, 512, 2))
self.head = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(512, num_classes))
def forward(self, x):
net = nn.Sequential(self.b1, self.b2, self.b3, self.b4, self.b5, self.head)
return net(x)
num_classes
,表示分类的类别数目self.b1
是一个包含了卷积层、批归一化层、ReLU激活函数和最大池化层的序列。它对输入数据进行卷积操作,然后进行批归一化、ReLU激活和最大池化,用于提取输入图像的特征。
self.b2
、self.b3
、self.b4
和self.b5
分别是几个残差块(resnet_block)的序列。这些残差块包含了卷积层、批归一化层和ReLU激活函数,用于进一步提取输入数据的特征。
self.b2
使用构建了 2 个残差块,输入通道数为 64,输出通道数也为 64,并且指定 `first_block=True`,表示它是第一个残差块;self.head
是一个包含自适应平均池化层(AdaptiveAvgPool2d)、展平层(Flatten)和全连接层(Linear)的序列。它将输入数据进行自适应平均池化,然后展平为一维向量,并通过全连接层将特征映射到分类的类别数目上:
输入数据通过上述序列模块self.b1
、self.b2
、self.b3
、self.b4
、self.b5
和self.head
进行处理,最终输出分类结果。
# 导入必要的工具包
from torch import nn
import torch.nn.functional as F
# 残差连接, 输入和输出的维度有时是相同的, 有时是不同的, 所以需要 use_1x1conv来判断是否需要
class Residual(nn.Module):
def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
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
# 批量归一化层,将会在第7章讲到
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_channels)
def forward(self, X):
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)
# 残差网络是由几个不同的残差块组成的
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
class ResNet(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
self.b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
self.b3 = nn.Sequential(*resnet_block(64, 128, 2))
self.b4 = nn.Sequential(*resnet_block(128, 256, 2))
self.b5 = nn.Sequential(*resnet_block(256, 512, 2))
self.head = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(512, num_classes))
def forward(self, x):
net = nn.Sequential(self.b1, self.b2, self.b3, self.b4, self.b5, self.head)
return net(x)