PyTorch 介紹 | AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD

Deep_RS發表於2022-02-08

訓練神經網路時,最常用的演算法就是反向傳播。在該演算法中,引數(模型權重)會根據損失函式關於對應引數的梯度進行調整。

為了計算這些梯度,PyTorch內建了名為 torch.autograd 的微分引擎。它支援任意計算圖的自動梯度計算。

一個最簡單的單層神經網路,輸入 x,引數 wb,某個損失函式。它可以用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:

PyTorch 介紹 | AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD

在該網路中,wbparameters,是我們需要優化的。因此,我們需要能夠計算損失函式關於這些變數的梯度。因此,我們設定了這些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>

計算梯度

為了優化神經網路的引數權重,我們需要計算損失函式關於引數的導數,即,我們需要利用一些固定的 xy 計算\(\frac{\partial loss}{\partial w}\)\(\frac{\partial loss}{\partial b}\)。為計算這些導數,可以呼叫 loss.backward(),然後從 w.gradb.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。

選讀:Tensor梯度和Jacobian Products

延伸閱讀