一、核心概念与组件梳理

(一)神经网络核心组件

神经网络的构建与训练依赖四大核心组件,各组件分工明确、协同工作,构成完整的模型运行体系:

组件核心功能
层(Layer)神经网络的基本结构单元,负责对输入张量进行数据变换(如卷积、线性变换等),实现特征提取与转换。
模型(Model)由多个层按特定逻辑组合而成的网络结构,是完成任务(分类、回归等)的核心载体。
损失函数定义参数学习的目标函数,用于量化模型预测值(Y')与真实值(Y)之间的差异,是参数优化的依据。
优化器实现损失函数最小化的算法,通过调整模型可学习参数(如权重、偏置),使模型性能逐步提升。

组件间的协同流程为:输入数据(X)经层的变换后得到预测值(Y'),损失函数计算 Y' 与真实值(Y)的差异,优化器根据损失值更新层的权重参数,最终实现模型性能迭代。

(二)PyTorch 核心构建工具

PyTorch 提供nn.Modulenn.functional两大工具集用于构建神经网络,二者功能互补但使用方式存在显著差异。

1. 工具特性对比
对比维度nn.Modulenn.functional
本质面向对象的模块基类纯函数集合
典型应用卷积层、全连接层、Dropout 层、损失函数等激活函数、池化层等
语法格式nn.Xxx(参数)(需实例化后调用)nn.functional.xxx(输入, 参数)(直接调用函数)
参数管理自动定义和管理 weight、bias 等可学习参数需手动定义和传入 weight、bias,无自动管理机制
与容器兼容性可与nn.Sequential等模型容器无缝结合无法与模型容器结合,不利于结构化构建
状态切换Dropout 等模块可通过model.eval()自动切换状态需手动控制训练 / 测试状态,无自动切换功能
2. 适用场景总结
  • 优先使用nn.Module:构建包含可学习参数的层(如nn.Linearnn.Conv2d)、需要状态管理的模块(如nn.Dropout),或需通过容器组织的复杂模型。
  • 辅助使用nn.functional:调用无参数的功能性操作(如F.reluF.max_pool2d),尤其在forward方法中进行简单张量变换时更便捷。

二、模型构建方法详解

PyTorch 提供三种主流模型构建方式,分别适用于不同复杂度与灵活性需求的场景,核心均围绕nn.Module基类展开。

(一)继承 nn.Module 基类构建模型

这是最灵活的构建方式,需自定义网络层定义与前向传播逻辑,适用于复杂结构的模型设计。

代码实现与注释

python

运行

import torch
from torch import nn
import torch.nn.functional as F
# 定义模型类,必须继承nn.Module基类
class Model_Seq(nn.Module):
# 初始化方法:定义网络层结构与超参数
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
# 调用父类初始化方法,固定写法
super(Model_Seq, self).__init__()
# 1. 展平层:将二维输入(如28x28图像)转为一维张量
self.flatten = nn.Flatten()
# 2. 全连接层1:输入维度in_dim,输出维度n_hidden_1
self.linear1 = nn.Linear(in_dim, n_hidden_1)
# 3. 批归一化层1:对全连接层1的输出进行归一化,加速训练
self.bn1 = nn.BatchNorm1d(n_hidden_1)
# 4. 全连接层2:输入维度n_hidden_1,输出维度n_hidden_2
self.linear2 = nn.Linear(n_hidden_1, n_hidden_2)
# 5. 批归一化层2:对全连接层2的输出进行归一化
self.bn2 = nn.BatchNorm1d(n_hidden_2)
# 6. 输出层:输入维度n_hidden_2,输出维度out_dim(对应分类类别数)
self.out = nn.Linear(n_hidden_2, out_dim)
# 前向传播方法:定义数据流过网络的计算逻辑,必须实现
def forward(self, x):
# 第一步:展平输入张量
x = self.flatten(x)
# 第二步:全连接层1 -> 批归一化 -> ReLU激活
x = self.linear1(x)
x = self.bn1(x)
x = F.relu(x)
# 第三步:全连接层2 -> 批归一化 -> ReLU激活
x = self.linear2(x)
x = self.bn2(x)
x = F.relu(x)
# 第四步:输出层 -> Softmax激活(将输出转为概率分布,dim=1表示按样本维度计算)
x = self.out(x)
x = F.softmax(x, dim=1)
return x
# 超参数定义(以MNIST手写数字识别为例)
in_dim = 28 * 28  # 输入维度:28x28图像展平后为784
n_hidden_1 = 300  # 第一个隐藏层神经元数
n_hidden_2 = 100  # 第二个隐藏层神经元数
out_dim = 10      # 输出维度:10个数字类别
# 实例化模型
model_seq = Model_Seq(in_dim, n_hidden_1, n_hidden_2, out_dim)
# 打印模型结构
print(model_seq)
运行结果

plaintext

Model_Seq(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=784, out_features=300, bias=True)
(bn1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(linear2): Linear(in_features=300, out_features=100, bias=True)
(bn2): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(out): Linear(in_features=100, out_features=10, bias=True)
)

(二)使用 nn.Sequential 按层顺序构建模型

nn.Sequential是 PyTorch 提供的顺序容器,可按顺序封装多个层,自动实现前向传播(按添加顺序执行),适用于结构简单的线性网络。该方法支持三种实现方式:

1. 利用可变参数构建(无层名称)

python

运行

# 定义超参数(与上文一致)
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 直接传入层作为可变参数,按顺序构建模型
Seq_arg = nn.Sequential(
nn.Flatten(),          # 展平层
nn.Linear(in_dim, n_hidden_1),  # 全连接层1
nn.BatchNorm1d(n_hidden_1),     # 批归一化层1
nn.ReLU(),             # ReLU激活层
nn.Linear(n_hidden_1, n_hidden_2),  # 全连接层2
nn.BatchNorm1d(n_hidden_2),     # 批归一化层2
nn.ReLU(),             # ReLU激活层
nn.Linear(n_hidden_2, out_dim), # 输出层
nn.Softmax(dim=1)      # Softmax激活层
)
# 打印模型结构
print(Seq_arg)
2. 使用 add_module 方法构建(指定层名称)

python

运行

# 初始化空的Sequential容器
Seq_module = nn.Sequential()
# 调用add_module添加层,第一个参数为层名称,第二个参数为层实例
Seq_module.add_module("flatten", nn.Flatten())
Seq_module.add_module("linear1", nn.Linear(in_dim, n_hidden_1))
Seq_module.add_module("bn1", nn.BatchNorm1d(n_hidden_1))
Seq_module.add_module("relu1", nn.ReLU())
Seq_module.add_module("linear2", nn.Linear(n_hidden_1, n_hidden_2))
Seq_module.add_module("bn2", nn.BatchNorm1d(n_hidden_2))
Seq_module.add_module("relu2", nn.ReLU())
Seq_module.add_module("out", nn.Linear(n_hidden_2, out_dim))
Seq_module.add_module("softmax", nn.Softmax(dim=1))
# 打印模型结构
print(Seq_module)
3. 使用 OrderedDict 构建(有序字典指定层名称)

python

运行

from collections import OrderedDict
# 利用OrderedDict存储层名称与层实例的映射,保证顺序
layer_dict = OrderedDict([
("flatten", nn.Flatten()),
("linear1", nn.Linear(in_dim, n_hidden_1)),
("bn1", nn.BatchNorm1d(n_hidden_1)),
("relu1", nn.ReLU()),
("linear2", nn.Linear(n_hidden_1, n_hidden_2)),
("bn2", nn.BatchNorm1d(n_hidden_2)),
("relu2", nn.ReLU()),
("out", nn.Linear(n_hidden_2, out_dim)),
("softmax", nn.Softmax(dim=1))
])
# 传入OrderedDict构建模型
Seq_ordered = nn.Sequential(layer_dict)
# 打印模型结构
print(Seq_ordered)
三种方式的共性与差异
  • 共性:均按顺序执行前向传播,最终模型功能一致。
  • 差异:可变参数方式最简单但无法指定层名称;add_moduleOrderedDict方式可指定层名称,便于调试时定位层结构,其中OrderedDict更适合批量定义层。

(三)继承 nn.Module 并结合模型容器构建

当模型结构复杂(如包含多个子模块)时,可继承nn.Module基类,并结合nn.Sequentialnn.ModuleListnn.ModuleDict等容器封装子模块,兼顾灵活性与结构化。

1. 结合 nn.Sequential 容器

将多个层封装为子模块,使模型结构更清晰:

python

运行

class Model_lay(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Model_lay, self).__init__()
self.flatten = nn.Flatten()  # 独立展平层
# 子模块1:全连接层1 + 批归一化层1
self.layer1 = nn.Sequential(
nn.Linear(in_dim, n_hidden_1),
nn.BatchNorm1d(n_hidden_1)
)
# 子模块2:全连接层2 + 批归一化层2
self.layer2 = nn.Sequential(
nn.Linear(n_hidden_1, n_hidden_2),
nn.BatchNorm1d(n_hidden_2)
)
# 子模块3:输出层
self.out = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
x = self.flatten(x)
# 子模块1输出经ReLU激活
x = F.relu(self.layer1(x))
# 子模块2输出经ReLU激活
x = F.relu(self.layer2(x))
# 输出层经Softmax激活
x = F.softmax(self.out(x), dim=1)
return x
# 实例化模型
model_lay = Model_lay(in_dim, n_hidden_1, n_hidden_2, out_dim)
print(model_lay)
2. 结合 nn.ModuleList 容器

nn.ModuleList是存储层的列表容器,支持索引访问,适用于需要动态调整层数量的场景:

python

运行

class Model_lst(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Model_lst, self).__init__()
# 用ModuleList存储所有层,按顺序排列
self.layers = nn.ModuleList([
nn.Flatten(),
nn.Linear(in_dim, n_hidden_1),
nn.BatchNorm1d(n_hidden_1),
nn.ReLU(),
nn.Linear(n_hidden_1, n_hidden_2),
nn.BatchNorm1d(n_hidden_2),
nn.ReLU(),
nn.Linear(n_hidden_2, out_dim),
nn.Softmax(dim=1)
])
def forward(self, x):
# 遍历ModuleList中的层,按顺序执行前向传播
for layer in self.layers:
x = layer(x)
return x
# 实例化模型
model_lst = Model_lst(in_dim, n_hidden_1, n_hidden_2, out_dim)
print(model_lst)
3. 结合 nn.ModuleDict 容器

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__()
# 用ModuleDict存储层,键为层名称,值为层实例
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):
# 定义层的执行顺序(通过键列表控制)
layer_order = ["flatten", "linear1", "bn1", "relu", "linear2", "bn2", "relu", "out", "softmax"]
# 按顺序执行层计算
for layer_name in layer_order:
x = self.layers_dict[layer_name](x)
return x
# 实例化模型
model_dict = Model_dict(in_dim, n_hidden_1, n_hidden_2, out_dim)
print(model_dict)

三、自定义网络模块实践

对于复杂网络(如 ResNet),需自定义基础模块(如残差块),再组合成完整模型。以下以 ResNet 的核心组件 —— 残差块为例,说明自定义模块的实现方法。

(一)基础残差块(RestNetBasicBlock)

适用于输入与输出维度一致的场景,直接将输入与模块输出相加:

python

运行

import torch
import torch.nn as nn
from torch.nn import functional as F
class RestNetBasicBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(RestNetBasicBlock, self).__init__()
# 第一个3x3卷积层:保持通道数与分辨率不变(stride=1)
self.conv1 = nn.Conv2d(
in_channels, out_channels,
kernel_size=3, stride=stride, padding=1
)
self.bn1 = nn.BatchNorm2d(out_channels)  # 批归一化层1
# 第二个3x3卷积层:进一步提取特征
self.conv2 = nn.Conv2d(
out_channels, out_channels,
kernel_size=3, stride=stride, padding=1
)
self.bn2 = nn.BatchNorm2d(out_channels)  # 批归一化层2
def forward(self, x):
# 模块内部前向传播
output = self.conv1(x)
output = F.relu(self.bn1(output))  # 卷积1 -> 归一化 -> ReLU
output = self.conv2(output)
output = self.bn2(output)          # 卷积2 -> 归一化
# 残差连接:输入x与模块输出相加,再经ReLU激活
return F.relu(x + output)

(二)下采样残差块(RestNetDownBlock)

适用于输入与输出维度不一致的场景(如通道数增加、分辨率降低),通过 1×1 卷积调整输入维度后再相加:

python

运行

class RestNetDownBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(RestNetDownBlock, self).__init__()
# 第一个3x3卷积层:stride[0]控制分辨率下采样
self.conv1 = nn.Conv2d(
in_channels, out_channels,
kernel_size=3, stride=stride[0], padding=1
)
self.bn1 = nn.BatchNorm2d(out_channels)  # 批归一化层1
# 第二个3x3卷积层:stride[1]保持分辨率不变
self.conv2 = nn.Conv2d(
out_channels, out_channels,
kernel_size=3, stride=stride[1], padding=1
)
self.bn2 = nn.BatchNorm2d(out_channels)  # 批归一化层2
# 1×1卷积层:调整输入x的通道数与分辨率,使其与输出维度一致
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):
# 先通过1×1卷积调整输入维度
extra_x = self.extra(x)
# 模块内部前向传播
output = self.conv1(x)
output = F.relu(self.bn1(output))  # 卷积1 -> 归一化 -> ReLU
output = self.conv2(output)
output = self.bn2(output)          # 卷积2 -> 归一化
# 残差连接:调整后的输入与模块输出相加,再经ReLU激活
return F.relu(extra_x + output)

