PyTorch完全指南

PyTorch 是由 Meta AI 开发的深度学习框架,以动态计算图(Define-by-Run)为核心,支持 GPU 加速,广泛用于研究和生产环境。 验证安装: Tensor 是 PyTorch 的核心数据结构,类似 NumPy ndarray,但可在 GPU 上运算并支持自动微分。 torch.tensor 参数: PyTorch 通过动态计算图(DAG)自动追踪运算并反向传播梯度。 backward 参数: torch.nn.Module 是所有神经网络模块的基类。 全连接层(线性变换):y = xW^T + b 参数: 2D 卷积层。 参数:

分享

官方文档:https://pytorch.org/docs/stable/index.html
官方教程:https://pytorch.org/tutorials/

PyTorch 完全指南

PyTorch 是由 Meta AI 开发的深度学习框架,以动态计算图(Define-by-Run)为核心,支持 GPU 加速,广泛用于研究和生产环境。


安装

# 查看 CUDA 版本后选择对应命令:https://pytorch.org/get-started/locally/
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# CPU-only
pip install torch torchvision torchaudio

验证安装:

import torch
print(torch.__version__)
print(torch.cuda.is_available())    # GPU 是否可用
print(torch.backends.mps.is_available())  # Apple Silicon GPU

一、Tensor 张量

Tensor 是 PyTorch 的核心数据结构,类似 NumPy ndarray,但可在 GPU 上运算并支持自动微分。

1.1 创建 Tensor

函数 说明
torch.tensor(data) 从 Python list / NumPy array 创建,复制数据
torch.as_tensor(data) 从数组创建,尽可能共享内存(零拷贝)
torch.zeros(size) 全零张量
torch.ones(size) 全一张量
torch.full(size, fill_value) 填充常数
torch.eye(n) 单位矩阵
torch.rand(size) [0,1) 均匀分布
torch.randn(size) 标准正态分布
torch.randint(low, high, size) 随机整数
torch.arange(start, end, step) 等差序列
torch.linspace(start, end, steps) 等间隔序列
torch.empty(size) 未初始化张量

torch.tensor 参数:

参数 类型 默认值 说明
data array-like 必填 输入数据
dtype torch.dtype None(推断) 数据类型
device str/torch.device None(CPU) 存储设备
requires_grad bool False 是否参与梯度计算
import torch

# 从列表创建
t = torch.tensor([[1.0, 2.0], [3.0, 4.0]])

# 指定类型和设备
t = torch.tensor([1, 2, 3], dtype=torch.float32, device="cuda")

# 与 NumPy 互转(CPU tensor 共享内存)
import numpy as np
arr = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(arr)      # 共享内存
arr2 = t.numpy()               # 共享内存

# zeros_like / ones_like(继承形状和设备)
x = torch.rand(3, 4, device="cuda")
z = torch.zeros_like(x)

1.2 Tensor 属性

t = torch.randn(2, 3, 4)

t.shape        # torch.Size([2, 3, 4])
t.size()       # 同上,可传维度索引 t.size(0) -> 2
t.ndim         # 3
t.numel()      # 24(元素总数)
t.dtype        # torch.float32
t.device       # device(type='cpu')
t.requires_grad  # False
t.is_cuda      # False
t.T            # 转置(2D tensor 的语法糖)

1.3 数据类型

dtype 说明
torch.float32 / torch.float 单精度浮点(默认)
torch.float64 / torch.double 双精度浮点
torch.float16 / torch.half 半精度(GPU 混合精度)
torch.bfloat16 Brain Float(Ampere 架构推荐)
torch.int32 / torch.int 32 位整数
torch.int64 / torch.long 64 位整数(索引默认类型)
torch.bool 布尔型
t = t.float()     # 转 float32
t = t.long()      # 转 int64
t = t.to(torch.bfloat16)

1.4 设备管理

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

t = torch.randn(3, 3).to(device)
t = t.cuda()     # 移至 GPU 0
t = t.cpu()      # 移回 CPU

# 指定 GPU 编号
t = t.to("cuda:1")

1.5 基本运算

a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])

# 逐元素运算
a + b          # 或 torch.add(a, b)
a - b
a * b          # 逐元素乘
a / b
a ** 2         # 幂运算
torch.sqrt(a)
torch.abs(a)
torch.exp(a)
torch.log(a)   # 自然对数

