日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人工智能 > pytorch >内容正文

pytorch

深度学习框架PyTorch一书的学习-第四章-神经网络工具箱nn

發(fā)布時(shí)間:2025/3/8 pytorch 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深度学习框架PyTorch一书的学习-第四章-神经网络工具箱nn 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

參考https://github.com/chenyuntc/pytorch-book/tree/v1.0

希望大家直接到上面的網(wǎng)址去查看代碼,下面是本人的筆記

?

本章介紹的nn模塊是構(gòu)建與autograd之上的神經(jīng)網(wǎng)絡(luò)模塊

除了nn外還會介紹神經(jīng)網(wǎng)絡(luò)中常用的工具,比如優(yōu)化器optim、初始化init等

1.nn.Module

torch的核心數(shù)據(jù)結(jié)構(gòu)是Module,它是一個(gè)抽象的概念,既可以表示神經(jīng)網(wǎng)絡(luò)中的某個(gè)層,也可以表示一個(gè)包含很多層的神經(jīng)網(wǎng)絡(luò)

在實(shí)際使用中,最常見的做法是繼承nn.Module,攥寫自己的網(wǎng)絡(luò)層

下面先來看看如何使用nn.Module實(shí)現(xiàn)自己的全連接層。全連接層,又名仿射層,輸出y和輸入x滿足y=Wx +b,W和b是可以學(xué)習(xí)的參數(shù)

import torch as t from torch import nn from torch.autograd import Variable as V

定義函數(shù):

class Linear(nn.Module): #繼承nn.Moduledef __init__(self, in_features, out_features):super(Linear, self).__init__() #等價(jià)于nn.Module.__init__(self)self.w = nn.Parameter(t.randn(in_features,out_features)) #參數(shù)的命名規(guī)范下面會說明self.b = nn.Parameter(t.randn(out_features))def forward(self, x):x = x.mm(self.w)return x + self.b.expand_as(x)

運(yùn)行:

layer = Linear(4,3) input = V(t.randn(2,4)) output = layer(input) output

返回:

tensor([[-0.4199, 3.7252, 1.9104],[ 2.3267, 2.0576, -2.9361]], grad_fn=<AddBackward0>)

查看參數(shù):

for name, parameter in layer.named_parameters():print(name, parameter) #即w,b

返回:

w Parameter containing: tensor([[ 1.1147, -0.8054, -0.7915],[-0.3828, 0.1073, 2.0440],[-0.3297, 0.0465, 0.0759],[ 0.1022, 0.1638, 1.0872]], requires_grad=True) b Parameter containing: tensor([ 1.2872, 2.3990, -0.7711], requires_grad=True)

可見,全連接層的實(shí)現(xiàn)非常簡單,其代碼量不超過10行,但需注意以下幾點(diǎn):

  • 自定義層Linear必須繼承nn.Module,并且在其構(gòu)造函數(shù)中需調(diào)用nn.Module的構(gòu)造函數(shù),即super(Linear, self).__init__()?或nn.Module.__init__(self),推薦使用第一種用法,盡管第二種寫法更直觀。
  • 在構(gòu)造函數(shù)__init__中必須自己定義可學(xué)習(xí)的參數(shù),并封裝成Parameter,如在本例中我們把w和b封裝成parameter。parameter是一種特殊的Variable,但其默認(rèn)需要求導(dǎo)(requires_grad = True),感興趣的讀者可以通過nn.Parameter??,查看Parameter類的源代碼。
  • forward函數(shù)實(shí)現(xiàn)前向傳播過程,其輸入可以是一個(gè)或多個(gè)variable,對x的任何操作也必須是variable支持的操作。
  • 無需寫反向傳播函數(shù),因其前向傳播都是對variable進(jìn)行操作,nn.Module能夠利用autograd自動實(shí)現(xiàn)反向傳播,這點(diǎn)比Function簡單許多。
  • 使用時(shí),直觀上可將layer看成數(shù)學(xué)概念中的函數(shù),調(diào)用layer(input)即可得到input對應(yīng)的結(jié)果。它等價(jià)于layers.__call__(input),在__call__函數(shù)中,主要調(diào)用的是?layer.forward(x),另外還對鉤子做了一些處理。所以在實(shí)際使用中應(yīng)盡量使用layer(x)而不是使用layer.forward(x),關(guān)于鉤子技術(shù)將在下文講解。
  • Module中的可學(xué)習(xí)參數(shù)可以通過named_parameters()或者parameters()返回迭代器,前者會給每個(gè)parameter都附上名字,使其更具有辨識度。

可見利用Module實(shí)現(xiàn)的全連接層,比利用Function實(shí)現(xiàn)的更為簡單,因其不再需要寫反向傳播函數(shù)。

Module能夠自動檢測到自己的Parameter,并將其作為學(xué)習(xí)參數(shù)。除了parameter之外,Module還包含子Module,主Module能夠遞歸查找子Module中的parameter。下面再來看看稍微復(fù)雜一點(diǎn)的網(wǎng)絡(luò),多層感知機(jī)。

多層感知機(jī)的網(wǎng)絡(luò)結(jié)構(gòu)如圖4-1所示,它由兩個(gè)全連接層組成,采用函數(shù)作為激活函數(shù),圖中沒有畫出。

class Perceptron(nn.Module):def __init__(self, in_features, hidden_features, out_features):super(Perceptron, self).__init__()self.layer1 = Linear(in_features, hidden_features) #使用的是上面定義的Linear函數(shù)self.layer2 = Linear(hidden_features, out_features)def forward(self, x):x = self.layer1(x)x = t.sigmoid(x) #激活函數(shù)return self.layer2(x)

調(diào)用:

perceptron = Perceptron(3,4,1) for name, param in perceptron.named_parameters():print(name, param.size())

返回:

layer1.w torch.Size([3, 4]) layer1.b torch.Size([4]) layer2.w torch.Size([4, 1]) layer2.b torch.Size([1])


可見,即使是稍復(fù)雜的多層感知機(jī),其實(shí)現(xiàn)依舊很簡單。這里新增兩個(gè)知識點(diǎn):

  • 構(gòu)造函數(shù)__init__中,可利用前面自定義的Linear層(module),作為當(dāng)前module對象的一個(gè)子module,它的可學(xué)習(xí)參數(shù),也會成為當(dāng)前module的可學(xué)習(xí)參數(shù)。
  • 在前向傳播函數(shù)中,我們有意識地將輸出變量都命名成x,是為了能讓Python回收一些中間層的輸出,從而節(jié)省內(nèi)存。但并不是所有都會被回收,有些variable雖然名字被覆蓋,但其在反向傳播仍需要用到,此時(shí)Python的內(nèi)存回收模塊將通過檢查引用計(jì)數(shù),不會回收這一部分內(nèi)存。