(三)组合模块构建 ResNet18

将基础残差块与下采样残差块按 ResNet18 的结构组合,形成完整网络:

python

运行

class RestNet18(nn.Module):
def __init__(self):
super(RestNet18, self).__init__()
# 初始卷积层:3通道输入→64通道,7x7卷积下采样
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)  # 最大池化下采样
# 第一层:2个基础残差块(64通道→64通道,无下采样)
self.layer1 = nn.Sequential(
RestNetBasicBlock(64, 64, 1),
RestNetBasicBlock(64, 64, 1)
)
# 第二层:1个下采样残差块(64→128)+1个基础残差块
self.layer2 = nn.Sequential(
RestNetDownBlock(64, 128, [2, 1]),
RestNetBasicBlock(128, 128, 1)
)
# 第三层:1个下采样残差块(128→256)+1个基础残差块
self.layer3 = nn.Sequential(
RestNetDownBlock(128, 256, [2, 1]),
RestNetBasicBlock(256, 256, 1)
)
# 第四层:1个下采样残差块(256→512)+1个基础残差块
self.layer4 = nn.Sequential(
RestNetDownBlock(256, 512, [2, 1]),
RestNetBasicBlock(512, 512, 1)
)
self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))  # 自适应平均池化→1x1特征图
self.fc = nn.Linear(512, 10)  # 全连接层:512通道→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)  # 展平为(batch_size, 512)
out = self.fc(out)
return out

