CHAPTER 10

深度学习入门(PyTorch)

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

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