1、标量张量、向量张量

名称 数学样子 PyTorch 形状 (.shape) 举例
标量张量 一个数 () 空元组 torch.tensor(5.0)
向量张量 一列 / 一行数 (n,) 一维元组 torch.tensor([1., 2., 3.])

2、自动计算的是 “梯度”,不是 “负梯度”

先清楚计算和保存的是什么,至于具体是怎么计算的以及为什么要这么计算,继续往后看

问题:要计算最快速减少损失的方向,那就是“负梯度”,那 PyTorch 中保存的 .grad 是负梯度吗?还是梯度?

答案:.grad 保存的是「梯度」,不是「负梯度」。

也就是说,对于参数 w,w.grad 存的是:

而不是:

🔍 为什么?

  • 梯度 $\nabla_w \mathcal{L} = \frac{\partial \mathcal{L}}{\partial w}$ 指向的是 损失函数增长最快的方向

  • 所以,负梯度 $-\frac{\partial \mathcal{L}}{\partial w}$ 才是 损失下降最快的方向

但 PyTorch 的设计原则是:只负责正确计算数学导数,不替你决定优化方向。

因此:

  • .backward() 计算并存储的是 数学上的偏导数(即梯度)

  • 是否取负号、如何更新参数,由你自己或优化器(如 SGD)来处理。

✅ 示例验证

import torch

w = torch.tensor(1.0, requires_grad=True)
loss = w ** 2          # loss = w²,最小值在 w = 0
loss.backward()
print(w.grad)          # 输出: tensor(2.)
  • 数学上:$\frac{d}{dw}(w^2) = 2w = 2 \times 1 = 2$

  • 所以 w.grad = 2 → 这是正梯度

  • 要减小 loss,我们需要让 w 变小,即沿 -2 方向更新:

w \leftarrow w - \text{lr} \cdot w.\text{grad}

  • 其中个lr 是 学习率 learning_rate(也就是 常说的 η)

📌 总结

内容
w.grad 存的是 \displaystyle \frac{\partial \mathcal{L}}{\partial w}(梯度)
最速下降方向 \displaystyle -\frac{\partial \mathcal{L}}{\partial w}(负梯度)
参数更新公式 w \gets w - \text{lr} \cdot w.\text{grad}
PyTorch 是否存负梯度? ❌ 不存!它只存真实的数学梯度

💡 记住:负号是在 更新参数时加上去的,不是存在 .grad 里的!

3、线性函数的导数 = 全局变化率

清楚【“增加多少”的说法仅在线性关系下可直接用于有限变化;非线性情况下,导数仅适用于无穷小变化的局部近似。】

我们来彻底澄清这个看似矛盾、实则精妙的问题:

a = w · x,既然 \frac{\partial a}{\partial w}  = x = 2 跟 w 无关,为什么还能说“w 增加 1,a 就增加 2”?这合理吗?

✅ 答案是:完全合理!而且这恰恰体现了偏导数的“局部”和“瞬时”特性。

🔍 第一步:回顾函数关系

我们有:

a = w \cdot x

其中:

  • x = 2(看成固定值)

  • 所以实际上,在这个具体计算中,a 是 w 的线性函数

    a(w) = 2w

这是一个斜率为 2 的直线

📈 第二步:为什么“w 增加 1,a 增加 2”?

因为:

  • 当前 a = 2w

  • 如果 w 从 3 变成 4(+1),那么:

    • 新的 a = 2 \times 4 = 8

    • 原来的 a = 2 \times 3 = 6

    • 所以 a 增加了 8 - 6 = 2

✅ 这不是近似,而是精确成立!因为函数本身就是线性的。

💡 关键点:当函数关于某个变量是线性的时候,偏导数就是全局的“变化率”,不随该变量的位置改变。

❓那如果函数是非线性的呢?

