自動求導
- 一、計算圖
- 二、構造
- 三、自動求導代碼使用
一、計算圖
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
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/416157.html
標籤:AI
