文章目錄
- 概念
- 計算圖
- 自動求導的兩種模式
- 自動求導-代碼
- 標量的反向傳播
- 非標量變量的反向傳播
- 將某些計算移動到計算圖之外
概念
核心:鏈式法則
深度學習框架通過自動計算導數(自動微分)來加快求導。
實踐中,根據涉及號的模型,系統會構建一個計算圖,來跟蹤計算是哪些數據通過哪些操作組合起來產生輸出。
自動微分使系統能夠隨后反向傳播梯度。
反向傳播:跟蹤整個計算圖,填充關于每個參數的偏導數。
計算圖
- 將代碼分解成操作子,將計算表示成一個無環圖
- 將計算表示成一個無環圖、
自動求導的兩種模式
反向傳播
- 構造計算圖
- 前向:執行圖,存儲中間結果
- 反向:從相反方向執行圖 - 不需要的枝可以減去,比如正向里的x和y連接的那個枝
自動求導-代碼
標量的反向傳播
案例:假設對函數 y = 2 x T x y=2x^Tx y=2xTx關于列向量x求導
1.首先初始化一個向量
x = torch.arange(4.0) # 創建變量x并為其分配初始值
print(x) #tensor([0., 1., 2., 3.])
2.計算y關于x的梯度之前,需要一個地方來存儲梯度。
x.requires_grad_()
等價于x=torch.arange(4.0,requires_grad=True)
,這樣PyTorch會跟蹤x的梯度,并生成grad
屬性,該屬性里記錄梯度。
通常用于表示某個變量或返回值“有意為空”或"暫時沒有值",已經初始化但是沒有值
x.requires_grad_(True)
print(x.grad) # 默認值是None,存儲導數。
3.計算y的值,y是一個標量,在python中表示為tensor(28., )
,并記錄是通過某種乘法操作生成的。
y = 2 * torch.dot(x, x)
print(y) # tensor(28., grad_fn=<MulBackward0>)
4.調用反向傳播函數來自動計算y關于x每個分量的梯度。
y.backward()
print(x.grad) # tensor([ 0., 4., 8., 12.])
我們可以知道根據公式來算, y = 2 x T x y=2x^Tx y=2xTx關于列向量x求導的結果是4x,根據打印結果來看結果是正確的。
5.假設此時我們需要繼續計算x所有分量的和,也就是 y = x . s u m ( ) y=x.sum() y=x.sum()
在默認情況下,PyTorch會累計梯度,我們需要調用grad.zero_
清空之前的值。
x.grad.zero_()
y = x.sum() # y = x? + x? + x? + x?
print(y)
y.backward()
print(x.grad) # tensor([1., 1., 1., 1.])
非標量變量的反向傳播
在深度學習中,大部分時候目的是 將批次的損失求和之后(標量)再對分量求導。
y.sum()將 y的所有元素相加,得到一個標量 s u m ( y ) = ∑ i = 1 n x i 2 sum(y)=\sum_{i=1}^n x_i^2 sum(y)=∑i=1n?xi2?
y.sum().backward()
等價于y.backward(torch.ones(len(x))
:
x.grad.zero_()
y = x * x # y是一個矩陣
print(y) # tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>) 4*1的矩陣
# 等價于y.backward(torch.ones(len(x)))
y.sum().backward()
print(x.grad) # [0., 2., 4., 6.]
將某些計算移動到計算圖之外
假設 y = f ( x ) , z = g ( y , x ) y=f(x),z=g(y,x) y=f(x),z=g(y,x),我們需要計算 z z z關于 x x x的梯度,正常反向傳播時,梯度會通過 y y y和 x x x 兩條路徑傳播到 x x x: ? z ? x = ? g ? y ? y ? x + ? g ? x \frac{\partial z}{\partial x} = \frac{\partial g}{\partial y} \frac{\partial y}{\partial x} +\frac{\partial g}{\partial x} ?x?z?=?y?g??x?y?+?x?g?。但由于某種原因,希望將 y y y視為一個常數,忽略 y y y對 x x x的依賴: ? z ? x ∣ y 常數 = ? g ? x \frac{\partial z}{\partial x} |_{y常數} =\frac{\partial g}{\partial x} ?x?z?∣y常數?=?x?g?。
通過 detach() 方法將 y y y從計算圖中分離,使其不參與梯度計算。
z . s u m ( ) 求導 = ? ∑ z i ? x i = u i z.sum() 求導 = \frac{\partial \sum z_i}{\partial x_i} = u_i z.sum()求導=?xi??∑zi??=ui?
x.grad.zero_()
y = x * x
print(y) # tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>)
u = y.detach() # 把y看成一個常數從計算圖中分離,不參與梯度計算,但值還是x*x
print(u) # tensor([0., 1., 4., 9.])
z = u * x # z是一個常數*x
print(z) # tensor([ 0., 1., 8., 27.], grad_fn=<MulBackward0>)
z.sum().backward() print(x.grad == u) # tensor([True,True,true,True])
執行y.detach()
返回一個計算圖之外,但值同y
一樣的tensor,只是將函數z中的y替換成了這個等價變量。
但對于y本身來說還是一個在該計算圖中,就可以在y上調用反向傳播函數,得到 y = x ? x y=x*x y=x?x關于 x x x的導數 2 x 2x 2x
x.grad.zero_()
y.sum().backward()
print(x.grad == 2 * x) # tensor([True,True,true,True])