自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

PyTorch最佳實踐,怎樣才能寫出一手風(fēng)格優(yōu)美的代碼

開發(fā) 開發(fā)工具 深度學(xué)習(xí)
PyTorch是最優(yōu)秀的深度學(xué)習(xí)框架之一,它簡單優(yōu)雅,非常適合入門。本文將介紹PyTorch的最佳實踐和代碼風(fēng)格都是怎樣的。

雖然這是一個非官方的 PyTorch 指南,但本文總結(jié)了一年多使用 PyTorch 框架的經(jīng)驗,尤其是用它開發(fā)深度學(xué)習(xí)相關(guān)工作的***解決方案。請注意,我們分享的經(jīng)驗大多是從研究和實踐角度出發(fā)的。

這是一個開發(fā)的項目,歡迎其它讀者改進(jìn)該文檔:

https://github.com/IgorSusmelj/pytorch-styleguide。

本文檔主要由三個部分構(gòu)成:首先,本文會簡要清點(diǎn) Python 中的***裝備。接著,本文會介紹一些使用 PyTorch 的技巧和建議。***,我們分享了一些使用其它框架的見解和經(jīng)驗,這些框架通常幫助我們改進(jìn)工作流。

一、清點(diǎn) Python 裝備

1. 建議使用 Python 3.6 以上版本

根據(jù)我們的經(jīng)驗,我們推薦使用 Python 3.6 以上的版本,因為它們具有以下特性,這些特性可以使我們很容易寫出簡潔的代碼:

  • 自 Python 3.6 以后支持「typing」模塊
  • 自 Python 3.6 以后支持格式化字符串(f string)

2. Python 風(fēng)格指南

我們試圖遵循 Google 的 Python 編程風(fēng)格。請參閱 Google 提供的優(yōu)秀的 python 編碼風(fēng)格指南:

地址:https://github.com/google/styleguide/blob/gh-pages/pyguide.md。

在這里,我們會給出一個最常用命名規(guī)范小結(jié):

3. 集成開發(fā)環(huán)境

一般來說,我們建議使用 visual studio 或 PyCharm 這樣的集成開發(fā)環(huán)境。而 VS Code 在相對輕量級的編輯器中提供語法高亮和自動補(bǔ)全功能,PyCharm 則擁有許多用于處理遠(yuǎn)程集群任務(wù)的高級特性。

4. Jupyter Notebooks VS Python 腳本

一般來說,我們建議使用 Jupyter Notebook 進(jìn)行初步的探索,或嘗試新的模型和代碼。如果你想在更大的數(shù)據(jù)集上訓(xùn)練該模型,就應(yīng)該使用 Python 腳本,因為在更大的數(shù)據(jù)集上,復(fù)現(xiàn)性更加重要。

我們推薦你采取下面的工作流程:

  • 在開始的階段,使用 Jupyter Notebook
  • 對數(shù)據(jù)和模型進(jìn)行探索
  • 在 notebook 的單元中構(gòu)建你的類/方法
  • 將代碼移植到 Python 腳本中
  • 在服務(wù)器上訓(xùn)練/部署

5. 開發(fā)常備庫

常用的程序庫有:

6. 文件組織

不要將所有的層和模型放在同一個文件中。***的做法是將最終的網(wǎng)絡(luò)分離到獨(dú)立的文件(networks.py)中,并將層、損失函數(shù)以及各種操作保存在各自的文件中(layers.py,losses.py,ops.py)。最終得到的模型(由一個或多個網(wǎng)絡(luò)組成)應(yīng)該用該模型的名稱命名(例如,yolov3.py,DCGAN.py),且引用各個模塊。

主程序、單獨(dú)的訓(xùn)練和測試腳本應(yīng)該只需要導(dǎo)入帶有模型名字的 Python 文件。

二、PyTorch 開發(fā)風(fēng)格與技巧

我們建議將網(wǎng)絡(luò)分解為更小的可復(fù)用的片段。一個 nn.Module 網(wǎng)絡(luò)包含各種操作或其它構(gòu)建模塊。損失函數(shù)也是包含在 nn.Module 內(nèi),因此它們可以被直接整合到網(wǎng)絡(luò)中。

