01-全景与张量基础 入门 预计 25 分钟 ★ 最小路径

PyTorch 张量与自动微分上手

本知识点把 kp-002 的张量概念与 kp-003 的自动微分落到 PyTorch 的具体 API 上:创建、变形、广播张量,并用 requires_grad/backward 触发一次真实的梯度计算。

一句话定义

本知识点把 kp-002 的张量概念与 kp-003 的自动微分落到 PyTorch 的具体 API 上:创建、变形、广播张量,并用 requires_grad/backward 触发一次真实的梯度计算。

直觉

前两个知识点是「认识锤子和钉子」,本节是「亲手抡一次锤子」:读十遍 API 文档不如自己制造一次 shape mismatch 报错、再亲手修好它。

为什么重要

深度学习是动手学科,本库的两大战役——kp-016 手写训练循环与 kp-021 CIFAR-10 项目——全部在这套 API 上进行。张量操作与梯度机制的肌肉记忆,直接决定后续调试效率(kp-027 的一半排错手段就是检查张量形状与梯度)。

前置知识

  • kp-002(张量概念)、kp-003(自动微分机制)。
  • 环境:任意可运行 Python 的机器,安装 PyTorch CPU 版即可完成本节全部练习。

核心概念

  • 创建张量:torch.tensor(数据)、torch.zeros/ones/randn(形状)、torch.arange、torch.from_numpy。
  • requires_grad=True:声明该张量需要梯度,框架开始为其记账。
  • backward():从当前标量出发执行一次反向传播,梯度写入各张量的 .grad。
  • with torch.no_grad():上下文内不构建计算图,用于推理与评估。
  • .detach():从计算图摘下一个张量副本,阻断梯度回传。
  • .to(device):把张量搬到 CPU/GPU,跨设备运算必须同侧。

原理与机制

操作次序遵循「声明需求 → 前向记录 → 反向取数 → 手动清零」四拍。requires_grad 相当于在计算图上打开录像机;backward() 按 kp-003 的规则遍历图;.grad 是累加器,不清零会把上一批的梯度叠进来——这是初学者第一大坑,务必形成「每个迭代开头 zero_grad」的肌肉记忆。形状操作 API 对应 kp-002 的语义:view/reshape 改形状、permute 换轴、unsqueeze 加轴;广播在 *、+、均值减法中隐式发生,排查形状 bug 的第一动作永远是打印 tensor.shape。梯度检查法:对已知函数手推梯度(如 L=(2x+1)²),再对比 autograd 输出,是验证理解的标尺练习。

公式或模型

本节为操作实践,理论公式已在 kp-002/kp-003 给出,本节不适用——理由:本节目标是把已有公式映射为 API 调用次序,不引入新数学。

图示

四拍循环的流程图:

requires_grad=True   ──►  前向运算(框架记录图)  ──►  loss.backward()  ──►  param.grad 可用
     打开录像机              流水线开工                    倒放分责              查看结果
       ▲                                                              │
       └────────────────── optimizer.zero_grad() ◄────────────────────┘
                            (下一轮前清空累加器)

直观类比

.grad 像加油表的「累计里程」,zero_grad 是每次出发前的清零;no_grad 像「下班关摄像头」,不录像就不占存储(显存)。

实例或案例

完整练习(建议逐行敲入并预测输出后再运行):

import torch

# 1) 创建与形状
x = torch.randn(4, 3)          # [4,3] 随机张量
w = torch.randn(3, 2, requires_grad=True)   # 参数:需要梯度
b = torch.zeros(2, requires_grad=True)

# 2) 前向
y = x @ w + b                  # [4,3] @ [3,2] -> [4,4? no, [4,2]]
loss = (y ** 2).mean()         # 标量损失

# 3) 反向
loss.backward()
print(w.grad.shape)            # torch.Size([3, 2]) —— 梯度与参数同形状
print(w.grad[0])               # 具体数值

# 4) 清零与推理
w.grad.zero_()
with torch.no_grad():
    y_eval = x @ w + b         # 不记录图,节省内存

验证练习:令 x=2.0、构造 loss=(2x+1)**2,backward 后检查 x.grad 是否等于 2·(2x+1)·2=20。

常见误区

  • 对整数张量调用 requires_grad=True:框架只支持浮点类型求导,会报错。
  • 忘记 zero_grad 导致损失曲线异常抖动(与 kp-027 的「发散型曲线」相互印证)。
  • CPU 张量与 CUDA 张量直接运算报 device mismatch:先 .to(同一设备)。
  • 把 .detach() 与 no_grad 混为一谈:前者作用于「已存在的张量」,后者作用于「即将执行的计算」。

与其他知识点的关系

本节是 kp-016(训练循环)的直接预演:那里只会增加 DataLoader、损失函数与优化器三件套;kp-017 的卷积张量、kp-029 的设备与精度操作都以本节 API 为基础。

自测题

  1. 为什么每个训练迭代前要 optimizer.zero_grad()?

答案要点:.grad 是累加器,不清零会把历史批次梯度叠加,等价于用了错误的更新方向。

  1. torch.no_grad() 与 tensor.detach() 的区别?

答案要点:no_grad 关闭一段计算的图记录(面向「未来计算」);detach 摘下现有张量使其不回传梯度(面向「已有对象」)。

  1. 写出把 [8, 28, 28] 灰度批次变为 [8, 784] 的调用。

答案要点:x.reshape(8, 784) 或 x.view(8, -1)(要求内存连续)。

延伸阅读

  • 李沐等《动手学深度学习》第 2 章「预备知识」,与本章互为练习册。
  • PyTorch 官方教程 «What is torch.nn really?»,是从本节通向 kp-016 的最佳过渡材料。

学习状态

状态保存在浏览器本地,用于首页与路径页的进度统计。