PyTorch經驗指南:技巧與陷阱 (深度學習框架)
PyTorch經驗指南:技巧與陷阱 (深度學習框架)
News from: GPU Taiwan Facebook
PyTorch 是一種靈活的深度學習框架,它允許通過動態神經網絡(例如利用動態控流——如 if 語句或 while 循環的網絡)進行自動微分。它還支持 GPU 加速、分佈式訓練以及各類優化任務,同時還擁有許多更簡潔的特性。以下是作者關於如何利用 PyTorch 的一些說明,裏面雖然沒有包含該庫的所有細節或最優方法,但可能會對大家有所幫助。
神經網絡是計算圖的一個子類。計算圖接收輸入數據,數據被路由到對數據執行處理的節點,並可能被這些節點轉換。在深度學習中,神經網絡中的神經元(節點)通常利用參數或可微函數轉換數據,這樣可以優化參數以通過梯度下降將損失最小化。更廣泛地說,函數是隨機的,圖結構可以是動態的。所以說,雖然神經網絡可能非常適合數據流式編程,但 PyTorch 的 API 卻更關注命令式編程——一種編程更常考慮的形式。這令讀取代碼和推斷複雜程序變得簡單,而無需損耗不必要的性能;PyTorch 速度很快,且擁有大量優化,作爲終端用戶你毫無後顧之憂。
本文其餘部分寫的是關於 grokking PyTorch 的內容,都是基於 MINIST 官網實例,應該要在學習完官網初學者教程後再查看。爲便於閱讀,代碼以塊狀形式呈現,並帶有註釋,因此不會像純模塊化代碼一樣被分割成不同的函數或文件。
PyTorch 張量
正如 PyTorch 文檔所說,如果我們熟悉 NumPy 的多維數組,那麼 Torch 張量的很多操作我們能輕易地掌握。PyTorch 提供了 CPU 張量和 GPU 張量,並且極大地加速了計算的速度。
從張量的構建與運行就能體會,相比 TensorFLow,在 PyTorch 中聲明張量、初始化張量要簡潔地多。例如,使用 torch.Tensor(5, 3) 語句就能隨機初始化一個 5×3 的二維張量,因爲 PyTorch 是一種動態圖,所以它聲明和真實賦值是同時進行的。
在 PyTorch 中,torch.Tensor 是一種多維矩陣,其中每個元素都是單一的數據類型,且該構造函數默認爲 torch.FloatTensor。以下是具體的張量類型:
除了直接定義維度,一般我們還可以從 Python 列表或 NumPy 數組中創建張量。而且根據使用 Python 列表和元組等數據結構的習慣,我們可以使用相似的索引方式進行取值或賦值。PyTorch 同樣支持廣播(Broadcasting)操作,一般它會隱式地把一個數組的異常維度調整到與另一個算子相匹配的維度,以實現維度兼容。
自動微分模塊
TensorFlow、Caffe 和 CNTK 等大多數框架都使用靜態計算圖,開發者必須建立或定義一個神經網絡,並重復使用相同的結構來執行模型訓練。改變網絡的模式就意味着我們必須從頭開始設計並定義相關的模塊。
但 PyTorch 使用的技術爲自動微分(automatic differentiation)。在這種機制下,系統會有一個 Recorder 來記錄我們執行的運算,然後再反向計算對應的梯度。這種技術在構建神經網絡的過程中十分強大,因爲我們可以通過計算前向傳播過程中參數的微分來節省時間。
從概念上來說,Autograd 會維護一個圖並記錄對變量執行的所有運算。這會產生一個有向無環圖,其中葉結點爲輸入向量,根結點爲輸出向量。通過從根結點到葉結點追蹤圖的路徑,我們可以輕易地使用鏈式法則自動計算梯度。
PyTorch 實用指南
編寫與設備無關的代碼(可用時受益於 GPU 加速,不可用時會倒退回 CPU)時,選擇並保存適當的 torch.device, 不失爲一種好方法,它可用於確定存儲張量的位置。關於與設備無關代碼的更多內容請參閱官網文件。PyTorch 的方法是使用戶能控制設備,這對簡單示例來說有些麻煩,但是可以更容易地找出張量所在的位置——這對於 a)調試很有用,並且 b)可有效地使用手動化設備。
對於可重複實驗,有必要爲使用隨機數生成的任何數據設置隨機種子(如果也使用隨機數,則包括隨機或 numpy)。要注意,cuDNN 用的是非確定算法,可以通過語句 torch.backends.cudnn.enabled = False 將其禁用。
神經網絡初始化一般包括變量、包含可訓練參數的層級、可能獨立的可訓練參數和不可訓練的緩存器。隨後前向傳播將這些初始化參數與 F 中的函數結合,其中該函數爲不包含參數的純函數。有些開發者喜歡使用完全函數化的網絡(如保持所有參數獨立,使用 F.conv2d 而不是 nn.Conv2d),或者完全由 layers 函數構成的網絡(如使用 nn.ReLU 而不是 F.relu)。
在將 device 設置爲 GPU 時,.to(device) 是一種將設備參數(和緩存器)發送到 GPU 的便捷方式,且在將 device 設置爲 CPU 時不會做任何處理。在將網絡參數傳遞給優化器之前,把它們傳遞給適當的設備非常重要,不然的話優化器不能正確地追蹤參數。
神經網絡(nn.Module)和優化器(optim.Optimizer)都能保存和加載它們的內部狀態,而.load_state_dict(state_dict) 是完成這一操作的推薦方法,我們可以從以前保存的狀態字典中加載兩者的狀態並恢復訓練。此外,保存整個對象可能會出錯。
這裏沒討論的一些注意事項即前向傳播可以使用控制流,例如一個成員變量或數據本身能決定 if 語句的執行。此外,在前向傳播的過程中打印張量也是可行的,這令 debug 更加簡單。最後,前向傳播可以使用多個參數。以下使用間斷的代碼塊展示這一點:
網絡模塊默認設置爲訓練模式,這影響了某些模塊的工作方式,最明顯的是 dropout 和批歸一化。最好用.train() 對其進行手動設置,這樣可以把訓練標記向下傳播到所有子模塊。
在使用 loss.backward() 收集一系列新的梯度以及用 optimiser.step() 做反向傳播之前,有必要手動地將由 optimiser.zero_grad() 優化的參數梯度歸零。默認情況下,PyTorch 會累加梯度,在單次迭代中沒有足夠資源來計算所有需要的梯度時,這種做法非常便利。
PyTorch 使用一種基於 tape 的自動化梯度(autograd)系統,它收集按順序在張量上執行的運算,然後反向重放它們來執行反向模式微分。這正是爲什麼 PyTorch 如此靈活並允許執行任意計算圖的原因。如果沒有張量需要做梯度更新(當你需要爲該過程構建一個張量時,你必須設置 requires_grad=True),則不需要保存任何圖。然而,網絡傾向於包含需要梯度更新的參數,因此任何網絡輸出過程中執行的計算都將保存在圖中。因此如果想保存在該過程中得到的數據,你將需要手動禁止梯度更新,或者,更常見的做法是將其保存爲一個 Python 數(通過一個 Python 標量上的.item())或者 NumPy 數組。更多關於 autograd 的細節詳見官網文件。
截取計算圖的一種方式是使用.detach(),當通過沿時間的截斷反向傳播訓練 RNN 時,數據流傳遞到一個隱藏狀態可能會應用這個函數。當對損失函數求微分(其中一個成分是另一個網絡的輸出)時,也會很方便。但另一個網絡不應該用「loss - examples」的模式進行優化,包括在 GAN 訓練中從生成器的輸出訓練判別器,或使用價值函數作爲基線(例如 A2C)訓練 actor-critic 算法的策略。另一種在 GAN 訓練(從判別器訓練生成器)中能高效阻止梯度計算的方法是在整個網絡參數上建立循環,並設置 param.requires_grad=False,這在微調中也很常用。
除了在控制檯/日誌文件裏記錄結果以外,檢查模型參數(以及優化器狀態)也是很重要的。你還可以使用 torch.save() 來保存一般的 Python 對象,但其它標準選擇還包括內建的 pickle。
留言
張貼留言