# 矩阵乘法
A = torch.randn(2, 3)
B = torch.randn(3, 4)
C = A @ B                # 等价于 torch.matmul(A, B)
C = torch.mm(A, B)       # 仅限 2D

# 批量矩阵乘
A = torch.randn(8, 2, 3)
B = torch.randn(8, 3, 4)
C = torch.bmm(A, B)      # (8, 2, 4)

# 聚合运算
t.sum()
t.mean()
t.max()
t.min()
t.std()
t.var()
t.sum(dim=0)             # 沿维度聚合,dim 也可写 axis
t.max(dim=1)             # 返回 (values, indices)
t.max(dim=1).values

1.6 形状操作

方法 说明
t.view(*shape) 重塑,要求内存连续
t.reshape(*shape) 重塑,自动处理非连续内存
t.squeeze(dim) 移除大小为 1 的维度
t.unsqueeze(dim) 在指定位置插入维度 1
t.permute(*dims) 维度重排
t.transpose(dim0, dim1) 交换两个维度
t.contiguous() 确保内存连续
t.flatten(start, end) 展平指定维度范围
torch.cat(tensors, dim) 沿已有维度拼接
torch.stack(tensors, dim) 沿新维度堆叠
torch.split(t, size, dim) 分割
torch.chunk(t, chunks, dim) 均匀分块
t = torch.randn(2, 3, 4)

t.view(6, 4)          # (6, 4)
t.reshape(2, -1)      # -1 自动推断,(2, 12)
t.permute(2, 0, 1)    # (4, 2, 3)

# 添加/删除 batch 维度
x = torch.randn(3, 4)
x.unsqueeze(0)        # (1, 3, 4)
x.unsqueeze(0).squeeze(0)  # 恢复 (3, 4)

# 拼接
a = torch.zeros(2, 3)
b = torch.ones(2, 3)
torch.cat([a, b], dim=0)   # (4, 3)
torch.stack([a, b], dim=0) # (2, 2, 3)

1.7 索引与切片

t = torch.randn(4, 5, 6)

t[0]           # (5, 6)
t[0, 1]        # (6,)
t[0, 1, 2]     # scalar tensor
t[:, 1:3, :]   # 切片
t[..., 0]      # 省略号,等价于 t[:, :, 0]

# 高级索引
idx = torch.tensor([0, 2])
t[idx]         # 选取第 0 行和第 2 行

# 布尔索引
mask = t > 0
t[mask]        # 返回所有正数的一维张量

# gather(按索引收集)
# torch.gather(input, dim, index)

二、Autograd 自动微分

PyTorch 通过动态计算图(DAG)自动追踪运算并反向传播梯度。

2.1 requires_grad

x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1

y.backward()      # 反向传播
print(x.grad)     # dy/dx = 2x + 3 = 7

2.2 常用 API

操作 说明
tensor.backward(gradient=None) 反向传播,标量 tensor 可不传参数
tensor.grad 叶子节点的梯度
tensor.grad_fn 非叶子节点的梯度函数
tensor.is_leaf 是否为叶子节点
tensor.detach() 返回一个不参与梯度计算的视图
tensor.detach_() 原地分离
tensor.retain_grad() 保留非叶节点的梯度(默认丢弃)

backward 参数:

参数 类型 默认值 说明
gradient Tensor None 非标量 tensor 需传入与自身同形状的权重
retain_graph bool False 是否保留计算图(多次 backward 时设为 True)
create_graph bool False 是否构建高阶导数计算图
# 非标量的 backward 需传 gradient
x = torch.randn(3, requires_grad=True)
y = x ** 2
y.backward(torch.ones_like(y))   # 等价于 y.sum().backward()
print(x.grad)   # 2 * x

# 多次 backward(retain_graph=True)
y.backward(torch.ones_like(y), retain_graph=True)

2.3 禁用梯度追踪

# 推断/评估时禁用以节省内存和加速
with torch.no_grad():
    y = model(x)

# 函数装饰器
@torch.no_grad()
def evaluate(model, x):
    return model(x)

# 仅分离结果
y = model(x).detach()

