卷積神經網絡(convolutional neural network)是含有卷積層(convolutional layer)的神經網絡。本章中介紹的卷積神經網絡均使用最多見的二維卷積層。它有高和寬兩個空間維度,經常使用來處理圖像數據。本節中,咱們將介紹簡單形式的二維卷積層的工做原理。python
1. 二維互相關運算
雖然卷積層得名於卷積(convolution)運算,但咱們一般在卷積層中使用更加直觀的互相關(cross-correlation)運算。在二維卷積層中,一個二維輸入數組和一個二維核(kernel)數組經過互相關運算輸出一個二維數組。
咱們用一個具體例子來解釋二維互相關運算的含義。如圖5.1所示,輸入是一個高和寬均爲3的二維數組。咱們將該數組的形狀記爲 3 × 3 3 \times 3 3×3或(3,3)。核數組的高和寬分別爲2。該數組在卷積計算中又稱卷積核或過濾器(filter)。卷積核窗口(又稱卷積窗口)的形狀取決於卷積核的高和寬,即 2 × 2 2 \times 2 2×2。圖5.1中的陰影部分爲第一個輸出元素及其計算所使用的輸入和核數組元素: 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。
數組
在二維互相關運算中,卷積窗口從輸入數組的最左上方開始,按從左往右、從上往下的順序,依次在輸入數組上滑動。當卷積窗口滑動到某一位置時,窗口中的輸入子數組與核數組按元素相乘並求和,獲得輸出數組中相應位置的元素。圖5.1中的輸出數組高和寬分別爲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): h, w = K.shape 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
咱們能夠構造圖5.1中的輸入數組X
、核數組K
來驗證二維互相關運算的輸出。spa
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. 二維卷積層
二維卷積層將輸入和卷積核作互相關運算,並加上一個標量誤差來獲得輸出。卷積層的模型參數包括了卷積核和標量誤差。在訓練模型的時候,一般咱們先對卷積核隨機初始化,而後不斷迭代卷積核和誤差。code
下面基於corr2d
函數來實現一個自定義的二維卷積層。在構造函數__init__
裏咱們聲明weight
和bias
這兩個模型參數。前向計算函數forward
則是直接調用corr2d
函數再加上誤差。blog
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。
K = torch.tensor([[1, -1]])
下面將輸入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。