文章目錄
- 卷積神經網絡基礎
-
- 二維卷積層
-
- 二維互相關運算
- 二維卷積層
- 互相關運算與卷積運算
- 特征圖與感受野
- 填充和步幅
-
- 填充
- 步幅
- 多輸入通道和多輸出通道
-
- 多輸入通道
- 多輸出通道
- 1x1卷積層
- 卷積層與全連接配接層的對比
- 卷積層的簡潔實作
- 池化
-
- 二維池化層
- 池化層的簡潔實作
卷積神經網絡基礎
本節我們介紹卷積神經網絡的基礎概念,主要是卷積層和池化層,并解釋填充、步幅、輸入通道和輸出通道的含義。
二維卷積層
本節介紹的是最常見的二維卷積層,常用于處理圖像資料。
二維互相關運算
二維互相關(cross-correlation)運算的輸入是一個二維輸入數組和一個二維核(kernel)數組,輸出也是一個二維數組,其中核數組通常稱為卷積核或過濾器(filter)。卷積核的尺寸通常小于輸入數組,卷積核在輸入數組上滑動,在每個位置上,卷積核與該位置處的輸入子數組按元素相乘并求和,得到輸出數組中相應位置的元素。圖1展示了一個互相關運算的例子,陰影部分分别是輸入的第一個計算區域、核數組以及對應的輸出。

