第一章:入门——一切看起来都很美好
1.1 安装:第一个坑
pip install torch看起来很简单对吧?直到你发现:
- CPU 版本装了,但你有 GPU
- CUDA 版本不对,torch 和 CUDA 版本不兼容
- conda 和 pip 混用,环境彻底乱了
正确姿势:去 pytorch.org 选择对应的安装命令。
# 例如 CUDA 12.1
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu1211.2 第一个张量
import torch
# 创建张量
x = torch.tensor([1, 2, 3])
print(x) # tensor([1, 2, 3])
# 随机张量
y = torch.randn(3, 4) # 3x4 的正态分布随机数
# GPU 加速
if torch.cuda.is_available():
x = x.cuda() # 或者 x.to('cuda')到这里,你会觉得:"就这?这也太简单了吧!"
恭喜你,flag 已立。
1.3 自动求导:PyTorch 的灵魂
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1 # y = x² + 3x + 1
y.backward() # 反向传播
print(x.grad) # tensor([7.]) 因为 dy/dx = 2x + 3 = 7优雅,实在是太优雅了。
第二章:进阶——开始遇到问题
2.1 经典报错大赏
报错 1:维度不匹配
RuntimeError: mat1 and mat2 shapes cannot be multiplied (32x784 and 256x128)翻译:你的矩阵维度不对,自己好好算算。
解决方案:在每一层后面加 print(x.shape),然后一层层排查。
报错 2:CUDA out of memory
RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB翻译:显存不够了,穷鬼。
解决方案:
- 减小 batch_size
- 使用
torch.cuda.empty_cache() - 使用梯度累积
- 使用混合精度训练
- 或者,买张更好的卡
报错 3:梯度消失
# loss 一直不变,或者变成 nan可能原因:
- 学习率太大或太小
- 网络太深,没有用 BatchNorm 或残差连接
- 激活函数选择不当
2.2 为什么我的 loss 是 NaN?
这是每个炼丹师的必经之路。常见原因:
- 学习率太大:梯度爆炸
- 数据问题:输入包含 NaN 或 Inf
- 数值不稳定:比如
log(0)或除以零 - 梯度裁剪没做:RNN/LSTM 的常见问题
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)2.3 .detach() vs .data vs .item()
x = torch.tensor([1.0], requires_grad=True)
# .detach() - 返回新张量,不追踪梯度
y = x.detach()
# .data - 不推荐使用,可能导致梯度计算错误
z = x.data # 危险!
# .item() - 取出 Python 标量
val = x.item() # 1.0记住:用 .detach(),忘掉 .data。
第三章:实战——真正的炼丹开始
3.1 标准训练流程
import torch
import torch.nn as nn
import torch.optim as optim
# 1. 定义模型
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.fc2 = nn.Linear(256, 10)
self.relu = nn.ReLU()
def forward(self, x):
x = x.view(-1, 784) # 展平
x = self.relu(self.fc1(x))
x = self.fc2(x)
return x
# 2. 初始化
model = MyModel().cuda()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
# 3. 训练循环
for epoch in range(num_epochs):
model.train() # 别忘了这个!
for batch_x, batch_y in train_loader:
batch_x, batch_y = batch_x.cuda(), batch_y.cuda()
optimizer.zero_grad() # 清空梯度,非常重要!
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
# 验证
model.eval() # 别忘了这个!
with torch.no_grad(): # 省显存
# 验证代码...3.2 容易忘记的事情
| 忘记的事情 | 后果 |
|---|---|
optimizer.zero_grad() | 梯度累积,训练崩溃 |
model.train() | Dropout 和 BatchNorm 行为异常 |
model.eval() | 同上 |
with torch.no_grad() | 验证时浪费显存 |
.cuda() | 数据在 CPU,模型在 GPU,报错 |
3.3 数据加载的艺术
from torch.utils.data import DataLoader, Dataset
class MyDataset(Dataset):
def __init__(self, data, labels):
self.data = data
self.labels = labels
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx], self.labels[idx]
# DataLoader 参数的讲究
loader = DataLoader(
dataset,
batch_size=32,
shuffle=True, # 训练集要 shuffle
num_workers=4, # 多进程加载,Windows 上可能有坑
pin_memory=True, # 加速 GPU 传输
drop_last=True # 丢弃最后不完整的 batch
)Windows 用户注意:num_workers > 0 可能会报错,需要把代码放在 if __name__ == '__main__': 里面。
第四章:高级技巧——在放弃边缘疯狂试探
4.1 混合精度训练
用 FP16 训练,省显存,速度快:
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for batch_x, batch_y in train_loader:
optimizer.zero_grad()
with autocast(): # 自动混合精度
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()4.2 梯度累积
显存不够?假装 batch_size 很大:
accumulation_steps = 4
for i, (batch_x, batch_y) in enumerate(train_loader):
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
loss = loss / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()4.3 学习率调度
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR
# Cosine Annealing
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# One Cycle(训练 Transformer 常用)
scheduler = OneCycleLR(
optimizer,
max_lr=1e-3,
total_steps=num_epochs * len(train_loader)
)
# 在每个 step 或 epoch 后调用
scheduler.step()4.4 模型保存与加载
# 保存
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
}, 'checkpoint.pth')
# 加载
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])第五章:放弃——或者说,顿悟
5.1 什么时候该"放弃"
其实"放弃"不是真的放弃,而是:
- 放弃从零造轮子:用
timm、transformers、lightning等成熟库 - 放弃手动调参:用 Optuna、Ray Tune 等自动调参工具
- 放弃单打独斗:用分布式训练
- 放弃完美主义:先跑通,再优化
5.2 推荐的高级库
# PyTorch Lightning - 简化训练流程
import pytorch_lightning as pl
class LitModel(pl.LightningModule):
def __init__(self):
super().__init__()
self.model = MyModel()
def training_step(self, batch, batch_idx):
x, y = batch
y_hat = self.model(x)
loss = F.cross_entropy(y_hat, y)
return loss
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=1e-3)
# timm - 预训练视觉模型
import timm
model = timm.create_model('resnet50', pretrained=True, num_classes=10)
# transformers - 预训练 NLP 模型
from transformers import AutoModel
model = AutoModel.from_pretrained('bert-base-chinese')5.3 终极建议
- 多看官方文档:PyTorch 的文档写得很好
- 多读源码:
torch.nn的实现很清晰 - 多跑 baseline:先复现论文,再魔改
- 多用
print和assert:调试神器 - 善用
torch.set_printoptions(precision=10):看清数值精度问题
结语
PyTorch 的学习曲线是这样的:
信心
^
| /\
| / \
| / \ /
| / \ /
| / \______/
|/
+-----------------------> 时间
入门 遇坑 放弃 顿悟所谓"放弃",其实是放弃了幻想,接受了现实,然后在痛苦中成长。
每一个 RuntimeError,都是成长的勋章。
每一次 loss = nan,都是灵魂的洗礼。
每一回 CUDA out of memory,都在提醒你——
该买新显卡了。