四、模型训练流程

模型构建完成后,需经过标准化的训练流程实现参数优化,核心步骤包括:

1. 加载预处理数据集

通过 PyTorch 的torchvision等工具加载数据集,并进行预处理(如归一化、数据增强):

python

运行

import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader
# 数据预处理:归一化到[0,1]并转为张量
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 加载MNIST训练集与测试集
train_dataset = torchvision.datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = torchvision.datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
# 构建数据加载器(批量读取数据)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

2. 定义损失函数与优化器

根据任务类型选择损失函数,结合优化器实现参数更新:

python

运行

# 定义损失函数:交叉熵损失(适用于分类任务)
criterion = nn.CrossEntropyLoss()
# 定义优化器:Adam优化器,学习率0.001
optimizer = torch.optim.Adam(model_seq.parameters(), lr=0.001)

3. 循环训练与验证

交替进行训练(更新参数)与验证(评估性能),记录模型表现:

python

运行

epochs = 5  # 训练轮数
for epoch in range(epochs):
# 训练阶段:模型设为训练模式
model_seq.train()
train_loss = 0.0
for images, labels in train_loader:
# 前向传播:计算预测值
outputs = model_seq(images)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播与参数更新
optimizer.zero_grad()  # 清空梯度
loss.backward()        # 反向传播计算梯度
optimizer.step()       # 优化器更新参数
# 累加训练损失
train_loss += loss.item() * images.size(0)
# 计算平均训练损失
train_loss = train_loss / len(train_loader.dataset)
print(f'Epoch [{epoch+1}/{epochs}], Train Loss: {train_loss:.4f}')
# 验证阶段:模型设为评估模式
model_seq.eval()
correct = 0
total = 0
with torch.no_grad():  # 关闭梯度计算,节省资源
for images, labels in test_loader:
outputs = model_seq(images)
_, predicted = torch.max(outputs.data, 1)  # 取概率最大的类别
total += labels.size(0)
correct += (predicted == labels).sum().item()
# 计算验证准确率
val_acc = 100 * correct / total
print(f'Validation Accuracy: {val_acc:.2f}%')

