捷徑

簡介 || 張量 || Autograd || 建立模型 || TensorBoard 支援 || 訓練模型 || 模型理解

Autograd 的基本原理

建立於:2021 年 11 月 30 日 | 最後更新:2024 年 2 月 26 日 | 最後驗證:2024 年 11 月 05 日

請跟隨以下影片或在 youtube 上觀看。

PyTorch 的 Autograd 功能是讓 PyTorch 在建構機器學習專案時具有彈性且快速的原因之一。它允許快速且輕鬆地計算複雜運算的多個偏導數(也稱為梯度)。此操作是基於反向傳播的神經網路學習的核心。

autograd 的強大之處在於它在執行階段動態追蹤您的計算,這表示如果您的模型具有決策分支,或長度直到執行階段才知道的迴圈,計算仍然會被正確追蹤,並且您將獲得正確的梯度來驅動學習。將此與您的模型在 Python 中建構的事實相結合,提供了比依賴對更嚴格結構化模型的靜態分析來計算梯度的框架更大的彈性。

我們為什麼需要 Autograd?

機器學習模型是一個函數,具有輸入和輸出。對於此討論,我們將輸入視為一個 i 維向量 \(\vec{x}\),其元素為 \(x_{i}\)。然後,我們可以將模型 M 表示為輸入的向量值函數:\(\vec{y} = \vec{M}(\vec{x})\)。(我們將 M 的輸出值視為一個向量,因為一般來說,一個模型可以有任意數量的輸出。)

由於我們主要將在訓練的背景下討論 autograd,因此我們感興趣的輸出將是模型的損失。損失函數 L(\(\vec{y}\)) = L(\(\vec{M}\)(\(\vec{x}\))) 是模型輸出的單值純量函數。此函數表示我們的模型預測與特定輸入的理想輸出之間的差距。注意:在這一點之後,我們經常會省略向量符號,因為在語境上應該很清楚 - 例如,\(y\) 而不是 \(\vec y\)

在訓練模型時,我們希望最小化損失。在完美模型的理想情況下,這意味著調整其學習權重 - 也就是說,函數的可調整參數 - 使得所有輸入的損失都為零。在現實世界中,這意味著一個迭代的過程,即調整學習權重,直到我們看到對於各種輸入,我們獲得了可以接受的損失。

我們如何決定調整權重的距離和方向?我們想要最小化損失,這意味著使其對輸入的一階導數等於 0:\(\frac{\partial L}{\partial x} = 0\)

但是請回想一下,損失並非直接來自輸入,而是模型輸出的函數(模型輸出直接是輸入的函數),\(\frac{\partial L}{\partial x}\) = \(\frac{\partial {L({\vec y})}}{\partial x}\)。根據微分的鏈式法則,我們有 \(\frac{\partial {L({\vec y})}}{\partial x}\) = \(\frac{\partial L}{\partial y}\frac{\partial y}{\partial x}\) = \(\frac{\partial L}{\partial y}\frac{\partial M(x)}{\partial x}\)

\(\frac{\partial M(x)}{\partial x}\) 是事情變得複雜的地方。如果我們再次使用鏈式法則展開該表達式,則模型輸出相對於其輸入的偏導數將涉及許多局部偏導數,這些偏導數遍及每個相乘的學習權重、每個激活函數以及模型中的每個其他數學轉換。每個此類偏導數的完整表達式是計算圖中每條可能路徑的局部梯度之乘積之和,這些路徑以我們要測量其梯度的變數結尾。

特別是,我們對學習權重上的梯度感興趣 - 它們告訴我們改變每個權重的方向,以使損失函數更接近於零。

由於此類局部導數(每個對應於模型計算圖中的一條單獨路徑)的數量將隨著神經網路的深度而呈指數級增長,因此計算它們的複雜性也會呈指數級增長。這就是 autograd 發揮作用的地方:它追蹤每個計算的歷史記錄。您的 PyTorch 模型中的每個計算張量都帶有其輸入張量的歷史記錄以及用於建立它的函數。再加上 PyTorch 函數旨在作用於張量,每個函數都有一個內建的實作來計算自己的導數,這大大加快了學習所需的局部導數的計算速度。

一個簡單的例子

