PyTorch 官方学习笔记(深度研究版)

执行摘要

本文是一份面向“有编程基础但初学深度学习”的 PyTorch 学习笔记,严格限定研究范围为 PyTorch 官方网站(pytorch.org 及其所有子页面、教程、文档、示例与官方博客)。在内容组织上,本文以“从张量与自动微分 → 组装神经网络模块 → 训练闭环(数据/损失/优化/评估)→ 保存与部署 → 常见模型范式(CNN、RNN/Transformer、迁移学习、生成模型)”的路径展开,并穿插大量可运行代码示例、形状示意图、以及易踩坑点与调试方法。

读完本文,你应该能够:建立对 torch.Tensor、广播、视图(view)与内存连续性(contiguous)的直觉;理解 torch.autograd 如何构建反向图并计算梯度;用 nn.Module 规范地搭建模型、选择合适损失函数与优化器并写出“可复现、可保存、可部署”的训练循环;掌握 state_dict 的保存/加载最佳实践与安全注意事项(例如 weights_only=True);理解在 PyTorch 2.x 体系下从训练走向部署的主路径(torch.export/ONNX/边端部署生态)。

目录

本文结构如下(章节内部再按“核心概念 → 示例代码 → 关键行讲解 → 常见坑与调试”展开):

  • 研究范围与版本约定
  • 张量与自动微分(含形状、广播、view/reshape、叶子/非叶子张量、backward()
  • 构建网络与训练循环(nn.Moduletrain()/eval()、损失与优化、训练/验证闭环、训练流程图)
  • 数据加载与预处理(DatasetDataLoadercollate_fn、pin memory 与 non-blocking)
  • 模型保存与部署(state_dict、checkpoint、安全加载、torch.export、ONNX、边端方向)
  • 常用模型范式与最佳实践(CNN、RNN/Transformer、迁移学习、生成模型;FAQ/调试;学习路径)

研究范围与版本约定

研究范围声明:本文的所有概念解释、API 语义与最佳实践,均来自 PyTorch 官方网站体系(pytorch.org、docs.pytorch.org、tutorials.pytorch.org、官方博客与官方论坛等属于其子域/子页面的内容)。除运行代码所需的第三方依赖(如部分教程涉及的可选依赖)外,本文不依赖外部资料来“定义结论”。

版本约定:本文以 PyTorch 2.x 官方文档/教程体系为基准。官方博客在 2026-03-23 发布了 PyTorch 2.11 版本发布说明;因此本文默认你学习/实践时面向的是 PyTorch 2.x 的“当前稳定体系”。若某个细节在官方页面未明确标注具体版本差异,本文将标注为“未指定”。

生态与术语约定(重要):官方信息表明 TorchScript 已被标注为 deprecated(弃用/不再作为主发展方向),并建议以 torch.export 取代以往的 torch.jit.trace/script 主路径;面向边端部署则提及 ExecuTorch 作为生态方向之一。因不同项目仍可能存在历史包袱,本文会解释 TorchScript 的历史地位,但在“新项目建议”里会优先给出 PyTorch 2.x 的推荐路径。


张量与自动微分

核心概念速记

张量是什么torch.Tensor 是 PyTorch 的核心数据结构,承载数值、形状(shape)、数据类型(dtype)、设备位置(device)等信息,并支持大量张量运算。对深度学习而言,你可以把它理解成“带梯度系统的高维数组”。

广播(broadcasting)直觉:许多运算支持类似 NumPy 的广播语义:当两个张量形状不完全相同,但在尾部维度满足“相等或其中一个为 1”时,可以自动扩展到同形状而无需真正复制数据。广播带来表达简洁,但也会引入“某些元素共享同一底层内存位置”的风险——尤其是对 broadcast 后的结果做原地写入(in-place)可能产生不符合直觉的行为,因此官方建议需要写入时先 clone()

view / reshape / contiguous 三角关系(非常常见的坑)

  • Tensor.view() 试图返回“共享同一存储”的视图;如果 stride 不满足要求,就无法 view,需要先 contiguous() 或改用 reshape()。官方建议:不确定能否 view 时,用 reshape() 更安全,因为它会在能 view 时返回 view,否则就复制(等价于内部做 contiguous)。
  • transpose/permute 等常会产生非连续张量,后续若需要 view 成某个形状就容易报错。

autograd 是什么torch.autograd 是反向模式自动微分引擎,会在你执行张量运算时记录计算历史形成 DAG(有向无环图),并在调用 backward() 时沿链式法则自动计算梯度。

叶子张量(leaf)与 .grad

  • 一般而言,用户直接创建、并被设置 requires_grad=True 的张量是 leaf;由运算产生的中间结果通常是 non-leaf。非叶子张量默认不保留梯度到 .grad 字段(即使它参与了反向传播),若你要查看中间张量梯度,需对该张量调用 retain_grad()

推理加速的两把“开关”

  • torch.no_grad():关闭梯度记录,减少内存并加速推理;典型用于验证/推理阶段。
  • torch.inference_mode():比 no_grad 更“极端”,额外关闭视图跟踪与版本计数等,进一步降低开销,但也更严格:在 inference mode 下创建的张量不能拿到 autograd 记录环境中继续参与求导。

形状与运算示意图

广播示意(右对齐匹配)

A: (2, 4)
B: (1, 4)
A * B  →  (2, 4)   # B 的第 0 维从 1 “扩展”到 2(不复制数据的语义)

view/contiguous 典型情形

x: contiguous 的 (B, C, H, W)
x_t = x.transpose(1, 3)   # 可能变成 non-contiguous(stride 改变)
x_t.view(...) 可能失败 → 需要 x_t.contiguous().view(...) 或直接 reshape(...)

代码示例一:张量创建、广播、视图与内存连续性

下面示例可直接运行(CPU 即可),重点观察:广播结果形状、view()reshape() 的差异、以及 transpose 后为何需要 contiguous()

import torch

def main():
    # 1) 创建张量:手工 + 随机
    a = torch.tensor([[1., 2., 3., 4.],
                      [5., 6., 7., 8.]])           # shape: (2, 4)
    b = torch.tensor([[10., 20., 30., 40.]])        # shape: (1, 4)

    # 2) 广播:b 会“扩展”为 (2, 4) 的语义
    c = a * b                                       # shape: (2, 4)
    print("a.shape =", a.shape)
    print("b.shape =", b.shape)
    print("c.shape =", c.shape)
    print("c =", c)

    # 3) transpose 通常会改变 stride,从而导致 non-contiguous
    x = torch.randn(2, 3, 4)                        # (2, 3, 4)
    x_t = x.transpose(1, 2)                         # (2, 4, 3),可能 non-contiguous
    print("x.is_contiguous()   =", x.is_contiguous())
    print("x_t.is_contiguous() =", x_t.is_contiguous())

    # 4) reshape 更“宽容”:能 view 就 view,不能 view 就复制
    y = x_t.reshape(2, 12)                          # (2, 12),可能发生复制
    print("y.shape =", y.shape)

    # 5) view 要求更严格:如果失败,说明 stride 不满足要求
    try:
        y2 = x_t.view(2, 12)
        print("view success:", y2.shape)
    except RuntimeError as e:
        print("view failed:", e)

    # 6) 显式 contiguous 后再 view:最稳的“先整理内存,再改形状”的方式
    y3 = x_t.contiguous().view(2, 12)
    print("after contiguous, view ok:", y3.shape)

if __name__ == "__main__":
    main()

关键行讲解(按概念分组,不逐字逐行赘述)

  • c = a * b:触发广播,b 的形状 (1,4) 依据广播语义扩展为 (2,4) 再做逐元素乘法。
  • x_t = x.transpose(1, 2):转置通常不搬运数据,只改“解释方式”(stride);因此 x_t 可能不是 contiguous。
  • x_t.reshape(...):官方语义是“尽可能返回 view,否则复制”,因此更适合“我只想要这个形状,不想纠结底层是否共享存储”的场景。
  • x_t.view(...):要求更严格,无法 view 时会报错;官方建议不确定时用 reshape。
  • x_t.contiguous().view(...):把数据整理为连续内存后再 view,是“强制可 view”的常用写法。

代码示例二:autograd 最小闭环与 leaf/non-leaf 梯度观察

这个例子做一件事:用 autograd 训练一个最简单的一元线性回归 y ≈ w*x + b,并演示

  • requires_grad=True 如何让参数成为 leaf、从而在 .grad 里拿到梯度
  • non-leaf 中间张量默认不保留 .grad,若要观察需 retain_grad()
  • torch.autograd.backward/Tensor.backward 的基本使用方式
import torch

def main():
    torch.manual_seed(0)

    # 1) 构造一份合成数据:y = 2x + 3 + 噪声
    x = torch.linspace(-1, 1, steps=200).unsqueeze(1)          # (200, 1)
    noise = 0.1 * torch.randn_like(x)
    y = 2.0 * x + 3.0 + noise                                   # (200, 1)

    # 2) 定义“可学习参数”:requires_grad=True → autograd 会跟踪它们
    w = torch.randn(1, 1, requires_grad=True)                   # leaf
    b = torch.zeros(1, requires_grad=True)                      # leaf

    lr = 0.1
    for step in range(200):
        # 3) 前向:构建计算图(w、b 参与运算 → 记录历史)
        y_hat = x @ w + b                                       # (200, 1),通常 non-leaf
        if step == 0:
            # 仅用于演示:默认 non-leaf 的 .grad 不会被填充
            y_hat.retain_grad()

        # 4) 损失:均方误差
        loss = ((y_hat - y) ** 2).mean()

        # 5) 反向:对标量 loss 求导
        loss.backward()

        # 6) 梯度下降更新(手写 SGD)
        with torch.no_grad():
            w -= lr * w.grad
            b -= lr * b.grad
            # 清空梯度(否则下一轮会累加)
            w.grad = None
            b.grad = None

        if step in (0, 1, 2, 5, 20, 199):
            print(f"step={step:3d} loss={loss.item():.4f} w={w.item():.3f} b={b.item():.3f}")
            if step == 0:
                print("y_hat.grad exists?", y_hat.grad is not None)

if __name__ == "__main__":
    main()

关键点讲解

  • w/b 是 leaf(用户直接创建)且 requires_grad=True,因此反向结束后它们的 .grad 会被填充。
  • y_hat 是运算结果,通常是 non-leaf;默认情况下 .grad 不会被保留。调用 retain_grad() 才能在反向后访问 y_hat.grad
  • loss.backward():对标量 loss 做反向传播,autograd 沿计算图应用链式法则计算梯度。
  • 清梯度:如果不清理,下一次 backward() 会把梯度累加到已有 .grad 上(这是 PyTorch 的默认行为之一)。常见做法是把 .grad 设为 None 或使用优化器的 zero_grad(set_to_none=True)

构建网络与训练循环

模块化思维:nn.Module 是训练工程的“组织单位”

PyTorch 的 torch.nn 提供搭建网络所需的基本构件(层、容器、损失等),而几乎所有可训练模型都应以 nn.Module 作为最外层“组织单元”:在 __init__ 里声明子模块(层),在 forward 里描述数据如何流经这些层。官方强调这种嵌套式结构便于构建复杂网络与管理参数。

参数(Parameter)与缓冲(Buffer)state_dict 中不仅包含可学习参数(如 Linear/Conv 的权重),也包含被注册的 buffers(例如 BatchNorm 的 running_mean/running_var);这是保存/加载时非常重要的事实。

容器模块

  • nn.Sequential:最适合“线性堆叠”的网络,forward 会把输出依次传给下一个模块。citeturn21search3
  • nn.ModuleList / nn.ModuleDict:当你需要“用 Python 容器管理很多子模块”时,用它们而不是普通 list/dict,以确保子模块被正确注册并出现在 .parameters() / .to(device) / .state_dict() 等体系里。

torch.nn.functional vs torch.nn:如果某个操作持有参数/状态(如 Conv/Linear/BatchNorm),通常用 nn.Module;如果只是纯函数计算(如某些激活、张量变换),可用 torch.nn.functional。两者在工程上常混用:模块用于“持有东西”,functional 用于“算东西”。


训练闭环的官方“标准步骤”

官方教程反复强调一个典型训练过程包含:定义网络 → 迭代数据 → 前向得到输出 → 计算损失 → 反向传播得到梯度 → 优化器更新参数 → 周期性验证/测试。你可以把这理解为 PyTorch 训练循环的最小闭环。

下面给出一个训练流程图(mermaid),帮助你建立“训练时每一步在干什么”的结构化认知:

渲染错误: Mermaid 渲染失败: Parse error on line 2: ...r] --> B[model.train()] B --> C[取一个 ba -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

train()/eval()no_grad()/inference_mode() 的区别表

这是初学者最常混淆的点之一:model.eval() 不等价于 torch.no_grad()

  • model.eval() 切换“模块行为模式”,主要影响 Dropout、BatchNorm 等少数模块;
  • torch.no_grad()/torch.inference_mode() 切换“梯度记录模式”,影响 autograd 是否记录计算图。
机制 作用对象 主要效果 典型使用场景
model.train() / model.eval() 模块行为 Dropout/Bn 等在训练/评估模式下行为不同;官方说明只影响某些模块 训练 vs 验证/推理时切换
torch.no_grad() autograd 禁止梯度记录,减少内存、加速推理 验证、推理、只做前向
torch.inference_mode() autograd(更强) 比 no_grad 更高性能,但更严格(张量不能再用于需要求导的图) 明确只推理且追求性能时

代码示例一:从零写一个 nn.Module + 分类训练循环(可跑通的“最小工程模板”)

这个例子使用 TensorDataset + DataLoader 造一个合成分类任务(避免下载数据),核心目标是让你掌握“训练循环骨架 + 损失与优化器位置 + train/eval 切换”。其中分类损失使用 nn.CrossEntropyLoss(多分类常用)。

import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader

class TinyMLP(nn.Module):
    def __init__(self, in_dim: int, num_classes: int):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, 64),
            nn.ReLU(),
            nn.Linear(64, num_classes),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.net(x)

