自動求導(dǎo)(autograd)
直接用張量定義的運算時無法求導(dǎo)的,自動求導(dǎo)功能由 autograde
模塊提供贿讹。
這小結(jié)主要包括:
計算圖(computation graph)
autograde.Variable
backward( )
先來舉一個簡單的例子:實現(xiàn)
并計算其在 x = 3 處的導(dǎo)數(shù)够掠。因為
所以
import torch
from torch.autograd import Variable
# 定義變量x
x = Variable(torch.Tensor([3]), requires_grad = True)
# 定義函數(shù)
f = x*x - x # 向前構(gòu)建計算圖
# 求導(dǎo)數(shù)
f.backward() # 向后傳播求導(dǎo)數(shù)
print(x)
print('f(3)=', f.data[0])
print('f\'(3)=', x.grad.data[0])
tensor([3.], requires_grad=True)
f(3)= tensor(6.)
f'(3)= tensor(5.)
上面的例子演示了一個典型的自動求導(dǎo)的過程疯潭。
首先定義了 Variable
生產(chǎn)變量,x = Variable(value,requires_grad = True)
接著向前傳播計算函數(shù)(同時構(gòu)建計算圖): y = f(x)
反向傳播計算導(dǎo)數(shù): y.backward()
, 導(dǎo)數(shù) dy/dx 自動儲存在 x.grad
下面我們來看一下每一步在做什么哭廉。
Variable
Variable 類封裝了 Tensor 類相叁,其支持幾乎所有 Tensor 支持的運算增淹。和 Tensor 不同基于 Variable 的運算是會同時構(gòu)建計算圖。這個計算圖能幫助我們自動求導(dǎo)埠通。Variable 主要包含三個部分:
autograde.Variable:
data
grade
grad_fn
-
Variable.data: 儲存 Variable 的值端辱,有以下兩個可選參數(shù):
- require_grad (boolean): 是否需要對該變量進(jìn)行求導(dǎo)
- volatile (boolean): 為 True 時意味著構(gòu)建在該 variable 之上的圖都不會求導(dǎo),且 volatile 的優(yōu)先級高于 require_grade
value = torch.Tensor([1,2])
x = Variable(value, requires_grad = True)
print(x.data is value) # 檢查x.data 與 value 是否共享內(nèi)存
False
- Variable.grade_fn: 存儲該 Variable 是通過什么樣的基本運算得到荣病,它將被用于 backward 的時候求導(dǎo)渗柿。譬如 y = x+x,那么 grad_fn 記錄的就是 y 由 x 和 x 做加法得到颊亮。根據(jù)鏈?zhǔn)椒▌t陨溅,有了 dy ,那么 grad_fn 就會告訴我們?nèi)绾吻蟪?dx 雹有。
y = x + x
z = x**3
print(y.grad_fn)
print(z.grad_fn)
<AddBackward0 object at 0x000001B529EE0CF8>
<PowBackward0 object at 0x000001B529EE06D8>
葉子節(jié)點(leaf node):由用戶自己創(chuàng)建霸奕,不依賴于其他變量的節(jié)點质帅。葉子節(jié)點的 grad_fn 為 None临梗。
# check whether is leaf node
x.is_leaf,y.is_leaf
(True, False)
# 查看該變量的反向傳播函數(shù)
x.grad_fn,y.grad_fn
(None, <AddBackward0 at 0x1b529f04048>)
# next_functions 保存 grad_fn 的輸入,y 中兩個節(jié)點均為葉子節(jié)點票彪,需要求導(dǎo),梯度是累加的摇零。
y.grad_fn.next_functions
((<AccumulateGrad at 0x1b529f040f0>, 0),
(<AccumulateGrad at 0x1b529f040f0>, 0))
- Variable.grad: 存儲導(dǎo)數(shù)。注意:
- Variable.grad 本身還是個 Variable
- 一個變量 x 可能會屬于多個計算圖噪服,每次調(diào)用
backward()
, 導(dǎo)數(shù)是累加的仇味。所以如果不想導(dǎo)數(shù)累加,運行backward()
之前需要用x.grad.data.zero_()
對導(dǎo)數(shù)清零嬉愧。 - 在計算 x 的導(dǎo)數(shù)時揽惹,導(dǎo)數(shù)值會在向前的過程中形成 buffer ,在計算完成后會自動清空闪金。若是需要多次反向傳播哎垦,需要使用
backward(retain_graph = True)
來保留這些 buffer 漏设。
x = Variable(torch.Tensor([1]),requires_grad = True)
y = x + x # 計算圖1
z = x**3 # 計算圖2
# 第一次求導(dǎo)
y.backward()
print(x.grad.data) # dy/dx = 2
z.backward()
print(x.grad.data) # dy/dx + dz/dx = 2+3 = 5
tensor([2.])
tensor([5.])
x = Variable(torch.Tensor([1]),requires_grad = True)
y = x + x # 計算圖1
z = x**3 # 計算圖2
# 第一次求導(dǎo)
y.backward()
print(x.grad.data) # dy/dx = 2
x.grad.data.zero_()
z.backward()
print(x.grad.data) # dy/dx + dz/dx = 2+3 = 5
tensor([2.])
tensor([3.])
高階導(dǎo)數(shù)
在很多實際應(yīng)用中我們需要求高階導(dǎo)數(shù)。在實際大規(guī)模問題中犬性,直接二階導(dǎo)數(shù),是 Hessian 矩陣鹤耍,而這個矩陣往往是非常巨大的惰蜜,計算起來代價是不能接受的格侯。因此 PyTorch 提供的高階導(dǎo)數(shù)功能并不是直接求二階導(dǎo)數(shù),而是提供對梯度的函數(shù)求導(dǎo)撑教,也就是說我們可以做如下運算:
下面我們舉例子說明:
x = Variable(torch.Tensor([1,2,3]),requires_grad = True)
y = x*x
f = y.sum() # y 是一個向量
df = torch.autograd.grad(f, x, create_graph = True) # create_graph = True 會對反向傳播構(gòu)建計算圖排吴,用于計算二階導(dǎo)數(shù)
print(df[0])
tensor([2., 4., 6.], grad_fn=<AddBackward0>)
這里我們要掌握函數(shù)torch.autograd.grad(y,x,creat_graph = False)
, 該函數(shù)直接返回導(dǎo)數(shù) dy/dx. 不同于backward()
, 它并不會把導(dǎo)數(shù)累加到 x.grad 上面肛冶。另外creat_graph
參數(shù)表示反向傳播的時候是否構(gòu)建新計算圖珊肃,如果需要計算二階導(dǎo)數(shù),其值必須為True
延蟹;否則沒法對導(dǎo)數(shù)繼續(xù)求導(dǎo)阱飘。下面讓我們完成導(dǎo)數(shù)的導(dǎo)數(shù):
G = df[0].pow(2).sum()
dG = torch.autograd.grad(G,x)
print(dG[0])
tensor([ 8., 16., 24.])