module中parameter的命名規(guī)范:

  • 對于類似self.param_name = nn.Parameter(t.randn(3, 4)),命名為param_name
  • 對于子Module中的parameter,會其名字之前加上當(dāng)前Module的名字。如對于self.sub_module = SubModel(),SubModel中有個(gè)parameter的名字叫做param_name,那么二者拼接而成的parameter name 就是sub_module.param_name。

為方便用戶使用,PyTorch實(shí)現(xiàn)了神經(jīng)網(wǎng)絡(luò)中絕大多數(shù)的layer,這些layer都繼承于nn.Module,封裝了可學(xué)習(xí)參數(shù)parameter,并實(shí)現(xiàn)了forward函數(shù),且很多都專門針對GPU運(yùn)算進(jìn)行了CuDNN優(yōu)化,其速度和性能都十分優(yōu)異。本書不準(zhǔn)備對nn.Module中的所有層進(jìn)行詳細(xì)介紹,具體內(nèi)容讀者可參照官方文檔。閱讀文檔時(shí)應(yīng)主要關(guān)注以下幾點(diǎn):

  • 構(gòu)造函數(shù)的參數(shù),如nn.Linear(in_features, out_features, bias),需關(guān)注這三個(gè)參數(shù)的作用。
  • 屬性,可學(xué)習(xí)參數(shù),子module。如nn.Linear中有weight和bias兩個(gè)可學(xué)習(xí)參數(shù),不包含子module。
  • 輸入輸出的形狀,如nn.linear的輸入形狀是(N, input_features),輸出為(N,output_features),N是batch_size。

這些自定義layer對輸入形狀都有假設(shè):輸入的不是單個(gè)數(shù)據(jù),而是一個(gè)batch。

若想輸入一個(gè)數(shù)據(jù),則必須調(diào)用unsqueeze(0)函數(shù)將數(shù)據(jù)偽裝成batch_size=1的batch

?

下面將從應(yīng)用層面出發(fā),對一些常用的layer做簡單介紹,更詳細(xì)的用法請查看文檔,這里只作概覽參考。

2.常用神經(jīng)網(wǎng)絡(luò)層

1.圖像相關(guān)層

圖像相關(guān)層主要包括卷積層(Conv)、池化層(Pool)等,這些層在實(shí)際使用中可分為一維(1D)、二維(2D)、三維(3D),池化方式又分為平均池化(AvgPool)、最大值池化(MaxPool)、自適應(yīng)池化(AdaptiveAvgPool)等。而卷積層除了常用的前向卷積之外,還有逆卷積(TransposeConv)。下面舉例說明一些基礎(chǔ)的使用。

from PIL import Image from torchvision.transforms import ToTensor, ToPILImage to_tensor = ToTensor() #img -> tensor to_pil = ToPILImage() lena = Image.open('imgs/lena.png') #這是個(gè)灰度圖像 lena

返回圖片:

#輸入是一個(gè)batch,batch_size = 1 print(lena.size) #(1200200) input = to_tensor(lena).unsqueeze(0) #變成(1,1,200,200) print(input)
#銳化卷積核 kernel
= t.ones(3 ,3)/-9 kernel[1][1] = 1 conv = nn.Conv2d(1, 1, (3,3), 1, bias = False) conv.weight.data = kernel.view(1,1,3,3)out = conv(V(input)) to_pil(out.data.squeeze(0))

返回:

(200, 200) tensor([[[[0.6353, 0.6314, 0.6314, ..., 0.6118, 0.6667, 0.5922],[0.6353, 0.6314, 0.6314, ..., 0.6078, 0.6510, 0.5647],[0.6275, 0.6235, 0.6235, ..., 0.4824, 0.4157, 0.3098],...,[0.1961, 0.2078, 0.2078, ..., 0.2510, 0.3098, 0.3412],[0.1922, 0.2000, 0.2039, ..., 0.3098, 0.3686, 0.3804],[0.1843, 0.2078, 0.1961, ..., 0.3569, 0.3961, 0.4078]]]])

圖示:

?

池化層可以看作是一種特殊的卷積層,用來下采樣。但池化層沒有可學(xué)習(xí)參數(shù),其weight是固定的。

?

pool = nn.AvgPool2d(2,2) #平均池化 list(pool.parameters()) #返回[],因?yàn)闊o參數(shù)

?

out = pool(input) #對數(shù)據(jù)進(jìn)行池化 to_pil(out.data.squeeze(0)) #顯示結(jié)果

圖示:

?

除了卷積層和池化層,深度學(xué)習(xí)中還將常用到以下幾個(gè)層:

  • Linear:全連接層。
  • BatchNorm:批規(guī)范化層,分為1D、2D和3D。除了標(biāo)準(zhǔn)的BatchNorm之外,還有在風(fēng)格遷移中常用到的InstanceNorm層。
  • Dropout:dropout層,用來防止過擬合,同樣分為1D、2D和3D。 下面通過例子來說明它們的使用。

1)全連接層

#輸入batch_size = 2,維度為3 input = t.randn(2,3) linear = nn.Linear(3,4) h = linear(input) h

返回:

tensor([[ 0.5406, -0.0327, 0.7291, 0.5262],[ 0.1471, -0.1924, 0.8960, 0.7801]], grad_fn=<AddmmBackward>)

2)批規(guī)范化,即歸一化層

#4 channel,初始化標(biāo)準(zhǔn)差為4,均值為0 bn = nn.BatchNorm1d(4) #對小批量(mini-batch)的2d或3d輸入進(jìn)行批標(biāo)準(zhǔn)化(Batch Normalization)操作,即歸一化 bn.weight.data = t.ones(4) * 4 bn.bias.data = t.zeros(4)bn_out = bn(h) #注意輸出的均值和方差 #方差是標(biāo)準(zhǔn)差的平方,計(jì)算無偏方差分母會減1 #使用unbiased=False 分母不減1 bn_out.mean(0), bn_out.var(0, unbiased=False) #歸一化后平均值為0,方差為標(biāo)準(zhǔn)單位方差

返回:

(tensor([0., 0., 0., 0.], grad_fn=<MeanBackward0>),tensor([15.9959, 15.9749, 15.9771, 15.9901], grad_fn=<VarBackward1>))

Batch歸一化使用在z上,下面激活函數(shù)處的例子可見,對其進(jìn)行卷積 -> batch歸一化 -> 激活函數(shù)

3)dropout正則化層