上面講了很多理論 - 但在實踐中使用 autograd 是什麼樣子的呢?

讓我們先從一個簡單的例子開始。首先,我們將進行一些匯入,以便繪製我們的結果

# %matplotlib inline

import torch

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import math

接下來,我們會建立一個輸入張量,其中包含在區間 \([0, 2{\pi}]\) 上均勻間隔的值,並指定 requires_grad=True。(就像大多數建立張量的函式一樣,torch.linspace() 接受一個可選的 requires_grad 選項。)設定此標誌表示在接下來的每個計算中,autograd 都會將計算的歷史記錄累積到該計算的輸出張量中。

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
print(a)
tensor([0.0000, 0.2618, 0.5236, 0.7854, 1.0472, 1.3090, 1.5708, 1.8326, 2.0944,
        2.3562, 2.6180, 2.8798, 3.1416, 3.4034, 3.6652, 3.9270, 4.1888, 4.4506,
        4.7124, 4.9742, 5.2360, 5.4978, 5.7596, 6.0214, 6.2832],
       requires_grad=True)

接下來,我們會執行一個計算,並根據其輸入繪製其輸出

b = torch.sin(a)
plt.plot(a.detach(), b.detach())
autogradyt tutorial
[<matplotlib.lines.Line2D object at 0x7f06962a8f40>]

讓我們更仔細地看看張量 b。當我們列印它時,我們會看到一個指標,表明它正在追蹤其計算歷史記錄

print(b)
tensor([ 0.0000e+00,  2.5882e-01,  5.0000e-01,  7.0711e-01,  8.6603e-01,
         9.6593e-01,  1.0000e+00,  9.6593e-01,  8.6603e-01,  7.0711e-01,
         5.0000e-01,  2.5882e-01, -8.7423e-08, -2.5882e-01, -5.0000e-01,
        -7.0711e-01, -8.6603e-01, -9.6593e-01, -1.0000e+00, -9.6593e-01,
        -8.6603e-01, -7.0711e-01, -5.0000e-01, -2.5882e-01,  1.7485e-07],
       grad_fn=<SinBackward0>)

這個 grad_fn 給了我們一個提示,即當我們執行反向傳播步驟並計算梯度時,我們需要計算 \(\sin(x)\) 對於所有這個張量輸入的導數。

讓我們執行更多計算

c = 2 * b
print(c)

d = c + 1
print(d)
tensor([ 0.0000e+00,  5.1764e-01,  1.0000e+00,  1.4142e+00,  1.7321e+00,
         1.9319e+00,  2.0000e+00,  1.9319e+00,  1.7321e+00,  1.4142e+00,
         1.0000e+00,  5.1764e-01, -1.7485e-07, -5.1764e-01, -1.0000e+00,
        -1.4142e+00, -1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00,
        -1.7321e+00, -1.4142e+00, -1.0000e+00, -5.1764e-01,  3.4969e-07],
       grad_fn=<MulBackward0>)
tensor([ 1.0000e+00,  1.5176e+00,  2.0000e+00,  2.4142e+00,  2.7321e+00,
         2.9319e+00,  3.0000e+00,  2.9319e+00,  2.7321e+00,  2.4142e+00,
         2.0000e+00,  1.5176e+00,  1.0000e+00,  4.8236e-01, -3.5763e-07,
        -4.1421e-01, -7.3205e-01, -9.3185e-01, -1.0000e+00, -9.3185e-01,
        -7.3205e-01, -4.1421e-01,  4.7684e-07,  4.8236e-01,  1.0000e+00],
       grad_fn=<AddBackward0>)

最後,讓我們計算一個單元素輸出。當您在沒有參數的張量上呼叫 .backward() 時,它會期望呼叫張量僅包含一個單一元素,就像計算損失函數時的情況一樣。

out = d.sum()
print(out)
tensor(25., grad_fn=<SumBackward0>)

每個儲存在我們張量中的 grad_fn 都允許您透過其 next_functions 屬性,將計算一路回溯到其輸入。我們可以在下面看到,在 d 上向下鑽取這個屬性會顯示所有先前張量的梯度函數。請注意,a.grad_fn 被報告為 None,表示這是函數的輸入,沒有自己的歷史記錄。