繼承 nn.Module 的類必須擁有一個「forward」方法,它實現(xiàn)了各個層或操作的前向傳導(dǎo)。

一個 nn.module 可以通過「self.net(input)」處理輸入數(shù)據(jù)。在這里直接使用了對象的「call()」方法將輸入數(shù)據(jù)傳遞給模塊。

  1. output = self.net(input) 

1. PyTorch 環(huán)境下的一個簡單網(wǎng)絡(luò)

使用下面的模式可以實現(xiàn)具有單個輸入和輸出的簡單網(wǎng)絡(luò):

  1. class ConvBlock(nn.Module): 
  2.     def __init__(self): 
  3.         super(ConvBlock, self).__init__() 
  4.         block = [nn.Conv2d(...)] 
  5.         block += [nn.ReLU()] 
  6.         block += [nn.BatchNorm2d(...)] 
  7.         self.block = nn.Sequential(*block) 
  8.  
  9.     def forward(self, x): 
  10.         return self.block(x) 
  11.  
  12. class SimpleNetwork(nn.Module): 
  13.     def __init__(self, num_resnet_blocks=6): 
  14.         super(SimpleNetwork, self).__init__() 
  15.         # here we add the individual layers 
  16.         layers = [ConvBlock(...)] 
  17.         for i in range(num_resnet_blocks): 
  18.             layers += [ResBlock(...)] 
  19.         self.net = nn.Sequential(*layers) 
  20.  
  21.     def forward(self, x): 
  22.         return self.net(x) 

請注意以下幾點(diǎn):

  • 我們復(fù)用了簡單的循環(huán)構(gòu)建模塊(如卷積塊 ConvBlocks),它們由相同的循環(huán)模式(卷積、激活函數(shù)、歸一化)組成,并裝入獨(dú)立的 nn.Module 中。
  • 我們構(gòu)建了一個所需要層的列表,并最終使用「nn.Sequential()」將所有層級組合到了一個模型中。我們在 list 對象前使用「*」操作來展開它。
  • 在前向傳導(dǎo)過程中,我們直接使用輸入數(shù)據(jù)運(yùn)行模型。

2. PyTorch 環(huán)境下的簡單殘差網(wǎng)絡(luò)

  1. class ResnetBlock(nn.Module): 
  2.     def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): 
  3.         super(ResnetBlock, self).__init__() 
  4.         selfself.conv_block = self.build_conv_block(...) 
  5.  
  6.     def build_conv_block(self, ...): 
  7.         conv_block = [] 
  8.  
  9.         conv_block += [nn.Conv2d(...), 
  10.                        norm_layer(...), 
  11.                        nn.ReLU()] 
  12.         if use_dropout: 
  13.             conv_block += [nn.Dropout(...)] 
  14.  
  15.         conv_block += [nn.Conv2d(...), 
  16.                        norm_layer(...)] 
  17.  
  18.         return nn.Sequential(*conv_block) 
  19.  
  20.     def forward(self, x): 
  21.         out = x + self.conv_block(x) 
  22.         return ou 

在這里,ResNet 模塊的跳躍連接直接在前向傳導(dǎo)過程中實現(xiàn)了,PyTorch 允許在前向傳導(dǎo)過程中進(jìn)行動態(tài)操作。

3. PyTorch 環(huán)境下的帶多個輸出的網(wǎng)絡(luò)

對于有多個輸出的網(wǎng)絡(luò)(例如使用一個預(yù)訓(xùn)練好的 VGG 網(wǎng)絡(luò)構(gòu)建感知損失),我們使用以下模式:

  1. class Vgg19(torch.nn.Module): 
  2.   def __init__(self, requires_grad=False): 
  3.     super(Vgg19, self).__init__() 
  4.     vgg_pretrained_features = models.vgg19(pretrained=True).features 
  5.     self.slice1 = torch.nn.Sequential() 
  6.     self.slice2 = torch.nn.Sequential() 
  7.     self.slice3 = torch.nn.Sequential() 
  8.  
  9.     for x in range(7): 
  10.         self.slice1.add_module(str(x), vgg_pretrained_features[x]) 
  11.     for x in range(7, 21): 
  12.         self.slice2.add_module(str(x), vgg_pretrained_features[x]) 
  13.     for x in range(21, 30): 
  14.         self.slice3.add_module(str(x), vgg_pretrained_features[x]) 
  15.     if not requires_grad: 
  16.         for param in self.parameters(): 
  17.             param.requires_grad = False 
  18.  
  19.   def forward(self, x): 
  20.     h_relu1 = self.slice1(x) 
  21.     h_relu2 = self.slice2(h_relu1)         
  22.     h_relu3 = self.slice3(h_relu2)         
  23.     out = [h_relu1, h_relu2, h_relu3] 
  24.     return out 

