- 深度強化學習實踐(原書第2版)
- (俄)馬克西姆·拉潘
- 2100字
- 2021-08-18 17:39:20
3.2 梯度
即便對GPU的支持是透明的,如果沒有“殺手锏”——梯度的自動計算——所有與張量有關的計算都將變得很復雜。這個功能最初是在Caffe工具庫中實現的,然后成為DL庫中約定俗成的標準。
手動計算梯度實現和調試起來都非常痛苦,即使是最簡單的神經網絡(Neural Network,NN)。你必須計算所有函數的導數,應用鏈式法則,然后計算結果,并祈禱計算準確。對于理解DL的具體細節來說,這可能是一個非常有用的練習,但你肯定不想一遍又一遍地在不同的NN架構中重復計算。
幸運的是,那些日子已經過去了,就像使用烙鐵和真空管編寫硬件程序一樣,都過去了!現在,定義一個數百層的NN只需要從預先定義好的模塊中組裝即可,在一些極端情況下,也可以手動定義轉換表達式。
所有的梯度都會仔細計算好,通過反向傳播應用于網絡。為了能夠做到這一點,需要根據所使用的DL庫來定義網絡架構,它可能在細節上有所不同,但大體是相同的——就是必須定義好網絡輸入輸出的順序(見圖3.2)。

圖3.2 流經NN的數據和梯度
最根本的區別是如何計算梯度。有兩種方法:
- 靜態圖:在這種方法中,需要提前定義計算,并且以后也不能更改。在進行任何計算之前,DL庫將對圖進行處理和優化。此模型在TensorFlow(<2的版本)、Theano和許多其他DL工具庫中均已實現。
- 動態圖:不需要預先精確地定義將要執行的圖;只需要在實際數據上執行希望用于數據轉換的操作。在此期間,庫將記錄執行的操作的順序,當要求它計算梯度時,它將展開其操作歷史,積累網絡參數的梯度。這種方法也稱為notebook gradient,它已在PyTorch、Chainer和一些其他庫中實現。
兩種方法各有優缺點。例如,靜態圖通常更快,因為所有的計算都可以轉移到GPU,從而最小化數據傳輸開銷。此外,在靜態圖中,庫可以更自由地優化在圖中執行計算的順序,甚至可以刪除圖的某些部分。
另一方面,雖然動態圖的計算開銷較大,但它給了開發者更多的自由。例如,開發者可以說“對這部分數據,可以將這個網絡應用兩次,對另一部分數據,則使用一個完全不同的模型,并用批的均值修剪梯度。”動態圖模型的另一個非常吸引人的優點是,它可以通過一種更Pythonic的方式自然地表達轉換。最后,它只是一個有很多函數的Python庫,所以只需調用它們,讓庫發揮作用就可以了。
兼容性說明
PyTorch從1.0版本起就已經支持即時(Just-In-Time,JIT)編譯器,該編譯器支持PyTorch代碼并將其導入所謂的TorchScript中。這是一種中間表示形式,它可以在生產環境中更快地執行,并且不需要Python依賴。
張量和梯度
PyTorch張量有內置的梯度計算和跟蹤機制,因此你所需要做的就是將數據轉換為張量,并使用torch
提供的張量方法和函數執行計算。當然,如果要訪問底層的詳細信息,也是可以的,不過在大多數情況下PyTorch可以滿足你的期望。
每個張量都有幾個與梯度相關的屬性:
grad
:張量的梯度,與原張量形狀相同。is_leaf
:如果該張量是由用戶構造的,則為True
;如果是函數轉換的結果,則為False
。requires_grad
:如果此張量需要計算梯度,則為True
。此屬性是從葉張量繼承而來,葉張量從張量構建過程(torch.zeros()
或torch.tensor()
等)中獲得此值。默認情況下,構造函數的requires_grad = False
,如果要計算張量梯度,則需明確聲明。
為了更清楚地展示梯度機制,我們來看下面的例子:

上面的代碼創建了兩個張量。第一個要求計算梯度,第二個則不需要。

因此,現在我們將兩個向量逐個元素相加(向量[3, 3]),然后每個元素翻倍,再將它們求和。結果是零維張量,值為12。到目前依然很簡單。現在我們來看表達式創建的底層圖(見圖3.3)。

圖3.3 表達式的圖形表示
如果查看張量的屬性,會發現v1
和v2
是僅有的葉節點,并且每個變量(v2
除外)都需要計算梯度:

現在,讓PyTroch來計算圖中的梯度:

通過調用backward
函數,PyTorch計算了v_res
變量相對于圖中變量的數值導數。換句話說,圖中變量的變化會對v_res
變量產生什么樣的影響?在上面的例子中,v1
的梯度值為2,這意味v1
的任意元素增加1,v_res
的值將增加2。
如前所述,PyTorch僅針對requires_grad = True
的葉張量計算梯度。的確,如果查看v2
的梯度,會發現v2
沒有梯度:

這樣做主要是考慮計算和存儲方面的效率。實際情況下,網絡可以擁有數百萬個優化參數,并需要對它們執行數百個中間操作。在梯度下降優化過程中,我們對任何中間矩陣乘法的梯度都不感興趣。我們要在模型中調整的唯一參數,是與模型參數(權重)有關的損失的梯度。當然,如果你要計算輸入數據的梯度(如果想生成一些對抗性示例來欺騙現有的NN或調整預訓練的文本嵌入層,可能會很有用),可以簡單地通過在張量創建時傳遞requires_grad = True
來實現。
基本上,你現在已經擁有實現自己NN優化器所需的一切。本章的其余部分是關于額外的便捷函數的,提供NN結構中更高級的構建塊、流行的優化算法以及常見的損失函數。但是,請不要忘記,你可以按照自己喜歡的任何方式輕松地重新實現所有功能。這就是PyTorch在DL研究人員中如此受歡迎的原因,因為它優雅且靈活。
兼容性說明
支持張量的梯度計算是PyTorch 0.4.0版本的主要變化之一。在以前的版本中,圖形跟蹤和梯度計算是在非常輕量級的Variable
類中分別完成的。它用作張量的包裝器,自動保存了計算歷史以便能夠反向傳播。該類仍存在于0.4.0中,但已過時,它將很快消失,因此新代碼應避免使用。在我看來,這種變化是很好的,因為Variable
的邏輯確實很簡單,但是它仍然需要額外的代碼,并且開發人員還需要注意包裝和反包裝張量。現在,梯度變成張量的內置屬性,使得API更加整潔。