#每個(gè)元素以0.5的概率舍棄,實(shí)現(xiàn)dropout正則化,消除過擬合問題 dropout = nn.Dropout(0.5) o = dropout(bn_out) o #有一半左右的數(shù)變成0

返回:

tensor([[ 0.0000, 7.9937, -7.9943, -7.9975],[-0.0000, -0.0000, 7.9943, 7.9975]], grad_fn=<MulBackward0>)

以上很多例子中都對module的屬性直接操作,其大多數(shù)是可學(xué)習(xí)參數(shù),一般會隨著學(xué)習(xí)的進(jìn)行而不斷改變。實(shí)際使用中除非需要使用特殊的初始化,應(yīng)盡量不要直接修改這些參數(shù)。

?

2.激活函數(shù)

1)ReLu


relu = nn.ReLU(inplace=True) input = t.randn(2,3) print(input) output = relu(input) print(output) #小于0的都被截?cái)酁? #等價(jià)于input.clamp(min=0)

返回:

tensor([[ 1.2619, -0.9128, 0.6259],[-1.4834, 0.7297, -0.8562]]) tensor([[1.2619, 0.0000, 0.6259],[0.0000, 0.7297, 0.0000]])

ReLU函數(shù)有個(gè)inplace參數(shù),如果設(shè)為True,它會把輸出直接覆蓋到輸入中,這樣可以節(jié)省內(nèi)存/顯存。之所以可以覆蓋是因?yàn)樵谟?jì)算ReLU的反向傳播時(shí),只需根據(jù)輸出就能夠推算出反向傳播的梯度。

但是只有少數(shù)的autograd操作支持inplace操作(如tensor.sigmoid_()),除非你明確地知道自己在做什么,否則一般不要使用inplace操作。

?

在以上的例子中,基本上都是將每一層的輸出直接作為下一層的輸入,這種網(wǎng)絡(luò)稱為前饋傳播網(wǎng)絡(luò)(feedforward neural network)。

??

對于此類網(wǎng)絡(luò)如果每次都寫復(fù)雜的forward函數(shù)會有些麻煩,在此就有兩種簡化方式,ModuleList和Sequential。其中Sequential是一個(gè)特殊的module,它包含幾個(gè)子Module,前向傳播時(shí)會將輸入一層接一層的傳遞下去。ModuleList也是一個(gè)特殊的module,可以包含幾個(gè)子module,可以像用list一樣使用它,但不能直接把輸入傳給ModuleList。下面舉例說明。

1)Sequential

#Sequential的三種寫法 #第一種 net1 = nn.Sequential() net1.add_module('conv', nn.Conv2d(3,3,3)) net1.add_module('batchnorm', nn.BatchNorm2d(3)) net1.add_module('activation_layer', nn.ReLU())#第二種 net2 = nn.Sequential(nn.Conv2d(3,3,3),nn.BatchNorm2d(3),nn.ReLU())#第三種 from collections import OrderedDict net3 = nn.Sequential(OrderedDict([('conv1', nn.Conv2d(3,3,3)),('bn1', nn.BatchNorm2d(3)),('relu1',nn.ReLU())])) print('net1:', net1) print('net2:', net2) print('net3:', net3)

返回:

net1: Sequential((conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))(batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(activation_layer): ReLU() ) net2: Sequential((0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))(1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU() ) net3: Sequential((conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))(bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu1): ReLU() )

?

#可根據(jù)名字后序號取出子module net1.conv, net2[0], net3.conv1

返回:

(Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)))

調(diào)用方式為:

input = t.rand(1,3,4,4) output1 = net1(input) output2 = net2(input) output3 = net3(input) output4 = net3.relu1(net1.batchnorm(net1.conv(input)))

?

2)ModuleList

modellist = nn.ModuleList([nn.Linear(3,4), nn.ReLU(), nn.Linear(4,2)]) input = t.randn(1,3) for model in modellist:input = model(input) print(input)

返回:

tensor([[-0.6547, 0.8027]], grad_fn=<AddmmBackward>)

?

# 下面會報(bào)錯,因?yàn)閙odellist沒有實(shí)現(xiàn)forward方法 output = modellist(input)

看到這里,讀者可能會問,為何不直接使用Python中自帶的list,而非要多此一舉呢?這是因?yàn)镸oduleList是Module的子類,當(dāng)在Module中使用它的時(shí)候,就能自動識別為子module。

下面舉一個(gè)實(shí)現(xiàn)forward的例子進(jìn)行說明:

class MyModule(nn.Module):def __init__(self):super(MyModule, self).__init__()self.list = [nn.Linear(3,4), nn.ReLU()]self.module_list = nn.ModuleList([nn.Conv2d(3,3,3), nn.ReLU()])def forward(self):pass model = MyModule() model

返回:

MyModule((module_list): ModuleList((0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))(1): ReLU()) )

查看參數(shù):

for name, param in model.named_parameters():print(name, param.size())

返回:

module_list.0.weight torch.Size([3, 3, 3, 3]) module_list.0.bias torch.Size([3])

可見,list中的子module并不能被主module所識別,而ModuleList中的子module能夠被主module所識別。這意味著如果用list保存子module,將無法調(diào)整其參數(shù),因其未加入到主module的參數(shù)中。

除ModuleList之外還有ParameterList,其是一個(gè)可以包含多個(gè)parameter的類list對象。在實(shí)際應(yīng)用中,使用方式與ModuleList類似。

如果在構(gòu)造函數(shù)__init__中用到list、tuple、dict等對象時(shí),一定要思考是否應(yīng)該用ModuleList或ParameterList代替。

?

3.循環(huán)神經(jīng)網(wǎng)絡(luò)層(后面好好看看)

近些年隨著深度學(xué)習(xí)和自然語言處理的結(jié)合加深,RNN的使用也越來越多,關(guān)于RNN的基礎(chǔ)知識,推薦閱讀colah的文章1入門。PyTorch中實(shí)現(xiàn)了如今最常用的三種RNN:RNN(vanilla RNN)、LSTM和GRU。此外還有對應(yīng)的三種RNNCell。

RNN和RNNCell層的區(qū)別在于前者一次能夠處理整個(gè)序列,而后者一次只處理序列中一個(gè)時(shí)間點(diǎn)的數(shù)據(jù),前者封裝更完備更易于使用,后者更具靈活性。實(shí)際上RNN層的一種后端實(shí)現(xiàn)方式就是調(diào)用RNNCell來實(shí)現(xiàn)的。

t.manual_seed(1000) # 輸入:batch_size=3,序列長度都為2,序列中每個(gè)元素占4維 input = t.randn(2, 3, 4) # lstm輸入向量4維,隱藏元3,1層 lstm = nn.LSTM(4, 3, 1) # 初始狀態(tài):1層,batch_size=3,3個(gè)隱藏元 h0 = t.randn(1, 3, 3) c0 = t.randn(1, 3, 3) out, hn = lstm(input, (h0, c0)) out