print('d:')
print(d.grad_fn)
print(d.grad_fn.next_functions)
print(d.grad_fn.next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions[0][0].next_functions)
print('\nc:')
print(c.grad_fn)
print('\nb:')
print(b.grad_fn)
print('\na:')
print(a.grad_fn)
d:
<AddBackward0 object at 0x7f06964a8a90>
((<MulBackward0 object at 0x7f06964a8ac0>, 0), (None, 0))
((<SinBackward0 object at 0x7f06964a8ac0>, 0), (None, 0))
((<AccumulateGrad object at 0x7f06964a8a90>, 0),)
()

c:
<MulBackward0 object at 0x7f06964a8ac0>

b:
<SinBackward0 object at 0x7f06964a8ac0>

a:
None

有了所有這些機制,我們如何獲得導數呢?您可以在輸出上呼叫 backward() 方法,並檢查輸入的 grad 屬性以檢查梯度

out.backward()
print(a.grad)
plt.plot(a.detach(), a.grad.detach())
autogradyt tutorial
tensor([ 2.0000e+00,  1.9319e+00,  1.7321e+00,  1.4142e+00,  1.0000e+00,
         5.1764e-01, -8.7423e-08, -5.1764e-01, -1.0000e+00, -1.4142e+00,
        -1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00, -1.7321e+00,
        -1.4142e+00, -1.0000e+00, -5.1764e-01,  2.3850e-08,  5.1764e-01,
         1.0000e+00,  1.4142e+00,  1.7321e+00,  1.9319e+00,  2.0000e+00])

[<matplotlib.lines.Line2D object at 0x7f06cc7e1000>]

回想一下我們為到達此處所採取的計算步驟

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
b = torch.sin(a)
c = 2 * b
d = c + 1
out = d.sum()

添加一個常數,就像我們計算 d 時所做的那樣,不會改變導數。剩下的是 \(c = 2 * b = 2 * \sin(a)\),其導數應為 \(2 * \cos(a)\)。看看上面的圖,這正是我們所看到的。

請注意,只有計算的葉節點才會計算其梯度。 例如,如果您嘗試 print(c.grad),您會得到 None。 在這個簡單的範例中,只有輸入是葉節點,因此只有它會計算梯度。

訓練中的 Autograd

我們簡要地了解了 autograd 的工作原理,但是當它用於其預期目的時,它看起來如何呢? 讓我們定義一個小型模型,並檢查它在單一批次訓練後如何變化。 首先,定義一些常數、我們的模型以及一些輸入和輸出的佔位符

BATCH_SIZE = 16
DIM_IN = 1000
HIDDEN_SIZE = 100
DIM_OUT = 10

class TinyModel(torch.nn.Module):

    def __init__(self):
        super(TinyModel, self).__init__()

        self.layer1 = torch.nn.Linear(DIM_IN, HIDDEN_SIZE)
        self.relu = torch.nn.ReLU()
        self.layer2 = torch.nn.Linear(HIDDEN_SIZE, DIM_OUT)

    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

some_input = torch.randn(BATCH_SIZE, DIM_IN, requires_grad=False)
ideal_output = torch.randn(BATCH_SIZE, DIM_OUT, requires_grad=False)

model = TinyModel()

您可能注意到的一件事是,我們從未為模型的層指定 requires_grad=True。 在 torch.nn.Module 的子類別中,我們假設我們要追蹤層權重的梯度以進行學習。

如果我們查看模型的層,我們可以檢查權重的值,並驗證尚未計算任何梯度

print(model.layer2.weight[0][0:10]) # just a small slice
print(model.layer2.weight.grad)
tensor([ 0.0920,  0.0916,  0.0121,  0.0083, -0.0055,  0.0367,  0.0221, -0.0276,
        -0.0086,  0.0157], grad_fn=<SliceBackward0>)
None

讓我們看看當我們執行一個訓練批次時,這會如何改變。 對於損失函數,我們將僅使用 predictionideal_output 之間歐幾里得距離的平方,並且我們將使用基本的隨機梯度下降最佳化器。

tensor(211.2634, grad_fn=<SumBackward0>)

