選自github
本文轉自機器之心(nearhuman2014)
PyTorch是最優秀的深度學習框架之一,它簡單優雅,非常適合入門。本文將介紹PyTorch的最佳實踐和代碼風格都是怎樣的。雖然這是一個非官方的 PyTorch 指南,但本文總結了一年多使用 PyTorch 框架的經驗,尤其是用它開發深度學習相關工作的最優解決方案。請注意,我們分享的經驗大多是從研究和實踐角度出發的。
這是一個開發的項目,歡迎其它讀者改進該文檔:https://github.com/IgorSusmelj/pytorch-styleguide
本文檔主要由三個部分構成:首先,本文會簡要清點 Python 中的最好裝備。接著,本文會介紹一些使用 PyTorch 的技巧和建議。最後,我們分享了一些使用其它框架的見解和經驗,這些框架通常幫助我們改進工作流。
清點 Python 裝備
建議使用 Python 3.6 以上版本
根據我們的經驗,我們推薦使用 Python 3.6 以上的版本,因為它們具有以下特性,這些特性可以使我們很容易寫出簡潔的代碼:
自 Python 3.6 以後支持「typing」模塊
自 Python 3.6 以後支持格式化字符串(f string)
Python 風格指南
我們試圖遵循 Google 的 Python 編程風格。請參閱 Google 提供的優秀的 python 編碼風格指南:
地址:https://github.com/google/styleguide/blob/gh-pages/pyguide.md。
在這裡,我們會給出一個最常用命名規範小結:
集成開發環境
一般來說,我們建議使用 visual studio 或PyCharm這樣的集成開發環境。而 VS Code 在相對輕量級的編輯器中提供語法高亮和自動補全功能,PyCharm 則擁有許多用於處理遠程集群任務的高級特性。
Jupyter Notebooks VS Python 腳本
一般來說,我們建議使用 Jupyter Notebook 進行初步的探索,或嘗試新的模型和代碼。如果你想在更大的數據集上訓練該模型,就應該使用 Python 腳本,因為在更大的數據集上,復現性更加重要。
我們推薦你採取下面的工作流程:
在開始的階段,使用 Jupyter Notebook
對數據和模型進行探索
在 notebook 的單元中構建你的類/方法
將代碼移植到 Python 腳本中
在伺服器上訓練/部署
開發常備庫
常用的程序庫有:
文件組織
不要將所有的層和模型放在同一個文件中。最好的做法是將最終的網絡分離到獨立的文件(networks.py)中,並將層、損失函數以及各種操作保存在各自的文件中(layers.py,losses.py,ops.py)。最終得到的模型(由一個或多個網絡組成)應該用該模型的名稱命名(例如,yolov3.py,DCGAN.py),且引用各個模塊。
主程序、單獨的訓練和測試腳本應該只需要導入帶有模型名字的 Python 文件。
PyTorch 開發風格與技巧
我們建議將網絡分解為更小的可復用的片段。一個 nn.Module 網絡包含各種操作或其它構建模塊。損失函數也是包含在 nn.Module 內,因此它們可以被直接整合到網絡中。
繼承 nn.Module 的類必須擁有一個「forward」方法,它實現了各個層或操作的前向傳導。
一個 nn.module 可以通過「self.net(input)」處理輸入數據。在這裡直接使用了對象的「call()」方法將輸入數據傳遞給模塊。
PyTorch 環境下的一個簡單網絡
使用下面的模式可以實現具有單個輸入和輸出的簡單網絡:
請注意以下幾點:
我們復用了簡單的循環構建模塊(如卷積塊 ConvBlocks),它們由相同的循環模式(卷積、激活函數、歸一化)組成,並裝入獨立的 nn.Module 中。
我們構建了一個所需要層的列表,並最終使用「nn.Sequential()」將所有層級組合到了一個模型中。我們在 list 對象前使用「*」操作來展開它。
在前向傳導過程中,我們直接使用輸入數據運行模型。
PyTorch 環境下的簡單殘差網絡
在這裡,ResNet 模塊的跳躍連接直接在前向傳導過程中實現了,PyTorch 允許在前向傳導過程中進行動態操作。
PyTorch 環境下的帶多個輸出的網絡
對於有多個輸出的網絡(例如使用一個預訓練好的 VGG 網絡構建感知損失),我們使用以下模式:
請注意以下幾點:
我們使用由「torchvision」包提供的預訓練模型
我們將一個網絡切分成三個模塊,每個模塊由預訓練模型中的層組成
我們通過設置「requires_grad = False」來固定網絡權重
我們返回一個帶有三個模塊輸出的 list
自定義損失函數
即使 PyTorch 已經具有了大量標準損失函數,你有時也可能需要創建自己的損失函數。為了做到這一點,你需要創建一個獨立的「losses.py」文件,並且通過擴展「nn.Module」創建你的自定義損失函數:
訓練模型的最佳代碼結構
對於訓練的最佳代碼結構,我們需要使用以下兩種模式:
使用 prefetch_generator 中的 BackgroundGenerator 來加載下一個批量數據
使用 tqdm 監控訓練過程,並展示計算效率,這能幫助我們找到數據加載流程中的瓶頸
PyTorch 的多 GPU 訓練
PyTorch 中有兩種使用多 GPU 進行訓練的模式。
根據我們的經驗,這兩種模式都是有效的。然而,第一種方法得到的結果更好、需要的代碼更少。由於第二種方法中的 GPU 間的通信更少,似乎具有輕微的性能優勢。
對每個網絡輸入的 batch 進行切分
最常見的一種做法是直接將所有網絡的輸入切分為不同的批量數據,並分配給各個 GPU。
這樣一來,在 1 個 GPU 上運行批量大小為 64 的模型,在 2 個 GPU 上運行時,每個 batch 的大小就變成了 32。這個過程可以使用「nn.DataParallel(model)」包裝器自動完成。
將所有網絡打包到一個超級網絡中,並對輸入 batch 進行切分
這種模式不太常用。下面的代碼倉庫向大家展示了 Nvidia 實現的 pix2pixHD,它有這種方法的實現。
地址:https://github.com/NVIDIA/pix2pixHD
PyTorch 中該做和不該做的
在「nn.Module」的「forward」方法中避免使用 Numpy 代碼
Numpy 是在 CPU 上運行的,它比 torch 的代碼運行得要慢一些。由於 torch 的開發思路與 numpy 相似,所以大多數Numpy中的函數已經在 PyTorch 中得到了支持。
將「DataLoader」從主程序的代碼中分離
載入數據的工作流程應該獨立於你的主訓練程序代碼。PyTorch 使用「background」進程更加高效地載入數據,而不會干擾到主訓練進程。
不要在每一步中都記錄結果
通常而言,我們要訓練我們的模型好幾千步。因此,為了減小計算開銷,每隔 n 步對損失和其它的計算結果進行記錄就足夠了。尤其是,在訓練過程中將中間結果保存成圖像,這種開銷是非常大的。
使用命令行參數
使用命令行參數設置代碼執行時使用的參數(batch 的大小、學習率等)非常方便。一個簡單的實驗參數跟蹤方法,即直接把從「parse_args」接收到的字典(dict 數據)列印出來:
如果可能的話,請使用「Use .detach()」從計算圖中釋放張量
為了實現自動微分,PyTorch 會跟蹤所有涉及張量的操作。請使用「.detach()」來防止記錄不必要的操作。
使用「.item()」列印出標量張量
你可以直接列印變量。然而,我們建議你使用「variable.detach()」或「variable.item()」。在早期版本的 PyTorch(
使用「call」方法代替「nn.Module」中的「forward」方法
這兩種方式並不完全相同,正如下面的 GitHub 問題單所指出的:https://github.com/IgorSusmelj/pytorch-styleguide/issues/3
本文由機器之心編譯,轉載請聯繫本公眾號獲得授權。
Python學習交流群