照片由 mendhak 拍攝,版權所有。
卷積和卷積層是卷積神經網路中使用的主要構建模組。
卷積是將輸入簡單通過濾波器進行啟用。重複對輸入使用同一個濾波器得到的啟用後的圖稱為特徵圖/特徵對映(feature map),表示輸入(比如一張影像)中檢測到的特徵的位置和強度。
卷積神經網路在特定的預測建模問題(如影像分類)的約束下,能創新的針對訓練資料集並行自動學習大量濾波器。結果是可以在輸入影像的任何位置檢測到高度特定的特徵。
在本教程中,你將瞭解卷積在卷積神經網路中是如何工作的。
完成本教程後,你將知道:
- 卷積神經網路使用濾波器從輸入中得到特徵對映,該特徵對映彙總了輸入中檢測到的特徵的存在。
- 濾波器可以手工設計,例如線條檢測器,但卷積神經網路的創新是,訓練期間在特定預測問題的背景下學習濾波器。
- 在卷積神經網路中如何計算一維和二維卷積層的特徵對映。
讓我們開始吧。
教程概述
本教程分為四個部分,分別為:
- 卷積神經網路中的卷積
- 計算機視覺中的卷積
- 學習到的濾波器的能力
- 卷積層的樣例
卷積神經網路中的卷積
卷積神經網路,簡稱 CNN,是一種專門用於處理二維影像資料的神經網路模型,儘管其也可用於一維和三維資料。
卷積神經網路的核心是卷積層,這也是卷積神經網路命名的由來。該層執行的操作稱為“卷積”。
在卷積神經網路的語境中,卷積是涵蓋了一組權重與輸入相乘的線性操作,很像傳統的神經網路。由於該技術是為二維輸入設計的,這個乘法會在輸入資料陣列和二維權重陣列之間進行,稱為濾波器或核。
濾波器小於輸入資料,在濾波器大小的輸入區塊和濾波器之間應用的乘法被稱作點積。點積是濾波器大小的輸入區塊和濾波器之間的對應元素相乘,然後求和產生的單一值。因為它產生單一值,所以該操作通常被稱為“標積”。
我們故意使用小於輸入的濾波器,因為它允許相同的濾波器(權重集)在輸入的不同點處多次乘以輸入陣列。具體而言,濾波器從左到右、從上到下,系統地應用於輸入資料的每個重疊部分或濾波器大小的區塊。
在影像上系統地應用相同的濾波器是一個強大的想法。如果濾波器設計為檢測輸入中的特定型別的特徵,那麼在整個輸入影像上系統地應用該濾波器能有機會在影像中的任何位置發現該特徵。這種能力通常被稱為平移不變性,比如說,關注該特徵是否存在,而不是在哪裡存在。
如果我們更關心某個特徵是否存在而不是存在的確切位置,那麼本地平移不變性會是非常有用的屬性。例如,當判定影像是否包含面部時,我們不需要知道眼睛的畫素級準確位置,我們只需要知道面部左側和右側分別有一隻眼睛。
—— 第 342 頁,深度學習,2016。
將濾波器與輸入陣列相乘一次,可得到單一值。由於濾波器多次應用於輸入陣列,因此結果是一個表示輸入濾波後的二維陣列輸出值,這個結果被稱為“特徵對映”。
特徵對映建立後,我們可以通過非線性函式(例如 ReLU )傳遞特徵對映中的每個值,就像我們對全連線層的輸出所做的那樣。
對二維輸入建立特徵對映的濾波器示例
如果你來自數字訊號處理領域或相關的數學領域,你可能會將矩陣的卷積運算理解為不同的東西。尤其應用輸入前先翻轉濾波器(核)。理論上,在卷積神經網路中說的卷積實際上是“互相關”。然而在深度學習中,它被稱為“卷積”操作。
很多機器學習庫實現的互相關被稱為卷積。
—— 第 333 頁,深度學習,2016。
總之,我們有一個輸入,例如一個畫素值影像,我們還有一個濾波器,它也是一組權重,濾波器系統地應用於輸入資料從而建立出特徵對映。
計算機視覺中的卷積
對卷積神經網路來說,用卷積處理影像資料的想法並不新穎或獨特,它是計算機視覺中的常用技術。
歷史上,濾波器是由計算機視覺專家手工設計的,然後將其應用於影像以產生特徵對映或濾波後的輸出,這在某種程度上使影像分析更容易。
例如,下面是一個手工 3×3 元素的濾波器,用於檢測垂直線:
0.0, 1.0, 0.0
0.0, 1.0, 0.0
0.0, 1.0, 0.0
複製程式碼
將此濾波器應用於影像將生成僅包含垂直線的特徵對映。這是一個垂直線檢測器。
你能從濾波器的權重值中看到這一點:中心垂直線上的任何畫素值都將被正啟用,其它側的任何畫素值將被負啟用。在影像的畫素值上系統地拖拽此濾波器只能突出顯示垂直線畫素。
我們還可以建立水平線檢測器並將其應用於影像,例如:
0.0, 0.0, 0.0
1.0, 1.0, 1.0
0.0, 0.0, 0.0
複製程式碼
綜合兩個濾波器的結果,比如綜合兩個特徵對映,將會突出顯示影像中的所有的線。
可以設計一套數十甚至數百個其它的小型濾波器來檢測影像中的其它特徵。
在神經網路中使用卷積運算的創新之處在於,濾波器的值是網路訓練中需要學習到的權重。
網路將學習到從輸入中提取的特徵型別。具體而言,在隨機梯度下降的訓練中,網路定會學習從影像中提取特徵,該特徵最小化了網路被訓練要解決的特定任務的損失,例如,提取將影像分類為狗或貓最有用的特徵。
在這種情況下,你會看到這是一個很強大的想法。
學習到的濾波器的能力
學習針對機器學習任務的單個濾波器是一種強大的技術。
然而,卷積神經網路在實踐中實現了更多。
多個濾波器
卷積神經網路不只學習單個濾波器。事實上,它們對給定輸入並行學習多個特徵。
例如,對於給定輸入,卷積層通常並行地學習 32 到 512 個濾波器。
這樣提供給模型 32 甚至 512 種不同的從輸入中提取特徵的方式,或者說“學習看”的許多不同方式,以及訓練後“看”輸入資料的許多不同方式。
這種多樣性允許定製化,比如不僅是線條,還有特定訓練資料中的特定線條。
多個通道
彩色影像具有多個通道,通常每個顏色通道有一個,例如紅色,綠色和藍色。
從資料的角度來看,這意味著作為輸入的單個影像在模型上實際是三個影像。
濾波器必須始終具有與輸入相同的通道數量,通常稱為“深度”。如果輸入影像有 3 個通道(深度為 3),則應用於該影像的濾波器也必須有 3 個通道(深度為3).這種情況下,一個 3×3 濾波器實際上行、列和深度為 3x3x3 或 [3, 3, 3]。無論輸入和濾波器的深度如何,都使用點積運算將濾波器應用於輸入來產生單一值。
這意味著如果卷積層具有 32 個濾波器,則這 32 個濾波器對二維影像輸入不僅是二維的,還是三維的,對於三個通道中的每一個都具有特定的濾波器權重。然而,每個濾波器都會生成一個特徵對映,這意味著對於建立的32個特徵對映,應用 32 個濾波器的卷積層的輸出深度為 32。
多個層
卷積層不僅應用於輸入資料如原始畫素值,也可以應用於其他層的輸出。
卷積層的堆疊允許輸入的層次分解。
考慮直接對原始畫素值進行操作的濾波器將學習提取低階特徵,例如線條。
在第一線層的輸出上操作的濾波器可能提取綜合低階特徵的特徵,比如可表示形狀的多條線的特徵。
這個過程會一直持續到非常深的層,提取面部、動物、房屋等。
這些正是我們在實踐中看到的。隨著網路深度的增加,特徵的抽取會越來越高階。
卷積層的樣例
深度學習庫 Keras 提供了一系列卷積層。
通過看一些人為資料和手工濾波器的樣例,我們可以更好地理解卷積運算。
在本節中,我們將同時研究一維卷積層和二維卷積層的例子,兩者都具體化了卷積操作,也提供了使用 Keras 層的示範。
一維卷積層的樣例
我們可以定義一個具有八個元素的一維輸入,正中間兩個凸起元素值為 1.0,其餘元素值為 0.0。
[0, 0, 0, 1, 1, 0, 0, 0]
複製程式碼
對於一維卷積層,Keras 的輸入必須是三維的。
第一維指每個輸入樣本,在本例中我們只有一個樣本。第二個維度指每個樣本的長度,在本例中長度是 8。第三維指每個樣本中的通道數,在本例中我們只有一個通道。
因此,輸入陣列的 shape 為 [1, 8, 1]。
# define input data
data = asarray([0, 0, 0, 1, 1, 0, 0, 0])
data = data.reshape(1, 8, 1)
複製程式碼
我們將定義一個模型,其輸入樣本的 shape 為 [8, 1]。
該模型將具有一個濾波器,shape 為 3,或者說三個元素寬。Keras 將濾波器的 shape 稱為 kernel_size。
# create model
model = Sequential()
model.add(Conv1D(1, 3, input_shape=(8, 1)))
複製程式碼
預設情況下,卷積層中的濾波器使用隨機權重進行初始化。在這個人為例子中,我們將手動設定單個濾波器的權重。我們將定義一個能夠檢測凸起的濾波器,這是一個由低輸入值包圍的高輸入值,正如我們在輸入示例中定義的那樣。
我們將三元素濾波器定義如下:
[0, 1, 0]
複製程式碼
卷積層還具有偏差輸入值,該值也需要我們設定一個為 0 的權重。
因此,我們可以強制我們的一維卷積層的權重使用如下所示的手工濾波器:
# define a vertical line detector
weights = [asarray([[[0]],[[1]],[[0]]]), asarray([0.0])]
# store the weights in the model
model.set_weights(weights)
複製程式碼
權重必須以行、列、通道的三維結構被設定,濾波器有一行、三列、和一個通道。
我們可以檢索權重並確認它們被正確設定。
# confirm they were stored
print(model.get_weights())
複製程式碼
最後,我們可將單個濾波器應用於輸入資料。
我們可以通過在模型上呼叫 predict() 函式來實現這一點。這將直接返回特徵對映:這是在輸入序列中系統地應用濾波器的輸出。
# apply filter to input data
yhat = model.predict(data)
print(yhat)
複製程式碼
將所有這些結合在一起,完整的樣例如下所列。
# example of calculation 1d convolutions
from numpy import asarray
from keras.models import Sequential
from keras.layers import Conv1D
# define input data
data = asarray([0, 0, 0, 1, 1, 0, 0, 0])
data = data.reshape(1, 8, 1)
# create model
model = Sequential()
model.add(Conv1D(1, 3, input_shape=(8, 1)))
# define a vertical line detector
weights = [asarray([[[0]],[[1]],[[0]]]), asarray([0.0])]
# store the weights in the model
model.set_weights(weights)
# confirm they were stored
print(model.get_weights())
# apply filter to input data
yhat = model.predict(data)
print(yhat)
複製程式碼
執行該樣例,首先列印網路的權重,這證實了我們的手工濾波器在模型中是按照我們的預期設定的。
接下來,濾波器應用到輸入模式,計算並顯示出特徵對映。我們可以從特徵對映的值中看到凸起被正確檢測到。
[array([[[0.]],
[[1.]],
[[0.]]], dtype=float32), array([0.], dtype=float32)]
[[[0.]
[0.]
[1.]
[1.]
[0.]
[0.]]]
複製程式碼
讓我們仔細看看發生了什麼。
回想一下,輸入是一個八元素向量,其值為:[0, 0, 0, 1, 1, 0, 0, 0]。
首先,通過計算點積(“.”運算子)將三元素濾波器 [0, 1, 0] 應用於輸入的前三個輸入 [0, 0, 0],得到特徵對映中的單個輸出值 0。
回想一下,點積是對應元素相乘的總和,在這它是 (0 x 0) + (1 x 0) + (0 x 0) = 0。在 NumPy 中,這可以手動實現為:
from numpy import asarray
print(asarray([0, 1, 0]).dot(asarray([0, 0, 0])))
複製程式碼
在我們的手動示例中,具體如下:
[0, 1, 0] . [0, 0, 0] = 0
複製程式碼
然後濾波器沿著輸入序列的一個元素移動,並重復該過程。具體而言,在索引 1,2 和 3 處對輸入序列應用相同的濾波器,得到特徵對映中的輸出為 0。
[0, 1, 0] . [0, 0, 1] = 0
複製程式碼
我們是系統的,所以再一次,濾波器沿著輸入的另一個元素移動,並應用於索引 2、3 和 4 處的輸入。這次在特徵對映中輸出值是 1。我們檢測到該特徵並相應的啟用。
[0, 1, 0] . [0, 1, 1] = 1
複製程式碼
重複該過程,直到我們計算出整個特徵對映。
[0, 0, 1, 1, 0, 0]
複製程式碼
請注意,特徵對映有六個元素,而我們的輸入有八個元素。這是濾波器應用於輸入序列的手工結果。還有其它方法可以將濾波器應用於輸入序列可得到不同 shape 的特徵對映,例如填充,但我們不會在本文中討論這些方法。
你可以想象,通過不同的輸入,我們可以檢測到具有不同強度的特徵,且在濾波器中具有不同的權重,那麼我們將檢測到輸入序列中的不同特徵。
二維卷積層的樣例
我們可以將上一節的凸起檢測樣例擴充套件為二維影像的垂直線檢測器。
同樣的,我們可以約束輸入,在這裡為一個具有單個通道(如灰度)的正方形 8×8 畫素的輸入影像,其中間有一個垂直線。
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
複製程式碼
Conv2D(二維卷積層)的輸入必須是四維的。
第一個維度定義樣本,在本例中只有一個樣本。第二個維度定義行數,在本例中是 8。第三維定義列數,在本例中還是 8。最後定義通道數,本例中是 1。
因此,輸入必須具有四維 shape [樣本,列,行,通道],在本例中是 [1, 8, 8, 1]。
# define input data
data = [[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0]]
data = asarray(data)
data = data.reshape(1, 8, 8, 1)
複製程式碼
我們將用單個濾波器定義 Conv2D,就像我們在上一節對 Conv1D 樣例所做的那樣。
濾波器將是二維的,一個 shape 3×3 的正方形。該層將期望輸入樣本具有 shape [列,行,通道],在本例中為 [8, 8, 1]。
# create model
model = Sequential()
model.add(Conv2D(1, (3,3), input_shape=(8, 8, 1)))
複製程式碼
我們將定義一個垂直線檢測器的濾波器來檢測輸入資料中的單個垂直線。
濾波器如下所示:
0, 1, 0
0, 1, 0
0, 1, 0
複製程式碼
我們可以實現如下:
# define a vertical line detector
detector = [[[[0]],[[1]],[[0]]],
[[[0]],[[1]],[[0]]],
[[[0]],[[1]],[[0]]]]
weights = [asarray(detector), asarray([0.0])]
# store the weights in the model
model.set_weights(weights)
# confirm they were stored
print(model.get_weights())
複製程式碼
最後,我們將濾波器應用於輸入影像,將得到一個特徵對映,表明對輸入影像中垂直線的檢測,如我們希望的那樣。
# apply filter to input data
yhat = model.predict(data)
複製程式碼
特徵對映的輸出 shape 將是四維的,[批,行,列,濾波器]。我們將執行單個批處理,並且我們有一個濾波器(一個濾波器和一個輸入通道),因此輸出 shape 為 [1, ?, ?, 1]。我們可以完美列印出單個特徵對映的內容,如下所示:
for r in range(yhat.shape[1]):
# print each column in the row
print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
複製程式碼
將所有這些結合在一起,完整的樣例如下所列。
# example of calculation 2d convolutions
from numpy import asarray
from keras.models import Sequential
from keras.layers import Conv2D
# define input data
data = [[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0]]
data = asarray(data)
data = data.reshape(1, 8, 8, 1)
# create model
model = Sequential()
model.add(Conv2D(1, (3,3), input_shape=(8, 8, 1)))
# define a vertical line detector
detector = [[[[0]],[[1]],[[0]]],
[[[0]],[[1]],[[0]]],
[[[0]],[[1]],[[0]]]]
weights = [asarray(detector), asarray([0.0])]
# store the weights in the model
model.set_weights(weights)
# confirm they were stored
print(model.get_weights())
# apply filter to input data
yhat = model.predict(data)
for r in range(yhat.shape[1]):
# print each column in the row
print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
複製程式碼
執行該樣例,首先確認手工濾波器已在層權重中被正確定義。
接下來,列印計算出的特徵對映。從數字的規模我們可以看到,濾波器確實在特徵對映的中間檢測到具有單個強啟用的垂直線。
[array([[[[0.]],
[[1.]],
[[0.]]],
[[[0.]],
[[1.]],
[[0.]]],
[[[0.]],
[[1.]],
[[0.]]]], dtype=float32), array([0.], dtype=float32)]
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
複製程式碼
讓我們仔細看看計算了什麼。
首先,將濾波器應用於影像的左上角,或者說 3×3 元素的影像區塊。理論上,影像區塊是三維的,具有單個通道,濾波器具有相同的尺寸。在 NumPy 中我們不能使用 dot() 函式實現它,我們必須使用 tensordot() 函式代替,以便我們可以適當地對所有維度求和,例如:
from numpy import asarray
from numpy import tensordot
m1 = asarray([[0, 1, 0],
[0, 1, 0],
[0, 1, 0]])
m2 = asarray([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
print(tensordot(m1, m2))
複製程式碼
該計算得到單個輸出值 0.0,也就是未檢測到特徵。這給了我們特徵對映左上角的第一個元素。
手動如下所示:
0, 1, 0 0, 0, 0
0, 1, 0 . 0, 0, 0 = 0
0, 1, 0 0, 0, 0
複製程式碼
濾波器沿著一列向左移動,並重復該過程。同樣的,未檢測到該特徵。
0, 1, 0 0, 0, 1
0, 1, 0 . 0, 0, 1 = 0
0, 1, 0 0, 0, 1
複製程式碼
再向左移動到下一列,第一次檢測到該特徵並強啟用。
0, 1, 0 0, 1, 1
0, 1, 0 . 0, 1, 1 = 3
0, 1, 0 0, 1, 1
複製程式碼
重複此過程,直到濾波器的邊緣位於輸入影像的邊緣或最後一列上。這給出了特徵對映的第一個完整行中的最後一個元素。
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
複製程式碼
然後濾波器向下移動一行並返回到第一列,從左到右重複如上過程,給出特徵對映的第二行。直到濾波器的底部位於輸入影像的底部或最後一行。
與上一節一樣,我們可以看到特徵對映是一個 6×6 矩陣,比 8×8 的輸入影像小,因為濾波器應用於輸入影像的限制。
延伸閱讀
如果你希望更深入,本節將提供此主題的更多資源。
文章
書
- 第 9 章:卷積網路,深度學習(Deep Learning),2016。
- 第 5 章:計算機視覺的深度學習,使用 Python 深度學習(Deep Learning with Python),2017。
API
總結
在本教程中,你瞭解到卷積在卷積神經網路中是如何工作的。
具體來說,你學到了:
- 卷積神經網路使用濾波器從輸入中得到特徵對映,該特徵對映彙總了輸入中檢測到的特徵的存在。
- 濾波器可以手工設計,例如線條檢測器,但卷積神經網路的創新是,在訓練期間在特定預測問題的背景下學習濾波器。
- 在卷積神經網路中如何計算一維和二維卷積層的特徵對映。
你有什麼問題? 在下面的評論中提出您的問題,我會盡力回答。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。