現在,讓我們呼叫 loss.backward() 並看看會發生什麼

tensor([ 0.0920,  0.0916,  0.0121,  0.0083, -0.0055,  0.0367,  0.0221, -0.0276,
        -0.0086,  0.0157], grad_fn=<SliceBackward0>)
tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
         0.1732, -5.3835])

我們可以看見,每個學習權重的梯度都已經計算出來,但是權重保持不變,因為我們尚未執行最佳化器。 最佳化器負責根據計算出的梯度更新模型權重。

tensor([ 0.0791,  0.0886,  0.0098,  0.0064, -0.0106,  0.0293,  0.0186, -0.0300,
        -0.0088,  0.0211], grad_fn=<SliceBackward0>)
tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
         0.1732, -5.3835])

您應該會看到 layer2 的權重已更改。

關於這個過程的一件重要事情:在呼叫 optimizer.step() 之後,您需要呼叫 optimizer.zero_grad(),否則每次您執行 loss.backward() 時,學習權重上的梯度都會累積

print(model.layer2.weight.grad[0][0:10])

for i in range(0, 5):
    prediction = model(some_input)
    loss = (ideal_output - prediction).pow(2).sum()
    loss.backward()

print(model.layer2.weight.grad[0][0:10])

optimizer.zero_grad(set_to_none=False)

print(model.layer2.weight.grad[0][0:10])
tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
         0.1732, -5.3835])
tensor([ 19.2095, -15.9459,   8.3306,  11.5096,   9.5471,   0.5391,  -0.3370,
          8.6386,  -2.5141, -30.1419])
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

在執行上述儲存格後,您應該會看到,在多次執行 loss.backward() 後,大多數梯度的幅度會大得多。 在執行下一個訓練批次之前未能將梯度歸零,將會導致梯度以這種方式爆炸,從而導致不正確和不可預測的學習結果。

關閉和開啟 Autograd

在某些情況下,您需要對是否啟用 autograd 進行細粒度的控制。 根據具體情況,有多種方法可以執行此操作。

最簡單的方法是直接更改張量上的 requires_grad 標誌

a = torch.ones(2, 3, requires_grad=True)
print(a)

b1 = 2 * a
print(b1)

