《動手學深度學習》task5_1 卷積神經網絡基礎

系統學習《動手學深度學習》點擊這裏:

《動手學深度學習》task1_1 線性迴歸
《動手學深度學習》task1_2 Softmax與分類模型
《動手學深度學習》task1_3 多層感知機
《動手學深度學習》task2_1 文本預處理
《動手學深度學習》task2_2 語言模型
《動手學深度學習》task2_3 循環神經網絡基礎
《動手學深度學習》task3_1 過擬合、欠擬合及其解決方案
《動手學深度學習》task3_2 梯度消失、梯度爆炸
《動手學深度學習》task3_3 循環神經網絡進階
《動手學深度學習》task4_1 機器翻譯

《動手學深度學習》筆記:

《動手學深度學習》task1——線性迴歸、softmax與分類模型,多層感知機筆記
《動手學深度學習》task2——文本預處理,語言模型,循環神經網絡基礎筆記
《動手學深度學習》task3——過擬合、欠擬合及解決方案,梯度消失、梯度爆炸,循環神經網絡進階筆記

卷積神經網絡基礎

本節我們介紹卷積神經網絡的基礎概念,主要是卷積層和池化層,並解釋填充、步幅、輸入通道和輸出通道的含義。

二維卷積層

本節介紹的是最常見的二維卷積層,常用於處理圖像數據。

二維互相關運算

二維互相關(cross-correlation)運算的輸入是一個二維輸入數組和一個二維核(kernel)數組,輸出也是一個二維數組,其中核數組通常稱爲卷積核或過濾器(filter)。卷積核的尺寸通常小於輸入數組,卷積核在輸入數組上滑動,在每個位置上,卷積核與該位置處的輸入子數組按元素相乘並求和,得到輸出數組中相應位置的元素。圖1展示了一個互相關運算的例子,陰影部分分別是輸入的第一個計算區域、核數組以及對應的輸出。

Image Name
圖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×86 \times 8的圖像,中間4列爲黑(0),其餘爲白(1),希望檢測到顏色邊緣。我們的標籤是一個6×76 \times 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×21 \times 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 4.569
Step 10, loss 0.949
Step 15, loss 0.228
Step 20, loss 0.060
Step 25, loss 0.016
Step 30, loss 0.004
tensor([[ 1.0161, -1.0177]])
tensor([0.0009])

互相關運算與卷積運算

卷積層得名於卷積運算,但卷積層中用到的並非卷積運算而是互相關運算。我們將核數組上下翻轉、左右翻轉,再與輸入數組做互相關運算,這一過程就是卷積運算。由於卷積層的核數組是可學習的,所以使用互相關運算與使用卷積運算並無本質區別。

特徵圖與感受野

二維卷積層輸出的二維數組可以看作是輸入在空間維度(寬和高)上某一級的表徵,也叫特徵圖(feature map)。影響元素xx的前向計算的所有可能輸入區域(可能大於輸入的實際尺寸)叫做xx的感受野(receptive field)。

以圖1爲例,輸入中陰影部分的四個元素是輸出中陰影部分元素的感受野。我們將圖中形狀爲2×22 \times 2的輸出記爲YY,將YY與另一個形狀爲2×22 \times 2的核數組做互相關運算,輸出單個元素zz。那麼,zzYY上的感受野包括YY的全部四個元素,在輸入上的感受野包括其中全部9個元素。可見,我們可以通過更深的卷積神經網絡使特徵圖中單個元素的感受野變得更加廣闊,從而捕捉輸入上更大尺寸的特徵。

填充和步幅

我們介紹卷積層的兩個超參數,即填充和步幅,它們可以對給定形狀的輸入和卷積核改變輸出形狀。

填充

填充(padding)是指在輸入高和寬的兩側填充元素(通常是0元素),圖2裏我們在原輸入高和寬的兩側分別添加了值爲0的元素。

Image Name