4. 可视化结果

通过matplotlib等工具可视化训练损失与验证准确率,分析模型收敛情况:

python

运行

import matplotlib.pyplot as plt
# 假设已记录各轮次的损失与准确率
epochs_list = list(range(1, epochs+1))
train_losses = [1.2, 0.8, 0.5, 0.3, 0.2]  # 示例数据
val_accs = [60, 75, 85, 92, 95]           # 示例数据
# 绘制损失曲线
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_list, train_losses, 'b-', label='Train Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.legend()
# 绘制准确率曲线
plt.subplot(1, 2, 2)
plt.plot(epochs_list, val_accs, 'r-', label='Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.title('Validation Accuracy Curve')
plt.legend()
plt.tight_layout()
plt.show()

五、关键总结与注意事项

  1. 工具选择原则nn.Module用于可学习参数模块与状态管理,nn.functional用于无参数功能操作,二者结合可兼顾灵活性与简洁性。
  2. 模型构建范式:简单线性模型优先用nn.Sequential;复杂模型需继承nn.Module并自定义forward方法;多子模块场景结合ModuleList/ModuleDict实现结构化管理。
  3. 残差连接核心:当输入与输出维度不一致时,必须通过 1×1 卷积调整维度,否则无法实现残差相加。
  4. 训练关键操作:训练前需调用model.train()开启 dropout/batchnorm 等训练模式,验证前需调用model.eval()切换为评估模式,且验证时用torch.no_grad()关闭梯度计算。

通过以上内容的学习与实践,可掌握 PyTorch 构建与训练神经网络的核心能力,为复杂任务(图像识别、自然语言处理等)的模型开发奠定基础。

posted on 2025-09-24 10:45  ycfenxi  阅读(10)  评论(0)    收藏  举报