a.requires_grad = False
b2 = 2 * a
print(b2)
tensor([[1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
tensor([[2., 2., 2.],
        [2., 2., 2.]], grad_fn=<MulBackward0>)
tensor([[2., 2., 2.],
        [2., 2., 2.]])

在上面的儲存格中,我們看到 b1 有一個 grad_fn(即,追蹤的計算歷史記錄),這是我們所期望的,因為它是從一個開啟了 autograd 的張量 a 中導出的。 當我們使用 a.requires_grad = False 顯式關閉 autograd 時,計算歷史記錄不再被追蹤,正如我們在計算 b2 時所看到的那樣。

如果您只需要暫時關閉 autograd,則更好的方法是使用 torch.no_grad()

a = torch.ones(2, 3, requires_grad=True) * 2
b = torch.ones(2, 3, requires_grad=True) * 3

c1 = a + b
print(c1)

with torch.no_grad():
    c2 = a + b

print(c2)

c3 = a * b
print(c3)
tensor([[5., 5., 5.],
        [5., 5., 5.]], grad_fn=<AddBackward0>)
tensor([[5., 5., 5.],
        [5., 5., 5.]])
tensor([[6., 6., 6.],
        [6., 6., 6.]], grad_fn=<MulBackward0>)

torch.no_grad() 也可以用作函數或方法裝飾器

def add_tensors1(x, y):
    return x + y

@torch.no_grad()
def add_tensors2(x, y):
    return x + y


a = torch.ones(2, 3, requires_grad=True) * 2
b = torch.ones(2, 3, requires_grad=True) * 3

c1 = add_tensors1(a, b)
print(c1)

c2 = add_tensors2(a, b)
print(c2)
tensor([[5., 5., 5.],
        [5., 5., 5.]], grad_fn=<AddBackward0>)
tensor([[5., 5., 5.],
        [5., 5., 5.]])

有一個對應的上下文管理器 torch.enable_grad(),用於在 autograd 尚未開啟時開啟它。 它也可以用作裝飾器。

最後,您可能有一個需要梯度追蹤的張量,但是您想要一個不追蹤梯度的副本。 對於這個,我們有 Tensor 物件的 detach() 方法 - 它建立一個與計算歷史記錄分離的張量副本

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

print(x)
print(y)
tensor([0.0670, 0.3890, 0.7264, 0.3559, 0.6584], requires_grad=True)
tensor([0.0670, 0.3890, 0.7264, 0.3559, 0.6584])

當我們想要繪製一些張量的圖形時,我們在上面這樣做了。 這是因為 matplotlib 期望 NumPy 陣列作為輸入,並且對於 requires_grad=True 的張量,不會啟用從 PyTorch 張量到 NumPy 陣列的隱式轉換。 建立一個分離的副本可以讓我們繼續前進。

Autograd 和原地操作

到目前為止,在本筆記本中的每個範例中,我們都使用變數來捕獲計算的中間值。 Autograd 需要這些中間值來執行梯度計算。 因此,在使用 autograd 時,您必須小心使用原地操作。 這樣做可能會破壞您需要在 backward() 呼叫中計算導數的資訊。 如果您嘗試對需要 autograd 的葉變數執行原地操作,PyTorch 甚至會阻止您,如下所示。

注意

以下程式碼儲存格會拋出執行階段錯誤。 這是預期的。

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
torch.sin_(a)

Autograd 分析器

Autograd 會詳細追蹤您計算的每一個步驟。這樣的計算歷史記錄,結合時間資訊,會是一個方便的分析器 - 而 autograd 已經內建了這個功能。這裡有一個簡單的使用範例

device = torch.device('cpu')
run_on_gpu = False
if torch.cuda.is_available():
    device = torch.device('cuda')
    run_on_gpu = True

x = torch.randn(2, 3, requires_grad=True)
y = torch.rand(2, 3, requires_grad=True)
z = torch.ones(2, 3, requires_grad=True)

with torch.autograd.profiler.profile(use_cuda=run_on_gpu) as prf:
    for _ in range(1000):
        z = (z / x) * y

print(prf.key_averages().table(sort_by='self_cpu_time_total'))
/var/lib/workspace/beginner_source/introyt/autogradyt_tutorial.py:485: FutureWarning:

The attribute `use_cuda` will be deprecated soon, please use ``use_device = 'cuda'`` instead.

-------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------
                     Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls
-------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------
          cudaEventRecord        38.95%       8.633ms        38.95%       8.633ms       2.158us       0.000us         0.00%       0.000us       0.000us          4000
                aten::div        30.59%       6.780ms        30.59%       6.780ms       6.780us      11.369ms        50.20%      11.369ms      11.369us          1000
                aten::mul        30.41%       6.740ms        30.41%       6.740ms       6.740us      11.278ms        49.80%      11.278ms      11.278us          1000
    cudaDeviceSynchronize         0.06%      13.809us         0.06%      13.809us      13.809us       0.000us         0.00%       0.000us       0.000us             1
-------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------
Self CPU time total: 22.166ms
Self CUDA time total: 22.647ms

分析器也可以標記個別的程式碼子區塊、按輸入張量的形狀區分資料,並將資料匯出為 Chrome 追蹤工具檔案。有關 API 的完整詳細資訊,請參閱文件

進階主題:更多 Autograd 細節和高階 API

如果您有一個具有 n 維輸入和 m 維輸出的函式,\(\vec{y}=f(\vec{x})\),則完整的梯度是每個輸出相對於每個輸入的導數的矩陣,稱為雅可比矩陣 (Jacobian)

\[J = \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)\]

如果您有第二個函式,\(l=g\left(\vec{y}\right)\),它接受 m 維輸入(也就是說,與上述輸出相同的維度),並傳回純量輸出,您可以將其相對於 \(\vec{y}\) 的梯度表示為列向量,\(v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}\) - 這實際上只是一個單欄雅可比矩陣。

更具體地說,將第一個函式想像為您的 PyTorch 模型(可能具有許多輸入和許多輸出),將第二個函式想像為損失函式(以模型的輸出作為輸入,並以損失值作為純量輸出)。

如果我們將第一個函式的雅可比矩陣乘以第二個函式的梯度,並應用鏈式法則,我們得到

\[J^{T}\cdot 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)\]

