PyTorch实现#

本章提供完整的消融实验代码。代码基于 神经网络模块:搭建计算图完整训练流程 中的知识。

两种实现方式

本章提供两种层次的实现:

  1. 模型定义code/base-model.pycode/batch-normal.pycode/dropout.py) — 展示每个消融变体的架构差异,是理解"改了哪里"的关键

  2. 训练运行(使用社团的 mnist-helloworld 框架) — 训练循环、实验管理、结果对比由框架自动完成,专注实验设计本身

建议先阅读模型定义理解架构差异,再用框架运行实验。

基线模型实现#

基线CNN模型代码(含 Dropout)#
 1import torch
 2import torch.nn as nn
 3import torch.nn.functional as F
 4
 5class BaselineCNN(nn.Module):
 6    """
 7    基线CNN模型 - 用于消融研究的基准
 8    简化为2层卷积,适合CIFAR-10快速实验
 9    """
10    def __init__(self, num_classes=10):
11        super(BaselineCNN, self).__init__()
12
13        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
14        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
15        self.pool = nn.MaxPool2d(2, 2)
16        self.fc1 = nn.Linear(64 * 8 * 8, 512)
17        self.fc2 = nn.Linear(512, num_classes)
18        self.dropout = nn.Dropout(0.5)
19
20    def forward(self, x):
21        x = self.pool(F.relu(self.conv1(x)))
22        x = self.pool(F.relu(self.conv2(x)))
23        x = x.view(-1, 64 * 8 * 8)
24        x = self.dropout(F.relu(self.fc1(x)))
25        x = self.fc2(x)
26        return x

参数量计算

计算公式

参数量

Conv1

\((3 \times 3 \times 3 + 1) \times 32\)

896

Conv2

\((3 \times 3 \times 32 + 1) \times 64\)

18,496

FC1

\((4,096 + 1) \times 512\)

2,097,664

FC2

\((512 + 1) \times 10\)

5,130

总计

2,122,186

全连接层占 85% 以上参数,卷积层仅占 1.6%。这就是为什么可以冻结卷积层做迁移学习——特征提取器本身很轻量。

批归一化实现#

带 BatchNorm 的 CNN 代码#
 1import torch.nn as nn
 2import torch.nn.functional as F
 3
 4class CNNWithBN(nn.Module):
 5    """
 6    带批归一化(Batch Normalization)的CNN
 7    批归一化放在卷积后、激活前是标准做法
 8    """
 9    def __init__(self):
10        super(CNNWithBN, self).__init__()
11
12        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
13        self.bn1 = nn.BatchNorm2d(32)
14        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
15        self.bn2 = nn.BatchNorm2d(64)
16        self.pool = nn.MaxPool2d(2, 2)
17        self.fc1 = nn.Linear(64 * 8 * 8, 512)
18        self.fc2 = nn.Linear(512, 10)
19
20    def forward(self, x):
21        x = self.pool(F.relu(self.bn1(self.conv1(x))))
22        x = self.pool(F.relu(self.bn2(self.conv2(x))))
23        x = x.view(-1, 64 * 8 * 8)
24        x = F.relu(self.fc1(x))
25        x = self.fc2(x)
26        return x

消融实验对比:BatchNorm

预期结果(参见 实验设计):

  • 有 BN 的模型收敛更快(约 50% 的训练时间)

  • 有 BN 的模型允许使用更大学习率

  • 准确率可能略有提升或持平

实验方法

  1. 训练基线模型和无 BN 模型各 20 个 epoch

  2. 记录每轮的训练/测试准确率

  3. 绘制对比曲线,观察 BN 对收敛速度的影响

Dropout实现#

带 Dropout 的 CNN 代码#
 1import torch.nn as nn
 2import torch.nn.functional as F
 3
 4class CNNWithDropout(nn.Module):
 5    """
 6    带Dropout正则化的CNN
 7    Dropout只在全连接层使用,卷积层通常不需要
 8    """
 9    def __init__(self, dropout_rate=0.5):
