系統學習《動手學深度學習》點擊這裏:
《動手學深度學習》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展示了一個互相關運算的例子,陰影部分分別是輸入的第一個計算區域、核數組以及對應的輸出。
圖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
下面我們看一個例子,我們構造一張的圖像,中間4列爲黑(0),其餘爲白(1),希望檢測到顏色邊緣。我們的標籤是一個的二維數組,第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.]])
我們希望學習一個卷積層,通過卷積層來檢測顏色邊緣。
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)。影響元素的前向計算的所有可能輸入區域(可能大於輸入的實際尺寸)叫做的感受野(receptive field)。
以圖1爲例,輸入中陰影部分的四個元素是輸出中陰影部分元素的感受野。我們將圖中形狀爲的輸出記爲,將與另一個形狀爲的核數組做互相關運算,輸出單個元素。那麼,在上的感受野包括的全部四個元素,在輸入上的感受野包括其中全部9個元素。可見,我們可以通過更深的卷積神經網絡使特徵圖中單個元素的感受野變得更加廣闊,從而捕捉輸入上更大尺寸的特徵。
填充和步幅
我們介紹卷積層的兩個超參數,即填充和步幅,它們可以對給定形狀的輸入和卷積核改變輸出形狀。
填充
填充(padding)是指在輸入高和寬的兩側填充元素(通常是0元素),圖2裏我們在原輸入高和寬的兩側分別添加了值爲0的元素。
圖2 在輸入的高和寬兩側分別填充了0元素的二維互相關計算
如果原輸入的高和寬是和,卷積核的高和寬是和,在高的兩側一共填充行,在寬的兩側一共填充列,則輸出形狀爲:
我們在卷積神經網絡中使用奇數高寬的核,比如,的卷積核,對於高度(或寬度)爲大小爲的核,令步幅爲1,在高(或寬)兩側選擇大小爲的填充,便可保持輸入與輸出尺寸相同。
步幅
在互相關運算中,卷積核在輸入數組上滑動,每次滑動的行數與列數即是步幅(stride)。此前我們使用的步幅都是1,圖3展示了在高上步幅爲3、在寬上步幅爲2的二維互相關運算。
圖3 高和寬上步幅分別爲3和2的二維互相關運算
一般來說,當高上步幅爲,寬上步幅爲時,輸出形狀爲:
如果,,那麼輸出形狀將簡化爲。更進一步,如果輸入的高和寬能分別被高和寬上的步幅整除,那麼輸出形狀將是。
當時,我們稱填充爲;當時,我們稱步幅爲。
多輸入通道和多輸出通道
之前的輸入和輸出都是二維數組,但真實數據的維度經常更高。例如,彩色圖像在高和寬2個維度外還有RGB(紅、綠、藍)3個顏色通道。假設彩色圖像的高和寬分別是和(像素),那麼它可以表示爲一個的多維數組,我們將大小爲3的這一維稱爲通道(channel)維。
多輸入通道
卷積層的輸入可以包含多個通道,圖4展示了一個含2個輸入通道的二維互相關計算的例子。
圖4 含2個輸入通道的互相關計算
假設輸入數據的通道數爲,卷積核形狀爲,我們爲每個輸入通道各分配一個形狀爲的核數組,將個互相關運算的二維輸出按通道相加,得到一個二維數組作爲輸出。我們把個核數組在通道維上連結,即得到一個形狀爲的卷積核。
多輸出通道
卷積層的輸出也可以包含多個通道,設卷積核輸入通道數和輸出通道數分別爲和,高和寬分別爲和。如果希望得到含多個通道的輸出,我們可以爲每個輸出通道分別創建形狀爲的核數組,將它們在輸出通道維上連結,卷積核的形狀即。
對於輸出通道的卷積核,我們提供這樣一種理解,一個的核數組可以提取某種局部特徵,但是輸入可能具有相當豐富的特徵,我們需要有多個這樣的的核數組,不同的核數組提取的是不同的特徵。
1x1卷積層
最後討論形狀爲的卷積核,我們通常稱這樣的卷積運算爲卷積,稱包含這種卷積核的卷積層爲卷積層。圖5展示了使用輸入通道數爲3、輸出通道數爲2的卷積核的互相關計算。
圖5 1x1卷積核的互相關計算。輸入和輸出具有相同的高和寬
卷積核可在不改變高寬的情況下,調整通道數。卷積核不識別高和寬維度上相鄰元素構成的模式,其主要計算髮生在通道維上。假設我們將通道維當作特徵維,將高和寬維度上的元素當成數據樣本,那麼卷積層的作用與全連接層等價。
卷積層與全連接層的對比
二維卷積層經常用於處理圖像,與此前的全連接層相比,它主要有兩個優勢:
一是全連接層把圖像展平成一個向量,在輸入圖像上相鄰的元素可能因爲展平操作不再相鄰,網絡難以捕捉局部信息。而卷積層的設計,天然地具有提取局部信息的能力。
二是卷積層的參數量更少。不考慮偏置的情況下,一個形狀爲的卷積核的參數量是,與輸入圖像的寬高無關。假如一個卷積層的輸入和輸出形狀分別是和,如果要用全連接層進行連接,參數數量就是。使用卷積層可以以較少的參數數量來處理更大的圖像。
卷積層的簡潔實現
我們使用Pytorch中的nn.Conv2d
類來實現二維卷積層,主要關注以下幾個構造函數參數:
in_channels
(python:int) – Number of channels in the input imagout_channels
(python:int) – Number of channels produced by the convolutionkernel_size
(python:int or tuple) – Size of the convolving kernelstride
(python:int or tuple, optional) – Stride of the convolution. Default: 1padding
(python:int or tuple, optional) – Zero-padding added to both sides of the input. Default: 0bias
(bool, optional) – If True, adds a learnable bias to the output. Default: True
forward
函數的參數爲一個四維張量,形狀爲,返回值也是一個四維張量,形狀爲,其中是批量大小,分別表示通道數、高度、寬度。
代碼講解
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展示了池化窗口形狀爲的最大池化。
圖6 池化窗口形狀爲 2 x 2 的最大池化
二維平均池化的工作原理與二維最大池化類似,但將最大運算符替換成平均運算符。池化窗口形狀爲的池化層稱爲池化層,其中的池化運算叫作池化。
池化層也可以在輸入的高和寬兩側填充並調整窗口的移動步幅來改變輸出形狀。池化層填充和步幅與卷積層填充和步幅的工作機制一樣。
在處理多通道輸入數據時,池化層對每個輸入通道分別池化,但不會像卷積層那樣將各通道的結果按通道相加。這意味着池化層的輸出通道數與輸入通道數相等。
池化層的簡潔實現
我們使用Pytorch中的nn.MaxPool2d
實現最大池化層,關注以下構造函數參數:
kernel_size
– the size of the window to take a max overstride
– the stride of the window. Default value is kernel_sizepadding
– implicit zero padding to be added on both sides
forward
函數的參數爲一個四維張量,形狀爲,返回值也是一個四維張量,形狀爲,其中是批量大小,分別表示通道數、高度、寬度。
代碼講解
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
相同。