注意
點選此處下載完整示例程式碼
學習基礎知識 || 快速入門 || 張量 || 資料集與 DataLoaders || 轉換 || 構建模型 || 自動微分 || 最佳化 || 儲存與載入模型
使用 torch.autograd 進行自動微分¶
建立日期:2021年2月10日 | 最後更新:2024年1月16日 | 最後驗證:2024年11月5日
訓練神經網路時,最常用的演算法是反向傳播。在該演算法中,根據損失函式相對於給定引數的梯度來調整引數(模型權重)。
為了計算這些梯度,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)
張量、函式與計算圖¶
此程式碼定義了以下計算圖
在這個網路中,w 和 b 是引數,我們需要對其進行最佳化。因此,我們需要能夠計算損失函式相對於這些變數的梯度。為此,我們將這些張量的 requires_grad 屬性設定為 True。
注意
你可以在建立張量時設定 requires_grad 的值,也可以稍後使用 x.requires_grad_(True) 方法設定。
我們應用於張量以構建計算圖的函式實際上是 Function 類的一個物件。此物件知道如何執行函式在前向方向的計算,以及如何在反向傳播步驟中計算其導數。對反向傳播函式的引用儲存在張量的 grad_fn 屬性中。你可以在文件中找到更多關於 Function 的資訊。
Gradient function for z = <AddBackward0 object at 0x7f5842ad19c0>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f5842ad0610>
計算梯度¶
為了最佳化神經網路中的引數權重,我們需要計算損失函式相對於引數的導數,即在某些固定值 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.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530]])
tensor([0.3313, 0.0626, 0.2530])
注意
我們只能獲取計算圖葉子節點的
grad屬性,這些葉子節點的requires_grad屬性設定為True。對於圖中所有其他節點,梯度將不可用。出於效能考慮,我們只能在給定圖上使用
backward進行一次梯度計算。如果我們需要在同一個圖上進行多次backward呼叫,我們需要將retain_graph=True傳遞給backward呼叫。
停用梯度跟蹤¶
預設情況下,所有 requires_grad=True 的張量都會跟蹤其計算歷史並支援梯度計算。然而,有些情況下我們不需要這樣做,例如,當我們訓練完模型並只想將其應用於某些輸入資料時,即我們只想透過網路進行前向計算。我們可以透過將我們的計算程式碼放在 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
實現相同結果的另一種方法是對張量使用 detach() 方法。
False
- 你可能希望停用梯度跟蹤的原因包括:
將神經網路中的某些引數標記為凍結引數。
當你只進行前向傳播時,加快計算速度,因為不對梯度進行跟蹤的張量上的計算會更高效。
更多關於計算圖的資訊¶
從概念上講,autograd 記錄了資料(張量)和所有執行的操作(以及由此產生的新張量),形成一個由 Function 物件組成的有向無環圖 (DAG)。在這個 DAG 中,葉子節點是輸入張量,根節點是輸出張量。透過從根節點追溯到葉子節點,你可以使用鏈式法則自動計算梯度。
在前向傳播中,autograd 同時完成兩件事:
執行請求的操作以計算結果張量
在 DAG 中維護操作的梯度函式。
當在 DAG 的根節點上呼叫 .backward() 時,反向傳播啟動。autograd 隨後:
計算每個
.grad_fn的梯度,將它們累加到對應張量的
.grad屬性中使用鏈式法則,一直傳播到葉子張量。
注意
DAG 在 PyTorch 中是動態的 需要注意的一點是,圖是每次都從頭開始重建的;在每次 .backward() 呼叫之後,autograd 開始填充一個新圖。這正是允許你在模型中使用控制流語句的原因;如果需要,你可以在每次迭代中改變形狀、大小和操作。
可選閱讀:張量梯度與雅可比乘積¶
在許多情況下,我們有一個標量損失函式,並且需要計算相對於某些引數的梯度。然而,在某些情況下,輸出函式是一個任意的張量。在這種情況下,PyTorch 允許你計算所謂的雅可比乘積,而不是實際的梯度。
對於一個向量函式 \(\vec{y}=f(\vec{x})\),其中 \(\vec{x}=\langle x_1,\dots,x_n\rangle\) 和 \(\vec{y}=\langle y_1,\dots,y_m\rangle\),\(\vec{y}\) 相對於 \(\vec{x}\) 的梯度由雅可比矩陣給出:
PyTorch 不是計算雅可比矩陣本身,而是允許你計算對於給定輸入向量 \(v=(v_1 \dots v_m)\) 的雅可比乘積 \(v^T\cdot J\)。這透過呼叫帶引數 \(v\) 的 backward 實現。 \(v\) 的大小應與我們想要計算乘積的原始張量的大小相同。
inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")
First call
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.]])
Second call
tensor([[8., 4., 4., 4., 4.],
[4., 8., 4., 4., 4.],
[4., 4., 8., 4., 4.],
[4., 4., 4., 8., 4.]])
Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.]])
請注意,當我們第二次使用相同引數呼叫 backward 時,梯度的值是不同的。這是因為在進行 backward 傳播時,PyTorch 會累加梯度,即計算出的梯度值會新增到計算圖所有葉子節點的 grad 屬性中。如果你想計算正確的梯度,你需要先將 grad 屬性清零。在實際訓練中,最佳化器會幫助我們完成此操作。
注意
之前我們呼叫 backward() 函式時沒有引數。這本質上等同於呼叫 backward(torch.tensor(1.0)),這是在標量值函式(例如神經網路訓練期間的損失函式)的情況下計算梯度的一種有用方法。