@torch.no_grad()
def accuracy(logits: torch.Tensor, y: torch.Tensor) -> float:
    pred = logits.argmax(dim=1)
    return (pred == y).float().mean().item()

def main():
    torch.manual_seed(0)

    # 1) 合成数据:把 x 的某些线性组合当作“伪规律”
    n = 5000
    in_dim = 20
    num_classes = 3

    x = torch.randn(n, in_dim)
    true_w = torch.randn(in_dim, num_classes)
    logits = x @ true_w + 0.1 * torch.randn(n, num_classes)
    y = logits.argmax(dim=1)

    ds = TensorDataset(x, y)
    dl = DataLoader(ds, batch_size=128, shuffle=True)

    # 2) 模型 / 损失 / 优化器
    model = TinyMLP(in_dim, num_classes)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-1)

    # 3) 训练
    for epoch in range(5):
        model.train()
        for xb, yb in dl:
            pred = model(xb)
            loss = loss_fn(pred, yb)

            optimizer.zero_grad(set_to_none=True)
            loss.backward()
            optimizer.step()

        # 4) 验证(这里直接用训练集评估演示流程)
        model.eval()
        with torch.no_grad():
            full_logits = model(x)
            acc = accuracy(full_logits, y)
        print(f"epoch={epoch} loss={loss.item():.4f} acc={acc:.4f}")