2.4 梯度清零

optimizer.zero_grad()   # 每次反向传播前必须调用
# 或手动清零
for p in model.parameters():
    if p.grad is not None:
        p.grad.zero_()

三、nn.Module 神经网络

torch.nn.Module 是所有神经网络模块的基类。

3.1 定义模型

import torch.nn as nn
import torch.nn.functional as F

class MyModel(nn.Module):
    def __init__(self, in_features: int, out_features: int):
        super().__init__()
        self.fc1 = nn.Linear(in_features, 128)
        self.fc2 = nn.Linear(128, out_features)
        self.dropout = nn.Dropout(p=0.5)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        return self.fc2(x)

model = MyModel(784, 10)

3.2 模型信息

print(model)                        # 打印模型结构
sum(p.numel() for p in model.parameters())           # 总参数量
sum(p.numel() for p in model.parameters() if p.requires_grad)  # 可训练参数量

# 递归遍历子模块
for name, module in model.named_modules():
    print(name, module)

# 遍历参数
for name, param in model.named_parameters():
    print(name, param.shape)

3.3 训练/评估模式

model.train()    # 启用 Dropout、BatchNorm 的训练行为
model.eval()     # 禁用 Dropout,BatchNorm 使用运行统计

3.4 Sequential 容器

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(256, 10),
)

# 有序字典版本
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
    ("fc1", nn.Linear(784, 256)),
    ("relu", nn.ReLU()),
    ("fc2", nn.Linear(256, 10)),
]))
model.fc1   # 按名称访问

3.5 ModuleList 和 ModuleDict

# ModuleList:列表,参数会被自动注册
self.layers = nn.ModuleList([nn.Linear(128, 128) for _ in range(6)])

# ModuleDict:字典
self.heads = nn.ModuleDict({
    "cls": nn.Linear(128, 10),
    "reg": nn.Linear(128, 4),
})

四、常用层

4.1 nn.Linear

全连接层(线性变换):y = xW^T + b

参数:

参数 类型 默认值 说明
in_features int 必填 输入特征数
out_features int 必填 输出特征数
bias bool True 是否添加偏置
fc = nn.Linear(128, 64)
x = torch.randn(32, 128)  # (batch, in_features)
y = fc(x)                 # (32, 64)

4.2 nn.Conv2d

2D 卷积层。

参数:

参数 类型 默认值 说明
in_channels int 必填 输入通道数
out_channels int 必填 输出通道数(滤波器数)
kernel_size int/tuple 必填 卷积核大小
stride int/tuple 1 步长
padding int/tuple/str 0 填充,"same" 保持尺寸
dilation int/tuple 1 膨胀率
groups int 1 分组卷积,groups=in_channels 即深度可分离
bias bool True 偏置
padding_mode str "zeros" 填充模式
conv = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
x = torch.randn(8, 3, 224, 224)   # (N, C, H, W)
y = conv(x)                        # (8, 64, 224, 224)

# 输出尺寸公式:floor((H + 2*padding - dilation*(kernel-1) - 1) / stride + 1)

4.3 nn.BatchNorm2d

批归一化,一般放在 Conv 之后、激活函数之前。

参数:

参数 类型 默认值 说明
num_features int 必填 通道数(与 Conv out_channels 相同)
eps float 1e-5 数值稳定项
momentum float 0.1 运行均值/方差的更新动量
affine bool True 是否有可学习的 scale/bias(gamma/beta)
track_running_stats bool True 是否追踪运行统计
block = nn.Sequential(
    nn.Conv2d(64, 64, 3, padding=1),
    nn.BatchNorm2d(64),
    nn.ReLU(inplace=True),
)

其他归一化:nn.LayerNorm(NLP 常用)、nn.GroupNormnn.InstanceNorm2d

4.4 nn.Dropout / nn.Dropout2d

参数 类型 默认值 说明
p float 0.5 随机置零概率
inplace bool False 是否原地操作

nn.Dropout2d 以通道为单位置零(适用于卷积特征图)。

4.5 nn.Embedding

将整数索引映射到稠密向量(词嵌入)。

参数:

参数 类型 默认值 说明
num_embeddings int 必填 词汇表大小
embedding_dim int 必填 嵌入维度
padding_idx int None 该索引的嵌入向量恒为 0
max_norm float None 嵌入向量最大范数
scale_grad_by_freq bool False 按词频缩放梯度
sparse bool False 稀疏梯度更新(大词汇表加速)
emb = nn.Embedding(10000, 256, padding_idx=0)
x = torch.randint(0, 10000, (32, 50))   # (batch, seq_len)
y = emb(x)                              # (32, 50, 256)

4.6 nn.LSTM

参数:

参数 类型 默认值 说明
input_size int 必填 输入特征数
hidden_size int 必填 隐状态维度
num_layers int 1 堆叠层数
bias bool True 偏置
batch_first bool False True 时输入为 (batch, seq, feature)
dropout float 0 层间 dropout(num_layers > 1 时有效)
bidirectional bool False 双向
proj_size int 0 输出投影维度
lstm = nn.LSTM(input_size=256, hidden_size=512, num_layers=2,
               batch_first=True, bidirectional=True)

x = torch.randn(32, 50, 256)     # (batch, seq, feature)
output, (h_n, c_n) = lstm(x)
# output: (32, 50, 1024)  双向 hidden_size * 2
# h_n:    (4, 32, 512)    num_layers * num_directions, batch, hidden

4.7 nn.MultiheadAttention

参数:

参数 类型 默认值 说明
embed_dim int 必填 总嵌入维度
num_heads int 必填 头数(embed_dim 须被整除)
dropout float 0.0 注意力权重 dropout
bias bool True Q/K/V 投影偏置
batch_first bool False True 时输入为 (batch, seq, feat)
attn = nn.MultiheadAttention(embed_dim=512, num_heads=8, batch_first=True)
q = k = v = torch.randn(32, 50, 512)
out, weights = attn(q, k, v)    # out: (32, 50, 512)

五、激活函数

函数 nn 模块 F 函数 说明
ReLU nn.ReLU F.relu max(0, x),最常用
GELU nn.GELU F.gelu Transformer 常用
SiLU/Swish nn.SiLU F.silu x * sigmoid(x)
Sigmoid nn.Sigmoid F.sigmoid 二分类输出
Tanh nn.Tanh F.tanh RNN 隐状态
Softmax nn.Softmax F.softmax 多分类输出(dim 必须指定)
LogSoftmax nn.LogSoftmax F.log_softmax 配合 NLLLoss
LeakyReLU nn.LeakyReLU F.leaky_relu 负半轴斜率 negative_slope(默认 0.01)
# nn 模块作为层(有状态的放 Sequential 中)
act = nn.ReLU(inplace=True)   # inplace=True 节省内存,不能用在需要保留输入的场合

# F 函数用在 forward 中
def forward(self, x):
    return F.gelu(self.fc(x))

六、损失函数

6.1 常用损失

损失函数 场景 说明
nn.CrossEntropyLoss 多分类 内置 Softmax,输入为 logits
nn.BCEWithLogitsLoss 二分类/多标签 内置 Sigmoid,输入为 logits
nn.MSELoss 回归 均方误差
nn.L1Loss 回归 平均绝对误差,对异常值鲁棒
nn.SmoothL1Loss 目标检测回归 Huber Loss
nn.NLLLoss 配合 LogSoftmax 负对数似然
nn.KLDivLoss 分布对齐 KL 散度,输入须为 log 概率

6.2 CrossEntropyLoss 参数

参数 类型 默认值 说明
weight Tensor None 各类别权重(处理类别不平衡)
ignore_index int -100 忽略某个类别标签(如 padding)
reduction str "mean" "none" / "sum" / "mean"
label_smoothing float 0.0 标签平滑(正则化,推荐 0.1)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1, ignore_index=0)

# 输入:logits (N, C) 或 (N, C, H, W),目标:类别索引 (N,) 或 (N, H, W)
logits = torch.randn(32, 10)
targets = torch.randint(0, 10, (32,))
loss = criterion(logits, targets)

七、优化器

7.1 常用优化器

优化器 说明
optim.SGD 随机梯度下降,支持 momentum 和 weight_decay
optim.Adam 自适应学习率,一般首选
optim.AdamW Adam + 正确的 weight decay(推荐替代 Adam)
optim.RMSprop 适合 RNN
optim.Adagrad 稀疏梯度任务