返回:

tensor([[[-0.3610, -0.1643, 0.1631],[-0.0613, -0.4937, -0.1642],[ 0.5080, -0.4175, 0.2502]],[[-0.0703, -0.0393, -0.0429],[ 0.2085, -0.3005, -0.2686],[ 0.1482, -0.4728, 0.1425]]], grad_fn=<StackBackward>)

?

t.manual_seed(1000) input = t.randn(2, 3, 4) # 一個(gè)LSTMCell對應(yīng)的層數(shù)只能是一層 lstm = nn.LSTMCell(4, 3) hx = t.randn(3, 3) cx = t.randn(3, 3) out = [] for i_ in input:hx, cx=lstm(i_, (hx, cx))out.append(hx) t.stack(out)

返回:

tensor([[[-0.3610, -0.1643, 0.1631],[-0.0613, -0.4937, -0.1642],[ 0.5080, -0.4175, 0.2502]],[[-0.0703, -0.0393, -0.0429],[ 0.2085, -0.3005, -0.2686],[ 0.1482, -0.4728, 0.1425]]], grad_fn=<StackBackward>)

?

# 有4個(gè)詞,每個(gè)詞用5維的向量表示 embedding = nn.Embedding(4, 5) # 可以用預(yù)訓(xùn)練好的詞向量初始化embedding embedding.weight.data = t.arange(0,20).view(4,5) input = t.arange(3, 0, -1).long() output = embedding(input) output

返回:

tensor([[15, 16, 17, 18, 19],[10, 11, 12, 13, 14],[ 5, 6, 7, 8, 9]], grad_fn=<EmbeddingBackward>)

?

4.損失函數(shù)

這里以分類中最常用的交叉熵?fù)p失CrossEntropyloss為例說明:

#batch_size = 3,計(jì)算對應(yīng)每個(gè)類別的分?jǐn)?shù)(只有兩個(gè)類別) score = t.randn(3,2) #三個(gè)樣本分別屬于1,0,1類,label必須是LongTensor label = t.Tensor([1,0,1]).long()#loss與普通的layer無差異 criterion = nn.CrossEntropyLoss() loss = criterion(score, label) loss #返回tensor(0.5944)

?

5.優(yōu)化器

PyTorch將深度學(xué)習(xí)中常用的優(yōu)化方法全部封裝在torch.optim中,其設(shè)計(jì)十分靈活,能夠很方便的擴(kuò)展成自定義的優(yōu)化方法。

所有的優(yōu)化方法都是繼承基類optim.Optimizer,并實(shí)現(xiàn)了自己的優(yōu)化步驟。下面就以最基本的優(yōu)化方法——隨機(jī)梯度下降法(SGD)舉例說明。這里需重點(diǎn)掌握:

  • 優(yōu)化方法的基本使用方法
  • 如何對模型的不同部分設(shè)置不同的學(xué)習(xí)率
  • 如何調(diào)整學(xué)習(xí)率
#首先定義一個(gè)LeNet網(wǎng)絡(luò) class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.features = nn.Sequential(nn.Conv2d(3,6,5),nn.ReLU(),nn.MaxPool2d(2,2),nn.Conv2d(6,16,5),nn.ReLU(),nn.MaxPool2d(2,2))self.classifier = nn.Sequential( #全連接層nn.Linear(16*5*5, 120),nn.ReLU(),nn.Linear(120, 84),nn.ReLU(),nn.Linear(84, 10))def forward(self, x):x = self.features(x)x = x.view(-1,16*5*5) #將數(shù)據(jù)扁平化處理用傳入全連接層x = self.classifier(x)return x net = Net()

?

from torch import optim optimizer = optim.SGD(params=net.parameters(), lr=1) optimizer.zero_grad() #梯度清零,等價(jià)于net.zero_grad()input = t.randn(1,3,32,32) output = net(input) output.backward(output) #fake backward,后向傳播,計(jì)算梯度optimizer.step() #執(zhí)行優(yōu)化

?

# 為不同子網(wǎng)絡(luò)設(shè)置不同的學(xué)習(xí)率,在finetune中經(jīng)常用到 # 如果對某個(gè)參數(shù)不指定學(xué)習(xí)率,就使用最外層的默認(rèn)學(xué)習(xí)率 optimizer = optim.SGD([{'params' : net.features.parameters()}, #學(xué)習(xí)率為1e-5{'params' : net.classifier.parameters(), 'lr':1e-2}], lr=1e-5) optimizer

返回:

SGD ( Parameter Group 0dampening: 0lr: 1e-05momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.01momentum: 0nesterov: Falseweight_decay: 0 )

?

# 只為兩個(gè)全連接層設(shè)置較大的學(xué)習(xí)率,其余層的學(xué)習(xí)率較小 special_layers = nn.ModuleList([net.classifier[0], net.classifier[2]]) special_layers_params = list(map(id, special_layers.parameters())) base_params = filter(lambda p: id(p) not in special_layers_params, net.parameters())optimizer = t.optim.SGD([{'params': base_params},{'params': special_layers.parameters(), 'lr': 0.01}], lr=0.001 ) optimizer

返回:

SGD ( Parameter Group 0dampening: 0lr: 0.001momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.01momentum: 0nesterov: Falseweight_decay: 0 )


對于如何調(diào)整學(xué)習(xí)率,主要有兩種做法:

  • 一種是更簡單也是較為推薦的做法——新建優(yōu)化器,由于optimizer十分輕量級,構(gòu)建開銷很小,故而可以構(gòu)建新的optimizer。但是后者對于使用動量的優(yōu)化器(如Adam),會丟失動量等狀態(tài)信息,可能會造成損失函數(shù)的收斂出現(xiàn)震蕩等情況。
  • 一種是修改optimizer.param_groups中對應(yīng)的學(xué)習(xí)率

1)新建優(yōu)化器

#方法1:調(diào)整學(xué)習(xí)率,新建一個(gè)optimizer old_lr = 0.1 optimizer1 = optim.SGD([{'params': net.features.parameters()},{'params': net.classifier.parameters(), 'lr':old_lr *0.1} ], lr = 1e-5) optimizer1

返回:

SGD ( Parameter Group 0dampening: 0lr: 1e-05momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.010000000000000002momentum: 0nesterov: Falseweight_decay: 0 )

2)調(diào)整學(xué)習(xí)率

#方法2:調(diào)整學(xué)習(xí)率,手動衰減,保存動量 for param_group in optimizer.param_groups:param_group['lr'] *= 0.1 optimizer

