CHAPTER 10

深度学习入门(PyTorch)

神经网络是如何工作的?用 PyTorch 从零构建第一个深度学习模型,理解张量、前向传播、反向传播和训练循环的本质。

深度学习核心术语

  • Tensor(张量) 多维数组,PyTorch 的基本数据单元。0维张量是标量,1维是向量,2维是矩阵,3维及以上叫张量。与 NumPy ndarray 的主要区别是 Tensor 支持 GPU 计算和自动微分。
  • 计算图(Computational Graph) 记录张量运算历史的有向无环图(DAG)。PyTorch 使用动态计算图——每次前向传播都即时构建计算图,使得 Python 控制流(if/for)可以改变网络结构,调试更直观。
  • 自动微分(Autograd) PyTorch 自动计算梯度的机制。在 requires_grad=True 的 Tensor 上执行运算后,调用 .backward() 即可自动计算所有参与运算的 Tensor 的梯度,无需手动推导链式法则。
  • nn.Module PyTorch 神经网络模块的基类。所有网络结构都继承它,获得参数管理(.parameters())、设备转移(.to(device))、训练/评估模式切换(.train()/.eval())等能力。
  • 优化器(Optimizer) 根据梯度更新模型参数的算法。常用 Adam(自适应学习率,大多数情况的默认选择)、SGD with Momentum(图像任务)。优化器持有对模型参数的引用,调用 step() 即完成参数更新。
  • 损失函数(Loss Function) 衡量模型预测与真实标签差距的函数。分类任务常用 CrossEntropyLoss,回归任务常用 MSELoss。损失值越小,模型越准确。

Tensor —— PyTorch 的核心数据结构

PyTorch 的 Tensor 本质上是 NumPy ndarray 的升级版:支持 GPU 加速,并且记录计算历史用于自动微分。你已经会 NumPy,PyTorch Tensor 几乎零成本上手。

PYTHON · Tensor 基础
import torch
import torch.nn as nn
import numpy as np

# 创建 Tensor(语法几乎和 NumPy 一样)
t1 = torch.tensor([1.0, 2.0, 3.0])
t2 = torch.zeros(3, 4)
t3 = torch.ones(2, 3)
t4 = torch.randn(4, 4)   # 标准正态分布(权重初始化常用)
t5 = torch.arange(0, 10)

# Tensor 与 NumPy 互转
np_arr = np.array([1, 2, 3])
tensor = torch.from_numpy(np_arr)  # NumPy → Tensor
back   = tensor.numpy()              # Tensor → NumPy

# GPU 加速(有 NVIDIA GPU 时)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"使用: {device}")
t4 = t4.to(device)  # 把 Tensor 移到 GPU

# 形状操作(和 NumPy 几乎一样)
x = torch.randn(32, 28, 28)
print(x.shape)                 # torch.Size([32, 28, 28])
x_flat = x.view(32, -1)       # (32, 784)
x_flat = x.reshape(32, -1)    # 等价写法

自动微分(Autograd)

PyTorch 最神奇的功能是自动微分:只需写前向传播代码,PyTorch 会自动计算所有参数的梯度。这是神经网络反向传播的核心,也是 PyTorch 比 NumPy 强大得多的地方。

核心概念

梯度(Gradient)与反向传播

梯度告诉你:如果稍微增加某个参数,损失会增加还是减少?反向传播通过链式法则从损失出发,计算网络中每个参数的梯度。然后用梯度下降法(参数 -= 学习率 × 梯度)更新参数,让损失逐渐减小。

PYTHON · 自动微分
# requires_grad=True 告诉 PyTorch:需要计算这个 Tensor 的梯度
x = torch.tensor(2.0, requires_grad=True)
w = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)

# 前向传播:y = w*x + b
y    = w * x + b      # 7.0
loss = (y - 5.0) ** 2  # (7-5)² = 4.0(假设目标是5)

# 反向传播:自动计算所有梯度
loss.backward()

# 查看梯度(d_loss/d_w = 2*(y-5)*x = 2*2*2 = 8)
print(f"w 的梯度: {w.grad}")  # 8.0
print(f"b 的梯度: {b.grad}")  # 4.0

# 用梯度更新参数(梯度下降)
lr = 0.1
with torch.no_grad():   # 更新时不需要跟踪梯度
    w -= lr * w.grad
    b -= lr * b.grad
print(f"更新后 w={w.item():.2f}, b={b.item():.2f}")

构建神经网络

PyTorch 用 nn.Module 来定义网络结构。继承它,在 __init__ 里定义各层,在 forward 里定义数据流向——这就是第 6 章 OOP 的真实应用!

PYTHON · 定义神经网络
import torch
import torch.nn as nn
import torch.nn.functional as F

class MLP(nn.Module):
    """多层感知机(全连接神经网络)"""

    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        # nn.Linear:全连接层,参数会自动注册和更新
        self.fc1     = nn.Linear(input_dim, hidden_dim)
        self.fc2     = nn.Linear(hidden_dim, hidden_dim)
        self.fc3     = nn.Linear(hidden_dim, output_dim)
        self.dropout = nn.Dropout(0.3)  # 防过拟合

    def forward(self, x):
        # ReLU:激活函数,引入非线性
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        x = self.fc3(x)   # 最后一层不加激活(交给损失函数处理)
        return x

# 创建模型
model = MLP(input_dim=784, hidden_dim=128, output_dim=10)
print(model)

