作者:Vaibhav Kumar
編譯:ronghuaiyang
導讀
這篇文章詳細解析了PyTorch的自動求導機制,讓你了解PyTorch的核心魔法。
在這個過程中,它從不顯式地構造整個雅可比矩陣。直接計算JVP通常更簡單、更有效。
我們都同意,當涉及到大型神經網絡時,我們都不擅長微積分。通過顯式求解數學方程來計算這樣大的復合函數的梯度是不現實的,特別是這些曲線存在於大量的維數中,是無法理解的。
要處理14維空間中的超平面,想像一個三維空間,大聲地對自己說「14」。每個人都這麼做——Geoffrey Hinton
這就是PyTorch的autograd發揮作用的地方。它抽象了複雜的數學,幫助我們「神奇地」計算高維曲線的梯度,只需要幾行代碼。這篇文章試圖描述autograd的魔力。
PyTorch基礎
在進一步討論之前,我們需要了解一些基本的PyTorch概念。
張量:簡單地說,它只是PyTorch中的一個n維數組。張量支持一些額外的增強,這使它們獨一無二:除了CPU,它們可以加載或GPU更快的計算。在設置.requires_grad = True的時候,他們開始形成一個反向圖,跟蹤應用於他們的每個操作,使用所謂的動態計算圖(DCG)計算梯度(後面會進一步解釋)。
在早期版本的PyTorch中,使用torch.autograd.Variable類用於創建支持梯度計算和操作跟蹤的張量,但截至PyTorch v0.4.0,Variable類已被禁用。torch.Tensor和torch.autograd.Variable現在是同一個類。更準確地說, torch.Tensor能夠跟蹤歷史並表現得像舊的Variable。
import torch
import numpy as np
x = torch.randn(2, 2, requires_grad = True)
# From numpy
x = np.array([1., 2., 3.]) #Only Tensors of floating point dtype can require gradients
x = torch.from_numpy(x)
# Now enable gradient
x.requires_grad_(True)
# _ above makes the change in-place (its a common pytorch thing)
創建啟用梯度的張量的各種方法的代碼
注意:根據PyTorch的設計,梯度只能計算浮點張量,這就是為什麼我創建了一個浮點類型的numpy數組,然後將它設置為啟用梯度的PyTorch張量。
Autograd:這個類是一個計算導數的引擎(更精確地說是雅克比向量積)。它記錄了梯度張量上所有操作的一個圖,並創建了一個稱為動態計算圖的非循環圖。這個圖的葉節點是輸入張量,根節點是輸出張量。梯度是通過跟蹤從根到葉的圖形,並使用鏈式法則將每個梯度相乘來計算的。
神經網絡和反向傳播
神經網絡只不過是經過精心調整(訓練)以輸出所需結果的復合數學函數。調整或訓練是通過一種稱為反向傳播的出色算法完成的。反向傳播用來計算相對於輸入權值的損失梯度,以便以後更新權值,最終減少損失。
在某種程度上,反向傳播只是鏈式法則的一個花哨的名字—— Jeremy Howard
創建和訓練神經網絡包括以下基本步驟:
- 定義體系結構
- 使用輸入數據在體系結構上向前傳播
- 計算損失
- 反向傳播,計算每個權重的梯度
- 使用學習率更新權重
損失變化引起的輸入權值的微小變化稱為該權值的梯度,並使用反向傳播計算。然後使用梯度來更新權值,使用學習率來整體減少損失並訓練神經網絡。
這是以疊代的方式完成的。對於每個疊代,都要計算幾個梯度,並為存儲這些梯度函數構建一個稱為計算圖的東西。PyTorch通過構建一個動態計算圖(DCG)來實現這一點。此圖在每次疊代中從頭構建,為梯度計算提供了最大的靈活性。例如,對於前向操作(函數)Mul ,向後操作函數MulBackward被動態集成到後向圖中以計算梯度。
動態計算圖
支持梯度的張量(變量)和函數(操作)結合起來創建動態計算圖。數據流和應用於數據的操作在運行時定義,從而動態地構造計算圖。這個圖是由底層的autograd類動態生成的。你不必在啟動訓練之前對所有可能的路徑進行編碼——你運行的是你所區分的。
一個簡單的DCG用於兩個張量的乘法會是這樣的:
帶有requires_grad = False的DCG
圖中的每個點輪廓框是一個變量,紫色矩形框是一個操作。
每個變量對象都有幾個成員,其中一些成員是:
Data:它是一個變量持有的數據。x持有一個1x1張量,其值等於1.0,而y持有2.0。z持有兩個的乘積,即2.0。
requires_grad:這個成員(如果為true)開始跟蹤所有的操作歷史,並形成一個用於梯度計算的向後圖。對於任意張量a,可以按如下方式對其進行原地處理:a.requires_grad_(True)。
grad: grad保存梯度值。如果requires_grad 為False,它將持有一個None值。即使requires_grad 為真,它也將持有一個None值,除非從其他節點調用.backward()函數。例如,如果你對out關於x計算梯度,調用out.backward(),則x.grad的值為∂out/∂x。
grad_fn:這是用來計算梯度的向後函數。
is_leaf:如果:
- 它被一些函數顯式地初始化,比如x = torch.tensor(1.0)或x = torch.randn(1, 1)(基本上是本文開頭討論的所有張量初始化方法)。
- 它是在張量的操作之後創建的,所有張量都有requires_grad = False。
- 它是通過對某個張量調用.detach()方法創建的。
在調用backward()時,只計算requires_grad和is_leaf同時為真的節點的梯度。
當打開 requires_grad = True時,PyTorch將開始跟蹤操作,並在每個步驟中存儲梯度函數,如下所示:
requires_grad = True的DCG
在PyTorch下生成上圖的代碼是:
Backward()函數
Backward函數實際上是通過傳遞參數(默認情況下是1x1單位張量)來計算梯度的,它通過Backward圖一直到每個葉節點,每個葉節點都可以從調用的根張量追溯到葉節點。然後將計算出的梯度存儲在每個葉節點的.grad中。請記住,在正向傳遞過程中已經動態生成了後向圖。backward函數僅使用已生成的圖形計算梯度,並將其存儲在葉節點中。
讓我們分析以下代碼:
import torch
# Creating the graph
x = torch.tensor(1.0, requires_grad = True)
z = x ** 3
z.backward() #Computes the gradient
print(x.grad.data) #Prints '3' which is dz/dx
需要注意的一件重要事情是,當調用z.backward()時,一個張量會自動傳遞為z.backward(torch.tensor(1.0))。torch.tensor(1.0)是用來終止鏈式法則梯度乘法的外部梯度。這個外部梯度作為輸入傳遞給MulBackward函數,以進一步計算x的梯度。傳遞到.backward()中的張量的維數必須與正在計算梯度的張量的維數相同。例如,如果梯度支持張量x和y如下:
x = torch.tensor([0.0, 2.0, 8.0], requires_grad = True)
y = torch.tensor([5.0 , 1.0 , 7.0], requires_grad = True)
z = x * y
然後,要計算z關於x或者y的梯度,需要將一個外部梯度傳遞給z.backward()函數,如下所示:
z.backward(torch.FloatTensor([1.0, 1.0, 1.0])
z.backward() 會給出 RuntimeError: grad can be implicitly created only for scalar outputs
反向函數傳遞的張量就像梯度加權輸出的權值。從數學上講,這是一個向量乘以非標量張量的雅可比矩陣(本文將進一步討論),因此它幾乎總是一個維度的單位張量,與 backward張量相同,除非需要計算加權輸出。
tldr :向後圖是由autograd類在向前傳遞過程中自動動態創建的。Backward()只是通過將其參數傳遞給已經生成的反向圖來計算梯度。
數學—雅克比矩陣和向量
從數學上講,autograd類只是一個雅可比向量積計算引擎。雅可比矩陣是一個非常簡單的單詞,它表示兩個向量所有可能的偏導數。它是一個向量相對於另一個向量的梯度。
注意:在這個過程中,PyTorch從不顯式地構造整個雅可比矩陣。直接計算JVP (Jacobian vector product)通常更簡單、更有效。
如果一個向量X = [x1, x2,…xn]通過f(X) = [f1, f2,…fn]來計算其他向量,則雅可比矩陣(J)包含以下所有偏導組合:
雅克比矩陣
上面的矩陣表示f(X)相對於X的梯度。
假設一個啟用PyTorch梯度的張量X:
X = [x1,x2,…,xn](假設這是某個機器學習模型的權值)
X經過一些運算形成一個向量Y
Y = f(X) = [y1, y2,…,ym]
然後使用Y計算標量損失l。假設向量v恰好是標量損失l關於向量Y的梯度,如下:
向量v稱為grad_tensor,並作為參數傳遞給backward() 函數。
為了得到損失的梯度l關於權重X的梯度,雅可比矩陣J是向量乘以向量v
這種計算雅可比矩陣並將其與向量v相乘的方法使PyTorch能夠輕鬆地為非標量輸出提供外部梯度。
英文原文:https://towardsdatascience.com/pytorch-autograd-understanding-the-heart-of-pytorchs-magic-2686cd94ec95
請長按或掃描二維碼關注本公眾號