這不是Pytorch的官方風格指南。本文檔總結了使用Pytorch框架一年多的深度學習經驗中的最佳實踐。請注意,我們分享的學習主要來自研究和創業的角度。
這是一個開放項目,其他合作者非常歡迎編輯和改進文檔。
您會發現此文檔的三個主要部分。首先,快速回顧一下Python的最佳實踐,然後使用Pytorch提出一些提示和建議。最後,我們使用其他框架分享了一些見解和經驗,這些框架通常幫助我們改善了工作流程。
更新20.12.2020
更新30.4.2019
經過如此多的積極反饋,我還添加了我們項目中常用的構建塊的摘要:您會發現(自我注意,使用VGG,頻譜歸一化,自適應實例歸一化的構建塊),...)
損失,層和其他構件的代碼段
根據我們的經驗,我們建議使用Python 3.6+,因為具有以下功能,這些功能非常方便,用於簡單而簡單的代碼:
我們嘗試遵循Python的Google StyleGuide。請參閱Google提供的有關Python代碼的備受證明的樣式指南。
我們在這裡提供了最常用的規則的摘要:
從3.16.4起
| 類型 | 習俗 | 例子 |
|---|---|---|
| 軟件包和模塊 | lower_with_under | 來自prefetch_generator導入buckddenterator |
| 課程 | capwords | 類數據載體 |
| 常數 | caps_with_under | batch_size = 16 |
| 實例 | lower_with_under | 數據集=數據集 |
| 方法和功能 | lower_with_under() | def Visualize_tensor() |
| 變量 | lower_with_under | background_color ='藍色' |
通常,我們建議使用IDE,例如Visual Studio Code或Pycharm。而VS代碼在相對輕巧的編輯器Pycharm中提供語法突出顯示和自動完成,具有許多用於使用遠程簇的高級功能。通過快速增長的擴展生態系統,VS代碼變得非常強大。
確保已安裝以下擴展名:
如果正確設置,則可以執行以下操作:
通常,我們建議使用Jupyter筆記本電腦進行初始探索/播放新型號和代碼。一旦您想在更大的數據集上訓練模型,應立即使用Python腳本,在該模型中也更重要。
我們推薦的工作流程:
| Jupyter筆記本 | Python腳本 |
|---|---|
| +探索 | +在不中斷的情況下運行更長的作業 |
| +調試 | +易於跟踪git的變化 |
| - 可以成為一個巨大的文件 | - 調試主要意味著重新整個腳本 |
| - 可以中斷(不要用於長期培訓) | |
| - 容易出錯,變得一團糟 |
常用庫:
| 姓名 | 描述 | 用於 |
|---|---|---|
| 火炬 | 使用神經網絡的基礎框架 | 使用Backprop創建張量,網絡和培訓它們 |
| 火炬 | Pytorch計算機視覺模塊 | 圖像數據預處理,增強,後處理 |
| 枕頭(PIL) | Python成像庫 | 加載圖像並存儲它們 |
| numpy | Python的科學計算包 | 數據預處理和後處理 |
| prefetch_generator | 背景處理庫 | 在計算過程中將下一批加載在背景中 |
| TQDM | 進度欄 | 每個時期訓練期間的進展 |
| Torchinfo | 打印pytorch的類似凱拉斯的模型摘要 | 顯示網絡,每一層的參數和大小 |
| TORCH.UTILS.TENSORBOARD | pytorch中的張板 | 記錄實驗並將其顯示在張板中 |
不要將所有圖層和模型放入同一文件中。最好的做法是將最終網絡分開為單獨的文件( networks.py ),並將各個層,損失和操作保留在相應的文件中( layers.py.py , loss.py , ops.py )。完成的模型(由一個或多個網絡組成)應在其名稱的文件中參考(例如yolov3.py , dcgan.py )
主要例程,各自的火車和測試腳本應僅從具有模型名稱的文件中導入。
我們建議將網絡分解成其較小的可重複使用的部分。網絡是一個NN.模塊,由操作或其他nn.模塊作為構建塊組成。損耗功能也是nn.模塊,因此可以直接集成到網絡中。
從NN.模塊繼承的類必須具有實現相應層或操作的正向通過的正向方法。
使用self.net(輸入)可以將nn.模塊用於輸入數據。這只需使用對象的呼叫()方法即可通過模塊輸入輸入。
output = self . net ( input )對於具有單個輸入和單個輸出的簡單網絡使用以下模式:
class ConvBlock ( nn . Module ):
def __init__ ( self ):
super ( ConvBlock , self ). __init__ ()
self . block = nn . Sequential (
nn . Conv2d (...),
nn . ReLU (),
nn . BatchNorm2d (...)
)
def forward ( self , x ):
return self . block ( x )
class SimpleNetwork ( nn . Module ):
def __init__ ( self , num_resnet_blocks = 6 ):
super ( SimpleNetwork , self ). __init__ ()
# here we add the individual layers
layers = [ ConvBlock (...)]
for i in range ( num_resnet_blocks ):
layers += [ ResBlock (...)]
self . net = nn . Sequential ( * layers )
def forward ( self , x ):
return self . net ( x )注意以下內容:
class ResnetBlock ( nn . Module ):
def __init__ ( self , dim , padding_type , norm_layer , use_dropout , use_bias ):
super ( ResnetBlock , self ). __init__ ()
self . conv_block = self . build_conv_block (...)
def build_conv_block ( self , ...):
conv_block = []
conv_block += [ nn . Conv2d (...),
norm_layer (...),
nn . ReLU ()]
if use_dropout :
conv_block += [ nn . Dropout (...)]
conv_block += [ nn . Conv2d (...),
norm_layer (...)]
return nn . Sequential ( * conv_block )
def forward ( self , x ):
out = x + self . conv_block ( x )
return out在這裡, RESNET塊的跳過連接已直接在正向通行證中實現。 Pytorch允許在正向通行證期間進行動態操作。
對於需要多個輸出的網絡,例如使用驗證的VGG網絡構建感知損失,我們使用以下模式:
class Vgg19 ( nn . Module ):
def __init__ ( self , requires_grad = False ):
super ( Vgg19 , self ). __init__ ()
vgg_pretrained_features = models . vgg19 ( pretrained = True ). features
self . slice1 = torch . nn . Sequential ()
self . slice2 = torch . nn . Sequential ()
self . slice3 = torch . nn . Sequential ()
for x in range ( 7 ):
self . slice1 . add_module ( str ( x ), vgg_pretrained_features [ x ])
for x in range ( 7 , 21 ):
self . slice2 . add_module ( str ( x ), vgg_pretrained_features [ x ])
for x in range ( 21 , 30 ):
self . slice3 . add_module ( str ( x ), vgg_pretrained_features [ x ])
if not requires_grad :
for param in self . parameters ():
param . requires_grad = False
def forward ( self , x ):
h_relu1 = self . slice1 ( x )
h_relu2 = self . slice2 ( h_relu1 )
h_relu3 = self . slice3 ( h_relu2 )
out = [ h_relu1 , h_relu2 , h_relu3 ]
return out在此處注意以下內容:
即使Pytorch已經具有許多標準損失功能,有時也有必要創建自己的損失功能。為此,創建nn.Module單獨的文件losses.py 。
class CustomLoss ( nn . Module ):
def __init__ ( self ):
super ( CustomLoss , self ). __init__ ()
def forward ( self , x , y ):
loss = torch . mean (( x - y ) ** 2 )
return loss 該存儲庫的CIFAR10-示例文件夾中提供了一個完整示例。
請注意,我們使用以下模式:
# import statements
import torch
import torch . nn as nn
from torch . utils import data
...
# set flags / seeds
torch . backends . cudnn . benchmark = True
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )
...
# Start with main code
if __name__ == '__main__' :
# argparse for additional flags for experiment
parser = argparse . ArgumentParser ( description = "Train a network for ..." )
...
opt = parser . parse_args ()
# add code for datasets (we always use train and validation/ test set)
data_transforms = transforms . Compose ([
transforms . Resize (( opt . img_size , opt . img_size )),
transforms . RandomHorizontalFlip (),
transforms . ToTensor (),
transforms . Normalize (( 0.5 , 0.5 , 0.5 ), ( 0.5 , 0.5 , 0.5 ))
])
train_dataset = datasets . ImageFolder (
root = os . path . join ( opt . path_to_data , "train" ),
transform = data_transforms )
train_data_loader = data . DataLoader ( train_dataset , ...)
test_dataset = datasets . ImageFolder (
root = os . path . join ( opt . path_to_data , "test" ),
transform = data_transforms )
test_data_loader = data . DataLoader ( test_dataset ...)
...
# instantiate network (which has been imported from *networks.py*)
net = MyNetwork (...)
...
# create losses (criterion in pytorch)
criterion_L1 = torch . nn . L1Loss ()
...
# if running on GPU and we want to use cuda move model there
use_cuda = torch . cuda . is_available ()
if use_cuda :
net = net . cuda ()
...
# create optimizers
optim = torch . optim . Adam ( net . parameters (), lr = opt . lr )
...
# load checkpoint if needed/ wanted
start_n_iter = 0
start_epoch = 0
if opt . resume :
ckpt = load_checkpoint ( opt . path_to_checkpoint ) # custom method for loading last checkpoint
net . load_state_dict ( ckpt [ 'net' ])
start_epoch = ckpt [ 'epoch' ]
start_n_iter = ckpt [ 'n_iter' ]
optim . load_state_dict ( ckpt [ 'optim' ])
print ( "last checkpoint restored" )
...
# if we want to run experiment on multiple GPUs we move the models there
net = torch . nn . DataParallel ( net )
...
# typically we use tensorboardX to keep track of experiments
writer = SummaryWriter (...)
# now we start the main loop
n_iter = start_n_iter
for epoch in range ( start_epoch , opt . epochs ):
# set models to train mode
net . train ()
...
# use prefetch_generator and tqdm for iterating through data
pbar = tqdm ( enumerate ( BackgroundGenerator ( train_data_loader , ...)),
total = len ( train_data_loader ))
start_time = time . time ()
# for loop going through dataset
for i , data in pbar :
# data preparation
img , label = data
if use_cuda :
img = img . cuda ()
label = label . cuda ()
...
# It's very good practice to keep track of preparation time and computation time using tqdm to find any issues in your dataloader
prepare_time = start_time - time . time ()
# forward and backward pass
optim . zero_grad ()
...
loss . backward ()
optim . step ()
...
# udpate tensorboardX
writer . add_scalar (..., n_iter )
...
# compute computation time and *compute_efficiency*
process_time = start_time - time . time () - prepare_time
pbar . set_description ( "Compute efficiency: {:.2f}, epoch: {}/{}:" . format (
process_time / ( process_time + prepare_time ), epoch , opt . epochs ))
start_time = time . time ()
# maybe do a test pass every x epochs
if epoch % x == x - 1 :
# bring models to evaluation mode
net . eval ()
...
#do some tests
pbar = tqdm ( enumerate ( BackgroundGenerator ( test_data_loader , ...)),
total = len ( test_data_loader ))
for i , data in pbar :
...
# save checkpoint if needed
...Pytorch中有兩種不同的模式用於使用多個GPU進行訓練。根據我們的經驗,兩種模式都是有效的。但是,第一個結果在更好且更少的代碼中結果。由於GPU之間的溝通較少,第二個似乎具有略有性能優勢。我在官方Pytorch論壇上問了一個問題,涉及這裡的兩種方法
最常見的是,簡單地將所有網絡的批處理分為單個GPU。
因此,在1 GPU上運行的具有批量64的模型將在2 GPU上運行,每個GPU的批量大小為32。這可以通過NN.Dataparallear(模型)包裹模型來自動完成。
這種模式不太常用。 NVIDIA的Pix2PixHD實現中顯示了實現此方法的存儲庫
Numpy在CPU上運行,並且比火炬代碼慢。由於已經開發了割炬,因此,大多數numpy功能已經得到了pytorch的支持。
數據加載管道應獨立於您的主要培訓代碼。 Pytorch使用背景工人更有效地加載數據,而不會干擾主訓練過程。
通常,我們將模型訓練成千上萬個步驟。因此,足以記錄損失和其他結果,以減少開銷。特別是,保存中介結果是在培訓期間圖像的昂貴。
使用命令行參數在代碼執行期間設置參數非常方便(批處理大小,學習率等)。跟踪實驗論點的一種簡單方法是打印從parse_args收到的字典:
...
# saves arguments to config.txt file
opt = parser . parse_args ()
with open ( "config.txt" , "w" ) as f :
f . write ( opt . __str__ ())
...Pytorch跟踪所有涉及自動差異張量的操作。使用.detach()防止記錄不必要的操作。
您可以直接打印變量,但是建議使用valiable.detach()或variable.item() 。在早期的Pytorch版本<0.4中,您必須使用.data訪問變量的張量。
這兩種方法在這裡的問題之一中指出的兩種方式並不相同:
output = self . net . forward ( input )
# they are not equal!
output = self . net ( input )我們建議在代碼開頭設置以下種子:
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )在NVIDIA GPU上,您可以在代碼開頭添加以下行。這將允許CUDA後端在第一次執行時優化圖形。但是,請注意,如果您更改網絡輸入/輸出張量尺寸,則每次更改時,圖表都將被優化。這可能會導致運行時間非常緩慢,並且不會出現內存錯誤。僅當您的輸入和輸出始終具有相同的形狀時,僅設置此標誌。通常,這會提高約20%。
torch . backends . cudnn . benchmark = True這取決於所使用的機器,預處理管道和網絡大小。在1080TI GPU上運行SSD,我們看到的計算效率幾乎為1.0,這是一個理想的情況。如果使用淺(小)網絡或慢速硬盤,則數字可能會根據您的設置下降到0.1-0.2左右。
在Pytorch中,我們可以很容易地實現虛擬批次大小。我們只是防止優化器對參數進行更新,並總結批次循環的漸變。
...
# in the main loop
out = net ( input )
loss = criterion ( out , label )
# we just call backward to sum up gradients but don't perform step here
loss . backward ()
total_loss += loss . item () / batch_size
if n_iter % batch_size == batch_size - 1 :
# here we perform out optimization step using a virtual batch size
optim . step ()
optim . zero_grad ()
print ( 'Total loss: ' , total_loss )
total_loss = 0.0
...我們可以使用實例化優化器直接訪問學習率,如下所示:
...
for param_group in optim . param_groups :
old_lr = param_group [ 'lr' ]
new_lr = old_lr * 0.1
param_group [ 'lr' ] = new_lr
print ( 'Updated lr from {} to {}' . format ( old_lr , new_lr ))
...如果您想使用驗證的模型(例如VGG)來計算損失,而不是訓練它(例如,樣式轉移/ gans/ auto-編碼器的感知損失),則可以使用以下模式:
...
# instantiate the model
pretrained_VGG = VGG19 (...)
# disable gradients (prevent training)
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = False
...
# you don't have to use the no_grad() namespace but can just run the model
# no gradients will be computed for the VGG model
out_real = pretrained_VGG ( input_a )
out_fake = pretrained_VGG ( input_b )
loss = any_criterion ( out_real , out_fake )
...這些方法用於設置從訓練到推理模式的batchnorm2d或dropout2d之類的層。從NN.模塊繼承的每個模塊都有一個稱為ISTRAINing的屬性。 .eval()和.train()只需將此屬性設置為true/ false即可。有關如何實現此方法的更多信息,請查看Pytorch中的模塊代碼
確保在執行代碼期間沒有計算和存儲梯度。您可以簡單地使用以下模式確保:
with torch . no_grad ():
# run model here
out_tensor = net ( in_tensor )在Pytorch中,您可以冷凍層。這將阻止他們在優化步驟中進行更新。
# you can freeze whole modules using
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = False由於Pytorch 0.4 *變量和張量已合併。我們不必明確創建一個變量對象。
C ++版本的速度約為10%
待...
根據我們的經驗,您可以獲得約20%的速度。但是,第一次運行模型時,構建優化的圖表需要很長時間。在某些情況下(向前循環,沒有固定輸入形狀,如果/其他方向等。)此標誌可能會導致內存或其他錯誤。
待...
如果從計算圖中釋放張量。這裡顯示了一個不錯的插圖
請提供有關我們如何改善此樣式指南的反饋!您可以通過創建拉動請求來打開問題或提出更改。
如果您喜歡此存儲庫,請不要忘記查看我們的其他框架: