天天看點

【從零開始學習深度學習】21. 卷積神經網絡(CNN)之二維卷積層原理介紹、如何用卷積層檢測物體邊緣

目錄

    • 1. 二維互相關運算
    • 2. 自定義二維卷積層
    • 3. 卷積層的應用----圖像中物體邊緣檢測
    • 4. 通過資料學習核數組
    • 5. 互相關運算和卷積運算
    • 6. 特征圖和感受野
    • 總結

卷積神經網絡(convolutional neural network)是含有卷積層(convolutional layer)的神經網絡。最常見的卷積神經網絡均使用二維卷積層。它有高和寬兩個空間次元,常用來處理圖像資料。本文将介紹簡單形式的二維卷積層工作原理。

1. 二維互相關運算

雖然卷積層得名于卷積(convolution)運算,但我們通常在卷積層中使用更加直覺的互相關(cross-correlation)運算。在二維卷積層中,一個二維輸入數組和一個二維核(kernel)數組通過互相關運算輸出一個二維數組。

我們用一個具體例子來解釋二維互相關運算的含義。如下圖所示,輸入是一個高和寬均為3的二維數組。我們将該數組的形狀記為 3 × 3 3 \times 3 3×3或(3,3)。核數組的高和寬分别為2。該數組在卷積計算中又稱

卷積核或過濾器(filter)

。卷積核視窗(又稱卷積視窗)的形狀取決于卷積核的高和寬,即 2 × 2 2 \times 2 2×2。圖中的陰影部分為第一個輸出元素及其計算所使用的輸入和核數組元素: 0 × 0 + 1 × 1 + 3 × 2 + 4 × 3 = 19 0\times0+1\times1+3\times2+4\times3=19 0×0+1×1+3×2+4×3=19。

【從零開始學習深度學習】21. 卷積神經網絡(CNN)之二維卷積層原理介紹、如何用卷積層檢測物體邊緣

在二維互相關運算中,卷積視窗從輸入數組的最左上方開始,按從左往右、從上往下的順序,依次在輸入數組上滑動。當卷積視窗滑動到某一位置時,

視窗中的輸入子數組與核數組按對應元素相乘并求和

,得到輸出數組中相應位置的元素。圖中的輸出數組高和寬分别為2,其中的4個元素由二維互相關運算得出,計算如下:

0 × 0 + 1 × 1 + 3 × 2 + 4 × 3 = 19 , 1 × 0 + 2 × 1 + 4 × 2 + 5 × 3 = 25 , 3 × 0 + 4 × 1 + 6 × 2 + 7 × 3 = 37 , 4 × 0 + 5 × 1 + 7 × 2 + 8 × 3 = 43. 0\times0+1\times1+3\times2+4\times3=19,\\ 1\times0+2\times1+4\times2+5\times3=25,\\ 3\times0+4\times1+6\times2+7\times3=37,\\ 4\times0+5\times1+7\times2+8\times3=43.\\ 0×0+1×1+3×2+4×3=19,1×0+2×1+4×2+5×3=25,3×0+4×1+6×2+7×3=37,4×0+5×1+7×2+8×3=43.

下面我們将上述過程實作在

corr2d

函數裡。它接受輸入數組

X

與核數組

K

,并輸出數組

Y

import torch 
from torch import nn

def corr2d(X, K): 
    # K為卷積核
    h, w = K.shape
    # Y為卷積計算後的輸出,形狀為X.shape[0] - h + 1, X.shape[1] - w + 1
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - 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
           

我們可以構造上圖1中的輸入數組

X

、核數組

K

來驗證二維互相關運算的輸出。

X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
K = torch.tensor([[0, 1], [2, 3]])
corr2d(X, K)
           

輸出:

tensor([[19., 25.],
        [37., 43.]])
           

2. 自定義二維卷積層

二維卷積層将輸入和卷積核做互相關運算,并加上一個标量偏差來得到輸出。卷積層的模型參數包括了

卷積核

标量偏差

。在訓練模型的時候,通常我們先對卷積核随機初始化,然後不斷疊代卷積核和偏差。

下面基于

corr2d

函數來實作一個自定義的二維卷積層。在構造函數

__init__

裡我們聲明

weight

bias

這兩個模型參數。前向計算函數

forward

則是直接調用

corr2d

函數再加上偏差。

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
           

卷積視窗形狀為 p × q p \times q p×q的卷積層稱為 p × q p \times q p×q卷積層。同樣, p × q p \times q p×q卷積或 p × q p \times q p×q卷積核說明卷積核的高和寬分别為 p p p和 q q q。

