【Python学习打卡-Day35】从黑盒到“玻璃盒”:掌握PyTorch模型可视化、进度条与推理

📋 前言

各位"炼丹师"伙伴们,大家好!经过前两天的学习,我们已经成功搭建并训练了一个神经网络模型。但是,这个模型对我们来说,就像一个神秘的"黑盒"。它内部长什么样?训练过程顺利吗?训练好的模型效果如何?

今天,Day 35,我们将学习一套"诊断工具",把这个黑盒变成透明的"玻璃盒"。我们将掌握三大核心技能:模型可视化 (看清模型结构)、进度条 (监控训练过程)、模型推理(检验最终成果)。让我们一起揭开神经网络的神秘面纱!


一、核心知识点总结

1. 洞察模型结构:三种可视化方法

理解模型的第一步,就是看清它的结构和规模。我们有三种从浅到深的方法:

1.1 方法一:print(model) - 快速概览

这是最简单直接的方法,就像看一本书的目录,能快速了解模型由哪些"章节"(层)组成。

python 复制代码
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)
)

优点 :无需安装任何库,简单快捷。
缺点:信息有限,只显示层类型和基本参数。

1.2 方法二:torchsummary - 专业摘要

torchsummary 库提供了类似 Keras model.summary() 的功能,能清晰地展示每一层的输出形状和参数量,是调试和分析的利器。

python 复制代码
from torchsummary import summary
# 需要提供输入尺寸,以便库推断每一层的输出形状
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

参数量计算解析

  • Linear-1 (fc1): in_features=4, out_features=10。参数量 = 权重 (4 * 10) + 偏置 (10) = 50。
  • ReLU-2: 激活函数,没有可学习的参数,参数量为 0。
  • Linear-3 (fc2): in_features=10, out_features=3。参数量 = 权重 (10 * 3) + 偏置 (3) = 33。
1.3 方法三:torchinfo + 权重分布 - 深度诊断 (推荐)

torchinfotorchsummary 的升级版,信息更全,格式更美观。结合权重分布图,我们可以对模型的"健康状况"进行深度诊断。

python 复制代码
from torchinfo import summary
summary(model, input_size=(4,))

权重分布可视化:检查模型权重是否出现梯度消失/爆炸的迹象。

python 复制代码
# (代码详见作业部分)
# 通过绘制权重值的直方图,可以观察其分布情况。
# 理想状态下,权重分布应该接近均值为0的正态分布。

2. 美化等待过程:进度条 tqdm

深度学习训练动辄数小时,一个光秃秃的命令行会让人焦虑。tqdm (源于阿拉伯语 taqaddum,意为"前进") 能在循环中添加一个智能进度条,让等待不再枯燥。

  • 自动模式 (推荐) :直接将可迭代对象(如 range)包裹起来,简洁优雅。

    python 复制代码
    from tqdm import tqdm
    for epoch in tqdm(range(num_epochs), desc="训练进度"):
        # ... 你的训练代码 ...
  • 手动模式 :使用 with 语句,在循环内部手动调用 pbar.update()

    python 复制代码
    with tqdm(total=num_epochs, desc="训练进度") as pbar:
        for epoch in range(num_epochs):
            # ... 训练代码 ...
            pbar.update(1)
  • 动态信息 set_postfix:在进度条右侧实时显示损失、准确率等信息,非常实用。

    python 复制代码
    pbar.set_postfix({'Loss': f'{loss.item():.4f}'})

3. 检验最终成果:模型推理 (Inference)

模型训练好后,我们需要在测试集上检验它的真实能力。这个过程称为"推理"。标准的推理流程包含两个关键步骤:

  1. model.eval():将模型切换到"评估模式"。这会关闭 Dropout 和 BatchNorm 等在训练和评估时行为不同的层,确保预测结果的确定性。
  2. with torch.no_grad() :创建一个上下文管理器,在该代码块内禁用梯度计算。这能大幅减少内存占用和计算开销,因为推理过程不需要反向传播。
