1. CNN簡介
本文主要介紹利用pytorch對CNN的計(jì)算有一個(gè)直觀的認(rèn)識,在此認(rèn)為你已經(jīng)對CNN有了一些理解憔四,但是對如何計(jì)算還有一些迷糊。
卷積神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)一般用在圖像處理領(lǐng)域篙螟,優(yōu)化了全連接神經(jīng)網(wǎng)絡(luò)的參數(shù)過多問題览闰。CNN的結(jié)構(gòu)圖如下所示芯肤,一個(gè)卷積神經(jīng)網(wǎng)絡(luò)由若干卷積層、Pooling層压鉴、全連接層組成崖咨。常用的架構(gòu)模式為:
其中Pooling層不一定要有,參考如下的架構(gòu)圖油吭。關(guān)于CNN的細(xì)節(jié)不做太多介紹击蹲,具體可以參考這篇文章。
2. pytorch計(jì)算卷積CNN
2.1 pytorch的cnn實(shí)現(xiàn)函數(shù)介紹
首先介紹一下pytorch實(shí)現(xiàn)cnn的函數(shù)
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
- in_channels, out_channels為輸入輸出信號的通道婉宰;
- kernel_size為卷積核的大小歌豺,可以為int or (height, width),int時(shí)表示卷積核是高寬相等的心包,為touple表示卷積核的高度和寬度不相等类咧;
- stride 同樣可以為int or touple,參考kernel_size谴咸,卷積步長高度和寬度兩個(gè)方向不等時(shí)候轮听,采用touple;
- padding int or (height, width) 輸入的每一條邊補(bǔ)充0的層數(shù)岭佳;
- dilation(int or tuple, `optional``) – 卷積核元素之間的間距血巍,默認(rèn)為1,為2時(shí)候珊随,卷積核元素之間的間距拉大述寡,相當(dāng)于放大了卷積核看到的區(qū)域;
- bias bias為True叶洞,添加偏置鲫凶;
- groups(int, optional) – 從輸入通道到輸出通道的阻塞連接數(shù)
舉個(gè)例子:
import torch as t
from torch import nn
m = nn.Conv2d(in_channels=8,out_channels=3,kernel_size= 3)
input = t.randn(10, 8,5,5 )
output = m(input)
output.shape
>>>
torch.Size([10, 2, 3, 3])
這里的input為batch10衩辟,深度為8螟炫,高度和寬度都為5的三維矩陣(在此解釋一下,明明是3維矩陣艺晴,卻為啥是conv2d昼钻,二維的呢,個(gè)人理解是封寞,這里的2d是針對有高度和寬度然评,卷積核在這兩個(gè)方向上移動,所以雖然輸入的是三維矩陣狈究,但是只在兩個(gè)方向上移動碗淌,所以是Conv2d)。通過卷積核為355的卷積核后,output的高度和寬度為3(5-3+1)亿眠。
卷積變換的高度和寬度的計(jì)算公式為:
其中為卷積變換之后的高度碎罚,為卷積核的寬度,為補(bǔ)0的圈數(shù)缕探。
2.2 理解卷積的計(jì)算方法
卷積神經(jīng)網(wǎng)絡(luò)是如何計(jì)算的呢魂莫,下面是一張非常經(jīng)典的圖,input是一個(gè)深度為3的7*7的數(shù)據(jù)爹耗,2channel的filter,filter的深度要和input的深度 一致谜喊,因此CNN輸出矩陣的大小和filter有如下規(guī)范:
- filter 的深度要和input的深度一致潭兽;
- 卷積神經(jīng)網(wǎng)絡(luò)的輸出的channel是由filter的channel數(shù)決定的;
- 輸出的高度和寬度由卷積核的高度和寬度斗遏、卷積的步長straddle山卦、補(bǔ)零padding的圈數(shù)決定的;
卷積的計(jì)算方法可以參考下方的動態(tài)圖诵次,畫的非常清晰账蓉,filter和input做element-wise的相乘,不同層之間進(jìn)行求和逾一,最后加上bias铸本,得到output。
接下來遵堵,我們再繼續(xù)使用pytorch實(shí)踐一下計(jì)算過程:
import torch.nn.functional as f
input = t.randn(1,3,3,3) # 輸入為batch為2箱玷,深度為2的3*3矩陣
filter1 = t.randn(2,3,2,2) # 卷積核即filter為(3,2,2,2),即channel為3陌宿,卷積核的深度為2锡足,高寬為2*2
o1 = f.conv2d(input, filter1,stride=1) #因此輸出的維度為(2,3,2,2)
input
>>>
Out[18]:
tensor([[[[-0.1759, -0.3417, 1.4123],
[-1.6696, 0.9701, -2.3805],
[-0.7241, -0.2209, -0.8992]],
[[-1.3623, 0.8210, 0.7222],
[ 0.5904, 0.0083, -1.5792],
[ 0.3217, -0.8068, 0.7589]],
[[-0.5402, 1.2805, 0.7455],
[-0.8920, -1.0816, 1.1012],
[-0.0124, -1.6899, 1.2731]]]])
filter1
>>>
tensor([[[[-1.8587e+00, 1.2517e+00],
[-6.4202e-01, 5.6213e-01]],
[[-2.1839e-01, -1.9386e-01],
[ 7.2792e-01, -1.9432e+00]],
[[-1.7671e+00, -1.4224e+00],
[-1.5365e+00, -1.1106e+00]]],
[[[ 1.8957e+00, 5.6250e-01],
[-1.9311e+00, -8.1737e-02]],
[[ 7.7985e-01, -1.3875e-01],
[-8.8998e-01, -1.1328e+00]],
[[ 5.6669e-01, -1.1629e+00],
[ 3.2056e-01, -7.1626e-04]]]])
o1
>>>
Out[19]:
tensor([[[[ 3.7735, 0.3129],
[11.3403, -5.3765]],
[[-1.1724, 0.3006],
[ 0.6336, -1.3521]]]])
如上可以看到第一個(gè)元素o1[0,0,0,0]是3.7735,接下來我們?nèi)巳庥?jì)算一下第一個(gè)元素:
import torch as t
t.sum(input[0,:,0:2,0:2]*filter1[0,:,:,:])
>>>
out[20]
tensor(3.7735)
結(jié)果和利用nn.functional.Conv2d計(jì)算出的結(jié)果一致壳坪。
nn.Conv2d和nn.functional.Conv2很相似舶得,但是在構(gòu)建模型的時(shí)候一般采用nn.Conv2d來實(shí)現(xiàn)。