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

      網(wǎng)友投稿 931 2025-04-04

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


      這是一個(gè)開(kāi)發(fā)的項(xiàng)目,歡迎其它讀者改進(jìn)該文檔:https://github.com/IgorSusmelj/pytorch-styleguide。

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

      清點(diǎn) Python 裝備

      建議使用 Python 3.6 以上版本

      根據(jù)我們的經(jīng)驗(yàn),我們推薦使用 Python 3.6 以上的版本,因?yàn)樗鼈兙哂幸韵绿匦裕@些特性可以使我們很容易寫(xiě)出簡(jiǎn)潔的代碼:

      自 Python 3.6 以后支持「typing」模塊

      自 Python 3.6 以后支持格式化字符串(f string)

      Python 風(fēng)格指南

      我們?cè)噲D遵循 Google 的 Python 編程風(fēng)格。請(qǐng)參閱 Google 提供的優(yōu)秀的 python 編碼風(fēng)格指南:

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

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

      集成開(kāi)發(fā)環(huán)境

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

      Jupyter Notebooks VS Python 腳本

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

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

      在開(kāi)始的階段,使用 Jupyter Notebook

      對(duì)數(shù)據(jù)和模型進(jìn)行探索

      在 notebook 的單元中構(gòu)建你的類/方法

      將代碼移植到 Python 腳本中

      在服務(wù)器上訓(xùn)練/部署

      開(kāi)發(fā)常備庫(kù)

      常用的程序庫(kù)有:

      文件組織

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

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

      PyTorch 開(kāi)發(fā)風(fēng)格與技巧

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

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

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

      output?=?self.net(input)

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

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

      class ConvBlock(nn.Module):

      def __init__(self):

      super(ConvBlock, self).__init__()

      block = [nn.Conv2d(...)]

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

      block += [nn.ReLU()]

      block += [nn.BatchNorm2d(...)]

      self.block = nn.Sequential(*block)

      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)

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

      我們復(fù)用了簡(jiǎn)單的循環(huán)構(gòu)建模塊(如卷積塊 ConvBlocks),它們由相同的循環(huán)模式(卷積、激活函數(shù)、歸一化)組成,并裝入獨(dú)立的 nn.Module 中。

      我們構(gòu)建了一個(gè)所需要層的列表,并最終使用「nn.Sequential()」將所有層級(jí)組合到了一個(gè)模型中。我們?cè)?list 對(duì)象前使用「*」操作來(lái)展開(kāi)它。

      在前向傳導(dǎo)過(guò)程中,我們直接使用輸入數(shù)據(jù)運(yùn)行模型。

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

      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?ou

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

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

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

      class?Vgg19(torch.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

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

      我們使用由「torchvision」包提供的預(yù)訓(xùn)練模型

      我們將一個(gè)網(wǎng)絡(luò)切分成三個(gè)模塊,每個(gè)模塊由預(yù)訓(xùn)練模型中的層組成

      我們通過(guò)設(shè)置「requires_grad = False」來(lái)固定網(wǎng)絡(luò)權(quán)重

      我們返回一個(gè)帶有三個(gè)模塊輸出的 list

      自定義損失函數(shù)

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

      class?CustomLoss(torch.nn.Module): ????def?__init__(self): ????????super(CustomLoss,self).__init__() ????def?forward(self,x,y): ????????loss?=?torch.mean((x?-?y)**2) ????????return?loss

      訓(xùn)練模型的最佳代碼結(jié)構(gòu)

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

      使用 prefetch_generator 中的 BackgroundGenerator 來(lái)加載下一個(gè)批量數(shù)據(jù)

      使用 tqdm 監(jiān)控訓(xùn)練過(guò)程,并展示計(jì)算效率,這能幫助我們找到數(shù)據(jù)加載流程中的瓶頸

      #?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 訓(xùn)練

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

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

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

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

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

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

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

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

      PyTorch 中該做和不該做的

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

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

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

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

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

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

      使用命令行參數(shù)

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

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

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

      為了實(shí)現(xiàn)自動(dòng)微分,PyTorch 會(huì)跟蹤所有涉及張量的操作。請(qǐng)使用「.detach()」來(lái)防止記錄不必要的操作。

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

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

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

      這兩種方式并不完全相同,正如下面的 GitHub 問(wèn)題單所指出的:https://github.com/IgorSusmelj/pytorch-styleguide/issues/3

      output = self.net.forward(input)

      # they are not equal!

      output = self.net(input)

      EI 人工智能 AI

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:如何在excel單元格插入一個(gè)下拉列表(如何在excel單元格中添加下拉菜單)
      下一篇:業(yè)務(wù)流程圖制作模板圖片(業(yè)務(wù)流程圖制作軟件
      相關(guān)文章
      久久久久亚洲精品天堂| 香蕉视频在线观看亚洲| 久久精品国产精品亚洲艾| 国产亚洲AV手机在线观看| 亚洲日本一区二区三区在线不卡| 午夜亚洲国产成人不卡在线| 亚洲AV无码AV日韩AV网站| 亚洲爆乳精品无码一区二区| 亚洲精品自偷自拍无码| 亚洲高清一区二区三区电影| 亚洲国产成人无码AV在线影院| 亚洲免费综合色在线视频| 亚洲国产精品无码中文lv| 久久精品国产亚洲AV天海翼| 亚洲成A人片77777国产| 亚洲精品偷拍视频免费观看| 久久精品国产亚洲7777| 亚洲最大激情中文字幕| 久久久久久a亚洲欧洲aⅴ| 久久91亚洲精品中文字幕| 97se亚洲综合在线| 亚洲免费二区三区| 亚洲色大成网站www尤物| 亚洲av无码无线在线观看| 亚洲国产成人AV网站| 亚洲国产一区二区视频网站| 亚洲综合区小说区激情区| 亚洲精品成人网站在线观看 | 亚洲精品高清久久| 亚洲精品美女久久久久9999| 亚洲乱码在线播放| 亚洲国产精品嫩草影院| 国产精品亚洲一区二区三区久久| 亚洲成a人在线看天堂无码| 久久久久亚洲精品男人的天堂| 亚洲人成网站在线观看播放| 亚洲国语精品自产拍在线观看 | 国产精品亚洲w码日韩中文| 国产成A人亚洲精V品无码性色| 久久久久亚洲AV无码专区首JN| 亚洲国产夜色在线观看|