if __name__ == "__main__":
    main()

关键行讲解:citeturn26search3turn22search26turn23search1turn22search9

  • nn.Sequential(...):顺序堆叠层,适用于简单 MLP;forward 里只需 return self.net(x)。citeturn21search3turn26search1
  • loss_fn = nn.CrossEntropyLoss():多分类标准用法之一;输入应是 logits(未归一化分数),目标通常是类别索引。citeturn3search3turn3search11
  • optimizer = torch.optim.SGD(model.parameters(), ...):优化器拿到可迭代参数集合,并持有内部状态以更新参数。citeturn26search23turn11search21
  • optimizer.zero_grad(set_to_none=True):清理梯度。官方文档解释了 .grad=None.grad=0 的差异以及对优化器行为可能产生的影响。citeturn23search1turn23search0
  • model.train()/model.eval():切换模块模式;官方说明只影响某些模块(如 Dropout/BatchNorm)。

代码示例二:Dropout 在 train/eval 下的行为差异(把“概念”变成“眼见为实”)

这个小例子只做一件事:同一个输入,多次前向,观察 Dropout 在 train()eval() 下输出是否随机。该差异是 model.eval() 必须做、且不被 no_grad() 替代的典型原因之一。citeturn22search2turn22search9turn22search15

import torch
import torch.nn as nn

def main():
    torch.manual_seed(0)
    drop = nn.Dropout(p=0.5)
    x = torch.ones(1, 10)

    drop.train()
    y1 = drop(x)
    y2 = drop(x)
    print("train mode outputs differ:", not torch.allclose(y1, y2))
    print("train y1:", y1)
    print("train y2:", y2)

    drop.eval()
    y3 = drop(x)
    y4 = drop(x)
    print("eval mode outputs identical:", torch.allclose(y3, y4))
    print("eval y3:", y3)
    print("eval y4:", y4)

if __name__ == "__main__":
    main()

关键点:Dropout 文档明确其“训练时随机置零、评估时不随机”的语义;model.eval() 会让这类模块切换到评估行为,否则推理结果会不稳定。citeturn22search2turn22search23


数据加载与预处理

为什么 PyTorch 要把数据拆成 Dataset 与 DataLoader

官方基础教程把“数据代码与训练代码解耦”作为重要工程原则:

  • Dataset 负责“如何取到第 i 个样本、样本是什么结构”;
  • DataLoader 负责“如何批处理、是否打乱、如何并行加载、如何把样本拼成 batch”。citeturn18search5turn26search0

这种设计的价值是:模型/训练循环可以保持相对稳定,换数据集或换采样方式时主要改 Dataset/DataLoader。citeturn18search5


Map-style Dataset 与 Iterable-style Dataset 对比表

这是写自定义数据管道时必须先做的选择:citeturn18search13turn18search5

类型 典型接口 适合场景 常见注意点
Map-style Dataset __len__ + __getitem__(idx) 可随机访问(如图片文件列表、数组索引) 支持 shuffle 更自然
IterableDataset __iter__ 随机读取昂贵/不可能(如流式数据、数据库游标、日志流) 常要自己处理 worker 分片与随机性