3. 卷積層的應用----圖像中物體邊緣檢測

下面我們來看一個卷積層的簡單應用:檢測圖像中物體的邊緣,即找到像素變化的位置。首先我們構造一張 6 × 8 6\times 8 6×8的圖像(即高和寬分别為6像素和8像素的圖像)。它中間4列為黑(0),其餘為白(1)。

X = torch.ones(6, 8)
X[:, 2:6] = 0
X
           

輸出:

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.]])
           

然後我們構造一個高和寬分别為1和2的卷積核

K

當它與輸入做互相關運算時,如果橫向相鄰元素相同,輸出為0;否則輸出為非0

下面将輸入

X

和我們設計的卷積核

K

做互相關運算。可以看出,我們将從白到黑的邊緣和從黑到白的邊緣分别檢測成了1和-1。其餘部分的輸出全是0。

Y = corr2d(X, K)
Y
           

輸出:

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.]])
           

由此,我們可以看出,

卷積層可通過重複使用卷積核有效地表征局部空間

4. 通過資料學習核數組

我們使用上面物體邊緣檢測中的輸入資料

X

和輸出資料

Y

來學習我們構造的核數組

K

。我們首先構造一個卷積層,其

卷積核将被初始化成随機數組

。接下來在每一次疊代中,我們使用平方誤差來比較

Y

和卷積層的輸出,然後計算梯度來更新權重。

# 構造一個核數組形狀是(1, 2)的二維卷積層
conv2d = Conv2D(kernel_size=(1, 2))

step = 20
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
    
    # 梯度清0
    conv2d.weight.grad.fill_(0)
    conv2d.bias.grad.fill_(0)
    if (i + 1) % 5 == 0:
        print('Step %d, loss %.3f' % (i + 1, l.item()))
           

輸出:

Step 5, loss 1.844
Step 10, loss 0.206
Step 15, loss 0.023
Step 20, loss 0.003
           

可以看到,20次疊代後誤差已經降到了一個比較小的值。現在來看一下學習到的卷積核的參數。

print("weight: ", conv2d.weight.data)
print("bias: ", conv2d.bias.data)
           

輸出:

weight:  tensor([[ 0.9948, -1.0092]])
bias:  tensor([0.0080])
           

可以看到,學到的卷積核的權重參數與我們之前定義的核數組

K

較接近,而偏置參數接近0。

5. 互相關運算和卷積運算

實際上,卷積運算與互相關運算類似。為了得到卷積運算的輸出,我們隻需将核數組左右翻轉并上下翻轉,再與輸入數組做互相關運算。可見,卷積運算和互相關運算雖然類似,但如果它們使用相同的核數組,對于同一個輸入,輸出往往并不相同。

那麼,你也許會好奇卷積層為何能使用互相關運算替代卷積運算。其實,在深度學習中核數組都是學出來的:卷積層無論使用互相關運算或卷積運算都不影響模型預測時的輸出。為了解釋這一點,假設卷積層使用互相關運算學出圖1中的核數組。設其他條件不變,使用卷積運算學出的核數組即圖1中的核數組按上下、左右翻轉。也就是說,圖1中的輸入與學出的已翻轉的核數組再做卷積運算時,依然得到圖1中的輸出。為了與大多數深度學習文獻一緻,如無特别說明,本書中提到的卷積運算均指互相關運算。

6. 特征圖和感受野

二維卷積層輸出的二維數組可以看作是輸入在空間次元(寬和高)上某一級的表征,也叫

特征圖(feature map)

。影響元素 x x x的前向計算的所有可能輸入區域(可能大于輸入的實際尺寸)叫做 x x x的

感受野(receptive field)

。以圖1為例,輸入中陰影部分的四個元素是輸出中陰影部分元素的感受野。我們将圖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個元素。可見,我們可以通過更深的卷積神經網絡使特征圖中單個元素的感受野變得更加廣闊,進而捕捉輸入上更大尺寸的特征。

總結

  • 二維卷積層的核心計算是二維互相關運算。在最簡單的形式下,它對二維輸入資料和卷積核做互相關運算然後加上偏差。
  • 我們可以設計卷積核來檢測圖像中的邊緣。
  • 我們可以通過資料來學習卷積核。

對文章存在的問題,或者其他關于Python相關的問題,都可以在評論區留言或者私信我哦

如果文章内容對你有幫助,感謝點贊+關注!

關注下方GZH:阿旭算法與機器學習,可擷取更多幹貨内容~歡迎共同學習交流

繼續閱讀