訓練神經網路時,最常用的演算法就是反向傳播。在該演算法中,引數(模型權重)會根據損失函式關於對應引數的梯度進行調整。
為了計算這些梯度,PyTorch內建了名為 torch.autograd
的微分引擎。它支援任意計算圖的自動梯度計算。
一個最簡單的單層神經網路,輸入 x
,引數 w
和 b
,某個損失函式。它可以用PyTorch這樣定義:
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w) + b # 矩陣乘法
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
Tensors、Functions and Computational graph
上述程式碼定義了下面的computational graph:
在該網路中,w
和 b
是parameters,是我們需要優化的。因此,我們需要能夠計算損失函式關於這些變數的梯度。因此,我們設定了這些tensor的 requires_grad
屬性。
注意:在建立tensor時可以設定 requires_grad
的值,或者建立之後使用 x.requires_grad_(True)
方法。
我們應用到tensor上構成計算圖的function實際上是 Function
類的物件。該物件知道如何計算前向的函式,還有怎麼計算反向傳播步驟中函式的導數。反向傳播函式儲存在tensor的 grad_fn
屬性中。You can find more information of Function
in the documentation。
print('Gradient function for z =', z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)
輸出:
Gradient function for z = <AddBackward0 object at 0x7faea5ef7e10>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7faea5ef7e10>
計算梯度
為了優化神經網路的引數權重,我們需要計算損失函式關於引數的導數,即,我們需要利用一些固定的 x
和 y
計算\(\frac{\partial loss}{\partial w}\)和\(\frac{\partial loss}{\partial b}\)。為計算這些導數,可以呼叫 loss.backward()
,然後從 w.grad
和 b.grad
:
loss.backward()
print(w.grad)
print(b.grad)
輸出:
tensor([[0.0043, 0.2572, 0.3275],
[0.0043, 0.2572, 0.3275],
[0.0043, 0.2572, 0.3275],
[0.0043, 0.2572, 0.3275],
[0.0043, 0.2572, 0.3275]])
tensor([0.0043, 0.2572, 0.3275])
注意:
- 我們只能在計算圖中
requires_grad=True
的葉節點獲得grad
屬性。對於其它節點,梯度是無效的。 - 出於效能原因,我們只能對給定的graph使用
backward
執行梯度計算。如果需要在同一graph呼叫若干次backward
,在呼叫時,需要傳入retain_graph=True
。
禁用梯度跟蹤
預設情況下,所有 requires_grad=True
的tensor都會跟蹤它們的計算曆史,並支援梯度計算。但是在一些情況下並不需要,例如,當我們已經訓練了一個模型,並將其用在一些輸入資料上,即,僅僅經過網路做前向運算。那麼可以在我們的計算程式碼外包圍 torch.no_grad()
塊停止跟蹤計算。
z = torch.matmul(x, w) + b
print(z.requires_grad())
with torch.no_grad():
z = torch.matmul(x, w) + b
print(z.requires_grad)
輸出:
True
False
在tensor上使用 detach()
也能達到同樣的效果
z = torch.matmul(x, w) + b
z_det = z.detach()
print(z_det.requires_grad)
輸出:
False
禁止梯度跟蹤的幾個原因:
- 將神經網路的一些引數標記為frozen parameters。這在finetuning a pretrained network中是非常常見的指令碼。
- 當你只做前向過程,用於speed up computations,因為tensor計算而不跟蹤梯度將會更有效。
More on Coputational Graphs
概念上,autograd在一個由Function物件組成的有向無環圖(DAG)中保留了資料(tensors)記錄,還有所有執行的操作(以及由此產生的新的tensors)。在DAG中,葉節點是輸入tensor,根節點是輸出tensors。通過從根到葉跟蹤該圖,可以使用鏈式法則自動地計算梯度。
在前向過程中,autograd同時進行兩件事:
- 執行請求的操作計算結果tensor
- 在DAG中儲存操作的梯度函式
當在DAG根部呼叫 .backward()
時,後向過程就會開始。autograd
會:
- 由每一個
.grad_fn
計算梯度。 - 在對應tensor的 '.grad' 屬性累積梯度
- 使用鏈式法則,一直傳播到葉tensor
注意:DAGs在PyTorch是動態的,需要注意的一點是,graph是從頭開始建立的;在每次呼叫 .backward()
之後,autograd開始生成一個新的graph。這允許你在模型中使用控制流語句;如果需要,你可以在每次迭代中改變shape,size,and operations。