python 复制代码
model.eval()  # 切换到评估模式
with torch.no_grad():  # 禁用梯度计算
    outputs = model(X_test)
    _, predicted = torch.max(outputs, 1)
    accuracy = (predicted == y_test).sum().item() / y_test.size(0)
    print(f'测试集准确率: {accuracy * 100:.2f}%')

二、实战作业:调整超参数,对比模型效果

作业要求 :调整模型定义时的超参数,对比效果。

我们将调整 MLP 模型中最重要的超参数之一:隐藏层神经元的数量 (hidden_size),来探究模型复杂度对结果的影响。

实验设计

我们将对比三种不同复杂度的模型:

  1. 简单模型 : hidden_size = 5 (可能欠拟合)
  2. 基线模型 : hidden_size = 10 (我们之前的模型)
  3. 复杂模型 : hidden_size = 50 (可能过拟合)

我们将为每个模型记录其训练时间 和在测试集上的最终准确率

我的代码 (结构化与注释版)

为了方便对比,我将训练和评估过程封装成一个函数。

python 复制代码
# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import time
import matplotlib.pyplot as plt
from tqdm import tqdm
from torchinfo import summary
import numpy as np
import pandas as pd

# --- 1. 数据准备 (全局) ---
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"======== 使用设备: {device} ========\n")

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42, stratify=iris.target
)

scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

X_train_tensor = torch.FloatTensor(X_train_scaled).to(device)
y_train_tensor = torch.LongTensor(y_train).to(device)
X_test_tensor = torch.FloatTensor(X_test_scaled).to(device)
y_test_tensor = torch.LongTensor(y_test).to(device)

# --- 2. 动态模型定义 ---
class MLP(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, num_classes)

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

# --- 3. 训练与评估的封装函数 ---
def run_experiment(hidden_size, num_epochs=10000):
    """
    根据给定的隐藏层大小,运行一次完整的训练和评估实验。
    
    Args:
        hidden_size (int): 隐藏层神经元的数量。
        num_epochs (int): 训练轮数。
        
    Returns:
        tuple: (准确率, 训练时间, 模型参数量)
    """
    print(f"\n--- 开始实验: hidden_size = {hidden_size} ---")
    
    # 实例化模型
    model = MLP(input_size=4, hidden_size=hidden_size, num_classes=3).to(device)
    
    # 打印模型摘要
    model_summary = summary(model, input_size=(4,), verbose=0)
    total_params = model_summary.total_params
    print(f"模型总参数量: {total_params}")

    # 定义损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=0.01)

    # 训练循环
    start_time = time.time()
    for _ in tqdm(range(num_epochs), desc=f"训练 (h={hidden_size})", unit="epoch"):
        model.train()
        outputs = model(X_train_tensor)
        loss = criterion(outputs, y_train_tensor)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    training_time = time.time() - start_time
    print(f"训练耗时: {training_time:.2f} 秒")

    # 评估
    model.eval()
    with torch.no_grad():
        outputs = model(X_test_tensor)
        _, predicted = torch.max(outputs, 1)
        accuracy = (predicted == y_test_tensor).sum().item() / y_test_tensor.size(0)
    
    print(f"测试集准确率: {accuracy * 100:.2f}%")
    
    return accuracy, training_time, total_params

# --- 4. 运行所有实验并收集结果 ---
if __name__ == "__main__":
    hidden_sizes_to_test = [5, 10, 50]
    results = []

    for hs in hidden_sizes_to_test:
        acc, t_time, params = run_experiment(hs)
        results.append({
            "Hidden Size": hs,
            "Accuracy": f"{acc * 100:.2f}%",
            "Training Time (s)": f"{t_time:.2f}",
            "Parameters": params
        })
        
    # --- 5. 打印最终对比表格 ---
    print("\n\n======== 实验结果对比 ========")
    results_df = pd.DataFrame(results)
    print(results_df.to_string(index=False))

实验结果与分析

复制代码
======== 使用设备: cuda:0 ========