7.2 SGD 参数

参数 类型 默认值 说明
params iterable 必填 模型参数(model.parameters()
lr float 必填 学习率
momentum float 0 动量系数
weight_decay float 0 L2 正则化系数
nesterov bool False Nesterov 动量
dampening float 0 动量抑制

7.3 AdamW 参数

参数 类型 默认值 说明
params iterable 必填 模型参数
lr float 1e-3 学习率
betas tuple (0.9, 0.999) 一阶/二阶矩衰减系数
eps float 1e-8 数值稳定项
weight_decay float 1e-2 权重衰减(AdamW 的 decoupled decay)
amsgrad bool False 使用 AMSGrad 变体
import torch.optim as optim

# 全局参数
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)

# 分组参数(不同层不同 lr)
optimizer = optim.AdamW([
    {"params": model.backbone.parameters(), "lr": 1e-4},
    {"params": model.head.parameters(), "lr": 1e-3},
], weight_decay=1e-2)

# 训练循环中
optimizer.zero_grad()
loss.backward()
optimizer.step()

八、学习率调度器

from torch.optim import lr_scheduler
调度器 说明
StepLR(optimizer, step_size, gamma) step_size 个 epoch 乘以 gamma
MultiStepLR(optimizer, milestones, gamma) 在指定 epoch 降低 lr
ExponentialLR(optimizer, gamma) 每 epoch 乘以 gamma
CosineAnnealingLR(optimizer, T_max) 余弦退火,T_max 为半周期
CosineAnnealingWarmRestarts(optimizer, T_0) 带重启的余弦退火
ReduceLROnPlateau(optimizer, mode, factor, patience) 验证指标停滞时降低 lr
OneCycleLR(optimizer, max_lr, total_steps) 1Cycle 策略(训练超级快)
LinearLR(optimizer, start_factor, end_factor, total_iters) 线性变化
SequentialLR(optimizer, schedulers, milestones) 串联多个调度器
scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)

for epoch in range(100):
    train(...)
    scheduler.step()           # epoch-based 调度器每 epoch 调用一次
    print(scheduler.get_last_lr())

# OneCycleLR 在每个 batch 后调用
scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=1e-2,
                                     total_steps=len(train_loader) * epochs)
for batch in train_loader:
    ...
    optimizer.step()
    scheduler.step()

# Warmup + Cosine:用 SequentialLR
warmup = lr_scheduler.LinearLR(optimizer, start_factor=0.01, total_iters=5)
cosine = lr_scheduler.CosineAnnealingLR(optimizer, T_max=95)
scheduler = lr_scheduler.SequentialLR(optimizer, [warmup, cosine], milestones=[5])

九、数据处理

9.1 Dataset

from torch.utils.data import Dataset, DataLoader

class ImageDataset(Dataset):
    def __init__(self, paths, labels, transform=None):
        self.paths = paths
        self.labels = labels
        self.transform = transform

    def __len__(self):
        return len(self.paths)

    def __getitem__(self, idx):
        img = Image.open(self.paths[idx]).convert("RGB")
        if self.transform:
            img = self.transform(img)
        return img, self.labels[idx]

PyTorch 内置 Dataset:torchvision.datasets.ImageFoldertorchvision.datasets.CIFAR10torchvision.datasets.MNIST 等。

9.2 DataLoader 参数

参数 类型 默认值 说明
dataset Dataset 必填 数据集
batch_size int 1 批次大小
shuffle bool False 是否打乱(训练集设 True)
num_workers int 0 数据加载并行进程数
pin_memory bool False 将数据固定到内存(GPU 训练时加速)
drop_last bool False 丢弃最后一个不完整批次
collate_fn callable None 自定义批次组装函数
sampler Sampler None 自定义采样策略(与 shuffle 互斥)
prefetch_factor int 2 每个 worker 预取的批次数
persistent_workers bool False 保持 worker 存活(多 epoch 加速)
loader = DataLoader(
    dataset,
    batch_size=32,
    shuffle=True,
    num_workers=4,
    pin_memory=True,
    persistent_workers=True,
)

9.3 torchvision.transforms

from torchvision import transforms

