在深度学习的实践中,训练模型只是第一步。为了更好地理解模型到底学到了什么,我们需要对模型结构和参数进行可视化。而在模型训练完成后,如何正确地使用模型进行预测(即推理),也是必须掌握的核心技能。此外,在漫长的训练过程中,一个直观的进度条能让我们随时掌握训练状态,缓解等待的焦虑。
今天我们重点通过代码实战来掌握这三个方面的技能:模型结构可视化、tqdm进度条的使用、以及标准的模型推理流程。
一、 模型结构可视化
理解一个深度学习网络,关键在于弄清楚两点:一是损失函数是如何定义的,这决定了模型优化的方向;二是参数的总量和层级结构,这决定了模型的容量和计算复杂度。
PyTorch 提供了多种方式来查看模型结构,从最基础的内置方法到功能更强大的第三方库。
1.1 使用 nn.Module 自带方法
这是最直接的方式,不需要安装额外的库。
1. 直接打印模型对象
import torch
import torch.nn as nn
# 假设我们定义了一个简单的MLP模型
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.fc1 = nn.Linear(4, 10)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(10, 3)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
model = MLP()
print(model)
输出结果会清晰地列出每一层的类型和参数配置:
MLP(
(fc1): Linear(in_features=4, out_features=10, bias=True)
(relu): ReLU()
(fc2): Linear(in_features=10, out_features=3, bias=True)
)
2. 查看具体的参数信息
如果你想深入看每一个参数张量的形状,可以使用 named_parameters() 方法:
for name, param in model.named_parameters():
print(f"Parameter name: {name}, Shape: {param.shape}")
这对于检查权重是否正确初始化,或者排查参数形状不匹配的问题非常有帮助。
进阶技巧:权重分布可视化
我们还可以把权重提取出来,画出它们的分布直方图。这在调参时很有用,比如如果你发现权重的方差特别大,或者所有权重都挤在0附近,可能意味着初始化有问题或者训练出现了梯度消失/爆炸。
import matplotlib.pyplot as plt
weight_data = {}
for name, param in model.named_parameters():
if 'weight' in name:
weight_data[name] = param.detach().cpu().numpy()
# 简单的可视化逻辑
for name, weights in weight_data.items():
print(f"{name} 均值: {weights.mean():.4f}, 标准差: {weights.std():.4f}")
1.2 使用 torchsummary 库
虽然自带的方法够用,但它不能直观地告诉我们在每一层之后,数据的形状(Shape)变成了什么样,也不方便统计参数总量。torchsummary 库解决了这个问题。
你需要先安装它:pip install torchsummary
from torchsummary import summary
# 需要提供一个输入数据的尺寸示例,因为PyTorch是动态图,不知道输入它就算不出输出形状
# 这里假设输入特征维度是4
summary(model, input_size=(4,))
输出会像这就非常清晰:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 10] 50
ReLU-2 [-1, 10] 0
Linear-3 [-1, 3] 33
================================================================
Total params: 83
Trainable params: 83
Non-trainable params: 0
----------------------------------------------------------------
注意点:
input_size=(4,)是必须的。对于MLP是特征数,对于CNN通常是(通道数, 高, 宽)。- 输出形状中的
-1代表 Batch Size,因为它是可变的。 - 它不会显示输入层,因为在PyTorch中输入层不是一个“层”,而是你传进去的数据。
1.3 使用 torchinfo 库
torchinfo 是 torchsummary 的升级版,提供的信息更全,格式更现代。
安装:pip install torchinfo
from torchinfo import summary
summary(model, input_size=(4, ))
它会显示层次结构树、每一层的参数量、甚至估算的计算量(Mult-Adds)和显存占用。这对于在大模型上优化显存非常有参考价值。
二、 进度条功能 (tqdm)
在跑深度学习训练时,盯着控制台发呆是很痛苦的。tqdm 是一个快速、可扩展的Python进度条库,能在长循环中在终端显示智能进度条。
2.1 基础用法
手动更新模式
适合不知道具体要循环多少次,或者循环逻辑比较复杂的场景。
from tqdm import tqdm
import time
# total=10 表示总共有10步
with tqdm(total=10) as pbar:
for i in range(10):
time.sleep(0.1) # 模拟耗时操作
pbar.update(1) # 手动让进度条走一步
自动迭代模式(推荐)
最常用的方式,直接把 tqdm 包在迭代器外面。
# desc是进度条左边的描述文字,unit是右边的单位
for i in tqdm(range(10), desc="下载进度", unit="个"):
time.sleep(0.1)
2.2 进阶用法:实时显示指标
在训练神经网络时,我们不仅想看进度,还想实时看到当前的 Loss 是多少。set_postfix 方法可以实现这一点。
total = 0
with tqdm(total=10, desc="计算累加") as pbar:
for i in range(1, 11):
time.sleep(0.1)
total += i
pbar.update(1)
# 在进度条右边实时显示当前结果
pbar.set_postfix({"当前总和": total})
2.3 在深度学习训练中的实战
这是最经典的写法,建议直接背下来或者收藏。
num_epochs = 100
# 创建一个覆盖整个训练周期的进度条
with tqdm(total=num_epochs, desc="训练模型", unit="epoch") as pbar:
for epoch in range(num_epochs):
# ... 这里执行前向传播、反向传播、优化 ...
# 假设 loss 是当前轮次的损失值
loss_val = 0.123 # 模拟值
# 记录损失值并显示在进度条上
if (epoch + 1) % 10 == 0:
pbar.set_postfix({'Loss': f'{loss_val:.4f}'})
# 更新进度
pbar.update(1)
这样你就能在控制台看到类似 训练模型: 50%|█████ | 50/100 [00:10<00:10, 4.90epoch/s, Loss=0.1234] 的效果,非常专业。
三、 模型的推理 (Inference)
模型训练好后,我们要用它来处理新数据,这个过程叫推理。推理阶段的代码和训练阶段有两点核心区别:需要关闭 Dropout/BatchNorm 的训练行为,以及不需要计算梯度。
3.1 核心步骤
model.eval():- 这是一个开关。调用它会将模型切换到“评估模式”。
- 它会影响 Dropout 层(推理时不随机丢弃神经元,而是利用所有神经元)和 BatchNorm 层(使用训练时统计的全局均值和方差,而不是当前 Batch 的)。
- 必须调用,否则你的预测结果可能不稳定甚至完全错误。
with torch.no_grad()::- 这是一个上下文管理器。
- 在这个代码块内,PyTorch 不会构建计算图,也不会保存梯度信息。
- 这能极大地减少显存占用并加速计算。
3.2 完整的推理代码示例
# 1. 切换到评估模式
model.eval()
# 2. 禁用梯度计算
with torch.no_grad():
# X_test 是测试集数据,记得要先转成Tensor并放到正确的设备(GPU/CPU)上
outputs = model(X_test)
# 3. 获取预测结果
# outputs 的形状是 (样本数, 类别数),每行是概率(logits)
# torch.max(outputs, 1) 返回每行的最大值和对应的索引
# 我们只需要索引(predicted),因为它代表了预测的类别ID
_, predicted = torch.max(outputs, 1)
# 4. 计算准确率
# predicted == y_test 会生成一个布尔Tensor(True/False)
# .sum() 把True当1加起来,得到预测正确的数量
# .item() 把Tensor转成Python数值
correct = (predicted == y_test).sum().item()
accuracy = correct / y_test.size(0)
print(f'测试集准确率: {accuracy * 100:.2f}%')
3.3 常见疑问:为什么有了 eval() 还要 no_grad()?
这是一个很好的问题。
model.eval()改变的是层的行为(Dropout 不丢弃,BN 用统计值)。torch.no_grad()改变的是计算引擎的行为(不记录梯度)。
虽然在推理时我们通常既不需要 Dropout 也不需要梯度,但在某些特殊场景下(比如生成对抗样本,或者做可视化分析如 CAM 热力图),我们可能需要在 eval 模式下依然计算梯度。所以 PyTorch 把这两个功能解耦了。
但在常规的预测任务中,请务必同时使用这两者,这是标准规范。

838

被折叠的 条评论
为什么被折叠?



