一、构建神经网络的核心方法
PyTorch 中构建模型主要基于nn.Module
基类及模型容器,核心分为三种方式,均需结合forward
方法定义正向传播逻辑。
(一)直接继承nn.Module
基类构建模型
这是最灵活的模型构建方式,需手动定义所有网络层并编排传播顺序。
1. 实现步骤
- 定义模型类,继承
nn.Module
; - 在
__init__
方法中初始化网络层(如全连接层、批归一化层、展平层等); - 重写
forward
方法,定义输入数据的流转路径(层的调用顺序 + 激活函数等)。
2. 代码示例
python
import torch
from torch import nn
import torch.nn.functional as F
class Model_Seq(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Model_Seq, self).__init__()
self.flatten = nn.Flatten() # 展平层,将28×28图像转为一维向量
self.linear1 = nn.Linear(in_dim, n_hidden_1) # 全连接层1
self.bn1 = nn.BatchNorm1d(n_hidden_1) # 批归一化层1
self.linear2 = nn.Linear(n_hidden_1, n_hidden_2) # 全连接层2
self.bn2 = nn.BatchNorm1d(n_hidden_2) # 批归一化层2
self.out = nn.Linear(n_hidden_2, out_dim) # 输出层
def forward(self, x):
x = self.flatten(x)
x = self.linear1(x)
x = self.bn1(x)
x = F.relu(x) # 激活函数(使用nn.functional)
x = self.linear2(x)
x = self.bn2(x)
x = F.relu(x)
x = self.out(x)
x = F.softmax(x, dim=1) # 输出概率分布
return x
# 超参数赋值与模型实例化
in_dim, n_hidden_1, n_hidden_2, out_dim = 28*28, 300, 100, 10
model_seq = Model_Seq(in_dim, n_hidden_1, n_hidden_2, out_dim)
3. 运行特点
模型打印时会清晰展示各层结构及参数(如输入输出维度、批归一化参数等)。
(二)使用nn.Sequential
按层顺序构建模型
nn.Sequential
是有序的层容器,按传入顺序自动执行正向传播,代码更简洁,适合简单线性结构的模型。
1. 三种实现方式
方式 | 特点 | 代码示例片段 |
---|---|---|
可变参数传入 | 无法为层指定名称,仅按顺序编号 | Seq_arg = nn.Sequential(nn.Flatten(), nn.Linear(in_dim, n_hidden_1), ...) |
add_module 方法 |
可手动为每层指定名称,动态添加层 | Seq_module.add_module("flatten", nn.Flatten()) |
OrderedDict 传入 |
通过有序字典指定层名和层,结构更清晰 | from collections import OrderedDict``Seq_dict = nn.Sequential(OrderedDict([("flatten", nn.Flatten()), ...])) |
2. 运行特点
使用add_module
或OrderedDict
时,打印模型会显示自定义的层名(如flatten
、linear1
),便于调试。
(三)继承nn.Module
+ 模型容器构建模型
结合基类的灵活性与容器的结构化优势,常用容器包括nn.Sequential
、nn.ModuleList
、nn.ModuleDict
。
1. 三种容器的用法对比
容器类型 | 核心特点 | 代码实现要点 |
---|---|---|
nn.Sequential |
层有序组合,自动执行正向传播 | 在__init__ 中定义子容器(如self.layer1 = nn.Sequential(Linear, BatchNorm1d) ),forward 中调用子容器 |
nn.ModuleList |
层的列表,需手动遍历执行 | 用列表初始化(self.layers = nn.ModuleList([Flatten(), Linear(), ...]) ),forward 中循环调用每层 |
nn.ModuleDict |
层的字典(键为层名),需指定执行顺序 | 用字典初始化(self.layers_dict = nn.ModuleDict({"flatten": Flatten(), ...}) ),forward 中按列表指定的层名顺序调用 |
2. 示例:nn.ModuleDict
实现
python
class Model_dict(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Model_dict, self).__init__()
self.layers_dict = nn.ModuleDict({
"flatten": nn.Flatten(),
"linear1": nn.Linear(in_dim, n_hidden_1),
"bn1": nn.BatchNorm1d(n_hidden_1),
"relu": nn.ReLU(),
"linear2": nn.Linear(n_hidden_1, n_hidden_2),
"bn2": nn.BatchNorm1d(n_hidden_2),
"out": nn.Linear(n_hidden_2, out_dim),
"softmax": nn.Softmax(dim=1)
})
def forward(self, x):
# 手动指定层的执行顺序
layers = ["flatten", "linear1", "bn1", "relu", "linear2", "bn2", "relu", "out", "softmax"]
for layer in layers:
x = self.layers_dict[layer](x)
return x
二、自定义网络模块(以残差块为例)
针对复杂网络(如 ResNet),需自定义基础模块,再组合成完整网络。残差块解决了深层网络梯度消失问题,核心是 "输入与输出相加" 的残差连接。
(一)两种核心残差块
1. 基础残差块(RestNetBasicBlock
)
适用场景:输入与输出的通道数、分辨率一致(无需维度调整)。
结构:2 个 3×3 卷积层 + 批归一化层 + ReLU 激活,最后将输入与卷积输出相加后再激活。
代码示例:
python
class RestNetBasicBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(RestNetBasicBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1)
self.bn2 = nn.BatchNorm2d(out_channels)
def forward(self, x):
output = self.conv1(x)
output = F.relu(self.bn1(output))
output = self.conv2(output)
output = self.bn2(output)
return F.relu(x + output) # 残差连接:输入x与输出相加
2. 下采样残差块(RestNetDownBlock
)
适用场景:输入与输出的通道数或分辨率不一致(需维度匹配)。
额外结构 :添加 1×1 卷积层(extra
),调整输入的通道数和分辨率,确保与卷积输出可相加。
代码示例:
python
class RestNetDownBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(RestNetDownBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride[0], padding=1)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride[1], padding=1)
self.bn2 = nn.BatchNorm2d(out_channels)
# 1×1卷积调整输入维度
self.extra = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], padding=0),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
extra_x = self.extra(x) # 调整输入维度
output = self.conv1(x)
output = F.relu(self.bn1(output))
output = self.conv2(output)
output = self.bn2(output)
return F.relu(extra_x + output) # 调整后的输入与输出相加
(二)组合残差块构建 ResNet18
ResNet18 由 "初始卷积层 + 4 个残差层 + 池化层 + 全连接层" 组成,每个残差层含多个残差块。
1. 网络结构代码
python
class RestNet18(nn.Module):
def __init__(self):
super(RestNet18, self).__init__()
# 初始卷积与池化
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
self.bn1 = nn.BatchNorm2d(64)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 4个残差层(基础块+下采样块组合)
self.layer1 = nn.Sequential(RestNetBasicBlock(64, 64, 1), RestNetBasicBlock(64, 64, 1))
self.layer2 = nn.Sequential(RestNetDownBlock(64, 128, [2, 1]), RestNetBasicBlock(128, 128, 1))
self.layer3 = nn.Sequential(RestNetDownBlock(128, 256, [2, 1]), RestNetBasicBlock(256, 256, 1))
self.layer4 = nn.Sequential(RestNetDownBlock(256, 512, [2, 1]), RestNetBasicBlock(512, 512, 1))
# 自适应平均池化与全连接输出
self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))
self.fc = nn.Linear(512, 10) # 10分类任务
def forward(self, x):
out = self.conv1(x)
out = self.bn1(out)
out = self.maxpool(out)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.avgpool(out)
out = out.reshape(x.shape[0], -1) # 展平为向量
out = self.fc(out)
return out
三、模型训练核心流程
模型构建完成后,需按固定流程进行训练与验证,共 6 个关键步骤:
- 加载预处理数据集 :加载已完成归一化、划分(训练集 / 测试集)的数据集,使用
DataLoader
实现批量加载。 - 定义损失函数 :根据任务类型选择(如分类用
CrossEntropyLoss
,回归用MSELoss
),需实例化nn.XxxLoss
。 - 定义优化方法 :选择优化器(如 SGD、Adam),传入模型可学习参数与超参数(学习率、动量等),调用
torch.optim
库。 - 循环训练模型 :
- 切换模型至训练模式(
model.train()
); - 批量读取数据,执行正向传播(
output = model(input)
); - 计算损失(
loss = criterion(output, label)
); - 反向传播求梯度(
loss.backward()
); - 优化器更新参数(
optimizer.step()
),清空梯度(optimizer.zero_grad()
)。
- 切换模型至训练模式(
- 循环测试 / 验证模型 :
- 切换模型至评估模式(
model.eval()
); - 关闭梯度计算(
with torch.no_grad():
); - 计算验证集损失与准确率,评估模型泛化能力。
- 切换模型至评估模式(
- 可视化结果:绘制训练 / 验证损失曲线、准确率曲线,分析模型收敛情况(是否过拟合 / 欠拟合)。