Day 35 - 模型可视化与推理

在深度学习的实践中,训练模型只是第一步。为了更好地理解模型到底学到了什么,我们需要对模型结构和参数进行可视化。而在模型训练完成后,如何正确地使用模型进行预测(即推理),也是必须掌握的核心技能。此外,在漫长的训练过程中,一个直观的进度条能让我们随时掌握训练状态,缓解等待的焦虑。

今天我们重点通过代码实战来掌握这三个方面的技能:模型结构可视化、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 库

torchinfotorchsummary 的升级版,提供的信息更全,格式更现代。

安装: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 核心步骤

  1. model.eval()
    • 这是一个开关。调用它会将模型切换到“评估模式”。
    • 它会影响 Dropout 层(推理时不随机丢弃神经元,而是利用所有神经元)和 BatchNorm 层(使用训练时统计的全局均值和方差,而不是当前 Batch 的)。
    • 必须调用,否则你的预测结果可能不稳定甚至完全错误。
  2. 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 把这两个功能解耦了。

但在常规的预测任务中,请务必同时使用这两者,这是标准规范。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值