搜 索

Pytorch从入门到放弃

  • 281阅读
  • 2023年05月20日
  • 0评论
首页 / AI/大数据 / 正文

第一章:入门——一切看起来都很美好

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/cu121

1.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?

这是每个炼丹师的必经之路。常见原因:

  1. 学习率太大:梯度爆炸
  2. 数据问题:输入包含 NaN 或 Inf
  3. 数值不稳定:比如 log(0) 或除以零
  4. 梯度裁剪没做: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 什么时候该"放弃"

其实"放弃"不是真的放弃,而是:

  1. 放弃从零造轮子:用 timmtransformerslightning 等成熟库
  2. 放弃手动调参:用 Optuna、Ray Tune 等自动调参工具
  3. 放弃单打独斗:用分布式训练
  4. 放弃完美主义:先跑通,再优化

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 终极建议

  1. 多看官方文档:PyTorch 的文档写得很好
  2. 多读源码torch.nn 的实现很清晰
  3. 多跑 baseline:先复现论文,再魔改
  4. 多用 printassert:调试神器
  5. 善用 torch.set_printoptions(precision=10):看清数值精度问题

结语

PyTorch 的学习曲线是这样的:

信心
  ^
  |     /\
  |    /  \
  |   /    \          /
  |  /      \        /
  | /        \______/
  |/
  +-----------------------> 时间
  入门   遇坑   放弃   顿悟

所谓"放弃",其实是放弃了幻想,接受了现实,然后在痛苦中成长。

每一个 RuntimeError,都是成长的勋章。
每一次 loss = nan,都是灵魂的洗礼。
每一回 CUDA out of memory,都在提醒你——

该买新显卡了。

评论区
暂无评论
avatar