train_transform = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4),
    transforms.ToTensor(),                    # PIL/ndarray -> Tensor,[0,255]->[0,1]
    transforms.Normalize(                     # 减均值除标准差
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    ),
])

val_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

v2 API(推荐,支持 Tensor 输入和更多数据增强):

from torchvision.transforms import v2

transform = v2.Compose([
    v2.RandomResizedCrop(224, antialias=True),
    v2.RandomHorizontalFlip(),
    v2.ToDtype(torch.float32, scale=True),
    v2.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

十、完整训练流程

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.optim import AdamW
from torch.optim.lr_scheduler import CosineAnnealingLR

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = MyModel().to(device)
optimizer = AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs)
criterion = nn.CrossEntropyLoss()

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True,
                          num_workers=4, pin_memory=True)
val_loader = DataLoader(val_dataset, batch_size=128, shuffle=False, num_workers=4)


def train_epoch(model, loader, optimizer, criterion, device):
    model.train()
    total_loss = 0.0
    correct = 0

    for images, labels in loader:
        images, labels = images.to(device, non_blocking=True), labels.to(device, non_blocking=True)

        optimizer.zero_grad()
        logits = model(images)
        loss = criterion(logits, labels)
        loss.backward()

        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 梯度裁剪
        optimizer.step()

        total_loss += loss.item() * images.size(0)
        correct += (logits.argmax(1) == labels).sum().item()

    return total_loss / len(loader.dataset), correct / len(loader.dataset)


@torch.no_grad()
def evaluate(model, loader, criterion, device):
    model.eval()
    total_loss = 0.0
    correct = 0

    for images, labels in loader:
        images, labels = images.to(device), labels.to(device)
        logits = model(images)
        loss = criterion(logits, labels)
        total_loss += loss.item() * images.size(0)
        correct += (logits.argmax(1) == labels).sum().item()

    return total_loss / len(loader.dataset), correct / len(loader.dataset)


best_acc = 0.0
for epoch in range(num_epochs):
    train_loss, train_acc = train_epoch(model, train_loader, optimizer, criterion, device)
    val_loss, val_acc = evaluate(model, val_loader, criterion, device)
    scheduler.step()

    print(f"Epoch {epoch+1}: train_loss={train_loss:.4f} train_acc={train_acc:.4f} "
          f"val_loss={val_loss:.4f} val_acc={val_acc:.4f}")

    if val_acc > best_acc:
        best_acc = val_acc
        torch.save(model.state_dict(), "best_model.pth")

十一、模型保存与加载

11.1 保存/加载 state_dict(推荐)

# 保存
torch.save(model.state_dict(), "model.pth")

# 加载
model = MyModel()
model.load_state_dict(torch.load("model.pth", map_location="cpu"))
model.eval()

map_location="cpu" 将 GPU 上保存的模型加载到 CPU,避免跨设备问题。

11.2 保存完整检查点

checkpoint = {
    "epoch": epoch,
    "model": model.state_dict(),
    "optimizer": optimizer.state_dict(),
    "scheduler": scheduler.state_dict(),
    "best_acc": best_acc,
}
torch.save(checkpoint, "checkpoint.pth")

# 恢复
ckpt = torch.load("checkpoint.pth", map_location=device)
model.load_state_dict(ckpt["model"])
optimizer.load_state_dict(ckpt["optimizer"])
scheduler.load_state_dict(ckpt["scheduler"])
start_epoch = ckpt["epoch"] + 1

11.3 TorchScript 导出

scripted = torch.jit.script(model)
scripted.save("model_scripted.pt")
loaded = torch.jit.load("model_scripted.pt")

11.4 ONNX 导出

dummy_input = torch.randn(1, 3, 224, 224).to(device)
torch.onnx.export(
    model, dummy_input, "model.onnx",
    input_names=["input"], output_names=["output"],
    dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}},
    opset_version=17,
)

十二、混合精度训练(AMP)

混合精度(FP16/BF16)在不损失精度的前提下降低显存、加速训练。

from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

for images, labels in train_loader:
    images, labels = images.to(device), labels.to(device)

    optimizer.zero_grad()
    with autocast():              # 自动选择 FP16 操作
        logits = model(images)
        loss = criterion(logits, labels)

    scaler.scale(loss).backward()
    scaler.unscale_(optimizer)
    nn.utils.clip_grad_norm_(model.parameters(), 1.0)
    scaler.step(optimizer)
    scaler.update()