返回:

SGD ( Parameter Group 0dampening: 0lr: 0.0001momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.001momentum: 0nesterov: Falseweight_decay: 0 )

?

3.nn.functional

nn中還有一個(gè)很常用的模塊:nn.functional,nn中的大多數(shù)layer,在functional中都有一個(gè)與之相對應(yīng)的函數(shù)。

nn.functional中的函數(shù)和nn.Module的主要區(qū)別在于:

  • nn.Module實(shí)現(xiàn)的layers是一個(gè)特殊的類,都是由class layer(nn.Module)定義,會自動提取可學(xué)習(xí)的參數(shù)
  • nn.functional中的函數(shù)更像是純函數(shù),由def function(input)定義。

下面舉例說明functional的使用,并指出二者的不同之處。

input = t.randn(2,3) model = nn.Linear(3,4) output1 = model(input) #使用上面使用的w,b,兩種寫法返回的結(jié)果是相同的 output2 = nn.functional.linear(input, model.weight, model.bias) output1 == output2

返回:

tensor([[1, 1, 1, 1],[1, 1, 1, 1]], dtype=torch.uint8)

?

b = nn.functional.relu(input) b2 = nn.ReLU()(input) b == b2

返回:

tensor([[1, 1, 1],[1, 1, 1]], dtype=torch.uint8)

此時(shí)讀者可能會問,應(yīng)該什么時(shí)候使用nn.Module,什么時(shí)候使用nn.functional呢?

答案很簡單,如果模型有可學(xué)習(xí)的參數(shù),最好用nn.Module,否則既可以使用nn.functional也可以使用nn.Module,二者在性能上沒有太大差異,具體的使用取決于個(gè)人的喜好。

如激活函數(shù)(ReLU、sigmoid、tanh),池化(MaxPool)等層由于沒有可學(xué)習(xí)參數(shù),則可以使用對應(yīng)的functional函數(shù)代替,而對于卷積、全連接等具有可學(xué)習(xí)參數(shù)的網(wǎng)絡(luò)建議使用nn.Module。

下面舉例說明,如何在模型中搭配使用nn.Module和nn.functional。

??另外雖然dropout操作也沒有可學(xué)習(xí)操作,但建議還是使用nn.Dropout而不是nn.functional.dropout,因?yàn)閐ropout在訓(xùn)練和測試兩個(gè)階段的行為有所差別,使用nn.Module對象能夠通過model.eval操作加以區(qū)分。

from torch.nn import functional as F class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3,6,5)self.conv2 = nn.Conv2d(6,16,5)self.fc1 = nn.Linear(16*5*5, 120)self.fc2 = nn.Linear(120,84)self.fc3 = nn.Linear(84, 10)def forward(self,x):x = F.pool(F.relu(self.conv1(x)),2)x = F.pool(F.relu(self.conv2(x)),2)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


對于不具備可學(xué)習(xí)參數(shù)的層(激活層、池化層等),將它們用函數(shù)代替,這樣則可以不用放置在構(gòu)造函數(shù)__init__中。對于有可學(xué)習(xí)參數(shù)的模塊,也可以用functional來代替,只不過實(shí)現(xiàn)起來較為繁瑣,需要手動定義參數(shù)parameter,如前面實(shí)現(xiàn)自定義的全連接層,就可將weight和bias兩個(gè)參數(shù)單獨(dú)拿出來,在構(gòu)造函數(shù)中初始化為parameter。

class MyLinear(nn.Module):def __init__(self):super(MyLinear, self).__init__()self.weight = nn.Parameter(t.randn(3,4))self.bias = nn.Parameter(t.zeros(3))def forward(self):return F.linear(input, weight, bias)

?

4.初始化策略

在深度學(xué)習(xí)中參數(shù)的初始化十分重要,良好的初始化能讓模型更快收斂,并達(dá)到更高水平,而糟糕的初始化則可能使得模型迅速癱瘓。

PyTorch中nn.Module的模塊參數(shù)都采取了較為合理的初始化策略,因此一般不用我們考慮,當(dāng)然我們也可以用自定義初始化去代替系統(tǒng)的默認(rèn)初始化。而當(dāng)我們在使用Parameter時(shí),自定義初始化則尤為重要,因t.Tensor()返回的是內(nèi)存中的隨機(jī)數(shù),很可能會有極大值,這在實(shí)際訓(xùn)練網(wǎng)絡(luò)中會造成溢出或者梯度消失。

PyTorch中nn.init模塊就是專門為初始化而設(shè)計(jì),如果某種初始化策略nn.init不提供,用戶也可以自己直接初始化。

?

使用的初始化策略是:

torch.nn.init.xavier_normal_(tensor, gain=1)

參數(shù):

  • tensor?– n維的torch.Tensor
  • gain?- 可選的縮放因子

用一個(gè)正態(tài)分布生成值,填充輸入的張量或變量。結(jié)果張量中的值采樣自均值為0,標(biāo)準(zhǔn)差為gain * sqrt(2/(fan_in + fan_out))的正態(tài)分布。也被稱為Glorot initialisation.

1)直接初始化:

#利用nn.init初始化 from torch.nn import init linear = nn.Linear(3,4)t.manual_seed(1) #等價(jià)于linear.weight.data.normal_(0, std),std是正態(tài)分布的標(biāo)準(zhǔn)差 init.xavier_normal_(linear.weight)

返回:

Parameter containing: tensor([[ 0.3535, 0.1427, 0.0330],[ 0.3321, -0.2416, -0.0888],[-0.8140, 0.2040, -0.5493],[-0.3010, -0.4769, -0.0311]], requires_grad=True)

2)手動初始化

#手動初始化 import math t.manual_seed(1)#xavier初始化的計(jì)算公式 std = math.sqrt(2)/math.sqrt(7.)#3+4 = 7 linear.weight.data.normal_(0, std)

返回:

tensor([[ 0.3535, 0.1427, 0.0330],[ 0.3321, -0.2416, -0.0888],[-0.8140, 0.2040, -0.5493],[-0.3010, -0.4769, -0.0311]])

?

#對模型的所有參數(shù)進(jìn)行初始化 for name, params in net.named_parameters():if name.find('linear') != -1:#init linearparams[0] #weightparams[1] #biaselif name.find('conv') != -1:passelif name.find('norm') != -1:pass

?

5.nn.Module深入分析

如果想要更深入地理解nn.Module,究其原理是很有必要的。首先來看看nn.Module基類的構(gòu)造函數(shù):

