PyTorch微分与梯度:从原理到实战
在深度学习的宏大叙事中,PyTorch 的 torch.autograd 模块无疑是那个最沉默却最强大的幕后推手。它实现了自动微分机制,让复杂的神经网络训练变得像搭积木一样简单。很多开发者习惯于调用 .backward(),却未必真正理解其背后的数学原理与工程细节。
今天,我们就剥开 PyTorch 的“黑盒”,深入探讨它是如何通过计算图追踪梯度,以及反向传播与梯度下降是如何协同工作,让模型从“无知”变得“睿智”。
计算图与自动微分:PyTorch 的“记忆”
PyTorch 的微分机制基于动态计算图。当你使用 Tensor 进行运算时,PyTorch 不仅在计算数值,还在后台构建一个有向无环图,记录数据是如何流动的。
要让一个张量进入这个“追踪系统”,只需设置 requires_grad=True。一旦开启,PyTorch 就会记录所有作用在该张量上的操作。
1 | import torch |
在这个例子中,y 不仅仅是一个数值 11,它还持有着一条“线索”,指向它是如何通过 x 计算得来的。这就是反向传播的基础。
反向传播:链式法则的自动化
当我们得到最终的输出(通常是损失值 Loss)后,调用 .backward() 方法,PyTorch 就会启动自动微分引擎。它利用链式法则,从输出端向输入端反向遍历计算图,计算梯度。
对于标量(如 Loss),PyTorch 可以直接计算梯度;对于非标量,通常需要传入梯度参数。但在训练场景下,我们主要关注标量 Loss 对参数的梯度。
1 | # 执行反向传播 |
何为梯度?
grad 就是导数,更准确地说,它是梯度。
在数学上,梯度本质上就是偏导数组成的向量。你可以把 grad 理解为 PyTorch 帮你算出来的“导数结果”。
我们可以从以下三个层面来看:
数学层面:导数 vs 梯度
一元函数(只有一个变量 $x$):
比如 $y = x^2$。
这时候,梯度 = 导数。
导数是 $2x$。如果你的 $x=3$,那么导数就是 6。PyTorch 算出来的x.grad就是tensor(6.)。多元函数(有多个变量 $x_1, x_2, …$):
比如 $z = x^2 + y^2$。
这时候,我们要分别求 $z$ 对 $x$ 的导数(偏导数)和 $z$ 对 $y$ 的导数(偏导数)。
把这两个导数拼在一起,就变成了一个向量:$(\frac{\partial z}{\partial x}, \frac{\partial z}{\partial y})$。
这个向量就叫梯度。
结论: 在深度学习中,我们的损失函数通常有成千上万个参数(权重和偏置),所以求出来的“导数”是一个巨大的向量,我们习惯称之为梯度。
代码层面:grad 存的是什么?
在 PyTorch 中,x.grad 是一个张量,它的形状和 $x$ 完全一样,里面存的每一个数字,都是损失函数对 $x$ 中对应元素的导数。
看这个简单的例子:
1 | import torch |
为什么是 6?
因为 $y = x^2$ 的导数公式是 $2x$。
当 $x = 3$ 时,导数 $2 \times 3 = 6$。
PyTorch 把计算结果 6 存进了 x.grad 里。
直观理解:它代表什么?
grad(导数/梯度)代表的是“变化率”和“方向”。
- 数值大小:代表变化的快慢。
- 正负号:代表变化的方向。
- 如果
grad是正数:说明 $x$ 增加,结果 $y$ 也会增加(上坡)。 - 如果
grad是负数:说明 $x$ 增加,结果 $y$ 反而会减小(下坡)。
- 如果
在训练神经网络时,我们就是利用这个 grad:
- 如果
grad是正的,我们就把权重减小一点(为了让 Loss 变小)。 - 如果
grad是负的,我们就把权重增大一点。
所以当你看到 x.grad 时,你可以直接把它读作:“损失函数相对于 x 的导数”。
梯度下降:从理论到参数更新
计算出梯度只是第一步,梯度的物理意义是“函数增长最快的方向”。为了让模型拟合数据(即最小化损失函数),我们需要沿着梯度的反方向更新参数。这就是梯度下降。
结语
PyTorch 的微分机制将复杂的数学推导封装在了简洁的 API 之下。从 requires_grad 开启追踪,到 .backward() 自动计算梯度,再到优化器执行梯度下降,这一整套流程构成了深度学习训练的基石。