CNN-二維卷積層
卷積神經網絡(convolutional neural network)是含有卷積層(convolutional layer)的神經網絡。卷積神經網絡均使用最常見的二維卷積層。它有高和寬兩個空間維度,常用來處理圖像數據。
二維互相關運算
雖然卷積層得名於卷積(convolution)運算,但我們通常在卷積層中使用更加直觀的互相關(cross-correlation)運算。在二維卷積層中,一個二維輸入數組和一個二維核(kernel)數組通過互相關運算輸出一個二維數組。 我們用一個具體例子來解釋二維互相關運算的含義。如圖5.1所示,輸入是一個高和寬均為3的二維數組。我們將該數組的形狀記為3*3或(3,3)。核數組的高和寬分別為2。該數組在卷積計算中又稱卷積核或過濾器(filter)。卷積核窗口(又稱卷積窗口)的形狀取決於卷積核的高和寬,即2*2。下圖的陰影部分為第一個輸出元素及其計算所使用的輸入和核數組元素:0*0+1*1+3*2+4*3 = 19。
在二維互相關運算中,卷積窗口從輸入數組的最左上方開始,按從左往右、從上往下的順序,依次在輸入數組上滑動。當卷積窗口滑動到某一位置時,窗口中的輸入子數組與核數組按元素相乘並求和,得到輸出數組中相應位置的元素。上圖中的輸出數組高和寬分別為2,其中的4個元素由二維互相關運算得出:
二維卷積層
二維卷積層將輸入和卷積核做互相關運算,並加上一個標量偏差來得到輸出。卷積層的模型參數包括了卷積核和標量偏差。在訓練模型的時候,通常我們先對卷積核隨機初始化,然后不斷迭代卷積核和偏差。
互相關運算和卷積運算
實際上,卷積運算與互相關運算類似。為了得到卷積運算的輸出,我們只需將核數組左右翻轉並上下翻轉,再與輸入數組做互相關運算。可見,卷積運算和互相關運算雖然類似,但如果它們使用相同的核數組,對於同一個輸入,輸出往往並不相同。
那么,卷積層為何能使用互相關運算替代卷積運算。其實,在深度學習中核數組都是學出來的:卷積層無論使用互相關運算或卷積運算都不影響模型預測時的輸出。為了解釋這一點,假設卷積層使用互相關運算學出上圖中的核數組。設其他條件不變,使用卷積運算學出的核數組即上圖中的核數組按上下、左右翻轉。也就是說,上圖中的輸入與學出的已翻轉的核數組再做卷積運算時,依然得到上圖中的輸出。為了與大多數深度學習文獻一致,如無特別說明,本書中提到的卷積運算均指互相關運算。
特征圖和感受野
維卷積層輸出的二維數組可以看作是輸入在空間維度(寬和高)上某一級的表征,也叫特征圖(feature map)。影響元素x的前向計算的所有可能輸入區域(可能大於輸入的實際尺寸)叫做x的感受野(receptive field)。以上圖為例,輸入中陰影部分的四個元素是輸出中陰影部分元素的感受野。我們將上圖中形狀為2×2的輸出記為Y,並考慮一個更深的卷積神經網絡:將Y與另一個形狀為2×2的核數組做互相關運算,輸出單個元素z。那么,z在Y上的感受野包括Y的全部四個元素,在輸入上的感受野包括其中全部9個元素。可見,我們可以通過更深的卷積神經網絡使特征圖中單個元素的感受野變得更加廣闊,從而捕捉輸入上更大尺寸的特征。
我們常使用“元素”一詞來描述數組或矩陣中的成員。在神經網絡的術語中,這些元素也可稱為“單元”。當含義明確時,本書不對這兩個術語做嚴格區分。
- 二維卷積層的核心計算是二維互相關運算。在最簡單的形式下,它對二維輸入數據和卷積核做互相關運算然后加上偏差。
- 我們可以設計卷積核來檢測圖像中的邊緣。
- 我們可以通過數據來學習卷積核。
填充和步幅
填充
步幅
- 填充可以增加輸出的高和寬。這常用來使輸出與輸入具有相同的高和寬。
- 步幅可以減小輸出的高和寬,例如輸出的高和寬僅為輸入的高和寬的1/n(n為大於1的整數)。
多輸入通道和多輸出通道
前面我們用到的輸入和輸出都是二維數組,但真實數據的維度經常更高。例如,彩色圖像在高和寬2個維度外還有RGB(紅、綠、藍)3個顏色通道。假設彩色圖像的高和寬分別是h和w(像素),那么它可以表示為一個3×h×w的多維數組。我們將大小為3的這一維稱為通道(channel)維。本節介紹含多個輸入通道或多個輸出通道的卷積核。
多輸入通道
多輸出通道
1×1卷積層
- 使用多通道可以拓展卷積層的模型參數。
- 假設將通道維當作特征維,將高和寬維度上的元素當成數據樣本,那么1×11×1卷積層的作用與全連接層等價。
- 1×11×1卷積層通常用來調整網絡層之間的通道數,並控制模型復雜度。
池化層
構造卷積核從而精確地找到了像素變化的位置。設任意二維數組X
的i
行j
列的元素為X[i, j]
。如果我們構造的卷積核輸出Y[i, j]=1
,那么說明輸入中X[i, j]
和X[i, j+1]
數值不一樣。這可能意味着物體邊緣通過這兩個元素之間。但實際圖像里,我們感興趣的物體不會總出現在固定位置:即使我們連續拍攝同一個物體也極有可能出現像素位置上的偏移。這會導致同一個邊緣對應的輸出可能出現在卷積輸出Y
中的不同位置,進而對后面的模式識別造成不便。
在本節中介紹池化(pooling)層,它的提出是為了緩解卷積層對位置的過度敏感性。
二維最大池化層和平均池化層
填充和步幅
同卷積層一樣,池化層也可以在輸入的高和寬兩側的填充並調整窗口的移動步幅來改變輸出形狀。池化層填充和步幅與卷積層填充和步幅的工作機制一樣。
多通道
在處理多通道輸入數據時,池化層對每個輸入通道分別池化,而不是像卷積層那樣將各通道的輸入按通道相加。這意味着池化層的輸出通道數與輸入通道數相等。
- 最大池化和平均池化分別取池化窗口中輸入元素的最大值和平均值作為輸出。
- 池化層的一個主要作用是緩解卷積層對位置的過度敏感性。
- 可以指定池化層的填充和步幅。
- 池化層的輸出通道數跟輸入通道數相同。
卷積神經網絡(LeNet)
對Fashion-MNIST數據集中的圖像進行分類。每張圖像高和寬均是28像素。我們將圖像中的像素逐行展開,得到長度為784的向量,並輸入進全連接層中。然而,這種分類方法有一定的局限性。
- 圖像在同一列鄰近的像素在這個向量中可能相距較遠。它們構成的模式可能難以被模型識別。
- 對於大尺寸的輸入圖像,使用全連接層容易導致模型過大。假設輸入是高和寬均為1,000像素的彩色照片(含3個通道)。即使全連接層輸出個數仍是256,該層權重參數的形狀也是3,000,000×256:它占用了大約3GB的內存或顯存。這會帶來過於復雜的模型和過高的存儲開銷。
卷積層嘗試解決這兩個問題。一方面,卷積層保留輸入形狀,使圖像的像素在高和寬兩個方向上的相關性均可能被有效識別;另一方面,卷積層通過滑動窗口將同一卷積核與不同位置的輸入重復計算,從而避免參數尺寸過大。
卷積神經網絡就是含卷積層的網絡。本節里我們將介紹一個早期用來識別手寫數字圖像的卷積神經網絡:LeNet。這個名字來源於LeNet論文的第一作者Yann LeCun。LeNet展示了通過梯度下降訓練卷積神經網絡可以達到手寫數字識別在當時最先進的結果。這個奠基性的工作第一次將卷積神經網絡推上舞台,為世人所知。
LeNet模型
eNet分為卷積層塊和全連接層塊兩個部分。下面我們分別介紹這兩個模塊。
卷積層塊里的基本單位是卷積層后接最大池化層:卷積層用來識別圖像里的空間模式,如線條和物體局部,之后的最大池化層則用來降低卷積層對位置的敏感性。卷積層塊由兩個這樣的基本單位重復堆疊構成。在卷積層塊中,每個卷積層都使用5×5的窗口,並在輸出上使用sigmoid激活函數。第一個卷積層輸出通道數為6,第二個卷積層輸出通道數則增加到16。這是因為第二個卷積層比第一個卷積層的輸入的高和寬要小,所以增加輸出通道使兩個卷積層的參數尺寸類似。卷積層塊的兩個最大池化層的窗口形狀均為2×2,且步幅為2。由於池化窗口與步幅形狀相同,池化窗口在輸入上每次滑動所覆蓋的區域互不重疊。
卷積層塊的輸出形狀為(批量大小, 通道, 高, 寬)。當卷積層塊的輸出傳入全連接層塊時,全連接層塊會將小批量中每個樣本變平(flatten)。也就是說,全連接層的輸入形狀將變成二維,其中第一維是小批量中的樣本,第二維是每個樣本變平后的向量表示,且向量長度為通道、高和寬的乘積。全連接層塊含3個全連接層。它們的輸出個數分別是120、84和10,其中10為輸出的類別個數。
- 卷積神經網絡就是含卷積層的網絡。
- LeNet交替使用卷積層和最大池化層后接全連接層來進行圖像分類。
LeNet模型代碼實現

