PyTorch
一、速查🧀
下面各行独立查阅。示例中的 x、y 是张量;涉及维度时,需要满足对应的形状要求。维度从 0 开始,dim=-1 表示最后一维。
1、创建🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 从数据创建 | torch.tensor([1, 2, 3]) | 复制数据,默认推断类型 |
| 指定类型 | torch.tensor([1, 2], dtype=torch.float32) | 创建浮点张量 |
| 从 NumPy 创建 | torch.from_numpy(arr) | 与原数组共享内存 |
| 尽量复用数据 | torch.as_tensor(data) | 类型、设备等条件允许时不复制 |
| 全零 / 全一 | torch.zeros(2, 3) / torch.ones(2, 3) | 形状为 [2, 3] |
| 指定填充值 | torch.full((2, 3), 5.0) | 所有元素为 5.0 |
| 单位矩阵 | torch.eye(3) | 形状为 [3, 3] |
| 等差序列 | torch.arange(0, 10, 2) | 左闭右开,得到 0, 2, 4, 6, 8 |
| 等距取点 | torch.linspace(0, 1, 5) | 含两端,共取 5 个点 |
| 均匀随机数 | torch.rand(2, 3) | 范围 [0, 1) |
| 标准正态 | torch.randn(2, 3) | 均值 0、方差 1 |
| 随机整数 | torch.randint(0, 10, (2, 3)) | 范围 [0, 10) |
| 随机排列 | torch.randperm(5) | 打乱 0 到 4 |
| 参照张量 | torch.zeros_like(x) / torch.randn_like(x) | 默认继承形状、类型和设备,随机正态要求浮点或复数类型 |
| 未初始化 | torch.empty(2, 3) | 分配空间,内容不确定 |
| 随机种子 | torch.manual_seed(42) | 设置随机数种子 |
2、属性🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 形状 | x.shape / x.size() | 如 torch.Size([2, 3]) |
| 某一维长度 | x.size(0) | 第 0 维长度 |
| 维数 | x.ndim / x.dim() | 形状中轴的数量 |
| 元素数 | x.numel() | 所有维度长度的乘积 |
| 数据类型 | x.dtype | 如 torch.float32 |
| 所在设备 | x.device | 如 cpu、cuda:0 |
| 是否求导 | x.requires_grad | 是否需要跟踪梯度 |
| 是否连续 | x.is_contiguous() | 内存布局是否连续 |
| 步长 | x.stride() | 沿各轴移动一个位置跨过的元素数 |
3、索引🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 取行 / 列 | x[0] / x[:, 0] | 对二维张量,分别取第一行、第一列 |
| 切片 | x[:, 1:3] | 取第 1、2 列 |
| 保留维度 | x[0:1] | 取第一项,但不删掉第 0 维 |
| 指定下标 | x[[0, 2]] | 取第 0、2 项 |
| 布尔筛选 | x[x > 0] | 得到所有正元素的一维张量 |
| 条件选择 | torch.where(x > 0, x, 0) | 正数保留,其余置零 |
| 非零下标 | torch.nonzero(x) | 每行是一组非零元素坐标 |
| 按轴选择 | torch.index_select(x, 0, idx) | idx 是一维整数索引 |
| 逐行取值 | torch.gather(x, 1, idx) | 二维 x 与 idx 行数相同,按各行索引取列 |
| 掩码填充 | x.masked_fill(mask, 0) | 布尔掩码为真的位置填 0 |
Tip
多个条件组合时使用 &、|、~,每个比较加括号,如 (x > 0) & (x < 1);不要使用 Python 的 and、or。
4、变形🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 改变形状 | x.reshape(2, -1) | 自动推算一维,元素总数不变 |
| 视图变形 | x.view(2, -1) | 要求原形状和步长兼容 |
| 展平 | x.flatten() | 合成一维 |
| 保留批次 | x.flatten(1) | 从第 1 维开始展平 |
| 增加维度 | x.unsqueeze(0) | 在第 0 维插入长度 1 |
| 删除单维 | x.squeeze(1) | 仅当第 1 维长度为 1 时删除 |
| 交换两轴 | x.transpose(0, 1) | 交换第 0、1 维 |
| 调整轴序 | x.permute(0, 2, 3, 1) | 四维张量由 NCHW 变为 NHWC |
| 连续布局 | x.contiguous() | 必要时复制为连续内存 |
| 广播扩展 | x.expand(4, -1) | 将 [1, D] 扩为 [4, D],共享存储 |
| 复制重复 | x.repeat(4, 1) | 将二维张量沿第 0 维重复 4 次 |
5、拼接🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 沿原轴拼接 | torch.cat([x, y], dim=0) | 除拼接轴外,其余维度相同 |
| 沿新轴堆叠 | torch.stack([x, y], dim=0) | 两个张量形状必须相同 |
| 按长度切分 | x.split(2, dim=0) | 每段长度为 2,最后一段可以更短 |
| 按段数切分 | x.chunk(3, dim=0) | 尝试分为 3 段,可能返回更少段 |
| 指定段数 | torch.tensor_split(x, 3, dim=0) | 恰好分成 3 段,允许空段 |
| 拆掉一轴 | x.unbind(dim=0) | 返回元组,每项少一维 |
6、运算🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 逐元素运算 | x + y、x - y、x * y、x / y | 形状相同或可广播 |
| 幂 / 开方 | x.square() / x.sqrt() | 开方时注意取值范围 |
| 指数 / 对数 | x.exp() / x.log() | 实数对数要求输入为正 |
| 绝对值 | x.abs() | 逐元素绝对值 |
| 截断范围 | x.clamp(min=0, max=1) | 限制在 [0, 1] |
| 逐元素极值 | torch.maximum(x, y) / torch.minimum(x, y) | 比较对应元素 |
| 矩阵乘法 | x @ y / torch.matmul(x, y) | 也支持向量和批次广播 |
| 二维矩阵乘法 | torch.mm(x, y) | [M, K] @ [K, N] → [M, N] |
| 批量矩阵乘法 | torch.bmm(x, y) | [B, M, K] @ [B, K, N],不广播 |
| 向量点积 | torch.dot(x, y) | 两个等长一维张量 |
| 爱因斯坦求和 | torch.einsum("bnd,bd->bn", x, y) | 沿重复且未出现在输出中的 d 求和 |
| L2 范数 | torch.linalg.vector_norm(x, dim=-1) | 沿最后一维计算 |
| 单位化 | F.normalize(x, dim=-1) | 默认按 L2 范数归一化 |
7、统计🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 求和 / 均值 | x.sum(dim=1) / x.mean(dim=1) | 均值要求浮点或复数类型 |
| 保留轴 | x.sum(dim=1, keepdim=True) | 被归约的轴保留为长度 1 |
| 全局极值 | x.max() / x.min() | 返回单个极值 |
| 沿轴极值 | values, indices = x.max(dim=1) | 同时返回值和下标 |
| 极值下标 | x.argmax(dim=-1) / x.argmin(dim=-1) | 常用于取预测类别 |
| 方差 / 标准差 | x.var(correction=0) / x.std(correction=0) | correction=0 除以 N,默认 1 除以 N-1 |
| 累加 | x.cumsum(dim=0) | 沿指定轴累计求和 |
| 排序 | values, indices = x.sort(dim=-1) | 默认升序 |
| 排序下标 | x.argsort(dim=-1, descending=True) | 按降序返回下标 |
| 前 k 大 | values, indices = x.topk(3, dim=-1) | 最后一维至少有 3 个元素 |
| 去重 | torch.unique(x) | 默认对所有元素去重 |
| 任意 / 全部 | (x > 0).any() / (x > 0).all() | 返回布尔张量 |
| 精确比较 | torch.equal(x, y) | 形状和元素是否相同 |
| 近似比较 | torch.allclose(x, y) | 允许浮点误差 |
| 异常值 | torch.isnan(x) / torch.isfinite(x) | 逐元素检查 NaN / 有限值 |
| 概率分布 | torch.softmax(x, dim=-1) | 沿最后一维归一化 |
| 对数概率 | torch.log_softmax(x, dim=-1) | 比先 softmax 再 log 更稳定 |
8、转换🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 转浮点 / 整数 | x.float() / x.long() | 转为 float32 / int64 |
| 转布尔 | x.bool() | 零为假,非零为真 |
| 指定类型、设备 | x.to(device=device, dtype=torch.float32) | 接住返回值 |
| 移至 CPU | x.cpu() | 返回 CPU 张量 |
| 取单个值 | x.item() | 仅适用于只有一个元素的张量 |
| 转列表 | x.tolist() | 转成 Python 标量或嵌套列表 |
| 转 NumPy | x.detach().cpu().numpy() | 常规数值张量转数组 |
| 复制 | x.clone() | 独立存储,仍保留求导关系 |
| 脱离计算图 | x.detach() | 不再跟踪梯度,但共享存储 |
| 独立副本 | x.detach().clone() | 独立存储,且脱离计算图 |
9、训练🧀
| 操作 | 写法 | 说明 |
|---|---|---|
| 训练 / 评估 | model.train() / model.eval() | 切换 Dropout、BatchNorm 等层的行为 |
| 清空梯度 | optimizer.zero_grad(set_to_none=True) | 将梯度设为 None |
| 反向传播 | loss.backward() | 将梯度累积到叶子张量 |
| 更新参数 | optimizer.step() | 根据梯度更新参数 |
| 禁用梯度 | with torch.no_grad(): | 不记录反向计算图 |
| 纯推理 | with torch.inference_mode(): | 额外减少跟踪开销 |
| 冻结参数 | model.requires_grad_(False) | 模型参数不再计算梯度 |
| 查看参数 | model.named_parameters() | 遍历参数名和张量 |
| 梯度裁剪 | nn.utils.clip_grad_norm_(model.parameters(), 1.0) | 在 backward 后、step 前使用 |
| 保存权重 | torch.save(model.state_dict(), path) | 保存参数和持久缓冲区 |
二、张量🧀
1、形状🧀
Tensor 是多维数组,标量、向量、矩阵都可以用它表示。
| 数据 | 常见形状 | 含义 |
|---|---|---|
| 标量 | [] | 单个值,如 loss |
| 向量 | [D] | D 个特征 |
| 表格 | [N, D] | N 个样本,每个 D 个特征 |
| 图像 | [N, C, H, W] | 批次、通道、高、宽 |
| 序列 | [B, L, D] | 批次、序列长度、特征维度 |
dim 指操作沿哪一根轴进行。对 [N, D],dim=0 沿样本轴,dim=1 沿特征轴。
求和
2、类型🧀
| 类型 | 常见用途 |
|---|---|
torch.float32 | 默认的浮点计算、模型参数 |
torch.float16 / torch.bfloat16 | 混合精度训练、推理,需设备和算子支持 |
torch.float64 | 更高精度的数值计算 |
torch.int64,即 torch.long | 类别标签、索引 |
torch.bool | 掩码、条件筛选 |
输入特征、模型参数和标签的类型要与算子要求一致。e.g. 全连接层通常接收浮点输入,交叉熵的类别下标使用 long。
3、设备🧀
torch.cuda.is_available() 检查当前 PyTorch 环境能否使用 CUDA;只有显卡还不够,还需要匹配的驱动和 PyTorch 安装。
模型与参与同一次计算的输入应放在兼容的设备上。训练时,特征和标签通常都要移动。
Tip
x.to(device) 不会直接修改变量 x,要写成 x = x.to(device)。模型的 model.to(device) 会修改模块自身。
4、变形🧀
reshape 改形状,permute 改轴序。 e.g. 将图片从 NCHW 改成 NHWC,应该调整轴序,不能直接 reshape。
view 只能在步长兼容时返回视图;reshape 可以返回视图,也可能复制。不确定内存布局时可以使用 reshape,但不要依赖它一定共享内存。
Warning
x.squeeze() 会删除所有长度为 1 的维度。batch size 为 1 时可能连批次轴一起删掉,通常应明确指定 dim。
5、广播🧀
逐元素运算时,从最右侧对齐形状。对应维度满足以下任意条件即可:
- 长度相同;
- 其中一个长度为
1; - 某一边没有该维度,按
1处理。
偏置
[2, 3] 和 [3] 可以广播;[2, 3] 和 [2] 不可以,因为最右侧的 3 与 2 不兼容。若想给每行加一个数,可将后者变成 [2, 1]。
expand 利用广播共享存储,repeat 会实际复制数据。需要修改扩展后的各个元素时,先 clone()。
6、拼接🧀
cat 与 stack
后两者形状虽然相同,轴的含义不同:dim=0 时第一轴选择 x 或 y,dim=1 时第二轴选择 x 或 y。
7、乘法🧀
*:对应位置相乘,可以广播。@:矩阵乘法,内侧维度必须匹配。- 批量矩阵乘法:
[B, M, K] @ [B, K, N] → [B, M, N],对每个批次分别计算。
8、内存🧀
torch.tensor(data) 复制数据;torch.from_numpy(arr) 与 NumPy 数组共享存储。基本切片通常返回视图,修改它会影响原张量。
共享与复制
带下划线的操作通常是原地操作,如 x.add_(1)、x.zero_()。它们会修改原数据,涉及共享存储或自动求导时要格外注意。
三、数据🧀
1、Dataset🧀
Dataset 负责按下标取样本,DataLoader 负责组装 batch、打乱顺序和加载数据。
已经有张量时,可以直接用 TensorDataset(features, labels);需要读文件或自定义预处理时,再实现自己的 Dataset。
2、DataLoader🧀
| 参数 | 含义 |
|---|---|
batch_size | 每批样本数 |
shuffle | 每轮是否打乱,训练通常开启,验证和测试通常关闭 |
num_workers | 加载数据的子进程数,0 表示在主进程中加载 |
drop_last | 是否丢弃最后不足一个 batch 的样本 |
pin_memory | 使用锁页内存,常用于配合向 CUDA 传输 |
collate_fn | 自定义多个样本如何组成一个 batch |
1 epoch = 遍历训练集一轮;1 step 通常指更新一次参数。普通训练中每个 batch 更新一次,梯度累积时则不一定。
Tip
默认拼批会沿新轴堆叠张量,样本长度不同会报错。变长序列需要在 collate_fn 中做 padding,或保留为列表。Windows 使用多进程加载时,将入口放进 if __name__ == "__main__":。
四、模型🧀
1、Module🧀
网络继承 nn.Module,在 __init__ 中定义层,在 forward 中描述计算。
调用时用 model(x),这样模块的 hooks 等机制才能正常参与。可训练参数由 model.parameters() 交给优化器。
多个层动态组成列表时,使用 nn.ModuleList;需要注册自己的可训练张量时,使用 nn.Parameter。
2、常用层🧀
| 层 | 写法 | 作用 |
|---|---|---|
| 全连接 | nn.Linear(D, H) | 最后一维 D → H,计算线性变换 |
| 卷积 | nn.Conv2d(3, 32, 3, padding=1) | 提取局部空间特征 |
| 嵌入 | nn.Embedding(V, D) | 整数编号映射为 D 维向量 |
| 展平 | nn.Flatten(start_dim=1) | 保留批次轴,展平其余轴 |
| ReLU | nn.ReLU() | max(0, x) |
| Sigmoid | nn.Sigmoid() | 映射到 (0, 1) |
| GELU | nn.GELU() | Transformer 中常见的激活函数 |
| Dropout | nn.Dropout(p=0.2) | 训练时随机置零部分元素并缩放 |
| BatchNorm | nn.BatchNorm1d(H) | 对 [N, H] 按通道归一化 |
| LayerNorm | nn.LayerNorm(H) | 对最后的 H 维归一化 |
全连接层属于线性变换,不是激活函数。最后一层要不要加激活,取决于损失函数需要 logits 还是概率。
3、损失🧀
| 任务 | 损失 | 输入与标签 |
|---|---|---|
| 回归 | nn.MSELoss() | 预测、标签均为浮点,通常形状相同 |
| 回归 | nn.L1Loss() | 绝对误差 |
| 单标签多分类 | nn.CrossEntropyLoss() | 常见用法:logits [N, C],类别下标 [N] |
| 二分类 / 多标签 | nn.BCEWithLogitsLoss() | logits 与浮点标签形状相同,标签通常为 0/1 |
交叉熵
CrossEntropyLoss 接收未经 softmax 的 logits。上例标签为类别下标,不需要 one-hot;如果使用概率标签,标签的形状、类型和含义也要相应改变。
BCEWithLogitsLoss 同理,前面不要再接 sigmoid;需要展示概率时,再对输出做 sigmoid。
Warning
回归预测为 [N, 1]、标签为 [N] 时,可能广播成 [N, N],而不是逐个样本比较。计算 loss 前先核对形状。
4、优化器🧀
| 优化器 | 常见参数 |
|---|---|
SGD | lr、momentum、weight_decay |
Adam | lr、betas、eps |
AdamW | lr、betas、weight_decay,权重衰减与梯度更新解耦 |
lr 是学习率,需要根据模型、数据和 batch size 调整。优化器负责更新参数,梯度由 backward() 计算。
五、求导🧀
1、计算图🧀
当输入中存在需要梯度的张量时,可求导运算会被记录到计算图中。
这里 \(L = \sum_i x_i^2\),所以 \(\frac{\partial L}{\partial x_i} = 2x_i\)。
requires_grad=True:跟踪求导。backward():从输出沿计算图反向计算。.grad:默认保留在需要梯度的叶子张量上,如模型参数。- 非标量输出调用
backward()时,需要传入匹配的外部梯度,或先归约为标量。
2、累积🧀
梯度默认累加,不会自动清零。
重复求导
普通训练每个 batch 前清空梯度。若有意做梯度累积,则多个 batch 后再更新,并按累积方式调整 loss 的缩放。
一次反向传播后,图中的中间缓存通常会释放。下一步训练重新前向即可,不需要每次都加 retain_graph=True。
3、推理🧀
| 操作 | 影响 |
|---|---|
model.eval() | 切换层的行为,不关闭梯度 |
torch.no_grad() | 不记录反向计算图,减少内存开销 |
torch.inference_mode() | 适合纯推理,进一步减少跟踪开销 |
x.detach() | 让某个张量脱离计算图,仍与原张量共享存储 |
Tip
验证和推理通常需要同时设置 eval() 与关闭梯度。若得到的张量还要参与后续需记录梯度的计算,优先考虑 no_grad(),因为 inference mode 创建的张量有额外限制。
六、训练🧀
1、循环🧀
下面沿用前面的 MyModel 和 loader。
清梯度 → 前向 → loss → 反向 → 更新。
上面是普通单标签分类,每个 batch 的 loss 为样本平均,因此乘回 batch 大小后再累计。这样最后一个 batch 较小时,也不会被赋予和其他 batch 一样的权重。
2、验证🧀
训练集用于更新参数,验证集用于选择模型和调参,测试集用于最终评估。普通验证循环不等于交叉验证;交叉验证还要轮换训练与验证的划分。
这里同样假设使用未加权、未忽略标签的默认平均交叉熵,且 loader 非空。验证结束后若继续训练,需要重新 model.train()。
3、保存🧀
state_dict 包含模型参数和持久缓冲区,如 BatchNorm 的运行统计量,不包含模型类的实现。
加载时要先构造相同结构。map_location 决定权重加载到哪里,方便在 CPU 与 GPU 之间切换。
断点续训还需要保存优化器、轮数等状态;若使用调度器或混合精度,也应保存对应状态。
断点
从 start_epoch 继续循环。若要精确复现中断前的随机过程,还需保存随机数生成器等状态。
七、实例🧀
MNIST
用一个两层全连接网络识别手写数字。依赖 torch 和 torchvision;保存为 Python 脚本运行,首次运行会下载 MNIST。
训练集拆成 54000 个训练样本和 6000 个验证样本,按验证 loss 保存最佳权重,最后在测试集上评估一次。
| mnist.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | |
看代码时可以沿着形状检查:[N, 1, 28, 28] → [N, 784] → [N, 128] → [N, 10],标签始终是 [N]。
换成自己的数据时,主要调整 Dataset、预处理、输入维度和类别数。固定种子便于复查结果,但不保证不同设备、版本下完全一致。