注意:您也可以使用等效的運算 \(v^{T}\cdot J\),並取回一個行向量。

產生的列向量是第二個函式相對於第一個函式的輸入的梯度 - 或者在我們的模型和損失函式的情況下,是損失相對於模型輸入的梯度。

``torch.autograd`` 是一個用於計算這些乘積的引擎。 這就是我們在反向傳播期間累積學習權重的梯度的方式。

因此,backward() 呼叫可以接受一個可選的向量輸入。這個向量表示張量上的一組梯度,這些梯度乘以其前面的 autograd 追蹤張量的雅可比矩陣。讓我們嘗試一個帶有小向量的具體範例

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)
tensor([  299.4868,   425.4009, -1082.9885], grad_fn=<MulBackward0>)

如果我們現在嘗試呼叫 y.backward(),我們會收到一個運行時錯誤,並且訊息說明只能為純量輸出隱式計算梯度。對於多維輸出,autograd 期望我們提供這些三個輸出的梯度,它可以將其乘到雅可比矩陣中

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float) # stand-in for gradients
y.backward(v)

print(x.grad)
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

(請注意,輸出梯度都與 2 的冪相關 - 這正是我們從重複的加倍運算中預期的。)

高階 API

autograd 上有一個 API,可讓您直接存取重要的微分矩陣和向量運算。特別是,它允許您計算特定函式對於特定輸入的雅可比矩陣和黑塞矩陣 (Hessian)。(黑塞矩陣就像雅可比矩陣一樣,但表示所有偏二階導數。)它還提供了用於對這些矩陣進行向量乘積的方法。

讓我們取得一個簡單函式的雅可比矩陣,該函式針對 2 個單元素輸入進行評估

def exp_adder(x, y):
    return 2 * x.exp() + 3 * y

inputs = (torch.rand(1), torch.rand(1)) # arguments for the function
print(inputs)
torch.autograd.functional.jacobian(exp_adder, inputs)
(tensor([0.7212]), tensor([0.2079]))

(tensor([[4.1137]]), tensor([[3.]]))

如果您仔細觀察,第一個輸出應該等於 \(2e^x\)(因為 \(e^x\) 的導數是 \(e^x\)),第二個值應該是 3。

當然,您可以使用更高階的張量來執行此操作

inputs = (torch.rand(3), torch.rand(3)) # arguments for the function
print(inputs)
torch.autograd.functional.jacobian(exp_adder, inputs)
(tensor([0.2080, 0.2604, 0.4415]), tensor([0.5220, 0.9867, 0.4288]))

(tensor([[2.4623, 0.0000, 0.0000],
        [0.0000, 2.5950, 0.0000],
        [0.0000, 0.0000, 3.1102]]), tensor([[3., 0., 0.],
        [0., 3., 0.],
        [0., 0., 3.]]))

torch.autograd.functional.hessian() 方法的工作方式相同(假設您的函式是二次可微的),但會傳回所有二階導數的矩陣。

如果您提供向量,還有一個函數可以直接計算向量-雅可比矩陣乘積

def do_some_doubling(x):
    y = x * 2
    while y.data.norm() < 1000:
        y = y * 2
    return y

inputs = torch.randn(3)
my_gradients = torch.tensor([0.1, 1.0, 0.0001])
torch.autograd.functional.vjp(do_some_doubling, inputs, v=my_gradients)
(tensor([-665.7186, -866.7054,  -58.4194]), tensor([1.0240e+02, 1.0240e+03, 1.0240e-01]))

torch.autograd.functional.jvp() 方法執行與 vjp() 相同的矩陣乘法,但運算元反轉。vhp()hvp() 方法對向量-黑塞矩陣乘積執行相同的操作。

如需更多資訊,包括效能注意事項,請參閱功能 API 的文件

指令碼的總運行時間:(0 分鐘 0.764 秒)

由 Sphinx-Gallery 產生的圖庫

文件

存取 PyTorch 的完整開發人員文件

檢視文件

教學

取得針對初學者和進階開發人員的深入教學

檢視教學

資源

尋找開發資源並取得您問題的解答

檢視資源