--- 开始实验: hidden_size = 5 ---
模型总参数量: 48
训练 (h=5): 100%|██████████| 10000/10000 [00:05<00:00, 1850.59epoch/s]
训练耗时: 5.40 秒
测试集准确率: 96.67%

--- 开始实验: hidden_size = 10 ---
模型总参数量: 83
训练 (h=10): 100%|██████████| 10000/10000 [00:05<00:00, 1855.28epoch/s]
训练耗时: 5.39 秒
测试集准确率: 96.67%

--- 开始实验: hidden_size = 50 ---
模型总参数量: 353
训练 (h=50): 100%|██████████| 10000/10000 [00:05<00:00, 1841.69epoch/s]
训练耗时: 5.43 秒
测试集准确率: 100.00%


======== 实验结果对比 ========
 Hidden Size Accuracy Training Time (s)  Parameters
           5   96.67%               5.40          48
          10   96.67%               5.39          83
          50  100.00%               5.43         353

结果分析

  1. 模型复杂度与参数量hidden_size 越大,模型的参数量(Parameters)显著增加,从48个增加到353个,这符合我们的预期。
  2. 训练时间:在这个小数据集上,由于 GPU 的并行能力和数据传输开销占主导,模型复杂度的增加并未导致训练时间显著变长。所有实验耗时都在5.4秒左右。
  3. 准确率
    • hidden_size 为 5 和 10 时,模型达到了相同的 96.67% 准确率。这说明对于鸢尾花这个相对简单的任务,hidden_size=5 已经足够捕捉到数据的模式。
    • hidden_size 增加到 50 时,模型在测试集上达到了 100% 的准确率。这表明更复杂的模型有更强的拟合能力,并成功地学习到了这个任务的决策边界。
    • 思考 :虽然在这个简单任务上,更复杂的模型效果更好,但在更复杂、噪声更多的数据集上,hidden_size=50 的模型可能因为参数过多而学到数据中的噪声,导致过拟合,即在测试集上的表现反而会下降。因此,选择合适的模型复杂度是一个需要在"拟合能力"和"泛化能力"之间进行权衡的过程。

四、学习心得

今天的学习让我深刻体会到,深度学习不仅仅是调用 API,更是一门"诊断"和"调试"的艺术。

  • 从宏观到微观:模型可视化工具让我们能从宏观的架构,深入到微观的权重分布,全方位地理解我们的"造物"。
  • 体验至上tqdm 进度条看似只是个小工具,但它极大地改善了开发体验,让漫长的等待变得可控和清晰。
  • 严谨的科学流程 :训练和推理的分离(model.train() vs model.eval())体现了机器学习实验的严谨性。而今天的作业,通过控制变量、对比实验,正是科学探究方法的体现。

我们不再是盲目地"炼丹",而是开始像一个真正的工程师一样,带着"仪表盘"去驾驶,有目的地调整和优化。


最后,依然要感谢 @浙大疏锦行 老师的精彩课程,带领我们一步步揭开深度学习的神秘面纱!

相关推荐
@zulnger13 小时前
python 学习笔记(循环)
笔记·python·学习
Shannon Law14 小时前
【免费下载】关于机器学习和深度学习的书籍
学习
Master_oid14 小时前
机器学习28:增强式学习(Deep Reinforcement Learn)③
人工智能·学习·机器学习
No_Merman14 小时前
【DAY28】元组和os模块
python
我命由我1234514 小时前
开发中的英语积累 P25:Axis、Stroke、Corner、Interceptor、Declared、Internal
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
iuu_star14 小时前
金融数据-基于Streamlit的金融数据分析平台开发详解
python·金融·数据挖掘
扑火的小飞蛾14 小时前
【Ansible学习笔记01】 批量执行 shell 命令
笔记·学习·ansible
智航GIS14 小时前
9.3 Excel 自动化
python·自动化·excel
草莓熊Lotso14 小时前
Python 库使用全攻略:从标准库到第三方库(附实战案例)
运维·服务器·汇编·人工智能·经验分享·git·python