citeturn18search13turn18search5


性能与工程细节:num_workers、pin memory、non-blocking

  • DataLoader(num_workers=...) 会启动多进程/多 worker 并行加载;如何选取合适值与平台/数据形态相关,社区讨论常见建议是逐步增大到性能不再提升为止。citeturn18search14turn18search2
  • 训练常见瓶颈是 CPU→GPU 拷贝。官方教程专门讨论 pin_memory()to(..., non_blocking=True):pin memory 可用于提升拷贝效率;而 non_blocking=True 允许主机侧异步,但需要满足一定前提(如 pinned memory)。citeturn18search0turn18search16turn18search23

代码示例一:自定义 Dataset + DataLoader + 自定义 collate_fn(处理变长序列)

很多任务(NLP、时间序列)会遇到“每条样本长度不同”。这时你往往需要在 collate_fn 里做 padding 并生成 mask。下面例子用纯随机数据模拟“变长 token 序列”,展示如何组成 batch。citeturn18search5turn28search7turn28search0

import torch
from torch.utils.data import Dataset, DataLoader
from typing import List, Tuple

class ToyVarLenDataset(Dataset):
    def __init__(self, n: int = 2000, vocab_size: int = 50, min_len: int = 5, max_len: int = 30):
        self.vocab_size = vocab_size
        self.samples: List[Tuple[torch.Tensor, int]] = []
        g = torch.Generator().manual_seed(0)
        for _ in range(n):
            L = int(torch.randint(min_len, max_len + 1, (1,), generator=g).item())
            tokens = torch.randint(1, vocab_size, (L,), generator=g)  # 0 留作 PAD
            label = int(tokens.sum().item() % 2)  # 伪标签:偶/奇
            self.samples.append((tokens, label))

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

    def __getitem__(self, idx: int):
        return self.samples[idx]

def collate_pad(batch: List[Tuple[torch.Tensor, int]]):
    tokens_list, labels = zip(*batch)
    lengths = torch.tensor([t.numel() for t in tokens_list], dtype=torch.long)
    max_len = int(lengths.max().item())

    pad_id = 0
    x = torch.full((len(batch), max_len), pad_id, dtype=torch.long)
    for i, t in enumerate(tokens_list):
        x[i, :t.numel()] = t

    y = torch.tensor(labels, dtype=torch.long)

    # mask: True 表示“这是 PAD 位”,常用于 attention / pooling 忽略
    pad_mask = (x == pad_id)
    return x, y, lengths, pad_mask

def main():
    ds = ToyVarLenDataset()
    dl = DataLoader(ds, batch_size=4, shuffle=True, collate_fn=collate_pad)

    x, y, lengths, pad_mask = next(iter(dl))
    print("x.shape =", x.shape)
    print("y.shape =", y.shape)
    print("lengths =", lengths.tolist())
    print("pad_mask.shape =", pad_mask.shape)
    print("x =", x)

if __name__ == "__main__":
    main()

关键行讲解:citeturn18search5turn28search0

  • pad_id=0:把 0 预留为 padding token,这是构造 mask 的常见做法。
  • x = torch.full((B, max_len), pad_id, ...):先用 PAD 填满,再把每条序列拷进前缀。
  • pad_mask = (x == pad_id):为后续 Transformer/RNN pooling 提供“哪些位置应被忽略”的信息;类似思想也出现在 Transformer 相关接口(如 padding mask 参数)中。citeturn28search0turn28search4

代码示例二:pin memory 与 non-blocking 的最小用法模板

该示例展示“写法模板”,是否提速依赖设备与数据形态。重点是理解参数位置与约束关系。citeturn18search0turn18search23turn18search16

import torch
from torch.utils.data import DataLoader, TensorDataset

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

    x = torch.randn(4096, 128)
    y = torch.randint(0, 10, (4096,))
    dl = DataLoader(
        TensorDataset(x, y),
        batch_size=256,
        shuffle=True,
        pin_memory=True,     # 关键:启用 pinned memory(对 GPU 拷贝常有帮助)
    )

    for xb, yb in dl:
        # 关键:non_blocking=True 常与 pin_memory 配套
        xb = xb.to(device, non_blocking=True)
        yb = yb.to(device, non_blocking=True)
        # ... forward/backward

    print("done on", device)

if __name__ == "__main__":
    main()

要点:官方专文讨论 pin memory 与 non-blocking 的语义边界与正确使用方式;不要把它当作“无条件加速开关”,而应在性能分析后再决定是否启用。citeturn18search0turn18search16


模型保存与部署

保存/加载的核心结论:优先保存 state_dict

官方“保存与加载”教程给出明确建议:推理场景下通常只需要保存训练得到的参数;用 torch.save(model.state_dict(), ...) 保存权重,通常是“最灵活、也最推荐”的方式,并且 .pt/.pth 是常见文件后缀约定。citeturn25search1turn23search22

安全注意事项(非常重要)torch.load() 底层使用反序列化(unpickling),官方警告“不要加载不可信来源的文件”,并提供了 weights_only 等机制来限制反序列化期间可执行的内容。基础教程明确把 weights_only=True 称为加载权重的最佳实践之一。citeturn25search2turn25search0turn11search10


TorchScript、torch.export 与 ONNX:新项目更推荐的路径

官方信息指出 TorchScript 已被标注为 deprecated,并建议用 torch.export 替代 jit trace/script API;在更广泛的部署互通场景,ONNX 导出也在教程体系中提供了路径,并且文档说明当 dynamo=True 时会基于 torch.export 的新逻辑导出,且这是“推荐且默认”的导出方式。citeturn14view0turn16search1turn16search3turn16search18

边端与微边端:ExecuTorch 文档将其定位为 PyTorch Edge 生态的一部分,支持将模型部署到移动与嵌入式等平台;官方博客也给出将模型部署到 micro-edge 的方向性介绍。citeturn16search19turn16search2turn16search8


保存/部署方式对比表

