1.正向
2.反向
3.复杂度
正向和反向的计算复杂度都是O(n)。因为计算梯度都需要遍历一遍图。
正向的内存复杂度为O(1),而反向的为O(n),因为反向是需要正向一遍来保存中间值的,中间值占掉了空间。
1.显示构造(Tensorflow/theano/MXNet)
先构造好公式(计算图),然后在带入数值计算,一般数学上都是属于显示构造。
2.隐式构造(pytorch/MXNet)
直接写程序流程图,然后框架在后台进行计算图的构造
从上面不难发现,pytorch是采用隐式构造这就是说明,在这上面求梯度只能调用反向求导,先前向构图在反向计算
1.假设对y=2xTx,关于列向量X求导
(1)先生成一个向量
import torch
x = torch.arange(4.)
(2)存梯度
在计算y关于x的梯度之前,需要一个地方存梯度。先用requies_grad_(True)说明需要存梯度,再存在grad里面。
x.requires_grad_(True) # 需要存储中间结果
x.grad # 查看x的梯度,默认为None
(3)计算y
y = 2*torch.dot(x,x) # 点积
y # tensor(28., grad_fn=<MulBackward0>)
dot求内积。11+22+33+44=28.
y是隐式构造出来的计算图,所以y有一个求梯度的函数存在了grad_fn里面。
(4)调用反向传播函数
y.backward() # 对y进行反向传播
x.grad # 查看BP得到的梯度
(backpropagation:反向传播)
x.grad==4*x #tensor([True,True,True,True])说明y的导数为4*x
2.计算下一个于x有关的函数
将x.grad清理,重新输入函数y等于x的累加。
x.grad_zero_() # Pytorch的梯度会累积,这里是将0写入梯度中
y = x.sum()
y.backward() # 对y进行反向传播
x.grad #tensor([1.,1.,1.,1.])
为什么y要假设为x的转置乘以x,而不是x乘以x呢?
因为对于向量x来说,xTx就相当于两倍点积是一个标量,而x*x则是一个矩阵。深度学习一般都是求标量的倒数。
对于非标量调用“backward”需要传入一个“gradient”参数。一般情况下会对非标量进行求和再调用backward。
为什么进行求和再调用backward函数呢?
因为sum的是,对这个函数求和,利用链式法则,先对求导变成2x,在对sum(x)求导。x是向量,对x求导后就是全为1的向量,在乘以2x。最后的结果就是全为2x的向量。
x.grad_zero_()
y = x*x
y.sum().backward()# 相当于x*x == sum(y·y)
x.grad #tensor([0.,2.,4.,6.])
3.将计算移动到计算图之外
# 将计算移动到计算图之外
x.grad_zero_()
y = x*x
u = y.detach() #将ydetach掉,那u就是一个常数,值就是x*x
z = u*x #常数乘以向量,最后的z还是一个向量
z.sum().backward() #对向量求导,要调用sum先变成标量
x.gard==u #tensor([True,True,True,True])
detach在一些需要将网络参数固定住的地方是很有用的,比如loss的求导。
4.及时构建函数的计算图需要通过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
# Torch就先生成计算图,保存中间值,方便反向传播。
# a生成的是随机数randn。size=()说明a是一个标量,并且需要记录梯度requires_grad=True。
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
# f()可以理解成是一个计算图的流程,最终得到d
# 从上面的函数可以发现,是一个线性函数,他的梯度就是斜率d/a
a.grad == d / a
5.测试代码
import torch
x = torch.arange(4.)
x.requires_grad_(True) # 需要存储中间结果
# 上面两个式子等价于 x = torch.arange(4.,requires_grad=True)
x.grad # 查看x的梯度,默认为None
y = 2*torch.dot(x,x) # 点称
y # tensor(28., grad_fn=<MulBackward0>)
y.backward() # 对y进行反向传播
x.grad # 查看BP得到的梯度
# y=2x^2 y'=2x,可以判断BP得到的梯度是否是对的
4*x == x.grad
x.grad_zero_() # Pytorch的梯度会累积,这里是将x重新写为全0的向量,最后一个_就是重新的意思
y = x.sum() # sum() 求导之后相当于点乘一个全为1的向量
y.backward()
x.grad
x.grad_zero_()
y = x*x
# x*x == sum(y·y)
y.sum().backward()
x.grad
# 将计算移动到计算图之外
x.grad_zero_()
y = x*x
u = y.detach()
z = u*x
z.sum().backward()
x.gard==u
# 及时构建函数的计算图需要通过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
# size=() 标量
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
a.grad == d / a