圖1 二維互相關運算
下面我們用
corr2d
函數實作二維互相關運算,它接受輸入數組
X
與核數組
K
,并輸出數組
Y
。
import torch
import torch.nn as nn
def corr2d(X, K):
H, W = X.shape
h, w = K.shape
Y = torch.zeros(H - h + 1, W - w + 1)
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
return Y
構造上圖中的輸入數組
X
、核數組
K
來驗證二維互相關運算的輸出。
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
K = torch.tensor([[0, 1], [2, 3]])
Y = corr2d(X, K)
print(Y)
tensor([[19., 25.],
[37., 43.]])
二維卷積層
二維卷積層将輸入和卷積核做互相關運算,并加上一個标量偏置來得到輸出。卷積層的模型參數包括卷積核和标量偏置。
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
下面我們看一個例子,我們構造一張 6 × 8 6 \times 8 6×8的圖像,中間4列為黑(0),其餘為白(1),希望檢測到顔色邊緣。我們的标簽是一個 6 × 7 6 \times 7 6×7的二維數組,第2列是1(從1到0的邊緣),第6列是-1(從0到1的邊緣)。
X = torch.ones(6, 8)
Y = torch.zeros(6, 7)
X[:, 2: 6] = 0
Y[:, 1] = 1
Y[:, 5] = -1
print(X)
print(Y)
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.]])
tensor([[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.]])
我們希望學習一個 1 × 2 1 \times 2 1×2卷積層,通過卷積層來檢測顔色邊緣。
conv2d = Conv2D(kernel_size=(1, 2))
step = 30
lr = 0.01
for i in range(step):
Y_hat = conv2d(X)
l = ((Y_hat - Y) ** 2).sum()
l.backward()
# 梯度下降
conv2d.weight.data -= lr * conv2d.weight.grad
conv2d.bias.data -= lr * conv2d.bias.grad
# 梯度清零
conv2d.weight.grad.zero_()
conv2d.bias.grad.zero_()
if (i + 1) % 5 == 0:
print('Step %d, loss %.3f' % (i + 1, l.item()))
print(conv2d.weight.data)
print(conv2d.bias.data)
Step 5, loss 3.431
Step 10, loss 0.784
Step 15, loss 0.199
Step 20, loss 0.053
Step 25, loss 0.015
Step 30, loss 0.004
tensor([[ 0.9833, -0.9845]])
tensor([0.0007])
互相關運算與卷積運算
卷積層得名于卷積運算,但卷積層中用到的并非卷積運算而是互相關運算。我們将核數組上下翻轉、左右翻轉,再與輸入數組做互相關運算,這一過程就是卷積運算。由于卷積層的核數組是可學習的,是以使用互相關運算與使用卷積運算并無本質差別。
特征圖與感受野
二維卷積層輸出的二維數組可以看作是輸入在空間次元(寬和高)上某一級的表征,也叫特征圖(feature map)。影響元素 x x x的前向計算的所有可能輸入區域(可能大于輸入的實際尺寸)叫做 x x x的感受野(receptive field)。
以圖1為例,輸入中陰影部分的四個元素是輸出中陰影部分元素的感受野。我們将圖中形狀為 2 × 2 2 \times 2 2×2的輸出記為 Y Y Y,将 Y Y Y與另一個形狀為 2 × 2 2 \times 2 2×2的核數組做互相關運算,輸出單個元素 z z z。那麼, z z z在 Y Y Y上的感受野包括 Y Y Y的全部四個元素,在輸入上的感受野包括其中全部9個元素。可見,我們可以通過更深的卷積神經網絡使特征圖中單個元素的感受野變得更加廣闊,進而捕捉輸入上更大尺寸的特征。
填充和步幅
我們介紹卷積層的兩個超參數,即填充和步幅,它們可以對給定形狀的輸入和卷積核改變輸出形狀。
填充
填充(padding)是指在輸入高和寬的兩側填充元素(通常是0元素),圖2裡我們在原輸入高和寬的兩側分别添加了值為0的元素。
圖2 在輸入的高和寬兩側分别填充了0元素的二維互相關計算
如果原輸入的高和寬是 n h n_h nh和 n w n_w nw,卷積核的高和寬是 k h k_h kh和 k w k_w kw,在高的兩側一共填充 p h p_h ph行,在寬的兩側一共填充 p w p_w pw列,則輸出形狀為:
( n h + p h − k h + 1 ) × ( n w + p w − k w + 1 ) (n_h+p_h-k_h+1)\times(n_w+p_w-k_w+1) (nh+ph−kh+1)×(nw+pw−kw+1)
我們在卷積神經網絡中使用奇數高寬的核,比如 3 × 3 3 \times 3 3×3, 5 × 5 5 \times 5 5×5的卷積核,對于高度(或寬度)為大小為 2 k + 1 2 k + 1 2k+1的核,令步幅為1,在高(或寬)兩側選擇大小為 k k k的填充,便可保持輸入與輸出尺寸相同。
步幅
在互相關運算中,卷積核在輸入數組上滑動,每次滑動的行數與列數即是步幅(stride)。此前我們使用的步幅都是1,圖3展示了在高上步幅為3、在寬上步幅為2的二維互相關運算。
圖3 高和寬上步幅分别為3和2的二維互相關運算
一般來說,當高上步幅為 s h s_h sh,寬上步幅為 s w s_w sw時,輸出形狀為:
⌊ ( n h + p h − k h + s h ) / s h ⌋ × ⌊ ( n w + p w − k w + s w ) / s w ⌋ \lfloor(n_h+p_h-k_h+s_h)/s_h\rfloor \times \lfloor(n_w+p_w-k_w+s_w)/s_w\rfloor ⌊(nh+ph−kh+sh)/sh⌋×⌊(nw+pw−kw+sw)/sw⌋
如果 p h = k h − 1 p_h=k_h-1 ph=kh−1, p w = k w − 1 p_w=k_w-1 pw=kw−1,那麼輸出形狀将簡化為 ⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋。更進一步,如果輸入的高和寬能分别被高和寬上的步幅整除,那麼輸出形狀将是 ( n h / s h ) × ( n w / s w ) (n_h / s_h) \times (n_w/s_w) (nh/sh)×(nw/sw)。
當 p h = p w = p p_h = p_w = p ph=pw=p時,我們稱填充為 p p p;當 s h = s w = s s_h = s_w = s sh=sw=s時,我們稱步幅為 s s s。
多輸入通道和多輸出通道
之前的輸入和輸出都是二維數組,但真實資料的次元經常更高。例如,彩色圖像在高和寬2個次元外還有RGB(紅、綠、藍)3個顔色通道。假設彩色圖像的高和寬分别是 h h h和 w w w(像素),那麼它可以表示為一個 3 × h × w 3 \times h \times w 3×h×w的多元數組,我們将大小為3的這一維稱為通道(channel)維。
多輸入通道
卷積層的輸入可以包含多個通道,圖4展示了一個含2個輸入通道的二維互相關計算的例子。
圖4 含2個輸入通道的互相關計算
假設輸入資料的通道數為 c i c_i ci,卷積核形狀為 k h × k w k_h\times k_w kh×kw,我們為每個輸入通道各配置設定一個形狀為 k h × k w k_h\times k_w kh×kw的核數組,将 c i c_i ci個互相關運算的二維輸出按通道相加,得到一個二維數組作為輸出。我們把 c i c_i ci個核數組在通道維上連結,即得到一個形狀為 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的卷積核。
多輸出通道
卷積層的輸出也可以包含多個通道,設卷積核輸入通道數和輸出通道數分别為 c i c_i ci和 c o c_o co,高和寬分别為 k h k_h kh和 k w k_w kw。如果希望得到含多個通道的輸出,我們可以為每個輸出通道分别建立形狀為 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的核數組,将它們在輸出通道維上連結,卷積核的形狀即 c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw。
對于輸出通道的卷積核,我們提供這樣一種了解,一個 c i × k h × k w c_i \times k_h \times k_w ci×kh×kw的核數組可以提取某種局部特征,但是輸入可能具有相當豐富的特征,我們需要有多個這樣的 c i × k h × k w c_i \times k_h \times k_w ci×kh×kw的核數組,不同的核數組提取的是不同的特征。
1x1卷積層
最後讨論形狀為 1 × 1 1 \times 1 1×1的卷積核,我們通常稱這樣的卷積運算為 1 × 1 1 \times 1 1×1卷積,稱包含這種卷積核的卷積層為 1 × 1 1 \times 1 1×1卷積層。圖5展示了使用輸入通道數為3、輸出通道數為2的 1 × 1 1\times 1 1×1卷積核的互相關計算。
圖5 1x1卷積核的互相關計算。輸入和輸出具有相同的高和寬
1 × 1 1 \times 1 1×1卷積核可在不改變高寬的情況下,調整通道數。 1 × 1 1 \times 1 1×1卷積核不識别高和寬次元上相鄰元素構成的模式,其主要計算發生在通道維上。假設我們将通道維當作特征維,将高和寬次元上的元素當成資料樣本,那麼 1 × 1 1\times 1 1×1卷積層的作用與全連接配接層等價。
卷積層與全連接配接層的對比
二維卷積層經常用于處理圖像,與此前的全連接配接層相比,它主要有兩個優勢:
一是全連接配接層把圖像展平成一個向量,在輸入圖像上相鄰的元素可能因為展平操作不再相鄰,網絡難以捕捉局部資訊。而卷積層的設計,天然地具有提取局部資訊的能力。
二是卷積層的參數量更少。不考慮偏置的情況下,一個形狀為 ( c i , c o , h , w ) (c_i, c_o, h, w) (ci,co,h,w)的卷積核的參數量是 c i × c o × h × w c_i \times c_o \times h \times w ci×co×h×w,與輸入圖像的寬高無關。假如一個卷積層的輸入和輸出形狀分别是 ( c 1 , h 1 , w 1 ) (c_1, h_1, w_1) (c1,h1,w1)和 ( c 2 , h 2 , w 2 ) (c_2, h_2, w_2) (c2,h2,w2),如果要用全連接配接層進行連接配接,參數數量就是 c 1 × c 2 × h 1 × w 1 × h 2 × w 2 c_1 \times c_2 \times h_1 \times w_1 \times h_2 \times w_2 c1×c2×h1×w1×h2×w2。使用卷積層可以以較少的參數數量來處理更大的圖像。
卷積層的簡潔實作
我們使用Pytorch中的
nn.Conv2d
類來實作二維卷積層,主要關注以下幾個構造函數參數:
-
(python:int) – Number of channels in the input imagin_channels
-
(python:int) – Number of channels produced by the convolutionout_channels
-
(python:int or tuple) – Size of the convolving kernelkernel_size
-
(python:int or tuple, optional) – Stride of the convolution. Default: 1stride
-
(python:int or tuple, optional) – Zero-padding added to both sides of the input. Default: 0padding
-
(bool, optional) – If True, adds a learnable bias to the output. Default: Truebias
forward
函數的參數為一個四維張量,形狀為 ( N , C i n , H i n , W i n ) (N, C_{in}, H_{in}, W_{in}) (N,Cin,Hin,Win),傳回值也是一個四維張量,形狀為 ( N , C o u t , H o u t , W o u t ) (N, C_{out}, H_{out}, W_{out}) (N,Cout,Hout,Wout),其中 N N N是批量大小, C , H , W C, H, W C,H,W分别表示通道數、高度、寬度。
代碼講解
X = torch.rand(4, 2, 3, 5)
print(X.shape)
conv2d = nn.Conv2d(in_channels=2, out_channels=3, kernel_size=(3, 5), stride=1, padding=(1, 2))
Y = conv2d(X)
print('Y.shape: ', Y.shape)
print('weight.shape: ', conv2d.weight.shape)
print('bias.shape: ', conv2d.bias.shape)
torch.Size([4, 2, 3, 5])
Y.shape: torch.Size([4, 3, 3, 5])
weight.shape: torch.Size([3, 2, 3, 5])
bias.shape: torch.Size([3])
池化
二維池化層
池化層主要用于緩解卷積層對位置的過度敏感性。同卷積層一樣,池化層每次對輸入資料的一個固定形狀視窗(又稱池化視窗)中的元素計算輸出,池化層直接計算池化視窗内元素的最大值或者平均值,該運算也分别叫做最大池化或平均池化。圖6展示了池化視窗形狀為 2 × 2 2\times 2 2×2的最大池化。
圖6 池化視窗形狀為 2 x 2 的最大池化
二維平均池化的工作原理與二維最大池化類似,但将最大運算符替換成平均運算符。池化視窗形狀為 p × q p \times q p×q的池化層稱為 p × q p \times q p×q池化層,其中的池化運算叫作 p × q p \times q p×q池化。
池化層也可以在輸入的高和寬兩側填充并調整視窗的移動步幅來改變輸出形狀。池化層填充和步幅與卷積層填充和步幅的工作機制一樣。
在處理多通道輸入資料時,池化層對每個輸入通道分别池化,但不會像卷積層那樣将各通道的結果按通道相加。這意味着池化層的輸出通道數與輸入通道數相等。
池化層的簡潔實作
我們使用Pytorch中的
nn.MaxPool2d
實作最大池化層,關注以下構造函數參數:
-
– the size of the window to take a max overkernel_size
-
– the stride of the window. Default value is kernel_sizestride
-
– implicit zero padding to be added on both sidespadding
forward
函數的參數為一個四維張量,形狀為 ( N , C , H i n , W i n ) (N, C, H_{in}, W_{in}) (N,C,Hin,Win),傳回值也是一個四維張量,形狀為 ( N , C , H o u t , W o u t ) (N, C, H_{out}, W_{out}) (N,C,Hout,Wout),其中 N N N是批量大小, C , H , W C, H, W C,H,W分别表示通道數、高度、寬度。
代碼講解
X = torch.arange(32, dtype=torch.float32).view(1, 2, 4, 4)
pool2d = nn.MaxPool2d(kernel_size=3, padding=1, stride=(2, 1))
Y = pool2d(X)
print(X)
print(Y)
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]],
[[16., 17., 18., 19.],
[20., 21., 22., 23.],
[24., 25., 26., 27.],
[28., 29., 30., 31.]]]])
tensor([[[[ 5., 6., 7., 7.],
[13., 14., 15., 15.]],
[[21., 22., 23., 23.],
[29., 30., 31., 31.]]]])
平均池化層使用的是
nn.AvgPool2d
,使用方法與
nn.MaxPool2d
相同。