在之前我們學(xué)習(xí)了如何用Pytorch去導(dǎo)入我們的數(shù)據(jù)和數(shù)據(jù)集,并且對數(shù)據(jù)進(jìn)行預(yù)處理。接下來我們就需要學(xué)習(xí)如何利用Pytorch去構(gòu)建我們的神經(jīng)網(wǎng)絡(luò)了。
目錄
基本網(wǎng)絡(luò)框架Module搭建
卷積層
從conv2d方法了解原理
從Conv2d方法了解使用
池化層
填充層
非線性層
線性層
基本網(wǎng)絡(luò)框架Module搭建
Pytorch里面有一個工具包位于torch下面的nn,這里的nn代表的是Netural Network神經(jīng)網(wǎng)絡(luò),這就表示這個包用于我們創(chuàng)建基本的神經(jīng)網(wǎng)絡(luò)。
而關(guān)于Pytorch里面的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)、方法和使用這些方法的教程都可以在Pytorch官網(wǎng)里面找到:
接下來我們看看如何構(gòu)建一個最簡單的神經(jīng)網(wǎng)絡(luò),我要它實現(xiàn)的功能是將我的輸入矩陣每一個元素都擴大十倍。而看了Module類的介紹文檔后我們知道我們要想創(chuàng)建一個新的Module類型的神經(jīng)網(wǎng)絡(luò)首先需要創(chuàng)建一個新的類Mymodule,并且其繼承自torch.nn里面的Module類。這里我們根據(jù)我們的需要,對我們新創(chuàng)出來的類進(jìn)行方法重寫:
import torch
from torch import nn
class Mymodule(nn.Module):
def __init__(self) -> None:
super().__init__()
def forward(self, x):
return x * 10
Module = Mymodule()
x = torch.tensor([[1.0, 2.0, 3.0], [1.0, 2.0, 3.0], [1.0, 2.0, 3.0]])
print('input:', x)
print('output:', Module(x))
可以看到這樣一個簡單的神經(jīng)網(wǎng)絡(luò)就構(gòu)建好了
卷積層
接下來我們來看看如何構(gòu)建一個卷積層。假設(shè)現(xiàn)在我們有一個二維的矩陣,這個矩陣的大小是5x5,而我們卷積層的作用是利用一個卷積核(kernel)來提取這個矩陣?yán)锩娴年P(guān)鍵特征,常見的方法就是用這個卷積核去和輸入的二維矩陣中對應(yīng)每一項進(jìn)行乘積操作,并且將其單次比較的所有乘積求和就是新的輸出矩陣對應(yīng)位置的數(shù)值。它表示輸入矩陣在該范圍(卷積核大小)內(nèi)與卷積核所表示的特征的關(guān)聯(lián)程度,這個位置的輸出越大,表示關(guān)聯(lián)程度越大。同時它也會剔除一些與我們判斷無關(guān)的信息,下圖是我在網(wǎng)上找到有關(guān)卷積層的功能的圖片,左邊的就是輸入,中間是卷積核,而右邊就是輸出。
從conv2d方法了解原理
關(guān)于卷積實現(xiàn)的基本原理模型就是位于torch.nn.functional下面的這些conv1d、conv2d、conv3d方法,?不過我們常用的還是一維和二維的這些卷積函數(shù)。
假如我們以使用一個二維的卷積方法為例,發(fā)現(xiàn)它需要我們輸入一些參數(shù),比如說input輸入、weight權(quán)重、卷積核、bias偏置、stride步長等等這些參數(shù)。
輸入
首先我們來看一下input,我們可以看到它需求的input是一個含有4種形狀描述的tensor,而我們常見的二維tensor就是寬和高兩個參數(shù),那就表示如果我們要想使用卷積函數(shù),就需要對我們的tensor進(jìn)行形狀轉(zhuǎn)換。
?其中torch為我們提供了tensor形狀轉(zhuǎn)換的方法,就是reshape。
那我們就把一個二維的tensor變量的形狀重塑為符號題目要求的輸入,不難發(fā)現(xiàn)我們的一個二維矩陣的前面兩個參數(shù)minbatch一批最小的數(shù)量和in_channels通道數(shù)都是1,而后面的兩個高和寬就分別為5和5即可。
x = torch.tensor([[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0]
])
x = torch.reshape(x, (1,1,5,5))
print(x.shape)
卷積核
接下來我們來看看卷積核怎么去處理,我們可以通過下面的圖片知道權(quán)重需要的也是一個4參數(shù)形狀的tensor,并且它的第一個參數(shù)為輸出的通道數(shù),第二個為輸入的通道數(shù)除以groups組數(shù),將整體卷積細(xì)化為分組卷積,然后后面兩個為高和寬。
那么我們就可以對我們的卷積核進(jìn)行處理了:
kernel = torch.tensor([[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0]])
kernel = torch.reshape(kernel, (1, 1, 3, 3))
步長
然后就是stride,這個表示kernel映射完一次后移動(向右或者向下)的步數(shù),默認(rèn)為1。它也可是同時設(shè)置寬和高方向的移動步長。
下圖為步長為1和步長為2的區(qū)別:
?
填充
padding是決定是否需要向我們的輸入矩陣進(jìn)行填充,以此進(jìn)行大小變換的參數(shù),如果我們讓padding等于1,那就默認(rèn)讓我們矩陣外周再增加一層,而且這一層填充的的默認(rèn)數(shù)值就是0,如果我們想要進(jìn)行修改,可以通過padding_mode這個參數(shù)進(jìn)行設(shè)置。如果我們讓padding等于2,那么同理它也會將我們的輸入矩陣外向擴展兩圈。
了解完幾個卷積層常用到的參數(shù)之后,我們可以嘗試使用一下conv2d這個函數(shù)進(jìn)行卷積操作了。下面就是利用模型對卷積層進(jìn)行一次封裝運算:
import torch
from torch import nn
class Mymodule(nn.Module):
def __init__(self) -> None:
super().__init__()
def forward(self, x, kernel):
return torch.nn.functional.conv2d(x, kernel, stride=1)
Module = Mymodule()
x = torch.tensor([[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0]
])
kernel = torch.tensor([[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0]])
x = torch.reshape(x, (1, 1, 5, 5))
kernel = torch.reshape(kernel, (1, 1, 3, 3))
print(Module(x, kernel))
輸出結(jié)果:
從Conv2d方法了解使用
與conv2d功能類似類似,只不過conv2d是可以直接使用的工具,而Conv2d是制造工具的模板。在nn包中還另外封裝好了一個我們平時更常用到的卷積層函數(shù)Conv2d,這個在nn包下面的Conv2d比起之前functional下面的conv2d方法更加普遍被我們使用,因為它的封裝程度比較好。我們可以打開Pytorch官網(wǎng)大致了解一下它的參數(shù):
不難看出它的參數(shù)列表和conv2d差不多,有區(qū)別的是Conv2d的第一個和第二個參數(shù)需要的是in_channels和out_channels,它們分別代表著輸入的通道數(shù)和輸出到通道數(shù)。我們利用不同數(shù)目的卷積核即可對應(yīng)得到不同數(shù)目的輸出通道,如下圖就是2個out_channels的輸出。
它的使用方式和前面conv2d的方法使用方式類似,下面是一個下載數(shù)據(jù)集并且使用卷積函數(shù)對里面的圖片數(shù)據(jù)進(jìn)行通道擴展的例子的過程:
import torch
from torch import nn
import torchvision
from torch.utils.data import DataLoader
tools = torchvision.transforms.Compose([torchvision.transforms.ToTensor()])
dataset = torchvision.datasets.MNIST('./dataset', train=True, transform=tools, download=True)
dataloader = DataLoader(dataset=dataset, batch_size=5, shuffle=True, drop_last=True)
class Module(torch.nn.Module):
def __init__(self) -> None:
super().__init__()
self.conv2d = nn.Conv2d(in_channels=1, out_channels=3, kernel_size=(3,3), stride=1, padding=0)
def forward(self, x):
x = self.conv2d(x)
return x
module = Module()
for data in dataloader:
imgs, target = data
output = module(imgs)
print(imgs.shape)
print(output.shape)
我們可以很明顯看到輸入的每一個數(shù)據(jù)和輸出的每一個數(shù)據(jù)的大小都發(fā)生了變化:從輸入到單通道變成了輸出到三通道,同時寬高也變小了,但是每個batch_size都保持是5沒變
池化層
在學(xué)習(xí)池化層之前我們需要知道池化的作用是什么,以最池化方式為最大池化方法為例。池化的目的是從輸入中的數(shù)據(jù)中提取出其中符合特征的一部分保留,以達(dá)到在減少數(shù)據(jù)量的同時保持了最能表現(xiàn)輸入特征的數(shù)據(jù)。
之前我們學(xué)習(xí)了卷積層,卷積層是利用一個卷積核篩選出輸入數(shù)據(jù)中各部分對于某個特征的符合程度,即減小了數(shù)據(jù)的大小,又能夠準(zhǔn)確描繪出各部分對于某個特征的符合程度。而卷積層輸出的參數(shù)一般來說是一個tensor,這個tensor中會包含了各部分對于某個特征的符合程度(有大有?。?,而我們池化層一般就跟在卷積層后面,它要做的工作是從卷積層輸出的tensor提取出那些符合特征的部分,而剔除掉那些不符合的部分,達(dá)到保留原先輸入的特征同時又減少了數(shù)據(jù)量的目的。
查看Pytorch的官網(wǎng)可知,池化的參數(shù)一般有下面幾個:
其中kernel_size就是我們匹配的池化核的大小,而stride就是步長,padding也是是否需要外擴,dilation是是否設(shè)置有空隙的kernel匹配核。這里比較新的是ceil_mode,這個表示我們池化數(shù)據(jù)對比過程中如果kernel的右端超出了輸入tensor的右端是否保留,True則為保留,這和我們之前在卷積層時一律不保留就不同了。
?當(dāng)ceil_mode模式為True時,我們一律設(shè)置stride=3,結(jié)果是:
當(dāng)ceil_mode模式為False時,我們一律設(shè)置stride=3,結(jié)果是:
而我們用代碼處理如下:不過這里要注意tensor里面要用浮點數(shù)torch.float32,并且因為MaxPool2d函數(shù)的特性,我們需要對輸入x進(jìn)行維度重塑,一批次1個,通道為1,高和寬均為5。但是值得注意的一點是池化后channel是不會變的,所有我們轉(zhuǎn)化回3通道圖片不需要reshape。
import torch
from torch.nn import Module
class Mymodule(Module):
def __init__(self) -> None:
super().__init__()
self.poolmax = torch.nn.MaxPool2d(kernel_size=(3,3), stride=3, padding=0, ceil_mode=True)
def forward(self, x):
return self.poolmax(x)
x = torch.tensor([[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, 5.0]
], dtype=torch.float32)
mymodule = Mymodule()
x = torch.reshape(x, [-1, 1, 5, 5])
x = mymodule(x)
print(x)
下面是ceil_mode為True的輸出:
下面是ceil_mode為False的輸出:
?
填充層
填充層也就是padding layer,它的主要作用是對我們傳入的輸入tensor進(jìn)行填充,其功能就類似于我們池化或者卷積層中的padding,不過它可以填充不同的常數(shù),總之這是一個我們可能平時用得比較少的層,有需要我們可以再去官網(wǎng)文檔那里查看??https://pytorch.org/docs/stable/nn.html#padding-layers。
非線性層
因為線性層和線性層疊加最終還是會變成線性層,這樣就會造成對一些非線性問題束手無策的問題,只能表示特征值和目標(biāo)值之間的簡單關(guān)系。正是因為線性層無法適用于去擬合我們?nèi)粘I钪械娜我夥蔷€性問題,所以引入了非線性層進(jìn)行數(shù)據(jù)整合和解決這些痛點。
而我們要想進(jìn)行非線性變換就需要使用到一些激活函數(shù),利用這些激活函數(shù)可以去擬合這些非線性問題。常見的激活函數(shù)有Relu、sigmoid等。
Relu和Sigmoid的函數(shù)如下面左右圖所示,通過多個這種函數(shù)我們可以大致擬合出任意一個我們需要的非線性函數(shù)。
?
下面我們就來看看如何用代碼來實現(xiàn)非線性層,主要還是看兩個常用的激活函數(shù):ReLU和Sigmoid。具體使用就是調(diào)用nn下面的ReLU函數(shù)和Sigmoid函數(shù),不過值得注意的是ReLU方法有一個參數(shù)inplace,它代表的意思是在經(jīng)過這個神經(jīng)網(wǎng)絡(luò)后原來的輸入會不會被修改,如果會就設(shè)置為True,不會則設(shè)置為False。這個為了保持我們數(shù)據(jù)的完整性我們一般設(shè)置為False,默認(rèn)情況也是為False。
import torch
from torch.nn import Module
class Mymodule(Module):
def __init__(self) -> None:
super().__init__()
self.relu = torch.nn.ReLU(inplace=False)
def forward(self, x):
return self.relu(x)
x = torch.tensor([[-1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, -2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, -3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, -4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, -5.0]
], dtype=torch.float32)
mymodule = Mymodule()
x = torch.reshape(x, [-1, 1, 5, 5])
y = mymodule(x)
下面就是x矩陣經(jīng)過使用ReLU的非線性層后輸出的矩陣:
線性層
線性層(Linear Layer)又稱為全連接層,這層的特點是該層中輸出的每一個節(jié)點都和上一層的所有的輸入節(jié)點相連。而線性層的其中一個很大的作用就是將輸入函數(shù)進(jìn)行線性組合,從而獲取到我們所需要的任意的非線性函數(shù)。
前面我們提到,非線性層的激活函數(shù)一個很大的作用就是可以作為構(gòu)造非線性函數(shù)的部件,即如何去組合這些非線性的激活函數(shù)的任務(wù)就交給了線性層。線性層模型的參數(shù)有三個部分,前面兩個是每個輸入的樣本的大小和每一個輸出的大小,而bias就是是否添加偏置。
同時我們對于線性層的輸入一般是將輸入的矩陣先轉(zhuǎn)化為(1,1,1,n)的矩陣,然后再輸入到線性層中,經(jīng)過線性層的轉(zhuǎn)換獲取到我們希望得到的輸出(1,1,1,m)。或者我們也可以利用torch下面的flatten函數(shù)來對一個tensor進(jìn)行展平。
下面是我們將一個四維(-1,1,5,5)的矩陣通過線性層轉(zhuǎn)化為一個1x5的矩陣的代碼:
import torch
from torch.nn import Module
class Mymodule(Module):
def __init__(self) -> None:
super().__init__()
self.Linear = torch.nn.Linear(25, 5)
def forward(self, x):
return self.Linear(x)
x = torch.tensor([[-1.0, 2.0, 3.0, 4.0, 5.0],
[1.0, -2.0, 3.0, 4.0, 5.0],
[1.0, 2.0, -3.0, 4.0, 5.0],
[1.0, 2.0, 3.0, -4.0, 5.0],
[1.0, 2.0, 3.0, 4.0, -5.0]
], dtype=torch.float32)
mymodule = Mymodule()
x = torch.reshape(x, [1, 1, 1, -1])
x = torch.flatten(x)
y = mymodule(x)
print(y.shape)
經(jīng)過線性層我們實現(xiàn)輸入線性的組合:
參考資料:文章來源:http://www.zghlxwxcb.cn/news/detail-416108.html
https://wenku.baidu.com/view/c4ec681064ec102de2bd960590c69ec3d5bbdb84.html?_wkts_=1679107455557&bdQuery=inchannels%2Fgroups文章來源地址http://www.zghlxwxcb.cn/news/detail-416108.html
到了這里,關(guān)于【Pytorch】神經(jīng)網(wǎng)絡(luò)搭建的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!