評價此頁
torch.autograd">

torch.autograd 簡介#

創建於:2017年3月24日 | 最後更新:2025年10月01日 | 最後驗證:2024年11月05日

torch.autograd 是 PyTorch 的自動微分引擎,它為神經網路的訓練提供了動力。在本節中,您將對 autograd 如何幫助神經網路進行訓練有一個概念性的理解。

背景#

神經網路 (NNs) 是一系列巢狀函式,它們在輸入資料上執行。這些函式由*引數*(包括權重和偏置)定義,在 PyTorch 中,這些引數儲存在張量中。

訓練神經網路分兩個步驟完成

前向傳播:在前向傳播中,神經網路對其正確輸出做出最佳猜測。它將輸入資料透過每個函式來做出這個猜測。

反向傳播:在反向傳播中,神經網路會根據其猜測的誤差成比例地調整其引數。它透過從輸出向後遍歷,收集誤差相對於函式*引數*(*梯度*)的導數,並使用梯度下降最佳化引數來完成此操作。有關反向傳播的更詳細演練,請參閱 3Blue1Brown 的影片

PyTorch 中的用法#

讓我們來看一個訓練步驟。在此示例中,我們從 torchvision 載入一個預訓練的 resnet18 模型。我們建立一個隨機資料張量來表示單個影像,它具有 3 個通道,高度和寬度均為 64,以及相應的 label,初始化為一些隨機值。預訓練模型中的標籤形狀為 (1,1000)。

注意

此教程僅在 CPU 上執行,在 GPU 裝置上(即使張量已移至 CUDA)也不起作用。

import torch
from torchvision.models import resnet18, ResNet18_Weights
model = resnet18(weights=ResNet18_Weights.DEFAULT)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /var/lib/ci-user/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth

  0%|          | 0.00/44.7M [00:00<?, ?B/s]
 72%|███████▏  | 32.2M/44.7M [00:00<00:00, 320MB/s]
100%|██████████| 44.7M/44.7M [00:00<00:00, 233MB/s]

接下來,我們透過模型的每一層執行輸入資料以進行預測。這是*前向傳播*。

prediction = model(data) # forward pass

我們使用模型的預測和相應的標籤來計算誤差(loss)。下一步是透過網路反向傳播此誤差。當我們對誤差張量呼叫 .backward() 時,將觸發反向傳播。Autograd 然後計算並存儲每個模型引數的梯度,這些梯度儲存在引數的 .grad 屬性中。

loss = (prediction - labels).sum()
loss.backward() # backward pass

接下來,我們載入一個最佳化器,在本例中為 SGD,學習率為 0.01,動量為 0.9。我們將模型的所有引數註冊到最佳化器中。

optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

最後,我們呼叫 .step() 來啟動梯度下降。最佳化器根據儲存在 .grad 中的梯度調整每個引數。

optim.step() #gradient descent

此時,您已擁有訓練神經網路所需的一切。以下各節詳細介紹了 autograd 的工作原理 — 您可以隨時跳過它們。


Autograd 中的微分#

讓我們看看 autograd 如何收集梯度。我們建立兩個張量 ab,並將 requires_grad=True。這會向 autograd 發出訊號,表明應跟蹤它們上的每個操作。

import torch

a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

我們從 ab 建立另一個張量 Q

\[Q = 3a^3 - b^2 \]
Q = 3*a**3 - b**2

假設 ab 是神經網路的引數,而 Q 是誤差。在神經網路訓練中,我們想要誤差相對於引數的梯度,即

\[\frac{\partial Q}{\partial a} = 9a^2 \]
\[\frac{\partial Q}{\partial b} = -2b \]

當我們對 Q 呼叫 .backward() 時,autograd 會計算這些梯度並將它們儲存在相應張量的 .grad 屬性中。

由於 Q 是一個向量,因此我們需要在 Q.backward() 中顯式傳遞 gradient 引數。 gradient 是一個與 Q 形狀相同的張量,它代表 Q 相對於自身的梯度,即

\[\frac{dQ}{dQ} = 1 \]

同樣,我們也可以將 Q 聚合為標量並隱式呼叫 backward,例如 Q.sum().backward()

a.gradb.grad 中現在存放著梯度。

# check if collected gradients are correct
print(9*a**2 == a.grad)
print(-2*b == b.grad)
tensor([True, True])
tensor([True, True])

可選閱讀 - 使用 autograd 進行向量微積分#

從數學上講,如果您有一個向量值函式 \(\vec{y}=f(\vec{x})\),那麼 \(\vec{y}\) 相對於 \(\vec{x}\) 的梯度是一個雅可比矩陣 \(J\)

\[J = \left(\begin{array}{cc} \frac{\partial \bf{y}}{\partial x_{1}} & ... & \frac{\partial \bf{y}}{\partial x_{n}} \end{array}\right) = \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\]

總的來說,torch.autograd 是一個用於計算向量-雅可比乘積的引擎。也就是說,給定任何向量 \(\vec{v}\),計算乘積 \(J^{T}\cdot \vec{v}\)

如果 \(\vec{v}\) 恰好是一個標量函式 \(l=g\left(\vec{y}\right)\) 的梯度

\[\vec{v} = \left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}\]

那麼根據鏈式法則,向量-雅可比乘積就是 \(l\) 相對於 \(\vec{x}\) 的梯度

\[J^{T}\cdot \vec{v} = \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right) = \left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)\]

向量-雅可比乘積的這個特性是我們上面例子中使用的; external_grad 代表 \(\vec{v}\)

計算圖#

從概念上講,autograd 在一個由 Function 物件組成的有向無環圖 (DAG) 中記錄資料(張量)和所有已執行的操作(以及生成的新張量)。在該 DAG 中,葉節點是輸入張量,根節點是輸出張量。透過從根節點到葉節點跟蹤此圖,您可以使用鏈式法則自動計算梯度。

在前向傳播中,autograd 同時執行兩項操作:

  • 執行所需的操作以計算結果張量,以及

  • 在 DAG 中維護該操作的*梯度函式*。

當在 DAG 根節點呼叫 .backward() 時,反向傳播啟動。然後,autograd

  • 從每個 .grad_fn 計算梯度,

  • 將它們累積到相應張量的 .grad 屬性中,並且

  • 使用鏈式法則,一直傳播到葉張量。

下面是我們示例中 DAG 的視覺化表示。圖中,箭頭指示前向傳播的方向。節點代表前向傳播中每個操作的反向函式。藍色的葉節點代表我們的葉張量 ab

../../_images/dag_autograd.png

注意

PyTorch 中的 DAG 是動態的 需要注意的一點是,圖是從頭開始重新建立的;每次呼叫 .backward() 後,autograd 都會開始填充一個新圖。這正是允許您在模型中使用控制流語句的原因;您可以根據需要,在每次迭代時更改形狀、大小和操作。

從 DAG 中排除#

torch.autograd 會跟蹤所有 requires_grad 標誌設定為 True 的張量的操作。對於不需要梯度的張量,將此屬性設定為 False 會將其從梯度計算 DAG 中排除。

即使只有一個輸入張量具有 requires_grad=True,操作的輸出張量也需要梯度。

x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)

a = x + y
print(f"Does `a` require gradients?: {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")
Does `a` require gradients?: False
Does `b` require gradients?: True

在神經網路中,不計算梯度的引數通常稱為*凍結引數*。如果您提前知道不需要這些引數的梯度,則“凍結”模型的某些部分會很有用(這可以透過減少 autograd 計算來提供一些效能優勢)。

在微調中,我們凍結模型的大部分,通常只修改分類器層來對新標籤進行預測。讓我們透過一個小例子來演示這一點。和以前一樣,我們載入一個預訓練的 resnet18 模型,並凍結所有引數。

from torch import nn, optim

model = resnet18(weights=ResNet18_Weights.DEFAULT)

# Freeze all the parameters in the network
for param in model.parameters():
    param.requires_grad = False

假設我們要在一個具有 10 個標籤的新資料集上微調模型。在 resnet 中,分類器是最後一個線性層 model.fc。我們可以簡單地用一個新的線性層(預設未凍結)替換它,該層充當我們的分類器。

model.fc = nn.Linear(512, 10)

現在,模型中除 model.fc 的引數外,所有引數都已凍結。唯一計算梯度的引數是 model.fc 的權重和偏置。

# Optimize only the classifier
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

請注意,儘管我們將所有引數都註冊到了最佳化器中,但唯一計算梯度(因此在梯度下降中更新)的引數是分類器的權重和偏置。

相同的排除功能可透過 torch.no_grad() 中的上下文管理器使用。


進一步閱讀:#

指令碼總執行時間: (0 分鐘 0.752 秒)