方式 主要产物 优点 风险/限制
state_dict (torch.save(model.state_dict())) 参数字典 官方推荐、灵活、向后兼容性通常更好 需要你“重新构建同结构模型”再 load_state_dict
保存 checkpoint(模型+优化器等) dict(含多项 state) 可继续训练、可恢复优化器状态 文件更复杂;仍需注意加载安全与兼容
torch.export ExportedProgram PyTorch 2.x 推荐导出图表示,面向 Python-less 环境 需要满足 export 约束;动态形状需显式表达
ONNX(torch.onnx.export(..., dynamo=True)) .onnx 跨框架/跨运行时互通;新路径基于 export 可能需要额外依赖;算子覆盖与数值一致性需验证

citeturn25search1turn16search1turn16search3turn25search2


代码示例一:保存/加载 state_dict(含 weights_only=True

该示例展示官方推荐的“先建同结构模型 → load_state_dicteval() 推理”的流程,并显式使用 weights_only=True。citeturn11search2turn25search0turn25search2turn22search23

import torch
import torch.nn as nn

class SmallNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 32)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(32, 3)

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

def main():
    torch.manual_seed(0)
    model = SmallNet()

    # 1) 假装训练后得到了权重,这里直接保存
    path = "smallnet_weights.pth"
    torch.save(model.state_dict(), path)

    # 2) 加载:先创建同结构模型实例,再 load_state_dict
    model2 = SmallNet()
    state = torch.load(path, weights_only=True)   # 关键:weights_only=True
    model2.load_state_dict(state)
    model2.eval()                                  # 关键:推理前 eval()

    # 3) 推理:配合 no_grad/inference_mode
    x = torch.randn(4, 10)
    with torch.no_grad():
        out = model2(x)
    print("out.shape =", out.shape)

if __name__ == "__main__":
    main()

关键点

  • “先建同结构模型再 load”是官方基础教程明确强调的做法。citeturn25search0
  • weights_only=True 被教程明确称为加载权重的最佳实践,并且 torch.load 文档强调不要加载不可信来源的文件。citeturn25search2turn25search0
  • 推理前 model.eval() 是官方在保存/加载教程中反复提醒的点,否则 Dropout/BatchNorm 行为可能不一致。citeturn22search23turn22search9

代码示例二:torch.export 的最小导出示例(新路径的“概念落地”)

此例把一个简单模块导出为 ExportedProgram。注意:torch.export 的语义是“用示例输入 tracing 捕获图”,默认假设静态形状;若要支持动态形状,需要显式表达 dynamism。citeturn16search1turn16search0turn16search17

import torch
import torch.nn as nn

class M(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(8, 4)

    def forward(self, x):
        return torch.relu(self.linear(x))

def main():
    model = M()
    example = (torch.randn(2, 8),)

    exported = torch.export.export(model, example)  # ExportedProgram
    print(type(exported))
    # 你可以进一步把 exported 交给后端做 lowering(服务器/边端路径在官方教程中有展开)

if __name__ == "__main__":
    main()

理解重点

  • torch.export.export() 会在示例输入上运行并记录运算与条件路径,形成可供后续部署/编译的单图表示。citeturn16search17turn16search1
  • “默认静态形状”意味着:如果运行时输入形状变化,你可能需要按官方文档的方法显式表达动态维度。citeturn16search0

常用模型范式与最佳实践

本节把 CNN、RNN/Transformer、迁移学习、生成模型串成“常见工作流模板”,并集中给出易错点、调试手法与学习路径建议。citeturn26search18turn19search3turn28search0turn27search7turn24search0


CNN:从张量形状直觉到可训练的图像分类器

官方 CIFAR10 教程给出了一个经典 CNN 训练流程:加载并归一化数据 → 定义 CNN → 定义损失与优化器 → 训练与测试。对初学者而言,CNN 最重要的直觉是:输入通常是 (B, C, H, W),卷积/池化改变空间分辨率,最终展平进入全连接输出类别 logits。citeturn26search18turn26search3

CNN 形状流动 ASCII 图

x: (B, 1, 28, 28)  # 以灰度图为例
 └─ Conv2d(1→16, k=3, pad=1) → (B,16,28,28)
 └─ ReLU
 └─ MaxPool2d(2)             → (B,16,14,14)
 └─ Conv2d(16→32, k=3, pad=1)→ (B,32,14,14)
 └─ ReLU
 └─ MaxPool2d(2)             → (B,32,7,7)
 └─ flatten                  → (B, 32*7*7)
 └─ Linear → logits          → (B, num_classes)

(卷积/网络构建与训练闭环可参考官方基础/Blitz 教程体系。)citeturn26search18turn26search1turn3search32

代码示例:一个小型 CNN(MNIST/FashionMNIST 风格输入)

该模型结构与训练循环是“可迁移模板”。你只需要把 DataLoader 换成真实数据即可。citeturn26search1turn3search7turn3search3turn26search6

import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset

class SmallCNN(nn.Module):
    def __init__(self, num_classes: int = 10):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 16, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2),

            nn.Conv2d(16, 32, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2),
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(32 * 7 * 7, 128),
            nn.ReLU(),
            nn.Linear(128, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        return self.classifier(x)

def main():
    torch.manual_seed(0)

    # 造一个“假数据集”:(B,1,28,28)
    x = torch.randn(2048, 1, 28, 28)
    y = torch.randint(0, 10, (2048,))
    dl = DataLoader(TensorDataset(x, y), batch_size=64, shuffle=True)

    model = SmallCNN(num_classes=10)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

    for epoch in range(3):
        model.train()
        for xb, yb in dl:
            logits = model(xb)
            loss = loss_fn(logits, yb)

            optimizer.zero_grad(set_to_none=True)
            loss.backward()
            optimizer.step()

        print("epoch", epoch, "loss", float(loss))

if __name__ == "__main__":
    main()

关键行讲解

  • nn.Conv2d/nn.MaxPool2d/nn.Flatten 属于 CNN 基本积木;forward 的形状演化可对照上方 ASCII 图自行验证。citeturn17search30turn26search1
  • CrossEntropyLoss 适用于多分类 logits;不需要你在最后一层手动加 softmax(交叉熵计算包含等价的 log-softmax 部分)。citeturn3search11turn3search3
  • 优化循环结构与官方 Quickstart/Optimization Loop 的描述一致:train loop + validation/test loop 可扩展为工程版。citeturn26search6turn26search3

RNN 与 Transformer:先把“输入形状与 mask”搞对

RNN(RNN/GRU/LSTM)最关键:序列维与 batch 维

nn.LSTM 为例,文档明确默认 batch_first=False,因此输入/输出张量形状通常是 (seq_len, batch, feature);若设置 batch_first=True,则是 (batch, seq_len, feature)。同时文档强调:该参数不影响隐藏状态/细胞状态的维度组织。citeturn28search13turn19search2turn28search7

Transformer 最关键:注意力模块的形状与 padding mask
  • nn.MultiheadAttention 文档同样强调 batch_first:默认 (seq, batch, embed),可设为 (batch, seq, embed)。citeturn28search0
  • nn.TransformerEncoder 文档给出示例输入 src = torch.rand(10, 32, 512),对应 (seq_len=10, batch=32, d_model=512);并提供 enable_nested_tensor 等影响 padding 高时性能的选项。citeturn28search4
  • 官方博客/教程提到 BetterTransformer/嵌套张量等用于提升 Transformer 推理性能,但这些属于“进阶性能优化”;初学阶段你应优先把数据形状、mask 与训练闭环写正确。citeturn28search8turn28search14turn20search0
代码示例一:一个最小 LSTM 分类器(演示 batch_first=True 的端到端)

我们复用前面“变长序列”数据集的思路(但这里用固定长以简化),演示 LSTM 的输入形状与输出取法:

  • 输入:(B, T) 的 token id
  • embedding 后:(B, T, E)
  • LSTM 输出:out 为时序输出,h_n 为最后隐状态堆叠
  • 分类常用:取 h_n[-1] 或取 out[:, -1, :](当非 packed 且按时间对齐时)citeturn19search2turn28search13
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset

class LSTMClassifier(nn.Module):
    def __init__(self, vocab_size=50, emb_size=32, hidden=64, num_classes=2):
        super().__init__()
        self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=0)
        self.lstm = nn.LSTM(
            input_size=emb_size,
            hidden_size=hidden,
            batch_first=True,   # 关键:输入输出按 (B,T,E)
        )
        self.fc = nn.Linear(hidden, num_classes)

    def forward(self, tokens):
        x = self.emb(tokens)               # (B,T,E)
        out, (h_n, c_n) = self.lstm(x)     # out: (B,T,H) 因 batch_first=True
        last = h_n[-1]                     # (B,H) 取最后一层的最后隐状态
        return self.fc(last)               # (B,C)

