機器學習(ML)十之CNN
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]:View Code