1 #Lenet模型實現 2 import d2lzh as d2l 3 import mxnet as mx 4 from mxnet import autograd, gluon, init, nd 5 from mxnet.gluon import loss as gloss, nn 6 import time 7 8 net = nn.Sequential() 9 net.add(nn.Conv2D(channels=6, kernel_size=5, activation='sigmoid'), 10 nn.MaxPool2D(pool_size=2, strides=2), 11 nn.Conv2D(channels=16, kernel_size=5, activation='sigmoid'), 12 nn.MaxPool2D(pool_size=2, strides=2), 13 # Dense會默認將(批量大小, 通道, 高, 寬)形狀的輸入轉換成 14 # (批量大小, 通道 * 高 * 寬)形狀的輸入 15 nn.Dense(120, activation='sigmoid'), 16 nn.Dense(84, activation='sigmoid'), 17 nn.Dense(10)) 18 19 20 # In[11]: 21 22 23 #構造一個高和寬均為28的單通道數據樣本,並逐層進行前向計算來查看每個層的輸出形狀。 24 X = nd.random.uniform(shape=(1, 1, 28, 28)) 25 net.initialize() 26 for layer in net: 27 X = layer(X) 28 print(layer.name, 'output shape:\t', X.shape) 29 30 31 # In[12]: 32 33 34 batch_size = 256 35 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size) 36 37 38 # In[13]: