如何構建PyTorch項目

  • 2019 年 10 月 28 日
  • 筆記

作者 | Branislav Hollander 來源 | Medium

編輯 | 代碼醫生團隊

自從開始訓練深度神經網絡以來,一直在想所有Python代碼的結構是什麼。理想情況下,良好的結構應支持對該模型進行廣泛的試驗,允許在一個緊湊的框架中實現各種不同的模型,並且每個閱讀代碼的人都容易理解。必須能夠通過編碼和重用各種數據加載器來使用來自不同數據源的數據。此外,如果模型支持在一個模型中組合多個網絡(例如GAN或原始R-CNN的情況),那就太好了。該框架還應該具有足夠的靈活性以允許進行複雜的可視化(這是在數據科學中的核心信念之一,即可視化使一切變得更加容易,尤其是在計算機視覺任務的情況下)。

深度學習框架的詳細實現當然取決於正在使用的基礎庫,無論是TensorFlow,PyTorch還是CNTK。在這篇文章中,將介紹基於PyTorch的方法。但是,認為一般結構同樣適用於使用的任何庫。可以在鏈接找到整個存儲庫。

https://gitlab.com/branislav.hollander/pytorchprojectframework

總體結構

深度學習框架的項目結構

在上圖(取自Python編輯器VS代碼)上,可以看到為框架創建的常規文件夾結構。該框架由一些啟動腳本(train.py,validate.py,hyperopt.py)以及隱藏在文件夾中的庫組成。該數據集文件夾中包含加載各種類型的數據的類和方法的訓練。損失的文件夾可以包含附加的功能損失或驗證指標。如果項目不需要任何自定義損失函數,則可能不需要此文件夾。該機型的文件夾是最重要的:它包含實際的模型。該優化的文件夾包括自定義優化程序的代碼。與losss文件夾一樣,如果沒有任何自定義優化器,則可以省略此文件夾。最後,utils文件夾包含整個框架使用的各種實用程序,最著名的是visualizer。還將注意到項目根文件夾中的config_segmentation.json文件。該文件包含訓練所需的所有配置選項。

可能已經猜到了,可以通過調用train.py腳本來啟動訓練。使用適當的配置文件作為命令行參數調用此腳本。它負責所有高級訓練內容,例如加載訓練和驗證數據集和模型,設置可視化,運行訓練循環以及最後導出訓練後的模型。

通過調用適當的腳本並將配置文件作為參數來傳遞,從而類似地使用驗證。

數據集

以2D分割數據集為例,數據集文件夾中的文件。

在上圖中,可以看到數據集文件夾的結構。它包含__init__.py模塊,該模塊包含一些用於查找和創建正確數據集的必要功能,以及一個自定義數據加載器,該數據加載器會將數據轉發到訓練管道(有關此的更多信息,請查看PyTorch API文檔)。顧名思義,base_dataset.py在框架中定義的每個數據集定義了抽象基類。

https://pytorch.org/tutorials/beginner/data_loading_tutorial.html

對於定義的每個自定義數據集,都必須實現__getitem__和__len__方法,以便PyTorch可以對其進行迭代。不再需要處理DataLoader,因為它已經在datasets / __ init__.py中定義。還可以為每個時期之前和之後要調用的數據集定義自定義回調。如果要使用某種預熱方法,該方法可以在前幾個時期將不同的數據饋送到模型,然後再切換到更複雜的數據集,則這可能會很有用。

要實例化數據集,train.py腳本調用以下代碼:

print(『Initializing dataset…』)  train_dataset =     create_dataset(configuration[『train_dataset_params』])  train_dataset_size = len(train_dataset)  print(『The number of training samples = {0}』.format(train_dataset_size))

這將調用create_dataset函數,該函數查看配置文件並根據其名稱選擇正確的數據集。命名數據集時,務必遵循慣例<datasetname> _dataset.py,因為這是腳本能夠根據配置文件中的字符串查找數據集的方式。最後,以上腳本在數據集上調用len()函數,以告知您其大小。

