动手学深度学习——自动求导|CSDN创作打卡

一、计算图

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

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>