比如:a = w^2,此时 \frac{\partial a}{\partial w} = 2w

  • 如果当前 w = 3,则导数 = 6

  • “w 增加 1,a 增加约 6” → 这只是近似(实际增加 4^2 - 3^2 = 7

  • 导数只在 “无穷小变化” 下精确

但在我们的情况中:

  • a = w \cdot x,而 x 是常数(=2)

  • 所以 a 对 w 是严格线性

  • 因此,“w 增加 1,a 增加 2” 不仅合理,而且精确

🧠 回答核心疑问

“a 对 w 的偏导等于 2,跟 w 都没有关系。但为什么还能说‘w 增加 1,a 就增加 2’?”

✅ 因为:

  1. 偏导数 \frac{\partial a}{\partial w} = x = 2 是在这个特定前向计算上下文下的值。

  2. 在这个上下文中,x 是已知常数(=2),所以 a 与 w 的关系就是一条斜率为 2 的直线。

  3. 对于线性函数,导数 = 全局变化率,所以“增加 1 → 输出增加 2”是准确无误的

换句话说:

“跟 w 无关” 正是它能精确预测变化的原因!

如果导数依赖于 w(如 2w),那“增加 1”的效果就会随 w 的位置变化;

但现在导数是常数(2),所以无论 w 是 3、100 还是 -5,w 增加 1,a 总是增加 2

🌟 终极总结

问题 解释
偏导是 2,跟 w 无关 是的!因为 x 固定为 2,所以 a = 2w 是线性函数
为什么能说“w+1 → a+2” 因为线性函数的变化率处处相同,导数 = 实际变化量
这是否普遍成立 只对线性关系精确成立;非线性关系下只是“无穷小”近似
PyTorch 是否利用这一点 是的!它在反向时使用前向保存的 x=2,直接算出 \frac{\partial a}{\partial w} = 2

这不是一个“巧合”,而是线性系统的优美性质

当输入和输出成正比时,比例系数就是导数,也是任意单位变化的精确响应。

4、上游梯度

📌 什么是“上游梯度”?

“上游梯度”指的是:从计算图的后方(靠近 loss 的方向)传过来的、当前变量对最终损失(loss)的总影响。

换句话说:

  • 它不是某一步自己的导数,

  • 而是已经包含了后面所有步骤影响的累积梯度

🔍 举个例子

我们有:

  • a = w \cdot x

  • b = a + 1

  • \text{loss} = b^2

反向传播时,从后往前走:

第一步:计算 \dfrac{\partial \text{loss}}{\partial b}

\frac{\partial \text{loss}}{\partial b} = 2b = 2 \times 7 = 14

这个 14 表示:b 变化一点,会对 loss 造成多大影响

当我们接下来要计算 a 对 loss 的影响 时,就需要用到这个 14。

👉 此时,对 a 来说,14 就是“上游梯度” —— 因为它是从“上游”(即更靠近 loss 的方向)传下来的总影响。

🔄 类比:水流或电流

想象一条小溪从山顶(loss)流下来,经过几个水车:

  • 最下面的水车是 b,它直接被水流冲击,感受到的水流强度是 14

  • 再往上的水车是 a,它不直接接触最终水流,但通过 b 间接感受到水流。

  • 那么,传给 a 的“水流强度”就是 14 —— 这就是它的“上游梯度”。

在计算 a 的梯度时:

✅ 总结一句话

“上游梯度” = 从 loss 方向传来的、当前节点之后所有变化的累积影响。

它是反向传播中“乘以前面局部导数”的那个“前面的数”。

📚 补充说明

术语 含义
局部导数 当前运算对其输入的导数(如 b = a + 1 的局部导数是 1)
上游梯度 从 loss 一路传到当前节点的梯度值(已包含后续所有影响)
本节点梯度 上游梯度 × 局部导数 = 当前变量对 loss 的总影响

例如:

  • 对 a 来说:

    • 局部导数(b 对 a 偏导) = 1

    • 上游梯度(loss 对 b 偏导) = 14

    • 所以 a 的梯度 = 14 \times 1 = 14

5、计算图

📌 什么是“计算图”(Computation Graph)?

计算图是一种用图形表示数学计算过程的方式:

  • 节点(Node) 表示变量或中间结果;

  • 边(Edge) 表示运算操作(如加法、乘法);

  • 整个图从输入开始,到输出结束,完整记录了前向计算的每一步

📌 “计算图” 什么时候开始创建?

计算图是在前向传播(forward pass)过程中,随着张量运算的执行而“实时动态创建”的。

PyTorch 在前向传播时,会为每个带 requires_grad=True 的张量构建动态计算图。

只要参与运算的张量中至少有一个设置了 requires_grad=True,PyTorch 就会自动记录该操作,并构建计算图的一部分。

有了这张图,系统就能自动反向遍历它来计算梯度——这就是自动微分的基础。

🔢 举个例子:手动画一个计算图

考虑以下计算:

假设 w = 3, x = 2。

✅ 前向计算流程(从左到右):

w ──┐
    × ──→ a ──→ + ──→ b ──→ ^2 ──→ loss
x ──┘           ↑
                1
  • 圆形/方框节点w, x, a, b, loss(变量)

  • 操作节点×(乘法)、+(加常数1)、^2(平方)

这个结构就是计算图

🧠 计算图的作用

1. 前向计算(Forward Pass)

  • 按图从左到右计算:

    • 先算 a = w * x = 6

    • 再算 b = a + 1 = 7

    • 最后算 loss = b² = 49

  • 所有中间值(a=6, b=7)会被临时保存,因为反向要用。

2. 反向传播(Backward Pass)

  • loss 开始,沿着图反向走(从右到左);

  • 每经过一个操作,就用该操作的求导规则计算局部导数;

  • 把上游传来的梯度 × 局部导数,得到当前节点的梯度。

例如:

  • loss = b² → 局部导数:2b = 14

  • b = a + 1 → 局部导数:1

  • a = w * x → 局部导数:x = 2

最终得到:

\frac{\partial \text{loss}}{\partial w} = 14 \times 1 \times 2 = 28

💡 计算图让链式法则变得可执行:每一步都知道“怎么求导”和“从哪接收梯度”。

🖥️ 在 PyTorch 中,计算图是“动态”的

  • PyTorch 使用动态计算图(Dynamic Computation Graph)

    • 每次前向计算时,实时构建一张新图

    • 反向传播完成后,图就被释放(除非设置 retain_graph=True)。

  • 这使得代码像普通 Python 一样灵活(可以写循环、条件判断),而图会自动适应实际执行路径。

例如:

y = x * 2
if y.sum() > 0:
    z = y ** 2   # ← 图只包含这一分支
else:
    z = y.abs()

计算图会根据 if 的结果动态生成。

📚 关键总结

概念 说明
计算图 用节点和边表示整个计算流程的有向图
节点类型

- 输入/参数(如 w

- 中间变量(如 a, b

- 输出(如 loss

边的含义 数学运算(+、×、sin、exp 等)
为什么需要它? 自动微分必须知道“每一步是怎么算出来的”,才能正确应用链式法则
PyTorch 特点 动态图:每次前向重新构建,用完即焚,灵活但不可复用(默认)

💡 类比理解

想象你在组装一台机器:

  • 前向计算 = 按说明书一步步装零件,最后得到成品;

  • 计算图 = 那份说明书,记录了每个零件怎么装、依赖哪些前置步骤;

  • 反向传播 = 出现问题后,按说明书倒着查:哪个零件松了?影响有多大?

没有说明书(计算图),你就无法定位问题根源(梯度)。

一句话记住

计算图 = 前向计算的“施工蓝图”,也是反向求导的“导航地图”。

6、如何计算梯度的

前面的所有铺垫,就是为了这里

📌 梯度究竟是如何计算的?——从“前向”到“反向”的完整过程

我们通过一个具体例子,一步步讲清楚: 计算机(或人)是如何算出“损失对某个参数的梯度”的?

🧮 1. 我们的计算任务

假设我们有以下三步计算:

  1. a = w \cdot x

  2. b = a + 1

  3. \text{loss} = b^2

其中:

  • w 和 x 是输入(比如模型参数或数据)

  • \text{loss} 是最终结果(比如误差大小)

已知当前数值:

  • w = 3

  • x = 2

我们的目标是:\dfrac{\partial \text{loss}}{\partial w},即“当 w 变化一点点时,loss 会怎么变?”

🔁 2. 第一步:前向计算(Forward Pass)

✅ 什么是“前向计算”?

前向计算就是按照公式顺序,从输入开始,一步一步算出最终结果的过程。

就像你手动解题一样:先算中间值,再算最后答案。

📝 在本例中:

  • 先算第1步:

a = w \cdot x = 3 \times 2 = 6

  • 再算第2步:

b = a + 1 = 6 + 1 = 7

  • 最后算第3步:

\text{loss} = b^2 = 7^2 = 49

✅ 所以,前向计算的结果是

a = 6,\quad b = 7,\quad \text{loss} = 49

这些中间值都会被暂时记住,因为反向计算时要用到它们

🔙 3. 第二步:反向传播(Backward Pass)

✅ 什么是“反向传播”?

反向传播是从最终结果(如 loss)出发,倒着往回走,计算每一步输入对最终结果的“影响程度”,这个影响程度就是梯度

它的数学基础是 链式法则(Chain Rule)

整体变化 = 每一段局部变化的乘积。

🔄 反向传播分三步进行:

▶ 第1段:b 对 loss 的影响

我们知道:

\text{loss} = b^2

对 b 求导(即“b 变一点,loss 变多少?”):

\frac{\partial \text{loss}}{\partial b} = 2b

但注意:这里的 b 不是符号,而是前向计算得到的具体数 b = 7。 所以代入得:

\frac{\partial \text{loss}}{\partial b} = 2 \times 7 = 14

👉 这个 14 就是传给上一步的“上游梯度”。

▶ 第2段:a 对 b 的影响

我们知道:

b = a + 1

对 a 求导:

\frac{\partial b}{\partial a} = 1

意思是:a 增加 1,b 就增加 1(精确成立,因为是线性关系)。

于是,a 对 loss 的总影响为:(这里就用到了链式法则)

\frac{\partial \text{loss}}{\partial a} = \frac{\partial \text{loss}}{\partial b} \cdot \frac{\partial b}{\partial a} = 14 \times 1 = 14

▶ 第3段:w 对 a 的影响

我们知道:

a = w \cdot x

对 w 求偏导(把 x 当作常数):

\frac{\partial a}{\partial w} = x

而前向计算中 x = 2,所以:

\frac{\partial a}{\partial w} = 2

意思是:w 增加 1,a 就增加 2(因为 a = w \times 2,这是线性关系,所以精确成立)。

于是,w 对 loss 的总影响(即梯度)为:

\frac{\partial \text{loss}}{\partial w} = \frac{\partial \text{loss}}{\partial a} \cdot \frac{\partial a}{\partial w} = 14 \times 2 = 28

这个 28 就是最终计算出的梯度值,也就是:

\frac{\partial \text{loss}}{\partial w} = 28

在自动微分系统(比如 PyTorch、TensorFlow)或手动计算中,这个值会被保存下来,通常称为 w 的梯度(gradient of w)。

🔍 更具体地说:

  • 在训练神经网络时,我们用这个梯度来更新参数 w,例如:

w_{\text{new}} = w - \eta \cdot \frac{\partial \text{loss}}{\partial w}

        其中 \eta 是学习率(比如 0.01),而 \frac{\partial \text{loss}}{\partial w} = 28 就是我们刚算出的值。

  • 所以,28 不仅是一个中间结果,更是决定参数如何调整的关键数值

  • 系统会把这个值存储在 w.grad(PyTorch)或类似字段中,供优化器使用。

📝 补充说明:为什么是“保存”而不是“丢掉”?

因为在前向计算中,我们只关心 loss 的值是多少(比如 49); 但在反向传播中,我们关心的是 每个可学习参数对 loss 的影响有多大

所以:

  • 前向:算出 loss = 49(用于评估好坏)

  • 反向:算出 \dfrac{\partial \text{loss}}{\partial w} = 28\dfrac{\partial \text{loss}}{\partial x} = ?(用于改进模型)

这些梯度值必须被保存下来,否则就无法更新参数。

项目 内容
计算结果 \dfrac{\partial \text{loss}}{\partial w} = 28
是否保存 ,作为参数 w 的梯度
用途 用于参数更新(如梯度下降)
存储位置 与变量 w 关联的 .grad 字段(在框架中)或手动记录(在纸上/代码中)

📚 4. 关键名词解释

名词 含义
前向计算(Forward Pass) 从输入开始,按顺序计算每一步,直到得到最终输出(如 loss)的过程。目的是得到所有中间变量的具体数值。
反向传播(Backward Pass) 从最终输出开始,倒着计算每个变量对输出的影响(即梯度)的过程。核心是链式法则。
梯度(Gradient) 表示某个变量对最终结果的“敏感程度”。数学上就是偏导数,如 \dfrac{\partial \text{loss}}{\partial w}
链式法则(Chain Rule) 复合函数求导的规则。例如:若 z = f(y), y = g(x),则 \dfrac{dz}{dx} = \dfrac{dz}{dy} \cdot \dfrac{dy}{dx}
局部导数(Local Derivative) 某一步运算对其输入的导数,如 b = a + 1 的局部导数是 \dfrac{\partial b}{\partial a} = 1
上游梯度(Incoming Gradient) 从后往前传来的“当前对最终结果的影响”,用于乘以局部导数,得到更前面变量的梯度。

💡 5. 为什么能说“w 增加 1,a 就增加 2”?

因为在这个计算中,x 被当作固定值(=2),所以:

a = w \cdot x = 2w

这是一个关于 w 的线性函数

  • 线性函数的特点:变化量 = 系数 × 自变量变化量

  • 所以 w 增加 1 → a 增加 2 \times 1 = 2精确成立

⚠️ 注意:这种说法只在线性关系下对有限变化成立。 如果是非线性关系(如 \text{loss} = b^2),导数(如 2b)只在变化量趋于 0 时精确,大变化时只是近似。

✅ 6. 总结流程

  1. 前向计算

    • 输入 w=3, x=2

    • 算出 a=6, b=7, \text{loss}=49

    • 保存所有中间值

  2. 反向传播

    • \text{loss} 开始,计算每一步的局部导数

    • 用链式法则将“影响”逐层传回

    • 最终得到 \dfrac{\partial \text{loss}}{\partial w} = 28

🎯 梯度的本质:不是凭空算出来的,而是依赖前向计算的具体数值 + 每一步的数学求导规则 + 链式法则的组合

7、loss 可以是什么

📌 损失函数(Loss)与梯度计算

1. 损失函数的来源

损失函数并不必须直接由 wb 得来,而是可以由模型的所有可训练参数通过一系列运算得到。实际上,在深度学习中,损失通常是模型输出与真实标签之间的某种差异度量(如均方误差、交叉熵等),而模型输出是由输入数据经过多层神经网络变换(即权重和偏置的作用)后得出的。

例如,假设我们有一个简单的线性回归模型:

\hat{y} = w \cdot x + b

其中:

  • w 是权重,

  • b 是偏置,

  • x 是输入特征,

  • \hat{y} 是预测值。

损失函数可能是预测值 \hat{y} 与真实值 y 之间的均方误差(MSE):

\text{loss} = (\hat{y} - y)^2

在这个例子中,虽然 \text{loss} 直接依赖于 \hat{y},但因为 \hat{y} 又依赖于 wb,所以最终 \text{loss} 也会对 wb 的变化敏感。

2. 反向传播的本质

反向传播的核心是利用链式法则,从最终的损失函数开始,逐层向前计算每个可训练参数对损失的导数(即梯度)。因此,只要损失函数可以通过一系列可微操作追溯回某个参数,那么就可以计算出该参数的梯度。

例如,考虑以下更复杂的计算图:

w ──┐
    × ──→ a ──→ + ──→ b ──→ ^2 ──→ loss
x ──┘           ↑
                c ──┐
                    │
                    e ──→ sin ──→ d ──┐
                    │                 │
                    └─── exp ───────-─┘

即使 loss 最终由 bd 计算得来,但由于 bd 都间接依赖于 w,因此仍然可以计算出 \frac{\partial \text{loss}}{\partial w}

3. 不可训练参数的情况

如果某些变量或中间结果不是可训练参数(即没有设置 requires_grad=True),它们仍然会参与前向计算和反向传播中的梯度计算,但不会被赋予 .grad 属性。例如:

# 假设 x 是输入数据,不需要求梯度
x = torch.tensor([2.0], requires_grad=False)
w = torch.tensor([3.0], requires_grad=True)

a = w * x  # 这里的乘法操作会被记录到计算图中
loss = a ** 2

loss.backward()
print(w.grad)  # 输出: tensor([24.])

在这种情况下,尽管 x 不需要求梯度,但它仍然会影响 w 的梯度计算。

🚀 关键点总结

项目 解释
损失函数来源 损失函数不一定要直接由 wb 得来,它可以是任何可微分的操作组合的结果,只要这些操作最终能追溯回 wb
反向传播条件 只要损失函数可以通过一系列可微操作追溯回某个参数,就能计算出该参数的梯度。
不可训练参数 即使某些变量没有设置 requires_grad=True,它们仍然会参与计算图的构建,影响其他可训练参数的梯度计算。

✅ 实际应用中的例子

在实际的神经网络训练中,损失函数通常由网络最后一层的输出(即预测值)与真实标签之间的差异计算得来。例如,在分类任务中,常用的损失函数是交叉熵损失:

\text{loss} = -\sum_{i} y_i \log(\hat{y}_i)

其中:

  • y_i是第 i 类的真实标签(one-hot 编码),

  • \hat{y}_i 是模型预测的概率分布。

尽管这个公式看起来与 wb 没有直接关系,但实际上,\hat{y}_i 是由输入数据经过多层神经网络变换(包括多个 wb)后得出的。因此,损失函数依然能够通过反向传播计算出所有 wb 的梯度。

一句话总结

loss 只要能通过计算图连接到参数,就能求梯度(不需显式包含)。

8、loss 只能是 0维 张量(学到这里时这样理解)

loss 可以是 0维 张量,也可以是向量,但学到这里时,只知道 loss 只能是 0维张量 就行了

只有标量才能调用 .backward()(除非指定 gradient 参数)

调用 .backward() 时,loss 必须是一个标量张量(0 维张量),不能是向量或高维张量。

🔍 详细解释:

  • 在 PyTorch 中,.backward() 默认只支持从标量出发的反向传播

  • 原因:

    • 优化目标是一个单一数值(如总误差);

    • 梯度定义为 标量对向量的导数,结果是一个与参数同形状的向量;

    • loss 是向量(如 w * xwx 是向量时),则导数是 Jacobian 矩阵,不符合优化器输入要求。

  • 因此,像 loss = w * x(结果为向量)不能直接调用 .backward(),会报错:

RuntimeError: grad can be implicitly created only for scalar outputs

✅ 正确做法:将 loss 聚合为标量

方法 说明
loss = (w * x).sum() 最常用,表示总损失
loss = (w * x).mean() 平均损失,适合 batch 训练
loss = torch.sum(w * x) 等价于 .sum()

📝 示例:

w = torch.tensor([2.0, 3.0], requires_grad=True)
x = torch.tensor([4.0, 5.0])

# ❌ 错误:loss 是向量(shape=[2])
# loss = w * x
# loss.backward()  # 报错!

# ✅ 正确:转为标量张量(shape=[])
loss = (w * x).sum()  # = 8 + 15 = 23
loss.backward()       # 成功
print(w.grad)         # tensor([4., 5.])

结论loss 必须是标量张量(0 维),可通过 .sum().mean() 等聚合操作实现。

📚 补充:什么是“标量张量”?

  • Python 的 float ≠ PyTorch 的标量张量;

  • 标量张量:torch.tensor(3.14),其 shape = torch.Size([])dim() = 0

  • 只有这种 0 维张量才能直接调用 .backward()

一句话总结

loss 必须是标量张量,才能触发自动反向传播(向量需先聚合)。

只有标量才能调用 .backward()(除非指定 gradient 参数)

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