构建网络:一站式实现模型搭建与训练
一、模型搭建整体流程
使用 PyTorch 构建和训练模型的基本流程分为以下 5 个步骤:[pdf_16]
|
|
|
|---|---|
| ① 定义网络结构 |
nn.Module 类,在 __init__() 中定义可学习参数,在 forward() 中定义前向传播
|
| ② 实例化模型 |
model = LinearModel()
|
| ③ 定义损失函数与优化器 |
|
| ④ 训练循环 |
|
| ⑤ 保存/查看训练结果 |
named_parameters() 或 state_dict() 查看参数,用 torch.save() 保存模型
|
二、自定义网络结构——nn.Module 详解
1. 三条核心规则
继承 nn.Module 类后,必须遵守以下规则:[pdf_16]
|
|
|
|---|---|
必须继承 nn.Module |
nn.Module
|
重写 __init__() |
super().__init__()**,否则会报错:AttributeError: cannot assign parameters before Module.__init__() call
|
重写 forward() |
|
★模块是 callable 的:调用
model(x)相当于执行forward函数。[pdf_16]
2. 线性回归模型示例
import torch
from torch import nn
classLinearModel(nn.Module):
def__init__(self):
super().__init__()
self.weight = nn.Parameter(torch.randn(1))
self.bias = nn.Parameter(torch.randn(1))
defforward(self, input):
return (input * self.weight) + self.bias
# 使用
model = LinearModel()
x = torch.tensor(3)
y = model(x)
★
nn.Parameter()的作用是作为nn.Module中可训练的参数使用。[pdf_16]
3. 模块化组合——将重复结构封装
对于深层网络,可以将重复结构封装为单独模块,如将 3×3 卷积与 2×2 卷积组合封装为 CustomLayer:[pdf_16]
classCustomLayer(nn.Module):
def__init__(self, input_channels, output_channels):
super().__init__()
self.conv1_1 = nn.Conv2d(in_channels=input_channels, out_channels=3, kernel_size=3)
self.conv1_2 = nn.Conv2d(in_channels=3, out_channels=output_channels, kernel_size=2)
defforward(self, input):
x = self.conv1_1(input)
x = self.conv1_2(x)
return x
然后在主模型中直接调用 CustomLayer:
classCustomModel(nn.Module):
def__init__(self):
super().__init__()
self.layer1 = CustomLayer(1, 1)
# ... 重复使用
self.layern = CustomLayer(1, 1)
defforward(self, input):
x = self.layer1(input)
# ...
x = self.layern(x)
return x
★残差块、Inception 块等多层组合结构,都可以采用上述模块化方式实现。[pdf_16]
三、模型训练完整过程
1. 准备训练数据
回归模型为 ,其中 x 与 y 对应 x_train 与 y_train,w 与 b 是要学习的参数:[pdf_16]
import numpy as np
import random
from matplotlib import pyplot as plt
W = 2
b = 3
xlim = [-10, 10]
x_train = np.random.randint(low=xlim[0], high=xlim[1], size=30)
y_train = [W * x + b + random.randint(0, 2) for x in x_train]
plt.plot(x_train, y_train, 'bo')
2. 训练代码(MSE 损失 + SGD 优化器)
model = LinearModel()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, weight_decay=1e-2, momentum=0.9)
y_train = torch.tensor(y_train, dtype=torch.float32)
for _ in range(1000):
input = torch.from_numpy(x_train)
output = model(input)
loss = nn.MSELoss()(output, y_train)
model.zero_grad()
loss.backward()
optimizer.step()
3. 查看训练后的参数
for parameter in model.named_parameters():
print(parameter)
# 输出:
# ('weight', Parameter containing: tensor([2.0071], requires_grad=True))
# ('bias', Parameter containing: tensor([3.1690], requires_grad=True))
训练后 weight ≈ 2.0071,bias ≈ 3.1690,与真实值 w=2、b=3 非常接近,模型成功学习了数据的规律。[pdf_16]
四、模型保存与加载
方式一:只保存训练好的参数(推荐)
# 保存
torch.save(model.state_dict(), './linear_model.pth')
# 加载(需先定义网络结构)
linear_model = LinearModel()
linear_model.load_state_dict(torch.load('./linear_model.pth'))
linear_model.eval()
state_dict 是一个字典,在模型定义后自动生成,存储模型的可训练参数:[pdf_16]
model.state_dict()
# 输出:OrderedDict([('weight', tensor([[2.0071]])), ('bias', tensor([3.1690]))])
方式二:保存网络结构与参数
# 保存整个模型
torch.save(model, './linear_model_with_arc.pth')
# 加载(不需要创建网络了)
linear_model_2 = torch.load('./linear_model_with_arc.pth')
linear_model_2.eval()
⚠️ 重要提醒
-
**加载模型后必须执行 model.eval()**:因为 Dropout 与 BN 等层在训练时与评估时状态不同,模型进入评估或上线前必须调用。[pdf_16] -
模型后缀名 .pt或.pth只是命名习惯,没有特殊要求。[pdf_16]
五、迁移学习——使用 Torchvision 预训练模型微调(AlexNet 示例)
1. 什么是微调
Torchvision 提供的模型最大的作用是当作预训练模型,用来加速模型收敛速度,这就是微调(fine-tuning)。文章介绍了两种微调方式:[pdf_16]
-
固定整个网络参数,只训练最后全连接层 -
修改全连接层之后,整个网络重新训练(使用预训练模型的参数作为初始化参数)—— 这是更常用的方式
2. 使用预训练模型的两个重点
|
|
|
|---|---|
| 输入数据格式 |
|
| 输入尺寸 |
|
3. 导入与验证预训练模型
import torchvision.models as models
# 方式一:在线下载
alexnet = models.alexnet(pretrained=True)
# 方式二:手动下载后加载
alexnet = models.alexnet()
alexnet.load_state_dict(torch.load('./model/alexnet-owt-4df8aa71.pth'))
验证模型加载是否成功——用一张狗图片预测,输出 263 对应 ImageNet 中的柯基狗:[pdf_16]
from PIL import Image
import torchvision.transforms as transforms
im = Image.open('dog.jpg')
transform = transforms.Compose([
transforms.RandomResizedCrop((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = transform(im).unsqueeze(0)
alexnet(input_tensor).argmax()
# 输出:263 ← 对应 ImageNet 中的 Pembroke(柯基狗)
4. 打印网络结构确定修改位置
print(alexnet)
AlexNet 结构包含:[pdf_16]
-
features:卷积层部分 -
avgpool:AdaptiveAvgPool2d(output_size=(6, 6)) -
classifier:Sequential 分类器(含 Dropout、Linear、ReLU 等层)
5. 使用 CIFAR-10 数据集进行微调
CIFAR-10 数据集:[pdf_16]
-
共 60000 张 32×32 RGB 图片,10 个类别 -
每类 6000 张,50000 张训练集,10000 张测试集
cifar10_dataset = torchvision.datasets.CIFAR10(
root='./data',
train=False,
transform=transforms.ToTensor(),
target_transform=None,
download=True
)
展示 CIFAR-10 图片数据:
from torch.utils.data import DataLoader
tensor_dataloader = DataLoader(dataset=cifar10_dataset, batch_size=32)
data_iter = iter(tensor_dataloader)
img_tensor, label_tensor = data_iter.next()
print(img_tensor.shape) # torch.Size([32, 3, 32, 32])
grid_tensor = torchvision.utils.make_grid(img_tensor, nrow=16, padding=2)
grid_img = transforms.ToPILImage()(grid_tensor)
display(grid_img)
6. 微调完整流程代码
修改全连接层(将最后一层替换为适配 10 类输出的 Linear 层):[pdf_16]
fc_in_features = alexnet.classifier[6].in_features
alexnet.classifier[6] = torch.nn.Linear(fc_in_features, 10)
定义优化器与 DataLoader:[pdf_16]
from torch.utils.data import DataLoader
dataloader = DataLoader(dataset=cifar10_dataset,
batch_size=32,
shuffle=True,
num_workers=2)
optimizer = torch.optim.SGD(alexnet.parameters(), lr=1e-4, weight_decay=1e-2, momentum=0.9)
训练循环:[pdf_16]
for epoch in range(3):
for item in dataloader:
output = alexnet(item[0])
target = item[1]
loss = nn.CrossEntropyLoss()(output, target)
print('Epoch {}, Loss {}'.format(epoch + 1, loss))
alexnet.zero_grad()
loss.backward()
optimizer.step()
六、核心要点总结
|
|
|
|---|---|
| 自定义网络 |
nn.Module,重写 __init__() 和 forward(),注意 super().__init__()
|
| nn.Parameter |
nn.Module 中可训练的参数使用
|
| 模块化封装 |
|
| 训练五步 |
|
| 模型保存方式一 | torch.save(model.state_dict(), ...)
|
| 模型保存方式二 | torch.save(model, ...)
|
model.eval() |
|
| 迁移学习 |
|
| AlexNet 结构 |
|
| CIFAR-10 |
|
七、每课一练
问题:请你自己构建一个卷积神经网络,基于 CIFAR-10,训练一个图像分类模型。因为还没有学习图像分类原理,我先帮你写好了网络的结构,需要你补全数据读取、损失函数(交叉熵损失)与优化方法(SGD)等部分。[pdf_16]
解题思路:
-
数据读取:使用 torchvision.datasets.CIFAR10加载数据集,配合DataLoader设置 batch size -
损失函数:使用 nn.CrossEntropyLoss() -
优化方法:使用 torch.optim.SGD(model.parameters(), lr=..., momentum=...) -
训练循环:参考上面的五步框架——梯度清零 → 前向传播 → 计算损失 → 反向传播 → 优化更新
八、小结
-
模型搭建三步:继承 nn.Module、重写__init__()、重写forward()。 -
模块化封装:将重复结构(如残差块)封装为子模块,便于复用。 -
训练五步:梯度清零 → 前向传播 → 计算损失 → 反向传播 → 优化更新。 -
模型保存与加载:两种方式——只保存参数或保存整个模型;加载后记得调用 model.eval()。 -
迁移学习:使用预训练模型初始化参数,修改全连接层适配新任务,整体重新训练,可加速收敛。 -
AlexNet + CIFAR-10:修改最后一层全连接层输出为 10 类,使用 SGD 优化器和交叉熵损失函数进行训练。 -
核心原则: model.eval()是评估和上线前必须调用的步骤,确保 Dropout 和 BN 层行为正确。