其中每個(gè)屬性的解釋如下:

  • _parameters:字典,保存用戶直接設(shè)置的parameter,self.param1 = nn.Parameter(t.randn(3, 3))會被檢測到,在字典中加入一個(gè)key為'param',value為對應(yīng)parameter的item。而self.submodule = nn.Linear(3, 4)中的parameter則不會存于此
  • _modules:子module,通過self.submodel = nn.Linear(3, 4)指定的子module會保存于此。
  • _buffers:緩存。如batchnorm使用momentum機(jī)制,每次前向傳播需用到上一次前向傳播的結(jié)果。
  • _backward_hooks與_forward_hooks:鉤子技術(shù),用來提取中間變量,類似variable的hook。
  • training:BatchNorm與Dropout層在訓(xùn)練階段和測試階段中采取的策略不同,通過判斷training值來決定前向傳播策略。

上述幾個(gè)屬性中,_parameters、_modules和_buffers這三個(gè)字典中的鍵值,都可以通過self.key方式獲得,效果等價(jià)于self._parameters['key'].

下面舉例說明:

1)定義網(wǎng)絡(luò):

class Net(nn.Module):def __init__(self):super(Net, self).__init__()#等價(jià)于self.register_parameter('param1', nn.Parameter(t.randn(3,3)))self.param1 = nn.Parameter(t.rand(3,3))self.submodel1 = nn.Linear(3,4)def forward(self, input):x = self.param1.mm(input)x = self.submodel1(x)return x net = Net() net

返回:

Net((submodel1): Linear(in_features=3, out_features=4, bias=True) )

2)

net._modules #查看設(shè)置的子模塊

返回:

OrderedDict([('submodel1', Linear(in_features=3, out_features=4, bias=True))])

另一種查看子模塊方法:

for name, submodel in net.named_modules():print(name, submodel)

返回:

Net((submodel1): Linear(in_features=3, out_features=4, bias=True) ) submodel1 Linear(in_features=3, out_features=4, bias=True)

nn.Module在實(shí)際使用中可能層層嵌套,一個(gè)module包含若干個(gè)子module,每一個(gè)子module又包含了更多的子module。

為方便用戶訪問各個(gè)子module,nn.Module實(shí)現(xiàn)了很多方法,如函數(shù)children可以查看直接子module,函數(shù)module可以查看所有的子module(包括當(dāng)前module)。

與之相對應(yīng)的還有函數(shù)named_childen和named_modules,其能夠在返回module列表的同時(shí)返回它們的名字。

3)

net._parameters #查看網(wǎng)絡(luò)中使用的參數(shù)

返回:

OrderedDict([('param1', Parameter containing:tensor([[0.3398, 0.5239, 0.7981],[0.7718, 0.0112, 0.8100],[0.6397, 0.9743, 0.8300]], requires_grad=True))])

另一種查看參數(shù)方法:

net.param1 #等價(jià)于net._parameters['param1']

返回:

Parameter containing: tensor([[0.3398, 0.5239, 0.7981],[0.7718, 0.0112, 0.8100],[0.6397, 0.9743, 0.8300]], requires_grad=True)

另一種查看參數(shù)方法,上面的方法沒辦法查看到層中使用的w,b參數(shù),下面的方法可以查看w,b參數(shù):

for name, param in net.named_parameters():print(name, param.size())

返回:

param1 torch.Size([3, 3]) submodel1.weight torch.Size([4, 3]) submodel1.bias torch.Size([4])

4)_buffers

bn = nn.BatchNorm1d(2) input = t.rand(3,2) output = bn(input) bn._buffers #上一次前向傳播結(jié)果

返回:

OrderedDict([('running_mean', tensor([0.0514, 0.0749])),('running_var', tensor([0.9116, 0.9068])),('num_batches_tracked', tensor(1))])

5)training

input = t.arange(0, 12).float().view(3,4) model = nn.Dropout() #在訓(xùn)練階段,會有一半的值被設(shè)置為0 model(input)

返回:

tensor([[ 0., 0., 4., 0.],[ 8., 0., 0., 14.],[ 0., 0., 0., 22.]])

?

#如果將training設(shè)置為False,那么dropout在測試階段將什么都不做 model.training = False model(input)

返回:

tensor([[ 0., 1., 2., 3.],[ 4., 5., 6., 7.],[ 8., 9., 10., 11.]])

對于batchnorm、dropout、instancenorm等在訓(xùn)練和測試階段行為差距巨大的層,如果在測試時(shí)不將其training值設(shè)為True,則可能會有很大影響,這在實(shí)際使用中要千萬注意。

雖然可通過直接設(shè)置training屬性,來將子module設(shè)為train和eval模式,但這種方式較為繁瑣,因如果一個(gè)模型具有多個(gè)dropout層,就需要為每個(gè)dropout層指定training屬性。

更為推薦的做法是調(diào)用model.train()函數(shù),它會將當(dāng)前module及其子module中的所有training屬性都設(shè)為True,相應(yīng)的,model.eval()函數(shù)會把training屬性都設(shè)為False

print(net.training, net.submodel1.training) net.eval() net.training, net.submodel1.training

返回:

True True (False, False)

?

6)register_forward_hook/register_backward_hook:中間變量

這兩個(gè)函數(shù)的功能類似于variable函數(shù)的register_hook,可在module前向傳播或反向傳播時(shí)注冊鉤子。每次前向傳播執(zhí)行結(jié)束后會執(zhí)行鉤子函數(shù)(hook)。前向傳播的鉤子函數(shù)具有如下形式:hook(module, input, output) -> None,而反向傳播則具有如下形式:hook(module, grad_input, grad_output) -> Tensor or None。

鉤子函數(shù)不應(yīng)修改輸入和輸出,并且在使用后應(yīng)及時(shí)刪除,以避免每次都運(yùn)行鉤子增加運(yùn)行負(fù)載。鉤子函數(shù)主要用在獲取某些中間結(jié)果的情景,如中間某一層的輸出或某一層的梯度。這些結(jié)果本應(yīng)寫在forward函數(shù)中,但如果在forward函數(shù)中專門加上這些處理,可能會使處理邏輯比較復(fù)雜,這時(shí)候使用鉤子技術(shù)就更合適一些。

下面考慮一種場景,有一個(gè)預(yù)訓(xùn)練好的模型,需要提取模型的某一層(不是最后一層)的輸出作為特征進(jìn)行分類,但又不希望修改其原有的模型定義文件,這時(shí)就可以利用鉤子函數(shù)。

下面給出實(shí)現(xiàn)的偽代碼:

model = VGG() features = t.Tensor() def hook(module, input, output):'''把這層的輸出拷貝到features中'''features.copy_(output.data)handle = model.layer8.register_forward_hook(hook) _ = model(input) # 用完hook后刪除 handle.remove()