BF16(Ampere 及以上架构更稳定):

with autocast(dtype=torch.bfloat16):
    ...

十三、迁移学习

import torchvision.models as models

# 加载预训练模型
backbone = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)

# 冻结主干参数
for param in backbone.parameters():
    param.requires_grad = False

# 替换分类头
backbone.fc = nn.Linear(backbone.fc.in_features, num_classes)

# 只有分类头参与训练
optimizer = AdamW(backbone.fc.parameters(), lr=1e-3)

# 微调:解冻后使用更小学习率
for param in backbone.parameters():
    param.requires_grad = True
optimizer = AdamW([
    {"params": backbone.layer4.parameters(), "lr": 1e-4},
    {"params": backbone.fc.parameters(), "lr": 1e-3},
])

十四、实用工具

14.1 torch.nn.functional

F 函数是无状态的纯函数,适合在 forward 中调用:

import torch.nn.functional as F

F.relu(x)
F.gelu(x)
F.softmax(x, dim=-1)
F.log_softmax(x, dim=-1)
F.cross_entropy(logits, targets)
F.binary_cross_entropy_with_logits(logits, targets)
F.mse_loss(pred, target)
F.dropout(x, p=0.5, training=self.training)   # 注意传 training 参数
F.pad(x, pad=(1, 1, 1, 1))     # (左, 右, 上, 下)
F.interpolate(x, size=(H, W), mode="bilinear", align_corners=False)
F.normalize(x, p=2, dim=1)     # L2 归一化

14.2 梯度裁剪

nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)   # L2 范数裁剪
nn.utils.clip_grad_value_(model.parameters(), clip_value=0.5) # 按值裁剪

14.3 随机种子固定

def set_seed(seed: int = 42):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    import numpy as np, random
    np.random.seed(seed)
    random.seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

14.4 torch.compile(PyTorch 2.0+)

model = torch.compile(model)   # 图编译,自动优化,首次运行有编译开销

14.5 profiling

from torch.profiler import profile, record_function, ProfilerActivity

with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
             record_shapes=True) as prof:
    with record_function("model_inference"):
        model(x)

print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))

十五、最佳实践

  • 设备管理:用 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 统一管理,所有 tensor 和模型调用 .to(device),DataLoader 设置 pin_memory=True 后在训练循环中用 non_blocking=True 传输。
  • 梯度清零optimizer.zero_grad(set_to_none=True)zero_grad() 更高效(直接释放梯度内存)。
  • model.eval() + torch.no_grad():评估和推断时两者缺一不可,eval() 改变 BN/Dropout 行为,no_grad() 节省内存。
  • 避免 .item() 在循环内频繁调用:每次 .item() 会同步 CPU/GPU,应累积后再统计。
  • 使用 persistent_workers=True:多 epoch 训练时避免每 epoch 重建 worker 进程。
  • AdamW 替代 Adam:Adam 的 L2 正则化实现有误(混入自适应学习率),AdamW 是修复版。
  • 权重初始化:PyTorch 对大多数层有合理默认初始化,自定义时用 nn.init.kaiming_normal_(ReLU)或 nn.init.xavier_uniform_(Tanh/Sigmoid)。

十六、常见陷阱

忘记调用 optimizer.zero_grad()

# 错误:梯度累积,结果不对
for batch in loader:
    loss = criterion(model(x), y)
    loss.backward()
    optimizer.step()

# 正确
for batch in loader:
    optimizer.zero_grad()
    loss = criterion(model(x), y)
    loss.backward()
    optimizer.step()

评估时忘记 model.eval()

# 错误:BN 和 Dropout 处于训练模式
with torch.no_grad():
    preds = model(val_x)

# 正确
model.eval()
with torch.no_grad():
    preds = model(val_x)
model.train()   # 评估结束后恢复训练模式

inplace 操作破坏计算图

# 错误:inplace 操作会破坏 autograd
x = x.relu_()   # 如果 x 是中间变量

# 正确:非 inplace
x = F.relu(x)