# 统计参数数量
total_params = sum(p.numel() for p in model.parameters())
print(f"总参数量: {total_params:,}")

# 前向传播测试
x      = torch.randn(32, 784)  # 模拟 32 张 MNIST 图像
output = model(x)
print(f"输出形状: {output.shape}")  # (32, 10)

完整训练循环

PyTorch 的训练流程是显式的:你自己写训练循环,每一步都清清楚楚。这比 scikit-learn 的 fit() 更底层,但也更灵活、更容易理解深度学习的本质。

PYTHON · 完整 PyTorch 训练循环
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset

# 准备数据(实际项目用真实数据集)
X_train = torch.randn(1000, 20)
y_train = (X_train[:, 0] > 0).long()  # 二分类标签
dataset    = TensorDataset(X_train, y_train)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

# 初始化模型、损失函数、优化器
model     = MLP(20, 64, 2)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# 训练循环
for epoch in range(20):
    model.train()                # 切换到训练模式
    total_loss = 0

    for batch_x, batch_y in dataloader:
        # 1. 清零梯度(上一步留下的)
        optimizer.zero_grad()

        # 2. 前向传播
        logits = model(batch_x)
        loss   = criterion(logits, batch_y)

        # 3. 反向传播(计算梯度)
        loss.backward()

        # 4. 更新参数
        optimizer.step()

        total_loss += loss.item()

    avg_loss = total_loss / len(dataloader)
    if (epoch + 1) % 5 == 0:
        print(f"Epoch [{epoch+1:2d}/20] Loss: {avg_loss:.4f}")

# 推理(评估/预测)
model.eval()
with torch.no_grad():          # 不计算梯度,节省内存
    test_x  = torch.randn(100, 20)
    test_y  = (test_x[:, 0] > 0).long()
    outputs = model(test_x)
    preds   = outputs.argmax(dim=1)
    acc     = (preds == test_y).float().mean()
    print(f"测试准确率: {acc:.4f}")
🤖

四步训练循环记住了吗?

PyTorch 训练的四个固定步骤:①zero_grad() → ②前向传播 → ③loss.backward() → ④optimizer.step()。这个顺序不能错,特别是每次都要先清零梯度,否则梯度会累加。背下这四步,任何神经网络都能训练。

保存与加载模型

PYTHON · 模型保存与加载
# 保存方式1:只保存权重(推荐)
torch.save(model.state_dict(), "model_weights.pt")

# 加载权重
new_model = MLP(20, 64, 2)           # 先创建同结构的模型
new_model.load_state_dict(torch.load("model_weights.pt"))
new_model.eval()

# 保存方式2:保存整个模型(包括结构)
torch.save(model, "full_model.pt")
loaded = torch.load("full_model.pt")

# 保存训练检查点(含优化器状态,方便继续训练)
torch.save({
    "epoch": epoch,
    "model_state_dict": model.state_dict(),
    "optimizer_state_dict": optimizer.state_dict(),
    "loss": avg_loss,
}, "checkpoint.pt")

# 从检查点恢复
checkpoint = torch.load("checkpoint.pt")
model.load_state_dict(checkpoint["model_state_dict"])
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
start_epoch = checkpoint["epoch"] + 1

常见误区与高级技巧

⚠️

忘记 optimizer.zero_grad() 导致梯度累加

PyTorch 的梯度是累加的,不是每次 backward 后自动清零。如果不在每个 batch 前调用 optimizer.zero_grad(),梯度会越来越大,训练会出错。唯一的例外是梯度累积(Gradient Accumulation)——刻意跳过几个 batch 的 zero_grad 来模拟更大的 batch size。

⚠️

忘记 model.eval() 导致测试结果不稳定

Dropout 和 BatchNorm 在训练和推理时行为不同:Dropout 训练时随机丢弃神经元,推理时关闭;BatchNorm 训练时用批次统计量,推理时用运行均值。不切换 model.eval(),每次推理结果会不一样。

PYTHON · 混合精度训练(节省显存)
import torch
from torch.cuda.amp import GradScaler, autocast

# 混合精度训练:用 float16 做前向传播,float32 做参数更新
# 显存节省 ~50%,速度提升 ~2x(在支持 Tensor Core 的 GPU 上)
scaler = GradScaler()   # 防止 float16 下梯度下溢

for batch_x, batch_y in dataloader:
    optimizer.zero_grad()

    # 自动使用 float16 进行前向传播
    with autocast():
        logits = model(batch_x)
        loss   = criterion(logits, batch_y)

    # scaler 自动处理 float16 的梯度缩放
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

# 使用学习率调度器(Scheduler)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=50  # 余弦退火,每50轮完成一个周期
)

for epoch in range(100):
    # ... 训练循环
    scheduler.step()       # 每 epoch 后更新学习率
    print(f"LR: {scheduler.get_last_lr()[0]:.6f}")
💡

本章小结

PyTorch 深度学习核心要素:Tensor(GPU 加速的多维数组)、Autograd(自动微分)、nn.Module(网络定义基类)、DataLoader(批量数据加载)、优化器+损失函数+训练循环。四步训练循环口诀:清零梯度 → 前向传播 → 反向传播 → 更新参数(zero_grad → forward → backward → step)。常见陷阱:忘记 zero_grad(梯度累加)、推理时忘记 eval() 和 no_grad()(Dropout/BN 行为不对,浪费计算)。进阶技术:混合精度训练(节省显存2倍)、学习率调度器(余弦退火等)、梯度裁剪(防止梯度爆炸)。