模型

以細分模型為例,models文件夾中的文件。

框架中的模型與數據集的工作方式相同:__init__.py模塊包含用於根據其模塊名稱和配置文件中定義的字符串查找和創建正確模型的函數。模型類本身繼承自抽象BaseModel類,並且必須實現兩個方法:

  • 向前(自己)運行前向預測。
  • 訓練通過後,optimize_parameters(self)可以修改網絡的權重。

所有其他方法都可能被覆蓋,或者可以使用默認的BaseClass實現。可能要覆蓋的功能包括pre_epoch_callback和post_epoch_callback(在每個時期之前和之後調用)或測試(在驗證期間調用)。

為了正確使用框架,了解如何使用網絡,優化器和模型中的損失非常重要。由於模型中可能有多個使用不同優化器的網絡以及多個不同的損失(例如,可能希望顯示語義本地化模型的邊界框分類和回歸損失),因此界面要涉及更多一點。具體來說,需要提供損失名稱和網絡名稱以及BaseModel類的優化程序,以了解如何訓練模型。在提供的代碼中,包括2D細分模型的示例以及示例數據集,以供了解應如何使用框架。

看一下提供的2D分割模型的__init __()函數:

class Segmentation2DModel(BaseModel):     def __init__(self, configuration):        super().__init__(configuration)        self.loss_names = [『segmentation』]        self.network_names = [『unet』]        self.netunet = UNet(1, 2)        self.netunet = self.netunet.to(self.device)        if self.is_train: # only defined during training time           self.criterion_loss = torch.nn.CrossEntropyLoss()           self.optimizer = torch.optim.Adam(self.netunet.parameters(), lr=configuration[『lr』])           self.optimizers = [self.optimizer]

這就是這裡發生的事情:首先,閱讀模型配置。然後,定義「分段」損失並將其放入self.loss_names列表中。損失的名稱很重要,因為將變量self.loss_segmentation用於損失。通過知道名稱,BaseModel可以查找丟失並將其打印在控制台中或將其可視化。同樣,定義網絡的名稱。這可以確保BaseModel知道如何訓練模型而無需明確定義它。接下來,初始化網絡(在本例中為U-Net)並將其移至GPU。如果處於訓練模式,還將定義損失標準並實例化優化器(在本例中為Adam)。最後,將優化器放入self.optimizers列表中。此列表再次在BaseModel類中使用,以更新學習率或從給定的檢查點恢復訓練。

看一下forward()和optimize_parameters()函數:

def forward(self):     self.output = self.netunet(self.input)  def backward(self):     self.loss_segmentation = self.criterion_loss(self.output, self.label)  def optimize_parameters(self):     self.loss_segmentation.backward() # calculate gradients     self.optimizer.step()     self.optimizer.zero_grad()

如您所見,這是標準的PyTorch代碼:它唯一的職責是在網絡本身上調用forward(),在計算出梯度後將優化器步進,並將其再次置零。為自己的模型實施此操作應該很容易。

可視化

utils文件夾中的文件

可視化可以在Visualizer類中找到。此類負責將損失信息打印到終端,並使用visdom庫可視化各種結果。它在訓練腳本的開頭進行初始化(將加載visdom服務器)。訓練腳本還調用其plot_current_losses()和print_current_losses()函數以可視化並寫出訓練損失。它還包含諸如plot_current_validation_metrics(),plot_roc_curve()和show_validation_images()之類的函數,這些函數不會自動調用,但可以從post_epoch_callback()模型中調用在驗證時進行一些有用的可視化。試圖使可視化工具保持一般性。當然,可以自己擴展可視化器的功能,使其對您更有用。

https://github.com/facebookresearch/visdom

結論

提出了一種編寫通用的深度學習框架的方法,該框架可用於深度學習的所有領域。通過使用此結構,將獲得清晰靈活的代碼庫,以進行進一步的開發。當然,有許多替代方法可以解決該問題。