Skip to content

动手学深度学习之自动求导

官网教程:自动微分

向量链式法则

自动求导

不同于:

  • 符号求导

  • 数值求导

计算图

  • 计算图是一种有向无环图(DAG),用于表示计算过程

  • 计算图中的节点表示变量或计算

  • 计算图中的边表示计算依赖关系

  • 将代码分解成操作子

  • 将计算表示成一个无环图

数学中常用显示构造,Pytorch 用隐式构造

两种模式

  • 正向累积

    • 计算复杂度:O(n)

    • 内存复杂度:O(1)

先里后外,存储中间结果

  • 反向累积(反向传递)

    • 计算复杂度:O(n)

    • 内存复杂度:O(n)

先外后里,去除不需要的枝

TIP

内存消耗大,但计算效率高

计算

python
x = torch.arange(4.0, requires_grad=True) # 这样就可以访问梯度了,注意要分配初始值

x.grad # 查看梯度,默认是 None

如果我们要计算对 y=2xTx 关于列向量 x 求导,就可以这样表示 y

python
y = 2 * torch.dot(x, x)

然后调用反向传播函数来自动计算 y 关于 x 的梯度:

python
y.backward()
x.grad # 查看梯度

TIP

反向传播函数 backward() 会自动计算 y 关于 x 的梯度,并将结果存储在 x.grad 中。

如果要计算关于 x 的多个函数,注意要先清空梯度。

python
x.grad.zero_() # 清空梯度

分离计算

如果希望将某个变量视为常数,可以调用 detach() 方法来分离计算图,这样就不会计算该变量的梯度。

python
u = y.detach()

复杂控制流

即使在复杂的控制流中,PyTorch 也能计算梯度。

python
def f(a):
    b = a * 2
    while b.norm() < 1000:
        b = b * 2
    if b.sum() > 0:
        c = b
    else:
        c = 100 * b
    return c

内容讲解

WARNING

下方部分内容采用了 Deepseek AI 生成讲解,并做了一定的修改,请读者注意鉴别内容真实性。

📌 深度学习求导核心要义(先看结论)

  1. 99%的求导场景标量(损失值)对参数矩阵/向量求导

    • 例如:loss=f(W,b) → 求 lossWlossb
  2. 形状匹配法则:梯度形状 = 参数形状

    • W是(3,2)矩阵 → lossW 也是(3,2)
  3. 自动求导实践:PyTorch的.backward() 自动处理形状对齐


🧮 矩阵求导快速入门(结合代码验证)

案例1:向量内积求导 y = xᵀx

python
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = torch.dot(x, x)  # 等价于xᵀx

y.backward()
print(x.grad)  # 输出:tensor([2., 4., 6.]) → 即2x

数学解释

  • 展开计算:y=x12+x22+x32

  • 对每个xᵢ求导:yxi=2xi

  • 组合结果:梯度向量 [2x1,2X2,2X3]

案例2:矩阵乘法求导 y=Wx+b

python
W = torch.randn(2,3, requires_grad=True)
x = torch.tensor([1.0, 2.0, 3.0])
b = torch.randn(2, requires_grad=True)
y = W @ x + b  # 形状(2,) @ 是矩阵乘法运算符

loss = y.sum()  # 必须转为标量才能backward()
loss.backward()

print(W.grad.shape)  # (2,3) → 与 W 形状一致
print(b.grad.shape)  # (2,) → 与 b 形状一致

梯度解读

  • lossW 的每个元素 =xj(第 j 个输入特征)
  • lossb 的所有元素 =1

📐 必须掌握的求导规则(附记忆技巧)

规则1:线性变换梯度

运算梯度公式代码验证方法
y=Wx(W:m×n)lossW=lossyxTy.sum().backward()看W.grad
y=xW(W:m×n)lossW=xlossyT同上
y=x2yx=2x简单向量测试

记忆口诀
"梯度形状跟随参数,线性层梯度藏输入"

规则2:链式法则实践

python
# 复合函数示例:y = relu(Wx + b)
x = torch.randn(3)
W = torch.randn(2,3, requires_grad=True)
b = torch.randn(2, requires_grad=True)

h = torch.relu(W @ x + b)
loss = h.sum()
loss.backward()  # PyTorch自动计算∂loss/∂W和∂loss/∂b

反向传播流程

  1. 计算 lossh → 全 1 向量(因为loss=h.sum())
  2. 计算 h(Wx+b)relu 的导数(0 或 1)
  3. 计算 (Wx+b)Wx

🚀 PyTorch求导最佳实践

1. 梯度清零的必要性

python
# 错误示范:梯度累积
for _ in range(10):
    y = x.sum()
    y.backward()  # 梯度会累加!

# 正确做法:
optimizer.zero_grad()  # 或x.grad.zero_()
y.backward()

2. 向量-Jacobian 乘积(VJP)机制

python
x = torch.randn(3, requires_grad=True)
y = x * 2

v = torch.tensor([1.0, 0.0, 0.0])  # 选择关心的梯度方向
y.backward(gradient=v)  # 只计算第一个分量的梯度
print(x.grad)  # 输出:[2, 0, 0]

3. 梯度检查技巧

python
def grad_check():
    # 数值梯度估算
    x = torch.tensor(3.0, requires_grad=True)
    y = x**2
    y.backward()
    analytic_grad = x.grad  # 解析解6.0

    # 数值计算
    eps = 1e-5
    numeric_grad = ( (x+eps)**2 - (x-eps)**2 ) / (2*eps)
    
    print(f"解析梯度:{analytic_grad}, 数值梯度:{numeric_grad}")

grad_check()

💡 直观理解梯度方向

想象你在山顶(高损失值)想要最快下山:

  • 梯度向量:指向最陡峭的下山方向
  • 参数更新W=Wlrgradient(逆梯度方向移动)

📝 常见面试问题解析

Q:为什么线性层的梯度是输入特征?
A:以 y=Wx 为例,每个权重 Wij 的梯度取决于对应的输入 xj,因为:

yiWij=xj

反向传播时,梯度通过链式法则传递到各参数

Q:矩阵求导时为什么要转置?

A:遵循 分子布局约定,保证梯度形状与参数形状一致。例如:

  • 参数 W 形状为 (m,n),则 lossW 也应为(m,n)

  • 数学推导可能出现转置,但深度学习框架自动处理形状对齐


🔧 调试技巧

当梯度出现异常时:

  1. 检查requires_grad=True是否设置正确
  2. 使用print(tensor.requires_grad)确认计算图连接
  3. 小规模数据重现问题(如2x2矩阵)
  4. 对比数值梯度与解析梯度

通过这个教程,你应该能够:

  1. 理解深度学习中的矩阵求导核心逻辑
  2. 通过 PyTorch 代码验证数学理论
  3. 掌握实际训练中的梯度管理技巧
  4. 快速定位求导相关的问题

建议边写代码边观察梯度变化,实践中巩固理解! (๑•̀ㅂ•́)و✧