def main():
    torch.manual_seed(0)

    B = 2048
    T = 20
    vocab = 50
    x = torch.randint(1, vocab, (B, T))
    y = (x.sum(dim=1) % 2).long()

    dl = DataLoader(TensorDataset(x, y), batch_size=64, shuffle=True)

    model = LSTMClassifier(vocab_size=vocab)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

    for epoch in range(3):
        model.train()
        for xb, yb in dl:
            logits = model(xb)
            loss = loss_fn(logits, yb)

            optimizer.zero_grad(set_to_none=True)
            loss.backward()
            optimizer.step()

        print("epoch", epoch, "loss", float(loss))

if __name__ == "__main__":
    main()

关键点:LSTM 的 batch_first 与输入输出形状在文档中有明确约定;如果你把 (B,T,E) 当成默认输入却忘了 batch_first=True,就会导致维度对不上或隐蔽的逻辑错误。citeturn28search13turn19search2turn28search7


代码示例二:最小 Transformer Encoder 分类器(含位置编码与 padding mask)

Transformer 不自带位置编码(positional encoding)是工程实践里最常见的误解之一;你需要显式把位置信息注入 embedding(例如加上正弦位置编码或可学习位置向量)。同时,若 batch 内有 padding,需要提供 padding mask 让注意力忽略 PAD 位。PyTorch 的注意力/Transformer API 提供了相关参数与 batch_first 选项。citeturn28search0turn28search4turn20search3

import math
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset

class SinusoidalPositionalEncoding(nn.Module):
    def __init__(self, d_model: int, max_len: int = 512):
        super().__init__()
        pe = torch.zeros(max_len, d_model)  # (T,E)
        pos = torch.arange(0, max_len).unsqueeze(1).float()
        div = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(pos * div)
        pe[:, 1::2] = torch.cos(pos * div)
        self.register_buffer("pe", pe)      # buffer:进 state_dict,但不是参数

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # x: (B,T,E)
        T = x.size(1)
        return x + self.pe[:T].unsqueeze(0)

class TransformerClassifier(nn.Module):
    def __init__(self, vocab_size=50, d_model=64, nhead=4, num_layers=2, num_classes=2):
        super().__init__()
        self.emb = nn.Embedding(vocab_size, d_model, padding_idx=0)
        self.pos = SinusoidalPositionalEncoding(d_model)

        enc_layer = nn.TransformerEncoderLayer(
            d_model=d_model,
            nhead=nhead,
            batch_first=True,          # 关键:用 (B,T,E)
        )
        self.encoder = nn.TransformerEncoder(enc_layer, num_layers=num_layers)
        self.fc = nn.Linear(d_model, num_classes)

    def forward(self, tokens: torch.Tensor):
        # tokens: (B,T)
        pad_mask = (tokens == 0)       # True 表示 PAD
        x = self.emb(tokens)           # (B,T,E)
        x = self.pos(x)                # (B,T,E)

        h = self.encoder(x, src_key_padding_mask=pad_mask)  # (B,T,E)
        # 简单池化:取非 PAD 的平均(演示用)
        valid = (~pad_mask).unsqueeze(-1)                    # (B,T,1)
        h = (h * valid).sum(dim=1) / valid.sum(dim=1).clamp_min(1)
        return self.fc(h)

def main():
    torch.manual_seed(0)

    # 造一个带 padding 的 toy 数据:长度不一,用 0 padding 到 T
    B = 2000
    T = 30
    vocab = 50
    x = torch.zeros(B, T, dtype=torch.long)
    lengths = torch.randint(5, T + 1, (B,))
    for i, L in enumerate(lengths.tolist()):
        x[i, :L] = torch.randint(1, vocab, (L,))
    y = (x.sum(dim=1) % 2).long()

    dl = DataLoader(TensorDataset(x, y), batch_size=64, shuffle=True)

    model = TransformerClassifier(vocab_size=vocab)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=2e-4)

    for epoch in range(3):
        model.train()
        for xb, yb in dl:
            logits = model(xb)
            loss = loss_fn(logits, yb)

            optimizer.zero_grad(set_to_none=True)
            loss.backward()
            optimizer.step()

        print("epoch", epoch, "loss", float(loss))