# ReLU(inplace=True) 在 Sequential 中通常安全,但用于 residual 连接时须谨慎

CPU/GPU tensor 混合运算

# 错误:a 在 GPU,b 在 CPU
loss = a + b

# 正确
b = b.to(a.device)

DataLoader num_workers 与 Windows 的问题

在 Windows 上多进程 DataLoader 需要在 if __name__ == "__main__": 保护下运行,否则会导致无限 fork。

# Windows 上
if __name__ == "__main__":
    loader = DataLoader(dataset, num_workers=4)
    for batch in loader:
        ...

.item() 阻断 GPU 流水线

# 低效:每个 batch 都强制 GPU/CPU 同步
for batch in loader:
    loss = criterion(model(x), y)
    running_loss += loss.item()   # 同步点

# 更好:累积 tensor,最后统一转换
losses = []
for batch in loader:
    loss = criterion(model(x), y)
    losses.append(loss.detach())
total_loss = torch.stack(losses).mean().item()

CrossEntropyLoss 输入不是 logits

# 错误:对 softmax 输出再用 CrossEntropyLoss(双重 softmax)
probs = F.softmax(logits, dim=-1)
loss = F.cross_entropy(probs, targets)

# 正确:直接传 logits
loss = F.cross_entropy(logits, targets)

最佳实践

训练循环统一调用 optimizer.zero_grad(set_to_none=True)set_to_none=True(PyTorch 2.0+ 默认)将梯度置为 None 而非全零,节省内存且在下一次 backward 时直接分配新张量,比 zero_grad() 更高效。

torch.compile() 加速模型(PyTorch 2.0+):一行代码即可将模型编译为优化的内核,多数场景有 10–30% 的训练吞吐提升,对推理提升更明显。首次调用有编译开销,适合长时间训练任务。

model = MyModel().to(device)
model = torch.compile(model)   # 透明包装,接口不变

数据加载器用 pin_memory=True + num_workerspin_memory=True 让数据预先锁定在内存,GPU 复制更快;num_workers 设为 CPU 核数的一半(通常 4–8),避免 GIL 瓶颈。Windows 下 num_workers 必须在 if __name__ == '__main__' 保护下使用。

torch.no_grad() 包裹推理代码:推理阶段不需要计算梯度,with torch.no_grad() 禁用自动微分图构建,降低内存占用并提速约 30%;评估时还需额外调用 model.eval() 关闭 Dropout 和 BatchNorm 的训练行为。

模型保存用 state_dict 而非整个模型torch.save(model.state_dict(), path) 只保存权重,与模型类定义解耦,迁移和版本管理更灵活;加载时用 model.load_state_dict(torch.load(path, weights_only=True))weights_only=True 避免反序列化任意代码。


常见陷阱

陷阱:忘记调用 optimizer.zero_grad() 导致梯度累积

现象: 损失异常偏高,训练不收敛,或某些步骤梯度爆炸。
原因: PyTorch 默认将梯度累加到 .grad 属性,若每步训练前不清零,梯度会在多个批次间叠加。
解决: 在每次 loss.backward() 之前调用 optimizer.zero_grad();仅在需要梯度累积(模拟大批次)时故意跳过清零,但须明确计数 N 步后再 step()

陷阱:loss.backward() 报错 Trying to backward through the graph a second time

现象: 第二次调用 loss.backward() 时抛出 RuntimeError。
原因: PyTorch 默认在 backward 后释放中间计算图节点。若需多次反传(如 GAN 中生成器和判别器分别反传),需保留计算图。
解决: 第一次调用时传 retain_graph=Trueloss.backward(retain_graph=True);或重构训练循环,每次从前向传播重新构建计算图。

陷阱:CPU 张量与 GPU 张量混用导致 RuntimeError: expected device

现象: 模型在 CUDA 上,输入张量在 CPU,前向传播时报设备不匹配错误。
原因: PyTorch 不会自动跨设备移动张量,所有参与计算的张量必须在同一设备上。
解决: 统一用 tensor.to(device)tensor.cuda() 移动张量,在数据加载循环中确保每个批次都移动到目标设备:x, y = x.to(device), y.to(device)


参见

Pandas完全指南
asyncio异步编程完全指南
scikit-learn完全指南

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog