注意
轉到末尾 下載完整的示例程式碼。
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 如何收集梯度。我們建立兩個張量 a 和 b,並將 requires_grad=True。這會向 autograd 發出訊號,表明應跟蹤它們上的每個操作。
import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
我們從 a 和 b 建立另一個張量 Q。
假設 a 和 b 是神經網路的引數,而 Q 是誤差。在神經網路訓練中,我們想要誤差相對於引數的梯度,即
當我們對 Q 呼叫 .backward() 時,autograd 會計算這些梯度並將它們儲存在相應張量的 .grad 屬性中。
由於 Q 是一個向量,因此我們需要在 Q.backward() 中顯式傳遞 gradient 引數。 gradient 是一個與 Q 形狀相同的張量,它代表 Q 相對於自身的梯度,即
同樣,我們也可以將 Q 聚合為標量並隱式呼叫 backward,例如 Q.sum().backward()。
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
a.grad 和 b.grad 中現在存放著梯度。
tensor([True, True])
tensor([True, True])
可選閱讀 - 使用 autograd 進行向量微積分#
從數學上講,如果您有一個向量值函式 \(\vec{y}=f(\vec{x})\),那麼 \(\vec{y}\) 相對於 \(\vec{x}\) 的梯度是一個雅可比矩陣 \(J\)
總的來說,torch.autograd 是一個用於計算向量-雅可比乘積的引擎。也就是說,給定任何向量 \(\vec{v}\),計算乘積 \(J^{T}\cdot \vec{v}\)
如果 \(\vec{v}\) 恰好是一個標量函式 \(l=g\left(\vec{y}\right)\) 的梯度
那麼根據鏈式法則,向量-雅可比乘積就是 \(l\) 相對於 \(\vec{x}\) 的梯度
向量-雅可比乘積的這個特性是我們上面例子中使用的; external_grad 代表 \(\vec{v}\)。
計算圖#
從概念上講,autograd 在一個由 Function 物件組成的有向無環圖 (DAG) 中記錄資料(張量)和所有已執行的操作(以及生成的新張量)。在該 DAG 中,葉節點是輸入張量,根節點是輸出張量。透過從根節點到葉節點跟蹤此圖,您可以使用鏈式法則自動計算梯度。
在前向傳播中,autograd 同時執行兩項操作:
執行所需的操作以計算結果張量,以及
在 DAG 中維護該操作的*梯度函式*。
當在 DAG 根節點呼叫 .backward() 時,反向傳播啟動。然後,autograd 會
從每個
.grad_fn計算梯度,將它們累積到相應張量的
.grad屬性中,並且使用鏈式法則,一直傳播到葉張量。
下面是我們示例中 DAG 的視覺化表示。圖中,箭頭指示前向傳播的方向。節點代表前向傳播中每個操作的反向函式。藍色的葉節點代表我們的葉張量 a 和 b。
注意
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 的引數外,所有引數都已凍結。唯一計算梯度的引數是 model.fc 的權重和偏置。
# Optimize only the classifier
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
請注意,儘管我們將所有引數都註冊到了最佳化器中,但唯一計算梯度(因此在梯度下降中更新)的引數是分類器的權重和偏置。
相同的排除功能可透過 torch.no_grad() 中的上下文管理器使用。
進一步閱讀:#
指令碼總執行時間: (0 分鐘 0.752 秒)