if __name__ == "__main__":
    main()

关键点讲解

  • register_buffer("pe", pe):位置编码不是可学习参数,但应随模型保存/加载;用 buffer 可以进入 state_dict。citeturn23search22turn25search1
  • batch_first=True:让编码器以 (B,T,E) 工作,降低与 DataLoader batch 组织方式的心智负担。PyTorch 在注意力与 RNN 相关模块中均提供 batch_first 选项,但默认往往是 False。citeturn28search0turn28search13turn19search2
  • src_key_padding_mask:padding mask 的概念在 Transformer 体系中至关重要;不提供 mask 可能导致模型把 PAD 当作真实 token 参与注意力,出现诡异学习现象。citeturn28search4turn28search0

迁移学习:冻结骨干网络 + 只训练新头部

官方迁移学习教程用视觉分类讲解了一个经典套路:加载预训练 CNN(如 ResNet 系列)→ 冻结大部分参数 → 替换最后分类层 → 只优化新层或少量解冻层。citeturn27search7turn24search5

同时,nn.Module.requires_grad_()(对模块参数批量设置 requires_grad)在文档中被明确描述为“冻结部分模块以便微调/或在 GAN 等场景分阶段训练”的有用方法。citeturn24search14

代码示例:以 ResNet18 为例的“冻结 + 替换 + 只优化头部”模板

该示例强调工程要点:

  • weights 参数加载预训练权重(新 API 思路);
  • requires_grad_(False) 冻结;
  • 替换 fc
  • 优化器只传入 model.fc.parameters()(仅训练头部)。citeturn27search1turn27search4turn24search14turn11search21
import torch
import torch.nn as nn
import torchvision.models as models

def main():
    # 1) 加载 backbone(是否下载权重取决于 weights 设置与环境)
    #    这里用 weights='DEFAULT' 的写法示意;若版本不支持字符串写法,可按文档改为对应 Weights 枚举(未指定)
    backbone = models.resnet18(weights="DEFAULT")

    # 2) 冻结 backbone
    backbone.requires_grad_(False)

    # 3) 替换分类头:resnet18 的 fc 输入维度通常是 512
    num_classes = 5
    backbone.fc = nn.Linear(backbone.fc.in_features, num_classes)

    # 4) 只优化新头部
    optimizer = torch.optim.SGD(backbone.fc.parameters(), lr=1e-2, momentum=0.9)
    loss_fn = nn.CrossEntropyLoss()

    # 5) 用假数据跑通训练闭环(真实任务把这里换成 DataLoader)
    x = torch.randn(16, 3, 224, 224)
    y = torch.randint(0, num_classes, (16,))

    backbone.train()
    logits = backbone(x)
    loss = loss_fn(logits, y)

    optimizer.zero_grad(set_to_none=True)
    loss.backward()
    optimizer.step()

    print("ok, loss =", float(loss))

if __name__ == "__main__":
    main()

关键点

  • TorchVision 文档说明预训练权重会通过 torch hub 体系下载并缓存,并可通过环境变量等配置缓存目录。citeturn27search1turn11search22
  • 冻结层后,优化器可以只接收“仍需训练”的参数集合,这是官方与社区反复强调的微调实践。citeturn24search14turn27search11turn11search21

生成模型:用 GAN 理解“两个优化器、两套目标、分阶段训练”

官方 DCGAN 教程把 GAN 的核心训练逻辑讲得非常清晰:生成器与判别器是两套网络,训练时需要在同一 iteration 内分别更新(通常是先更新 D 再更新 G),对应两套损失与两次 optimizer.step()。citeturn24search0turn24search6

同时,二分类对抗损失常用 BCE 系列:

  • nn.BCEWithLogitsLoss 把 Sigmoid 与 BCE 合在一起,并强调这种写法更数值稳定(log-sum-exp trick)。因此工程上常建议“判别器输出 logits + BCEWithLogitsLoss”,而不是手动 Sigmoid 再 BCELoss。citeturn24search1turn24search8
  • 分阶段训练(冻结某一方)可用 requires_grad_(),官方文档明确这是适合 GAN 等场景的方法之一。citeturn24search14
代码示例:一个极小 MLP-GAN(用 MNIST 风格向量演示训练骨架)

该例不追求高质量生成,只追求你把“GAN 训练骨架”写对:两套网络、两套优化器、两段损失、分阶段更新、以及冻结策略的写法。citeturn24search1turn24search14

import torch
import torch.nn as nn

class Generator(nn.Module):
    def __init__(self, z_dim=64, out_dim=784):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(z_dim, 256),
            nn.ReLU(),
            nn.Linear(256, out_dim),
        )

    def forward(self, z):
        return self.net(z)  # logits(这里不做 tanh,仅演示)

class Discriminator(nn.Module):
    def __init__(self, in_dim=784):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),  # 输出 logits
        )

    def forward(self, x):
        return self.net(x)

def main():
    torch.manual_seed(0)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    G = Generator().to(device)
    D = Discriminator().to(device)

    opt_g = torch.optim.Adam(G.parameters(), lr=2e-4)
    opt_d = torch.optim.Adam(D.parameters(), lr=2e-4)

    bce = nn.BCEWithLogitsLoss()

    # 用“假真实数据”演示:真实图像向量 x_real ~ N(0,1)
    batch_size = 64
    z_dim = 64

    for step in range(50):
        # -----------------------
        # (1) 训练判别器 D
        # -----------------------
        D.requires_grad_(True)
        G.requires_grad_(False)

        x_real = torch.randn(batch_size, 784, device=device)
        z = torch.randn(batch_size, z_dim, device=device)
        x_fake = G(z).detach()

        logit_real = D(x_real)
        logit_fake = D(x_fake)

        y_real = torch.ones_like(logit_real)
        y_fake = torch.zeros_like(logit_fake)

        loss_d = bce(logit_real, y_real) + bce(logit_fake, y_fake)

        opt_d.zero_grad(set_to_none=True)
        loss_d.backward()
        opt_d.step()

        # -----------------------
        # (2) 训练生成器 G
        # -----------------------
        D.requires_grad_(False)
        G.requires_grad_(True)

        z = torch.randn(batch_size, z_dim, device=device)
        x_fake = G(z)
        logit_fake = D(x_fake)

        # 生成器希望“骗过 D”:让 D 把 fake 判成 real
        loss_g = bce(logit_fake, torch.ones_like(logit_fake))

        opt_g.zero_grad(set_to_none=True)
        loss_g.backward()
        opt_g.step()

        if step % 10 == 0:
            print(f"step={step:03d} loss_d={loss_d.item():.3f} loss_g={loss_g.item():.3f}")