圖2 在輸入的高和寬兩側分別填充了0元素的二維互相關計算

如果原輸入的高和寬是nhn_hnwn_w,卷積核的高和寬是khk_hkwk_w,在高的兩側一共填充php_h行,在寬的兩側一共填充pwp_w列,則輸出形狀爲:
(nh+phkh+1)×(nw+pwkw+1) (n_h+p_h-k_h+1)\times(n_w+p_w-k_w+1)
我們在卷積神經網絡中使用奇數高寬的核,比如3×33 \times 35×55 \times 5的卷積核,對於高度(或寬度)爲大小爲2k+12 k + 1的核,令步幅爲1,在高(或寬)兩側選擇大小爲kk的填充,便可保持輸入與輸出尺寸相同。

步幅

在互相關運算中,卷積核在輸入數組上滑動,每次滑動的行數與列數即是步幅(stride)。此前我們使用的步幅都是1,圖3展示了在高上步幅爲3、在寬上步幅爲2的二維互相關運算。

Image Name

圖3 高和寬上步幅分別爲3和2的二維互相關運算

一般來說,當高上步幅爲shs_h,寬上步幅爲sws_w時,輸出形狀爲:
(nh+phkh+sh)/sh×(nw+pwkw+sw)/sw \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
如果ph=kh1p_h=k_h-1pw=kw1p_w=k_w-1,那麼輸出形狀將簡化爲(nh+sh1)/sh×(nw+sw1)/sw\lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor。更進一步,如果輸入的高和寬能分別被高和寬上的步幅整除,那麼輸出形狀將是(nh/sh)×(nw/sw)(n_h / s_h) \times (n_w/s_w)

ph=pw=pp_h = p_w = p時,我們稱填充爲pp;當sh=sw=ss_h = s_w = s時,我們稱步幅爲ss

多輸入通道和多輸出通道

之前的輸入和輸出都是二維數組,但真實數據的維度經常更高。例如,彩色圖像在高和寬2個維度外還有RGB(紅、綠、藍)3個顏色通道。假設彩色圖像的高和寬分別是hhww(像素),那麼它可以表示爲一個3×h×w3 \times h \times w的多維數組,我們將大小爲3的這一維稱爲通道(channel)維。

多輸入通道

卷積層的輸入可以包含多個通道,圖4展示了一個含2個輸入通道的二維互相關計算的例子。

Image Name

圖4 含2個輸入通道的互相關計算

假設輸入數據的通道數爲cic_i,卷積核形狀爲kh×kwk_h\times k_w,我們爲每個輸入通道各分配一個形狀爲kh×kwk_h\times k_w的核數組,將cic_i個互相關運算的二維輸出按通道相加,得到一個二維數組作爲輸出。我們把cic_i個核數組在通道維上連結,即得到一個形狀爲ci×kh×kwc_i\times k_h\times k_w的卷積核。

多輸出通道

卷積層的輸出也可以包含多個通道,設卷積核輸入通道數和輸出通道數分別爲cic_icoc_o,高和寬分別爲khk_hkwk_w。如果希望得到含多個通道的輸出,我們可以爲每個輸出通道分別創建形狀爲ci×kh×kwc_i\times k_h\times k_w的核數組,將它們在輸出通道維上連結,卷積核的形狀即co×ci×kh×kwc_o\times c_i\times k_h\times k_w

對於輸出通道的卷積核,我們提供這樣一種理解,一個ci×kh×kwc_i \times k_h \times k_w的核數組可以提取某種局部特徵,但是輸入可能具有相當豐富的特徵,我們需要有多個這樣的ci×kh×kwc_i \times k_h \times k_w的核數組,不同的核數組提取的是不同的特徵。

1x1卷積層

最後討論形狀爲1×11 \times 1的卷積核,我們通常稱這樣的卷積運算爲1×11 \times 1卷積,稱包含這種卷積核的卷積層爲1×11 \times 1卷積層。圖5展示了使用輸入通道數爲3、輸出通道數爲2的1×11\times 1卷積核的互相關計算。