?

7)__getattr__ / __setattr__
nn.Module對象在構(gòu)造函數(shù)中的行為看起來有些怪異,如果想要真正掌握其原理,就需要看兩個(gè)魔法方法__getattr__和__setattr__。

在Python中有兩個(gè)常用的buildin方法getattr和setattr,getattr(obj, 'attr1')等價(jià)于obj.attr,如果getattr函數(shù)無法找到所需屬性,Python會轉(zhuǎn)而調(diào)用obj.__getattr__('attr1')方法,即getattr函數(shù)無法找到的交給__getattr__函數(shù)處理,沒有實(shí)現(xiàn)__getattr__或者_(dá)_getattr__也無法處理的就會raise AttributeError。

setattr(obj, 'name', value)等價(jià)于obj.name=value,如果obj對象實(shí)現(xiàn)了__setattr__方法,setattr會直接調(diào)用obj.__setattr__('name', value),否則調(diào)用buildin方法。

總結(jié)一下:

  • result = obj.name會調(diào)用buildin函數(shù)getattr(obj, 'name'),如果該屬性找不到,會調(diào)用obj.__getattr__('name')
  • obj.name = value會調(diào)用buildin函數(shù)setattr(obj, 'name', value),如果obj對象實(shí)現(xiàn)了__setattr__方法,setattr會直接調(diào)用obj.__setattr__('name', value')

nn.Module實(shí)現(xiàn)了自定義的__setattr__函數(shù),當(dāng)執(zhí)行module.name=value時(shí),會在__setattr__中判斷value是否為Parameter或nn.Module對象,如果是則將這些對象加到_parameters和_modules兩個(gè)字典中,而如果是其它類型的對象,如Variable、list、dict等,則調(diào)用默認(rèn)的操作,將這個(gè)值保存在__dict__中。

1》

module = nn.Module() #直接使用nn.Module()對象 module.param = nn.Parameter(t.ones(2,2)) #設(shè)置參數(shù) module._parameters

返回:

OrderedDict([('param', Parameter containing:tensor([[1., 1.],[1., 1.]], requires_grad=True))])

2》

submodule1 = nn.Linear(2,2) submodule2 = nn.Linear(2,2) module_list = [submodule1, submodule2] #對于list對象,調(diào)用buildin函數(shù),保存在__dict__中 module.submodules = module_list #設(shè)置module,因?yàn)檫@里使用的是list,所以會存放在__dict__ print('_modules:', module._modules) print("__dict__['submodules']:", module.__dict__.get('submodules'))

返回:

_modules: OrderedDict() __dict__['submodules']: [Linear(in_features=2, out_features=2, bias=True), Linear(in_features=2, out_features=2, bias=True)]

3》

module_list = nn.ModuleList(module_list)#將上面的list類型轉(zhuǎn)成nn.Module對象類型 module.submodules = module_list #判斷是否為nn.Module對象類型 print('ModuleList is instance of nn.Module: ', isinstance(module_list, nn.Module)) print('_modules: ', module._modules) #這樣值就會存儲在這里,而不是__dict__ print("__dict__['submodules']:", module.__dict__.get('submodules'))

返回:

ModuleList is instance of nn.Module: True _modules: OrderedDict([('submodules', ModuleList((0): Linear(in_features=2, out_features=2, bias=True)(1): Linear(in_features=2, out_features=2, bias=True) ))]) __dict__['submodules']: None

4》

因_modules和_parameters中的item未保存在__dict__中,所以默認(rèn)的getattr方法無法獲取它,因而nn.Module實(shí)現(xiàn)了自定義的__getattr__方法,如果默認(rèn)的getattr無法處理,就調(diào)用自定義的__getattr__方法,嘗試從_modules、_parameters和_buffers這三個(gè)字典中獲取。

?

getattr(module, 'training')#等價(jià)于module.training #如果沒有得到值,就會調(diào)用module.__getattr__('training')

返回:

True

?

module.attr1 = 2 getattr(module, 'attr1') #返回2

?

getattr(module, 'param')

返回:

Parameter containing: tensor([[1., 1.],[1., 1.]], requires_grad=True)

?

8)state_dict()/load_state_dict()

在PyTorch中保存模型十分簡單,所有的Module對象都具有state_dict()函數(shù),返回當(dāng)前Module所有的狀態(tài)數(shù)據(jù)。將這些狀態(tài)數(shù)據(jù)保存后,下次使用模型時(shí)即可利用model.load_state_dict()函數(shù)將狀態(tài)加載進(jìn)來。優(yōu)化器(optimizer)也有類似的機(jī)制,不過一般并不需要保存優(yōu)化器的運(yùn)行狀態(tài)

#保存模型 t.save(net.state_dict(), 'net.pth') #然后就會在本地文件夾中生成一個(gè)net.pth文件#加載已經(jīng)保存的模型 net2 = Net() net2.load_state_dict(t.load('net.pth'))

?

9)運(yùn)行在GPU

將Module放在GPU上運(yùn)行也十分簡單,只需兩步:

  • model = model.cuda():將模型的所有參數(shù)轉(zhuǎn)存到GPU
  • input.cuda():將輸入數(shù)據(jù)也放置到GPU上

至于如何在多個(gè)GPU上并行計(jì)算,PyTorch也提供了兩個(gè)函數(shù),可實(shí)現(xiàn)簡單高效的并行GPU計(jì)算

  • nn.parallel.data_parallel(module, inputs, device_ids=None, output_device=None, dim=0, module_kwargs=None)
  • class torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)

可見二者的參數(shù)十分相似,通過device_ids參數(shù)可以指定在哪些GPU上進(jìn)行優(yōu)化,output_device指定輸出到哪個(gè)GPU上。

唯一的不同就在于前者直接利用多GPU并行計(jì)算得出結(jié)果,而后者則返回一個(gè)新的module,能夠自動在多GPU上進(jìn)行并行加速。

?

# method 1 new_net = nn.DataParallel(net, device_ids=[0, 1]) output = new_net(input)# method 2 output = nn.parallel.data_parallel(new_net, input, device_ids=[0, 1])

DataParallel并行的方式,是將輸入一個(gè)batch的數(shù)據(jù)均分成多份,分別送到對應(yīng)的GPU進(jìn)行計(jì)算,各個(gè)GPU得到的梯度累加。與Module相關(guān)的所有數(shù)據(jù)也都會以淺復(fù)制的方式復(fù)制多份,在此需要注意,在module中屬性應(yīng)該是只讀的。

?

6.nn和autograd的關(guān)系

