快速上手笔记,PyTorch模型训练实用教程(附代码)
機器之心發(fā)布
作者:余霆嵩
前言
自 2017 年 1 月 PyTorch 推出以來,其熱度持續(xù)上升,一度有趕超 TensorFlow 的趨勢。PyTorch 能在短時間內(nèi)被眾多研究人員和工程師接受并推崇是因為其有著諸多優(yōu)點,如采用 Python 語言、動態(tài)圖機制、網(wǎng)絡(luò)構(gòu)建靈活以及擁有強大的社群等。因此,走上學(xué)習(xí) PyTorch 的道路已刻不容緩。
本教程以實際應(yīng)用、工程開發(fā)為目的,著重介紹模型訓(xùn)練過程中遇到的實際問題和方法。如上圖所示,在機器學(xué)習(xí)模型開發(fā)中,主要涉及三大部分,分別是數(shù)據(jù)、模型和損失函數(shù)及優(yōu)化器。本文也按順序的依次介紹數(shù)據(jù)、模型和損失函數(shù)及優(yōu)化器,從而給大家?guī)砬逦臋C器學(xué)習(xí)結(jié)構(gòu)。
通過本教程,希望能夠給大家?guī)硪粋€清晰的模型訓(xùn)練結(jié)構(gòu)。當(dāng)模型訓(xùn)練遇到問題時,需要通過可視化工具對數(shù)據(jù)、模型、損失等內(nèi)容進(jìn)行觀察,分析并定位問題出在數(shù)據(jù)部分?模型部分?還是優(yōu)化器?只有這樣不斷的通過可視化診斷你的模型,不斷的對癥下藥,才能訓(xùn)練出一個較滿意的模型。
為什么寫此教程
前幾年一直在用 Caffe 和 MatConvNet,近期轉(zhuǎn) PyTorch。當(dāng)時只想快速地用上 PyTorch 進(jìn)行模型開發(fā),然而搜了一圈 PyTorch 的教程,并沒有找到一款適合的。很多 PyTorch 教程是從學(xué)習(xí)機器學(xué)習(xí) (深度學(xué)習(xí)) 的角度出發(fā),以 PyTorch 為工具進(jìn)行編寫,里面介紹很多模型,并且附上模型的 demo。
然而,工程應(yīng)用開發(fā)中所遇到的問題并不是跑一個模型的 demo 就可以的,模型開發(fā)需要對數(shù)據(jù)的預(yù)處理、數(shù)據(jù)增強、模型定義、權(quán)值初始化、模型 Finetune、學(xué)習(xí)率調(diào)整策略、損失函數(shù)選取、優(yōu)化器選取、可視化等等。鑒于此,我只能自己對著官方文檔,一步一步地學(xué)習(xí)。
起初,只是做了一些學(xué)習(xí)筆記,后來覺得這些內(nèi)容應(yīng)該對大家有些許幫助,畢竟在互聯(lián)網(wǎng)上很難找到這類內(nèi)容的分享,于是此教程就誕生了。
本教程內(nèi)容及結(jié)構(gòu)
本教程內(nèi)容主要為在 PyTorch 中訓(xùn)練一個模型所可能涉及到的方法及函數(shù),并且對 PyTorch 提供的數(shù)據(jù)增強方法(22 個)、權(quán)值初始化方法(10 個)、損失函數(shù)(17 個)、優(yōu)化器(6 個)及 tensorboardX 的方法(13 個)進(jìn)行了詳細(xì)介紹。
本教程分為四章,結(jié)構(gòu)與機器學(xué)習(xí)三大部分一致:
第一章,介紹數(shù)據(jù)的劃分,預(yù)處理,數(shù)據(jù)增強;
第二章,介紹模型的定義,權(quán)值初始化,模型 Finetune;
第三章,介紹各種損失函數(shù)及優(yōu)化器;
第四章,介紹可視化工具,用于監(jiān)控數(shù)據(jù)、模型權(quán)及損失函數(shù)的變化。
本教程適用讀者:
想熟悉 PyTorch 使用的朋友;
想采用 PyTorch 進(jìn)行模型訓(xùn)練的朋友;
正采用 PyTorch,但無有效機制去診斷模型的朋友;
干貨直達(dá):
1.6 transforms 的二十二個方法
2.2 權(quán)值初始化的十種方法
3.1 PyTorch 的十七個損失函數(shù)
3.3 PyTorch 的十個優(yōu)化器
3.4 PyTorch 的六個學(xué)習(xí)率調(diào)整方法
4.1 TensorBoardX
項目代碼:https://github.com/tensor-yu/PyTorch_Tutorial
為了展示該教程的內(nèi)容,讀者可試讀第二章的第一小節(jié),了解PyTorch如何搭建模型:
第二章 模型
第二章介紹關(guān)于網(wǎng)絡(luò)模型的一系列內(nèi)容,包括模型的定義,模型參數(shù)初始化方法,模型的保存和加載,模型的 finetune(本質(zhì)上還是模型權(quán)值初始化),首先介紹模型的定義。
2.1 模型的搭建
2.1.1 模型定義的三要
首先,必須繼承 nn.Module 這個類,要讓 PyTorch 知道這個類是一個 Module。
其次,在__init__(self) 中設(shè)置好需要的「組件"(如 conv、pooling、Linear、BatchNorm 等)。
最后,在 forward(self, x) 中用定義好的「組件」進(jìn)行組裝,就像搭積木,把網(wǎng)絡(luò)結(jié)構(gòu)搭建出來,這樣一個模型就定義好了。
接下來,請看代碼,在/Code/main_training/main.py 中可以看到定義了一個類 class Net(nn.Module),先看__init__(self) 函數(shù):
????????super(Net,?self).__init__()
????????self.conv1?=?nn.Conv2d(3,?6,?5)
????????self.pool1?=?nn.MaxPool2d(2,?2)
????????self.conv2?=?nn.Conv2d(6,?16,?5)
????????self.pool2?=?nn.MaxPool2d(2,?2)
????????self.fc1?=?nn.Linear(16?*?5?*?5,?120)
????????self.fc2?=?nn.Linear(120,?84)
????????self.fc3?=?nn.Linear(84,?10)
第一行是初始化,往后定義了一系列組件,如由 Conv2d 構(gòu)成的 conv1,有 MaxPool2d 構(gòu)成的 poo1l,這些操作均由 torch.nn 提供,torch.nn 中的操作可查看文檔:https://PyTorch.org/docs/stable/nn.html#。
當(dāng)這些組件定義好之后,就可以定義 forward() 函數(shù),用來搭建網(wǎng)絡(luò)結(jié)構(gòu),請看代碼:
????????x?=?self.pool1(F.relu(self.conv1(x)))
????????x?=?self.pool2(F.relu(self.conv2(x)))
????????x?=?x.view(-1,?16?*?5?*?5)
????????x?=?F.relu(self.fc1(x))
????????x?=?F.relu(self.fc2(x))
????????x?=?self.fc3(x)
????????return?x
x 為模型的輸入,第一行表示,x 經(jīng)過 conv1,然后經(jīng)過激活函數(shù) relu,再經(jīng)過 pool1 操作;
第二行于第一行一樣;第三行,表示將 x 進(jìn)行 reshape,為了后面做為全連接層的輸入;
第四,第五行的操作都一樣,先經(jīng)過全連接層 fc,然后經(jīng)過 relu;
第六行,模型的最終輸出是 fc3 輸出。
至此,一個模型定義完畢,接著就可以在后面進(jìn)行使用。例如,實例化一個模型 net = Net(),然后把輸入 inputs 扔進(jìn)去,outputs = net(inputs),就可以得到輸出 outputs。
2.1.2 模型定義多說兩句
上面只是介紹了模型定義的要素和過程,但是在工程應(yīng)用中會碰到各種各樣的網(wǎng)絡(luò)模型,這時,我們就需要一些實用工具來幫助我們定義模型了。
這里以 Resnet34 為例介紹「復(fù)雜」模型的定義,這部分代碼從 github 上獲取。
地址:https://github.com/yuanlairuci110/PyTorch-best-practice-master/blob/master/models/ResNet34.py
????'''
????實現(xiàn)子module:?Residual?Block
????'''
????def?__init__(self,?inchannel,?outchannel,?stride=1,?shortcut=None):
????????super(ResidualBlock,?self).__init__()
????????self.left?=?nn.Sequential(
????????????????nn.Conv2d(inchannel,?outchannel,?3,?stride,?1,?bias=False),
????????????????nn.BatchNorm2d(outchannel),
????????????????nn.ReLU(inplace=True),
????????????????nn.Conv2d(outchannel,?outchannel,?3,?1,?1,?bias=False),
????????????????nn.BatchNorm2d(outchannel)?)
????????self.right?=?shortcut
????def?forward(self,?x):
????????out?=?self.left(x)
????????residual?=?x?if?self.right?is?None?else?self.right(x)
????????out?+=?residual
????????return?F.relu(out)
class?ResNet34(BasicModule):
????'''
????實現(xiàn)主module:ResNet34
????ResNet34包含多個layer,每個layer又包含多個Residual?block
????用子module來實現(xiàn)Residual?block,用_make_layer函數(shù)來實現(xiàn)layer
????'''
????def?__init__(self,?num_classes=2):
????????super(ResNet34,?self).__init__()
????????self.model_name?=?'resnet34'
????????#?前幾層:?圖像轉(zhuǎn)換
????????self.pre?=?nn.Sequential(
????????????????nn.Conv2d(3,?64,?7,?2,?3,?bias=False),
????????????????nn.BatchNorm2d(64),
????????????????nn.ReLU(inplace=True),
????????????????nn.MaxPool2d(3,?2,?1))
????????#?重復(fù)的layer,分別有3,4,6,3個residual?block
????????self.layer1?=?self._make_layer(?64,?128,?3)
????????self.layer2?=?self._make_layer(?128,?256,?4,?stride=2)
????????self.layer3?=?self._make_layer(?256,?512,?6,?stride=2)
????????self.layer4?=?self._make_layer(?512,?512,?3,?stride=2)
????????#分類用的全連接
????????self.fc?=?nn.Linear(512,?num_classes)
????def?_make_layer(self,??inchannel,?outchannel,?block_num,?stride=1):
????????'''
????????構(gòu)建layer,包含多個residual?block
????????'''
????????shortcut?=?nn.Sequential(
????????????????nn.Conv2d(inchannel,outchannel,1,stride,?bias=False),
????????????????nn.BatchNorm2d(outchannel))
????????layers?=?[]
????????layers.append(ResidualBlock(inchannel,?outchannel,?stride,?shortcut))
????????for?i?in?range(1,?block_num):
????????????layers.append(ResidualBlock(outchannel,?outchannel))
????????return?nn.Sequential(*layers)
????def?forward(self,?x):
????????x?=?self.pre(x)
????????x?=?self.layer1(x)
????????x?=?self.layer2(x)
????????x?=?self.layer3(x)
????????x?=?self.layer4(x)
????????x?=?F.avg_pool2d(x,?7)
????????x?=?x.view(x.size(0),?-1)
????????return?self.fc(x)
還是從三要素出發(fā)看看是怎么定義 Resnet34 的。
首先,繼承 nn.Module;
其次,看__init__() 函數(shù),在__init__() 中,定義了這些組件,self.pre,self.layer1-4, self.fc ;
最后,看 forward(),分別用了在__init__() 中定義的一系列組件,并且用了 torch.nn.functional.avg_pool2d 這個操作。
至此,網(wǎng)絡(luò)定義完成。
以為就完了?怎么可能,__init__() 函數(shù)中的組件是怎么定義的,在__init__() 中出現(xiàn)了 torch.nn.Sequential。
組件定義還調(diào)用函數(shù)_make_layer(),其中也用到了 torch.nn.Sequential,其中還調(diào)用了 ResidualBlock(nn.Module),在 ResidualBlock(nn.Module) 中有一次調(diào)用了 torch.nn.Sequential。
torch.nn.Sequential 到底是什么呢?為什么都在用呢?
2.1.3 nn.Sequetial
torch.nn.Sequential 其實就是 Sequential 容器,該容器將一系列操作按先后順序給包起來,方便重復(fù)使用。例如 Resnet 中有很多重復(fù)的 block,就可以用 Sequential 容器把重復(fù)的地方包起來。
官方文檔中給出兩個使用例子:
model?=?nn.Sequential(
??????????nn.Conv2d(1,20,5),
??????????nn.ReLU(),
??????????nn.Conv2d(20,64,5),
??????????nn.ReLU()
????????)
#?Example?of?using?Sequential?with?OrderedDict
model?=?nn.Sequential(OrderedDict([
??????????('conv1',?nn.Conv2d(1,20,5)),
??????????('relu1',?nn.ReLU()),
??????????('conv2',?nn.Conv2d(20,64,5)),
??????????('relu2',?nn.ReLU())
????????]))
小結(jié):
模型的定義就是先繼承,再構(gòu)建組件,最后組裝。
其中基本組件可從 torch.nn 中獲取,或者從 torch.nn.functional 中獲取,同時為了方便重復(fù)使用組件,可以使用 Sequential 容器將一系列組件包起來,最后在 forward() 函數(shù)中將這些組件組裝成你的模型。
獲取方式一:
獲取方式二:
鏈接: https://pan.baidu.com/s/11hvPGusAopXNwuCsuLilCA?
提取碼: anw5?
機器之心CES 2019專題報道即將到來,歡迎大家積極關(guān)注。
點擊「閱讀原文」查看機器之心專題頁。
總結(jié)
以上是生活随笔為你收集整理的快速上手笔记,PyTorch模型训练实用教程(附代码)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 这种有序神经元,像你熟知的循环神经网络吗
- 下一篇: 新手福利:免费百页机器学习入门书