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.GroupNorm、nn.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.ImageFolder、torchvision.datasets.CIFAR10、torchvision.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_workers:pin_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=True:loss.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)。