nn.Module利用的也是autograd技術(shù),其主要工作是實(shí)現(xiàn)前向傳播。在forward函數(shù)中,nn.Module對輸入的tensor進(jìn)行的各種操作,本質(zhì)上都是用到了autograd技術(shù)。這里需要對比autograd.Function和nn.Module之間的區(qū)別:

  • autograd.Function利用了Tensor對autograd技術(shù)的擴(kuò)展,為autograd實(shí)現(xiàn)了新的運(yùn)算op,不僅要實(shí)現(xiàn)前向傳播還要手動實(shí)現(xiàn)反向傳播
  • nn.Module利用了autograd技術(shù),對nn的功能進(jìn)行擴(kuò)展,實(shí)現(xiàn)了深度學(xué)習(xí)中更多的層。只需實(shí)現(xiàn)前向傳播功能,autograd即會自動實(shí)現(xiàn)反向傳播
  • nn.functional是一些autograd操作的集合,是經(jīng)過封裝的函數(shù)

作為兩大類擴(kuò)充PyTorch接口的方法,我們在實(shí)際使用中應(yīng)該如何選擇呢?

如果某一個(gè)操作,在autograd中尚未支持,那么只能實(shí)現(xiàn)Function接口對應(yīng)的前向傳播和反向傳播。如果某些時(shí)候利用autograd接口比較復(fù)雜,則可以利用Function將多個(gè)操作聚合,實(shí)現(xiàn)優(yōu)化,正如第三章所實(shí)現(xiàn)的Sigmoid一樣,比直接利用autograd低級別的操作要快。而如果只是想在深度學(xué)習(xí)中增加某一層,使用nn.Module進(jìn)行封裝則更為簡單高效。

?

7.小試牛刀:搭建ResNet

Kaiming He的深度殘差網(wǎng)絡(luò)(ResNet)[^7]在深度學(xué)習(xí)的發(fā)展中起到了很重要的作用,ResNet不僅一舉拿下了當(dāng)年CV下多個(gè)比賽項(xiàng)目的冠軍,更重要的是這一結(jié)構(gòu)解決了訓(xùn)練極深網(wǎng)絡(luò)時(shí)的梯度消失問題。

首先來看看ResNet的網(wǎng)絡(luò)結(jié)構(gòu),這里選取的是ResNet的一個(gè)變種:ResNet34。

ResNet的網(wǎng)絡(luò)結(jié)構(gòu)如圖4-2所示,可見除了最開始的卷積池化和最后的池化全連接之外,網(wǎng)絡(luò)中有很多結(jié)構(gòu)相似的單元,這些重復(fù)單元的共同點(diǎn)就是有個(gè)跨層直連的shortcut。ResNet中將一個(gè)跨層直連的單元稱為Residual block,其結(jié)構(gòu)如圖4-3所示,左邊部分是普通的卷積網(wǎng)絡(luò)結(jié)構(gòu),右邊是直連,但如果輸入和輸出的通道數(shù)不一致,或其步長不為1,那么就需要有一個(gè)專門的單元將二者轉(zhuǎn)成一致,使其可以相加。

另外我們可以發(fā)現(xiàn)Residual block的大小也是有規(guī)律的,在最開始的pool之后有連續(xù)的幾個(gè)一模一樣的Residual block單元,這些單元的通道數(shù)一樣,在這里我們將這幾個(gè)擁有多個(gè)Residual block單元的結(jié)構(gòu)稱之為layer,注意和之前講的layer區(qū)分開來,這里的layer是幾個(gè)層的集合。

考慮到Residual block和layer出現(xiàn)了多次,我們可以把它們實(shí)現(xiàn)為一個(gè)子Module或函數(shù)。這里我們將Residual block實(shí)現(xiàn)為一個(gè)子moduke,而將layer實(shí)現(xiàn)為一個(gè)函數(shù)。下面是實(shí)現(xiàn)代碼,規(guī)律總結(jié)如下:

  • 對于模型中的重復(fù)部分,實(shí)現(xiàn)為子module或用函數(shù)生成相應(yīng)的module make_layer
  • nn.Module和nn.Functional結(jié)合使用
  • 盡量使用nn.Seqential

[^7]: He K, Zhang X, Ren S, et al. Deep residual learning for image recognition[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2016: 770-778.

from torch import nn import torch as t from torch.nn import functional as F

定義網(wǎng)絡(luò):

class ResidualBlock(nn.Module):''' 實(shí)現(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 = shortcutdef forward(self, x):out = self.left(x)residual = x if self.right is None else self.right(x)out += residualreturn F.relu(out)class ResNet(nn.Module):''' 實(shí)現(xiàn)主module:ResNet34ResNet34 包含多個(gè)layer,每個(gè)layer又包含多個(gè)residual block用子module來實(shí)現(xiàn)residual block,用_make_layer函數(shù)來實(shí)現(xiàn)layer''' def __init__(self, num_classes=1000):super(ResNet, self).__init__()# 前幾層圖像轉(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,46,3個(gè)residual blockself.layer1 = self._make_layer( 64, 64, 3)self.layer2 = self._make_layer( 64, 128, 4, stride=2)self.layer3 = self._make_layer( 128, 256, 6, stride=2)self.layer4 = self._make_layer( 256, 512, 3, stride=2)#分類用的全連接self.fc = nn.Linear(512, num_classes)def _make_layer(self, inchannel, outchannel, block_num, stride=1):''' 構(gòu)建layer,包含多個(gè)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)

調(diào)用:

model = ResNet() input = t.randn(1, 3, 224, 224) o = model(input)

感興趣的讀者可以嘗試實(shí)現(xiàn)Google的Inception網(wǎng)絡(luò)結(jié)構(gòu)或ResNet的其它變體,看看如何能夠簡潔明了地實(shí)現(xiàn)它,實(shí)現(xiàn)代碼盡量控制在80行以內(nèi)(本例去掉空行和注釋總共不超過50行)。

另外,與PyTorch配套的圖像工具包torchvision已經(jīng)實(shí)現(xiàn)了深度學(xué)習(xí)中大多數(shù)經(jīng)典的模型,其中就包括ResNet34,讀者可以通過下面兩行代碼使用:

from torchvision import models model = models.resnet34()

本例中ResNet34的實(shí)現(xiàn)就是參考了torchvision中的實(shí)現(xiàn)并做了簡化,感興趣的讀者可以閱讀相應(yīng)的源碼,比較這里的實(shí)現(xiàn)和torchvision中實(shí)現(xiàn)的不同。

?

轉(zhuǎn)載于:https://www.cnblogs.com/wanghui-garcia/p/10633458.html

總結(jié)

以上是生活随笔為你收集整理的深度学习框架PyTorch一书的学习-第四章-神经网络工具箱nn的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。