动手学深度学习之自动求导
向量链式法则
自动求导
不同于:
符号求导
数值求导
计算图
计算图是一种有向无环图(DAG),用于表示计算过程
计算图中的节点表示变量或计算
计算图中的边表示计算依赖关系
将代码分解成操作子
将计算表示成一个无环图
数学中常用显示构造,Pytorch 用隐式构造
两种模式
正向累积
计算复杂度:
内存复杂度:
先里后外,存储中间结果
反向累积(反向传递)
计算复杂度:
内存复杂度:
先外后里,去除不需要的枝
TIP
内存消耗大,但计算效率高
计算
x = torch.arange(4.0, requires_grad=True) # 这样就可以访问梯度了,注意要分配初始值
x.grad # 查看梯度,默认是 None
如果我们要计算对 x
求导,就可以这样表示 y
:
y = 2 * torch.dot(x, x)
然后调用反向传播函数来自动计算 y
关于 x
的梯度:
y.backward()
x.grad # 查看梯度
TIP
反向传播函数 backward()
会自动计算 y
关于 x
的梯度,并将结果存储在 x.grad
中。
如果要计算关于 x
的多个函数,注意要先清空梯度。
x.grad.zero_() # 清空梯度
分离计算
如果希望将某个变量视为常数,可以调用 detach()
方法来分离计算图,这样就不会计算该变量的梯度。
u = y.detach()
复杂控制流
即使在复杂的控制流中,PyTorch 也能计算梯度。
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 生成讲解,并做了一定的修改,请读者注意鉴别内容真实性。
📌 深度学习求导核心要义(先看结论)
99%的求导场景:标量(损失值)对参数矩阵/向量求导
- 例如:
→ 求 和
- 例如:
形状匹配法则:梯度形状 = 参数形状
- W是
(3,2)
矩阵 →也是 (3,2)
- W是
自动求导实践:PyTorch的
.backward()
自动处理形状对齐
🧮 矩阵求导快速入门(结合代码验证)
案例1:向量内积求导 y = xᵀx
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
数学解释:
展开计算:
对每个xᵢ求导:
组合结果:梯度向量
案例2:矩阵乘法求导
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 形状一致
梯度解读:
的每个元素 (第 j 个输入特征) 的所有元素
📐 必须掌握的求导规则(附记忆技巧)
规则1:线性变换梯度
运算 | 梯度公式 | 代码验证方法 |
---|---|---|
y.sum().backward() 看W.grad | ||
同上 | ||
简单向量测试 |
记忆口诀:
"梯度形状跟随参数,线性层梯度藏输入"
规则2:链式法则实践
# 复合函数示例: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
反向传播流程:
- 计算
→ 全 向量(因为loss=h.sum()) - 计算
→ 的导数(0 或 1) - 计算
→
🚀 PyTorch求导最佳实践
1. 梯度清零的必要性
# 错误示范:梯度累积
for _ in range(10):
y = x.sum()
y.backward() # 梯度会累加!
# 正确做法:
optimizer.zero_grad() # 或x.grad.zero_()
y.backward()
2. 向量-Jacobian 乘积(VJP)机制
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. 梯度检查技巧
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()
💡 直观理解梯度方向
想象你在山顶(高损失值)想要最快下山:
- 梯度向量:指向最陡峭的下山方向
- 参数更新:
(逆梯度方向移动)
📝 常见面试问题解析
Q:为什么线性层的梯度是输入特征?
A:以
反向传播时,梯度通过链式法则传递到各参数
Q:矩阵求导时为什么要转置?
A:遵循 分子布局约定,保证梯度形状与参数形状一致。例如:
参数
形状为 (m,n),则 也应为(m,n) 数学推导可能出现转置,但深度学习框架自动处理形状对齐
🔧 调试技巧
当梯度出现异常时:
- 检查
requires_grad=True
是否设置正确 - 使用
print(tensor.requires_grad)
确认计算图连接 - 小规模数据重现问题(如2x2矩阵)
- 对比数值梯度与解析梯度
通过这个教程,你应该能够:
- 理解深度学习中的矩阵求导核心逻辑
- 通过 PyTorch 代码验证数学理论
- 掌握实际训练中的梯度管理技巧
- 快速定位求导相关的问题
建议边写代码边观察梯度变化,实践中巩固理解! (๑•̀ㅂ•́)و✧