請注意以下幾點(diǎn):

  • 我們使用由「torchvision」包提供的預(yù)訓(xùn)練模型
  • 我們將一個網(wǎng)絡(luò)切分成三個模塊,每個模塊由預(yù)訓(xùn)練模型中的層組成
  • 我們通過設(shè)置「requires_grad = False」來固定網(wǎng)絡(luò)權(quán)重
  • 我們返回一個帶有三個模塊輸出的 list

4. 自定義損失函數(shù)

即使 PyTorch 已經(jīng)具有了大量標(biāo)準(zhǔn)損失函數(shù),你有時也可能需要創(chuàng)建自己的損失函數(shù)。為了做到這一點(diǎn),你需要創(chuàng)建一個獨(dú)立的「losses.py」文件,并且通過擴(kuò)展「nn.Module」創(chuàng)建你的自定義損失函數(shù):

  1. class CustomLoss(torch.nn.Module): 
  2.  
  3.     def __init__(self): 
  4.         super(CustomLoss,self).__init__() 
  5.  
  6.     def forward(self,x,y): 
  7.         loss = torch.mean((x - y)**2) 
  8.         return loss 

5. 訓(xùn)練模型的***代碼結(jié)構(gòu)

對于訓(xùn)練的***代碼結(jié)構(gòu),我們需要使用以下兩種模式:

  • 使用 prefetch_generator 中的 BackgroundGenerator 來加載下一個批量數(shù)據(jù)
  • 使用 tqdm 監(jiān)控訓(xùn)練過程,并展示計算效率,這能幫助我們找到數(shù)據(jù)加載流程中的瓶頸
  1. # import statements 
  2. import torch 
  3. import torch.nn as nn 
  4. from torch.utils import data 
  5. ... 
  6.  
  7. # set flags / seeds 
  8. torch.backends.cudnn.benchmark = True 
  9. np.random.seed(1) 
  10. torch.manual_seed(1) 
  11. torch.cuda.manual_seed(1) 
  12. ... 
  13.  
  14. # Start with main code 
  15. if __name__ == '__main__': 
  16.     # argparse for additional flags for experiment 
  17.     parser = argparse.ArgumentParser(description="Train a network for ..."
  18.     ... 
  19.     opt = parser.parse_args()  
  20.  
  21.     # add code for datasets (we always use train and validation/ test set) 
  22.     data_transforms = transforms.Compose([ 
  23.         transforms.Resize((opt.img_size, opt.img_size)), 
  24.         transforms.RandomHorizontalFlip(), 
  25.         transforms.ToTensor(), 
  26.         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) 
  27.     ]) 
  28.  
  29.     train_dataset = datasets.ImageFolder( 
  30.         root=os.path.join(opt.path_to_data, "train"), 
  31.         transform=data_transforms
  32.     train_data_loader = data.DataLoader(train_dataset, ...) 
  33.  
  34.     test_dataset = datasets.ImageFolder( 
  35.         root=os.path.join(opt.path_to_data, "test"), 
  36.         transform=data_transforms
  37.     test_data_loader = data.DataLoader(test_dataset ...) 
  38.     ... 
  39.  
  40.     # instantiate network (which has been imported from *networks.py*) 
  41.     net = MyNetwork(...) 
  42.     ... 
  43.  
  44.     # create losses (criterion in pytorch) 
  45.     criterion_L1 = torch.nn.L1Loss() 
  46.     ... 
  47.  
  48.     # if running on GPU and we want to use cuda move model there 
  49.     use_cuda = torch.cuda.is_available() 
  50.     if use_cuda: 
  51.         netnet = net.cuda() 
  52.         ... 
  53.  
  54.     # create optimizers 
  55.     optim = torch.optim.Adam(net.parameters(), lr=opt.lr) 
  56.     ... 
  57.  
  58.     # load checkpoint if needed/ wanted 
  59.     start_n_iter = 0 
  60.     start_epoch = 0 
  61.     if opt.resume: 
  62.         ckpt = load_checkpoint(opt.path_to_checkpoint) # custom method for loading last checkpoint 
  63.         net.load_state_dict(ckpt['net']) 
  64.         start_epoch = ckpt['epoch'] 
  65.         start_n_iter = ckpt['n_iter'] 
  66.         optim.load_state_dict(ckpt['optim']) 
  67.         print("last checkpoint restored") 
  68.         ... 
  69.  
  70.     # if we want to run experiment on multiple GPUs we move the models there 
  71.     net = torch.nn.DataParallel(net) 
  72.     ... 
  73.  
  74.     # typically we use tensorboardX to keep track of experiments 
  75.     writer = SummaryWriter(...) 
  76.  
  77.     # now we start the main loop 
  78.     n_iter = start_n_iter 
  79.     for epoch in range(start_epoch, opt.epochs): 
  80.         # set models to train mode 
  81.         net.train() 
  82.         ... 
  83.  
  84.         # use prefetch_generator and tqdm for iterating through data 
  85.         pbar = tqdm(enumerate(BackgroundGenerator(train_data_loader, ...)), 
  86.                     total=len(train_data_loader)) 
  87.         start_time = time.time() 
  88.  
  89.         # for loop going through dataset 
  90.         for i, data in pbar: 
  91.             # data preparation 
  92.             img, label = data 
  93.             if use_cuda: 
  94.                 imgimg = img.cuda() 
  95.                 labellabel = label.cuda() 
  96.             ... 
  97.  
  98.             # It's very good practice to keep track of preparation time and computation time using tqdm to find any issues in your dataloader 
  99.             prepare_time = start_time-time.time() 
  100.  
  101.             # forward and backward pass 
  102.             optim.zero_grad() 
  103.             ... 
  104.             loss.backward() 
  105.             optim.step() 
  106.             ... 
  107.  
  108.             # udpate tensorboardX 
  109.             writer.add_scalar(..., n_iter) 
  110.             ... 
  111.  
  112.             # compute computation time and *compute_efficiency* 
  113.             process_time = start_time-time.time()-prepare_time 
  114.             pbar.set_description("Compute efficiency: {:.2f}, epoch: {}/{}:".format( 
  115.                 process_time/(process_time+prepare_time), epoch, opt.epochs)) 
  116.             start_time = time.time() 
  117.  
  118.         # maybe do a test pass every x epochs 
  119.         if epoch % x == x-1: 
  120.             # bring models to evaluation mode 
  121.             net.eval() 
  122.             ... 
  123.             #do some tests 
  124.             pbar = tqdm(enumerate(BackgroundGenerator(test_data_loader, ...)), 
  125.                     total=len(test_data_loader))  
  126.             for i, data in pbar: 
  127.                 ... 
  128.  
  129.             # save checkpoint if needed 
  130.             ... 

三、PyTorch 的多 GPU 訓(xùn)練

PyTorch 中有兩種使用多 GPU 進(jìn)行訓(xùn)練的模式。

根據(jù)我們的經(jīng)驗,這兩種模式都是有效的。然而,***種方法得到的結(jié)果更好、需要的代碼更少。由于第二種方法中的 GPU 間的通信更少,似乎具有輕微的性能優(yōu)勢。

1. 對每個網(wǎng)絡(luò)輸入的 batch 進(jìn)行切分

最常見的一種做法是直接將所有網(wǎng)絡(luò)的輸入切分為不同的批量數(shù)據(jù),并分配給各個 GPU。

這樣一來,在 1 個 GPU 上運(yùn)行批量大小為 64 的模型,在 2 個 GPU 上運(yùn)行時,每個 batch 的大小就變成了 32。這個過程可以使用「nn.DataParallel(model)」包裝器自動完成。

2. 將所有網(wǎng)絡(luò)打包到一個超級網(wǎng)絡(luò)中,并對輸入 batch 進(jìn)行切分

這種模式不太常用。下面的代碼倉庫向大家展示了 Nvidia 實現(xiàn)的 pix2pixHD,它有這種方法的實現(xiàn)。

地址:https://github.com/NVIDIA/pix2pixHD

四、PyTorch 中該做和不該做的

1. 在「nn.Module」的「forward」方法中避免使用 Numpy 代碼

Numpy 是在 CPU 上運(yùn)行的,它比 torch 的代碼運(yùn)行得要慢一些。由于 torch 的開發(fā)思路與 numpy 相似,所以大多數(shù) Numpy 中的函數(shù)已經(jīng)在 PyTorch 中得到了支持。

2. 將「DataLoader」從主程序的代碼中分離

載入數(shù)據(jù)的工作流程應(yīng)該獨(dú)立于你的主訓(xùn)練程序代碼。PyTorch 使用「background」進(jìn)程更加高效地載入數(shù)據(jù),而不會干擾到主訓(xùn)練進(jìn)程。

3. 不要在每一步中都記錄結(jié)果

通常而言,我們要訓(xùn)練我們的模型好幾千步。因此,為了減小計算開銷,每隔 n 步對損失和其它的計算結(jié)果進(jìn)行記錄就足夠了。尤其是,在訓(xùn)練過程中將中間結(jié)果保存成圖像,這種開銷是非常大的。

4. 使用命令行參數(shù)

使用命令行參數(shù)設(shè)置代碼執(zhí)行時使用的參數(shù)(batch 的大小、學(xué)習(xí)率等)非常方便。一個簡單的實驗參數(shù)跟蹤方法,即直接把從「parse_args」接收到的字典(dict 數(shù)據(jù))打印出來:

  1. # saves arguments to config.txt file 
  2. opt = parser.parse_args()with open("config.txt", "w") as f: 
  3.     f.write(opt.__str__()) 

5. 如果可能的話,請使用「Use .detach()」從計算圖中釋放張量

為了實現(xiàn)自動微分,PyTorch 會跟蹤所有涉及張量的操作。請使用「.detach()」來防止記錄不必要的操作。

6. 使用「.item()」打印出標(biāo)量張量

你可以直接打印變量。然而,我們建議你使用「variable.detach()」或「variable.item()」。在早期版本的 PyTorch(< 0.4)中,你必須使用「.data」訪問變量中的張量值。

7. 使用「call」方法代替「nn.Module」中的「forward」方法

這兩種方式并不完全相同,正如下面的 GitHub 問題單所指出的:

https://github.com/IgorSusmelj/pytorch-styleguide/issues/3

  1. output = self.net.forward(input) 
  2. # they are not equal! 
  3. output = self.net(input) 

原文鏈接:https://github.com/IgorSusmelj/pytorch-styleguide

【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號“機(jī)器之心( id: almosthuman2014)”】 

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2011-04-19 17:09:52

代碼編程

2016-01-12 09:56:51

優(yōu)美C代碼

2012-12-27 14:11:21

簡歷應(yīng)屆畢業(yè)生

2020-02-10 13:22:35

編程語言機(jī)器學(xué)習(xí)Python

2022-02-21 08:00:23

開發(fā)代碼程序員

2023-07-16 22:57:38

代碼場景業(yè)務(wù)

2021-05-18 17:55:29

SaaS軟件

2010-09-02 09:44:16

室外WLAN

2009-07-06 18:24:51

IT資產(chǎn)運(yùn)維管理廣通信達(dá)科技

2018-07-16 12:36:48

編程語言PythonJava

2012-09-25 13:32:31

大數(shù)據(jù)Hadoop

2011-07-15 16:57:43

AJAX

2020-04-10 09:07:09

BEC商業(yè)郵件欺詐網(wǎng)絡(luò)釣魚

2022-04-27 10:07:02

Linux文檔命令

2023-08-24 21:49:54

人工智能高端算法工程師

2018-09-04 15:45:58

Python代碼編程語言

2022-03-25 08:34:57

物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)IOT

2019-11-15 15:09:27

Python代碼優(yōu)雅

2018-10-15 15:24:18

Python函數(shù)代碼

2015-07-01 16:11:30

數(shù)據(jù)人才數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號