10        super(CNNWithDropout, self).__init__()
11        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
12        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
13        self.pool = nn.MaxPool2d(2, 2)
14        self.fc1 = nn.Linear(64 * 8 * 8, 512)
15        self.fc2 = nn.Linear(512, 10)
16        self.dropout = nn.Dropout(dropout_rate)
17
18    def forward(self, x):
19        x = self.pool(F.relu(self.conv1(x)))
20        x = self.pool(F.relu(self.conv2(x)))
21        x = x.view(-1, 64 * 8 * 8)
22        x = self.dropout(F.relu(self.fc1(x)))
23        x = self.fc2(x)
24        return x

消融实验对比:Dropout

Dropout率

训练准确率

测试准确率

过拟合差距

0.0(无)

95%

78%

17%

0.5(有)

89%

78%

11%

关键观察

  • 无 Dropout:训练准确率高但测试低 = 过拟合

  • 有 Dropout:训练准确率降低但测试不变 = 更好的泛化

建议实验

  1. 训练 dropout_rate=0.0 的模型

  2. 训练 dropout_rate=0.5 的模型

  3. 对比两者的训练/测试准确率差距

  4. 体会"正则化"的实际意义

使用框架运行消融实验#

模型定义好之后,训练由社团的 mnist-helloworld 框架处理。详见使用训练框架中的添加新模型:3 步

注册模型#

将消融实验的模型注册到框架的 ModelRegistry

from code.base_model import BaselineCNN
from code.batch_normal import CNNWithBN
from code.dropout import CNNWithDropout

ModelRegistry.register("baseline_cnn", BaselineCNN)
ModelRegistry.register("cnn_with_bn", CNNWithBN)
ModelRegistry.register("cnn_with_dropout", CNNWithDropout)

实验配置#

每个消融变体对应一个 YAML 配置文件(见 code/configs/):

# configs/baseline.yaml
model:
  name: baseline_cnn
dataset:
  name: cifar10
training:
  epochs: 20
  batch_size: 64
optimization:
  optimizer: adam
  learning_rate: 0.001

运行实验#

# 基线模型
python train.py --config code/configs/baseline.yaml

# 消融:移除 Dropout
python train.py --config code/configs/abl_no_dropout.yaml

# 消融:添加 BatchNorm
python train.py --config code/configs/abl_with_bn.yaml

# 或直接在命令行覆盖参数
python train.py --model baseline_cnn --dataset cifar10 --epochs 20 --learning-rate 0.001

每次运行自动创建 runs/exp1/runs/exp2/… 目录,保存完整配置、checkpoint 和训练曲线。

对比实验结果#

# 训练完成后,直接比较两个实验的准确率
cat runs/exp1/logs/training.log | grep "Test Acc"
cat runs/exp2/logs/training.log | grep "Test Acc"

# 或查看训练曲线
open runs/exp1/training_curves.png
open runs/exp2/training_curves.png

对照:手写 vs 框架#

环节

手写实现

框架实现

模型定义

继承 nn.Module

继承 nn.Module(不变)

训练循环

~90 行 train_model()

Trainer 类自动处理

实验管理

手动建目录、命文件名

ExperimentManager,YOLO 自动编号

超参数

硬编码在脚本中

YAML 配置 + CLI 覆盖

结果对比

手动记录准确率、画图

自动保存训练曲线

代码使用指南#

实验流程

  1. 阅读 base-model.pybatch-normal.pydropout.py,理解每个变体的架构差异

  2. 阅读 实验设计 中的实验设计

  3. 将模型注册到框架,创建对应的 YAML 配置文件

  4. 运行基线实验,记录结果

  5. 逐一运行消融实验(每次只改一个组件)

  6. 对比各实验的训练曲线和最终准确率

  7. 分析组件重要性,撰写实验报告

贡献者与修订历史

查看详细修订记录
  • 0cdb1e4 2026-04-29 - Heyan Zhu: feat: add model-serving chapter and update related content
  • b5be2d6 2026-04-28 - Heyan Zhu: docs: update documentation and improve content organization
  • 0c291d7 2025-12-10 - Heyan Zhu: docs: restructure course materials and add new content