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