文档
PyTorch 入门教程 —— 从线性回归到神经网络
本章目标
- 理解动态计算图与自动微分
- 手写梯度下降 → 使用 PyTorch 的 optimizer
- 掌握 nn.Module 的模块化设计思想
- 理解损失函数与优化器的选择
1. 动态计算图:为什么 PyTorch 如此灵活?
静态图(TensorFlow 1.x): 先定义完整图 → 编译 → 运行
# TF 1.x 风格(不再推荐)
x = tf.placeholder(tf.float32, shape=[None, 784])
W = tf.Variable(tf.zeros([784, 10]))
y = tf.matmul(x, W) # 这时还没执行,只是在建图
# ... session.run() 才真正执行
动态图(PyTorch): 每一步操作立即执行,图随代码走
x = torch.randn(10, 784)
W = torch.randn(784, 10, requires_grad=True)
y = x @ W # 立即计算!可以 print、debug
loss = y.sum()
loss.backward() # 自动计算 W.grad
优势: 你可以在 forward 中使用 if/for/while,这对应变长序列、条件分支等场景极为关键。
2. 从零手写梯度下降 → 使用 PyTorch
阶段一:纯 NumPy 手动求导
import numpy as np
# 数据:y = 3x + 2 + noise
np.random.seed(42)
X = np.random.randn(100, 1)
y = 3 * X + 2 + np.random.randn(100, 1) * 0.3
w, b = np.random.randn(1), np.random.randn(1)
lr = 0.01
for epoch in range(1000):
y_pred = X * w + b
loss = ((y_pred - y) ** 2).mean()
# 手动计算梯度(容易出错!)
dw = (2 / len(y)) * (X * (y_pred - y)).sum()
db = (2 / len(y)) * (y_pred - y).sum()
w -= lr * dw
b -= lr * db
阶段二:PyTorch autograd 自动求导
import torch
X_t = torch.tensor(X, dtype=torch.float32)
y_t = torch.tensor(y, dtype=torch.float32)
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
for epoch in range(1000):
y_pred = X_t * w + b
loss = ((y_pred - y_t) ** 2).mean()
loss.backward() # 自动计算 w.grad, b.grad
with torch.no_grad(): # 更新时不需要梯度
w -= lr * w.grad
b -= lr * b.grad
w.grad.zero_() # 清零,否则会累积
b.grad.zero_()
阶段三:PyTorch optimizer + nn.Module
model = nn.Linear(1, 1) # 一行定义 w,b
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
for epoch in range(1000):
y_pred = model(X_t)
loss = criterion(y_pred, y_t)
optimizer.zero_grad() # 一行清零所有梯度
loss.backward()
optimizer.step() # 一行更新所有参数
3. nn.Module 模块化设计
class MLP(nn.Module):
"""多层感知机 —— 就像搭乐高"""
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
# 定义"层"(子模块)
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.bn = nn.BatchNorm1d(hidden_dim)
self.dropout = nn.Dropout(0.3)
def forward(self, x):
# 定义"连接关系"
x = F.relu(self.bn(self.fc1(x)))
x = F.relu(self.bn(self.fc2(x)))
x = self.dropout(x)
x = self.fc3(x)
return x
# PyTorch 自动追踪所有子模块
model = MLP(784, 256, 10)
print(list(model.children())) # 遍历子模块
print(sum(p.numel() for p in model.parameters())) # 总参数量
# 参数可整体迁移
model = model.to("cuda")
model = model.float() # 转 float32
model = model.half() # 转 float16(混合精度)
4. 损失函数速查
| 任务 | 损失函数 | PyTorch |
|---|---|---|
| 回归 | 均方误差 | nn.MSELoss() |
| 回归 | 平均绝对误差 | nn.L1Loss() |
| 二分类 | 二元交叉熵 | nn.BCEWithLogitsLoss() |
| 多分类 | 交叉熵(内置 softmax) | nn.CrossEntropyLoss() |
| 不平衡分类 | Focal Loss | 自定义 |
| 相似度学习 | Triplet Margin Loss | nn.TripletMarginLoss() |
关键:CrossEntropyLoss 自动含 softmax!
# ❌ 错误 —— 双重 softmax
output = F.softmax(logits, dim=1)
loss = nn.CrossEntropyLoss()(output, labels)
# ✅ 正确 —— CrossEntropyLoss 内部已含 LogSoftmax
loss = nn.CrossEntropyLoss()(logits, labels)
5. 优化器对比
| 优化器 | 特性 | 适用场景 |
|---|---|---|
SGD |
经典,需手动调 lr | 需要强泛化能力时 |
SGD + Momentum |
加速 + 抗震荡 | CV 任务常用 |
Adam |
自适应学习率 | 默认首选,NLP 常用 |
AdamW |
Adam + 解耦权重衰减 | 大模型/Transformer |
RMSprop |
适合非稳态目标 | RNN/强化学习 |
6. 实用技巧:梯度裁剪
# 防止梯度爆炸(RNN/Transformer 训练必备)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 完整的训练步骤
for batch in dataloader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
思考题
- 动态计算图相比静态图,性能上有损失吗?PyTorch 2.0 的
torch.compile如何解决? optimizer.zero_grad()如果忘记调用会怎样?model.train()和model.eval()具体影响了哪些层的行为?- 为什么
CrossEntropyLoss的输入不能经过 softmax?