if __name__ == "__main__":
    main()

关键点讲解

  • BCEWithLogitsLoss:官方强调其把 Sigmoid 与 BCE 合并、数值更稳定;因此判别器输出 logits 是常见推荐写法。citeturn24search1ంturn24search8
  • requires_grad_():用来冻结某一方(例如训练 D 时冻结 G,训练 G 时冻结 D),官方文档明确这是微调/分阶段训练(含 GAN)的典型用途。citeturn24search14

常见问题与调试技巧

梯度怎么变成 None?是不是没反传?

  • 先区分 leaf 与 non-leaf:non-leaf 默认不会把梯度写进 .grad,需要 retain_grad() 才能观察。citeturn17search0turn23search19
  • 再检查是否在 no_grad / inference_mode 作用域里跑了 forward;后者更严格,可能导致你“以为在训练其实没建图”。citeturn22search4turn22search1

训练/验证结果不稳定或推理效果变差

  • 是否忘了 model.eval():保存/加载教程明确提醒推理前必须 eval(),否则 Dropout/Bn 等行为会让结果飘。citeturn22search23turn22search9
  • 是否把 model.eval()torch.no_grad() 混为一谈:两者作用不同,评估时常常需要两者同时使用。citeturn22search15turn22search9turn22search4

CrossEntropyLoss 形状/标签不匹配

  • 交叉熵通常期望 logits 形状为 (N, C, ...)(C 为类别数)且 target 为类别索引;形状错往往来自你把 one-hot 当索引、或把 softmax 后概率再喂给交叉熵。建议用 nn.CrossEntropyLoss + logits 直接训练。citeturn3search3turn3search11turn3search19

view() 报错:为什么 reshape 可以、view 不行?

  • 官方明确:view() 对 stride/连续性要求严格;reshape() 会在必要时复制,因此更鲁棒。出现此类错误时先 is_contiguous(),必要时 contiguous()。citeturn17search1turn17search8turn17search14

加载模型报安全/反序列化相关风险提示

  • 牢记官方警告:不要从不可信来源加载。加载权重优先 weights_only=True。citeturn25search2turn25search0

实践建议与最佳实践清单

把训练循环写成“可复用函数”:官方 Optimization Loop 将每个 epoch 分成 train loop 与 validation/test loop;建议你把二者拆成函数(train_one_epoch / evaluate),并让它们只关心“给定 dataloader 执行一次循环”。citeturn26search6

验证/推理阶段默认 model.eval() + torch.no_grad():这是最稳的组合(前者切模块行为,后者关梯度记录)。如果你确定只推理并追求性能,可考虑 torch.inference_mode()。citeturn22search23turn22search4turn22search1

zero_grad(set_to_none=True) 管理梯度:官方文档解释了 .grad=None 的语义差异,并且性能调优指南也提到这种写法。一般训练代码里用它没问题,但如果你要做手动梯度处理,需要意识到 None 与全 0 张量的差异。citeturn23search1turn23search0turn22search29

保存优先 state_dict,并保存“训练恢复所需的一切”:如果你要断点续训,建议 checkpoint 里同时保存:model.state_dict()optimizer.state_dict()(以及 scheduler 等);社区讨论也强调如果只存模型不存优化器,继续训练时行为可能与之前不同。citeturn23search3turn25search7

部署思路优先对齐 PyTorch 2.x 推荐链路:新项目倾向于把 TorchScript 作为历史方案了解即可,把 torch.export 与基于它的 ONNX 导出/后端 lowering 作为主要方向;边端可进一步了解 ExecuTorch。citeturn14view0turn16search1turn16search3turn16search19


总结与学习路径建议

如果你把 PyTorch 学习当作“从写脚本到做工程”的升级,建议按以下顺序练习(每一步最好都能写出可运行的最小例子,并逐步替换为真实数据集与真实模型):citeturn26search4turn25search1turn16search1

  1. 张量基本功:熟练掌握 shape、广播、view/reshape/contiguous,能快速定位“形状错在哪里”。citeturn17search2turn17search1turn17search5
  2. autograd 心智模型:能解释“为什么某些 .grad 是 None、怎么观察中间梯度、什么时候该用 no_grad/inference_mode”。citeturn20search26turn17search0turn22search1turn22search4
  3. nn.Module 工程范式:固定写法:__init__ 定义层,forward 定义流;理解 Parameter vs buffer;会用 Sequential/ModuleList/ModuleDict。citeturn26search1turn23search22turn21search3turn21search0
  4. 训练闭环:把“数据→前向→loss→反向→step→验证→保存”写成可复用模板;理解 train/eval 的意义与常见误用。citeturn26search6turn22search9turn22search23
  5. 常见模型范式迁移:CNN(形状流)→ RNN(序列维/隐藏状态)→ Transformer(mask/位置编码)→ 迁移学习(冻结与微调)→ 生成模型(双网络双优化器)。citeturn26search18turn19search2turn28search4turn27search7turn24search0
  6. 从训练走向部署:先把 state_dict 与 checkpoint 的保存/加载做规范;再根据需求学习 torch.export 与 ONNX(以及边端生态)。citeturn25search1turn16search1turn16search3turn16search19

在这个路径里,你会逐渐发现 PyTorch 的“统一性”:无论是 CNN/RNN/Transformer/GAN,最终都回到同一套张量与自动微分体系、同一套模块化组织方式、同一套优化更新闭环,以及同一套序列化与导出部署的工程接口。citeturn17search18turn20search26turn26search1turn25search1turn16search1

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