知识点
向量链式法则
例 1:
例 2:
自动求导
自动求导计算一个函数在指定值上的导数
它有别于:
- 符号求导
- 数值求导
计算图
自动求导的原理是计算图
- 将代码分解成操作子
- 将计算表示成一个无环图
显式构造:Tensorflow/Theano/MXNet
from mxnet import sym
a = sym.var()
b = sym.var()
c = 2 * a + b
# 之后将数据绑定到 a 和 b 中
隐式构造:PyTorch/MXNet
from mxnet import autograd, nd
with autograd.record():
a = nd.ones((2,1))
b = nd.ones((2,1))
c = 2 * a + b
自动求导的两种模式
反向累积总结:
- 构造计算图
- 前向:执行图,存储中间结果
- 反向:从相反方向执行图
- 去除不需要的枝
复杂度:
- 计算复杂度:O(n),n 是操作子个数
- 通常正向和方向的代价类似
- 内存复杂度:O(n),因为需要存储正向的所有中间结果
- 跟正向累积对比:
- O(n) 计算复杂度用来计算一个变量的梯度
- O(1) 内存复杂度
代码
一个简单的例子
假设我们想对函数 关于列向量 求导
import torch
x = torch.arange(4.0)
x
'''
tensor([0., 1., 2., 3.])
'''
在计算 关于 的梯度之前,需要一个地方来存储梯度
x.requires_grad_(True) # 等价于 x = torch.arange(4.0, requires_grad=True)
x.grad # 默认值是 None
现在计算
y = 2 * torch.dot(x, x)
y
'''
tensor(28., grad_fn=<MulBackward0>)
'''
通过调用反向传播函数来自动计算 y
关于 x
每个分量的梯度
y.backward()
x.grad
'''
tensor([ 0., 4., 8., 12.])
'''
现在计算 x
的另一个函数
# 在默认情况下,PyTorch 会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
'''
tensor([1., 1., 1., 1.])
'''
非标量变量的反向传播
深度学习中,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和
# 对非标量调用 backward 需要传入一个 gradient 参数,该参数指定微分函数关于 self 的梯度
# 本例只想求偏导数的和,所以传递一个 1 的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于 y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
'''
tensor([0., 2., 4., 6.])
'''
分离计算
将某些计算移动到记录的计算图之外
x.grad.zero_()
y = x * x
u = y.detach() # 把 y 当作一个常数,而不是关于 x 的函数
# y 是 x 的函数,但 u 不是
z = u * x
z.sum().backward()
x.grad == u
'''
tensor([True, True, True, True])
'''
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x
'''
tensor([True, True, True, True])
'''
Python 控制流的梯度计算
即使构建函数的计算图需要通过 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
# 计算梯度
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
a.grad == d / a
'''
tensor(True)
'''