Gradcheck 機制¶
本說明介紹 gradcheck() 和 gradgradcheck() 函數的運作方式。
它將涵蓋實數值和複數值函數的正向和反向模式 AD,以及高階導數。本說明還涵蓋 gradcheck 的預設行為,以及傳遞 fast_mode=True 引數的情況(以下稱為快速 gradcheck)。
符號和背景資訊¶
在整個說明中,我們將使用以下慣例
- 、、、、、、 和 是實數值向量,而 是複數值向量,可以用兩個實數值向量表示為 。 
- 和 是我們將用於輸入和輸出空間維度的兩個整數。 
- 是我們基本的實數到實數函數,例如 。 
- 是我們基本的複數到實數函數,例如 。 
對於簡單的實數到實數情況,我們將與 相關聯的雅可比矩陣寫成 ,大小為 。此矩陣包含所有偏導數,使得位置 的條目包含 。然後,對於給定的 大小的向量 ,反向模式 AD 會計算量 。另一方面,對於給定的 大小的向量 ,正向模式 AD 會計算量 。
對於包含複數值的函數,情況要複雜得多。我們在這裡只提供要點,完整的描述可以在 複數的 Autograd 中找到。
滿足複變可微性(柯西-黎曼方程式)的約束對於所有實值損失函數來說過於嚴格,因此我們選擇使用 Wirtinger 微積分。在 Wirtinger 微積分的基礎設定中,鏈式法則需要同時使用 Wirtinger 導數(以下稱為 )和共軛 Wirtinger 導數(以下稱為 )。 和 都需要被傳播,因為一般來說,儘管它們的名稱如此,但它們並非彼此的複共軛。
為了避免必須傳播兩個值,對於反向模式自動微分,我們始終假設正在計算其導數的函數是實值函數或更大實值函數的一部分。這個假設意味著我們在反向傳播過程中計算的所有中間梯度也與實值函數相關聯。在實務上,這個假設在進行優化時並不具有限制性,因為此類問題需要實值目標(因為複數沒有自然的順序)。
在這個假設下,使用 和 的定義,我們可以證明 (我們在此處使用 表示複共軛),因此實際上只需要將兩個值中的一個“反向傳播通過圖”,因為另一個值可以很容易地恢復。為了簡化內部計算,PyTorch 使用 作為它在使用者要求梯度時反向傳播並返回的值。與實數情況類似,當輸出實際上在 中時,反向模式自動微分不會計算 而只計算 ,其中給定向量 。
對於正向模式自動微分,我們使用類似的邏輯,在這種情況下,假設該函數是更大函數的一部分,其輸入在 中。在此假設下,我們可以做出類似的聲明,即每個中間結果都對應於一個函數,其輸入在 中,並且在這種情況下,使用 和 定義,我們可以證明對於中間函數, 。為了確保正向和反向模式在一維函數的基本情況下計算相同的量,正向模式也會計算 。與實數情況類似,當輸入實際上在 中時,正向模式自動微分不會計算 ,而只會計算給定向量 的 。
預設反向模式 gradcheck 行為¶
實數到實數函數¶
為了測試函數 ,我們以兩種方式重建大小為 的完整雅可比矩陣 :解析法和數值法。解析版本使用我們的反向模式自動微分,而數值版本使用有限差分法。然後,逐元素比較兩個重建的雅可比矩陣是否相等。
預設實數輸入數值評估¶
如果我們考慮一維函數 () 的基本情況,那麼我們可以使用 維基百科文章 中的基本有限差分公式。為了更好的數值特性,我們使用「中心差分」
此公式可以輕鬆地推廣到多個輸出(),方法是將 設為大小為 的列向量,就像 一樣。在這種情況下,上述公式可以按原樣重複使用,並僅通過兩次使用者函數求值(即 和 )來近似完整的雅可比矩陣。
處理多個輸入()的情況在計算上更為昂貴。在這種情況下,我們會循環遍歷所有輸入,並將 擾動依次應用於 的每個元素。這使我們能夠逐列重建 矩陣。
預設實數輸入的解析求值¶
對於解析求值,我們使用上述事實,即反向模式 AD 計算 。對於單一輸出的函數,我們簡單地使用 來通過單次反向傳遞恢復完整的雅可比矩陣。
對於具有多個輸出的函數,我們使用一個 for 迴圈迭代輸出,其中每個 都是對應於每個輸出的單一熱向量。這允許逐行重建 矩陣。
複數到實數函數¶
為了測試函數 ,其中 ,我們重建包含 的(複數值)矩陣。
預設複數輸入數值評估¶
首先考慮 的基本情況。我們從這篇研究論文(第 3 章)中知道
請注意,上式中的 和 是 導數。為了對其進行數值評估,我們對實數到實數的情況使用上述方法。這使我們能夠計算 矩陣,然後將其乘以 。
請注意,在撰寫本文時,代碼以稍微複雜的方式計算此值
# Code from https://github.com/pytorch/pytorch/blob/58eb23378f2a376565a66ac32c93a316c45b6131/torch/autograd/gradcheck.py#L99-L105
# Notation changes in this code block:
# s here is y above
# x, y here are a, b above
ds_dx = compute_gradient(eps)
ds_dy = compute_gradient(eps * 1j)
# conjugate wirtinger derivative
conj_w_d = 0.5 * (ds_dx + ds_dy * 1j)
# wirtinger derivative
w_d = 0.5 * (ds_dx - ds_dy * 1j)
d[d_idx] = grad_out.conjugate() * conj_w_d + grad_out * w_d.conj()
# Since grad_out is always 1, and W and CW are complex conjugate of each other, the last line ends up computing exactly `conj_w_d + w_d.conj() = conj_w_d + conj_w_d = 2 * conj_w_d`.
預設複數輸入解析評估¶
由於反向模式 AD 已經計算了 導數的兩倍,因此我們在此處對實數到實數的情況使用相同的技巧,並在有多個實數輸出時逐行重建矩陣。
快速反向模式梯度檢查¶
雖然上述的梯度檢查公式非常出色,但為了確保正確性和可除錯性,它的速度非常慢,因為它需要重建完整的雅可比矩陣。本節將介紹一種在不影響正確性的情況下,以更快的方式執行梯度檢查的方法。可除錯性可以透過在偵測到錯誤時新增特殊邏輯來恢復。在這種情況下,我們可以執行重建完整矩陣的預設版本,以便向使用者提供完整的詳細資訊。
這裡的高階策略是找到一個可以透過數值和解析方法有效計算的純量,並且它能夠充分表示由慢速梯度檢查計算的完整矩陣,以確保它能夠捕捉雅可比矩陣中的任何差異。
實數到實數函數的快速梯度檢查¶
我們這裡要計算的純量是 ,給定一個隨機向量 和一個隨機單位範數向量 。
對於數值評估,我們可以有效地計算
然後我們計算此向量和 的點積,以獲得我們感興趣的純量值。
對於解析版本,我們可以使用反向模式自動微分直接計算 。然後我們與 進行點積以獲得期望值。
複數到實數函數的快速梯度檢查¶
與實數到實數的情況類似,我們想要對完整矩陣進行降維。但是 矩陣是複數值的,所以在這種情況下,我們將與複數純量進行比較。
由於我們可以在數值情況下有效計算的內容受到一些限制,並且為了將數值評估的次數保持在最低限度,我們計算以下(儘管令人驚訝)的純量值
其中 , 且 。
快速複數輸入數值評估¶
我們首先考慮如何使用數值方法計算 。為此,請記住我們正在考慮 ,其中 ,並且 ,我們將其改寫如下:
在這個公式中,我們可以看到 和 可以像實數到實數情況的快速版本一樣進行評估。一旦計算出這些實數值,我們就可以重建右側的複數向量,並與實數值 向量進行點積。
快速複數輸入解析評估¶
對於解析情況,事情更簡單,我們將公式改寫為:
因此,我們可以利用反向模式自動微分法提供了一種有效計算 的方法,然後將其實部與 進行點積,將其虛部與 進行點積,最後重建最終的複數純量 。
為什麼不使用複數 ¶
在這個時候,您可能會好奇為什麼我們不選擇一個複數的 並直接進行 的降維。為了深入探討,在本段中,我們將使用 的複數形式,記為 。使用這樣的複數 ,問題在於當進行數值評估時,我們需要計算...
這需要四次實數到實數的有限差分計算(與上述方法相比多了一倍)。由於此方法沒有更多自由度(實數變數數量相同),並且我們嘗試在此處獲得最快的計算速度,因此我們使用上述其他公式。
複數輸出函數的快速梯度檢查¶
就像在慢速情況下一樣,我們考慮兩個實值函數,並對每個函數使用上述適當的規則。