Image Name

圖5 1x1卷積核的互相關計算。輸入和輸出具有相同的高和寬

1×11 \times 1卷積核可在不改變高寬的情況下,調整通道數。1×11 \times 1卷積核不識別高和寬維度上相鄰元素構成的模式,其主要計算髮生在通道維上。假設我們將通道維當作特徵維,將高和寬維度上的元素當成數據樣本,那麼1×11\times 1卷積層的作用與全連接層等價。

卷積層與全連接層的對比

二維卷積層經常用於處理圖像,與此前的全連接層相比,它主要有兩個優勢:

一是全連接層把圖像展平成一個向量,在輸入圖像上相鄰的元素可能因爲展平操作不再相鄰,網絡難以捕捉局部信息。而卷積層的設計,天然地具有提取局部信息的能力。

二是卷積層的參數量更少。不考慮偏置的情況下,一個形狀爲(ci,co,h,w)(c_i, c_o, h, w)的卷積核的參數量是ci×co×h×wc_i \times c_o \times h \times w,與輸入圖像的寬高無關。假如一個卷積層的輸入和輸出形狀分別是(c1,h1,w1)(c_1, h_1, w_1)(c2,h2,w2)(c_2, h_2, w_2),如果要用全連接層進行連接,參數數量就是c1×c2×h1×w1×h2×w2c_1 \times c_2 \times h_1 \times w_1 \times h_2 \times w_2。使用卷積層可以以較少的參數數量來處理更大的圖像。

卷積層的簡潔實現

我們使用Pytorch中的nn.Conv2d類來實現二維卷積層,主要關注以下幾個構造函數參數:

  • in_channels (python:int) – Number of channels in the input imag
  • out_channels (python:int) – Number of channels produced by the convolution
  • kernel_size (python:int or tuple) – Size of the convolving kernel
  • stride (python:int or tuple, optional) – Stride of the convolution. Default: 1
  • padding (python:int or tuple, optional) – Zero-padding added to both sides of the input. Default: 0
  • bias (bool, optional) – If True, adds a learnable bias to the output. Default: True

forward函數的參數爲一個四維張量,形狀爲(N,Cin,Hin,Win)(N, C_{in}, H_{in}, W_{in}),返回值也是一個四維張量,形狀爲(N,Cout,Hout,Wout)(N, C_{out}, H_{out}, W_{out}),其中NN是批量大小,C,H,WC, 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×22\times 2的最大池化。

Image Name

圖6 池化窗口形狀爲 2 x 2 的最大池化

二維平均池化的工作原理與二維最大池化類似,但將最大運算符替換成平均運算符。池化窗口形狀爲p×qp \times q的池化層稱爲p×qp \times q池化層,其中的池化運算叫作p×qp \times q池化。

池化層也可以在輸入的高和寬兩側填充並調整窗口的移動步幅來改變輸出形狀。池化層填充和步幅與卷積層填充和步幅的工作機制一樣。

在處理多通道輸入數據時,池化層對每個輸入通道分別池化,但不會像卷積層那樣將各通道的結果按通道相加。這意味着池化層的輸出通道數與輸入通道數相等。

池化層的簡潔實現

我們使用Pytorch中的nn.MaxPool2d實現最大池化層,關注以下構造函數參數:

  • kernel_size – the size of the window to take a max over
  • stride – the stride of the window. Default value is kernel_size
  • padding – implicit zero padding to be added on both sides

forward函數的參數爲一個四維張量,形狀爲(N,C,Hin,Win)(N, C, H_{in}, W_{in}),返回值也是一個四維張量,形狀爲(N,C,Hout,Wout)(N, C, H_{out}, W_{out}),其中NN是批量大小,C,H,WC, 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相同。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章