卷積神經網絡這個詞,應該在你開始學習人工智能不久后就聽過了,那究竟什么叫卷積神經網絡,今天我們就聊一聊這個問題。
不用思考,左右兩張圖就是兩只可愛的小狗狗,但是兩張圖中小狗狗所處的位置是不同的,左側圖片小狗在圖片的左側,右側圖片小狗在圖片的右下方,這樣如果去用圖片特征識別出來的結果,兩張圖的特征很大部分是不同的,這不是我們希望的,那思考一下,為什么我們人就可以把它們都看成是可愛的小狗狗呢?這是因為平移不變性和空間層次結構,這兩個概念是卷積神經網絡中的概念。
平移不變性與模式的空間層次結構
這很好理解,我們要觀察或者識別的物體,在圖片上平行移動,我們都可以識別出來,因為無論他們在任何地方,都有相同的特征;我們識別物體的時候,先識別物體的局部特征信息,然后再腦袋中將局部信息組合起來,組合而成更高層次的特征信息,最終形成整體信息。比如上圖,我們認出他們是可愛的狗狗,但腦袋在實際運轉的過程中,是先看到了一些像素點(黑白紅等),然后將像素點連接起來形成輪廓或特征(耳朵、眼睛、舌頭),最后組合這些特征形成最后的結論(可愛的狗狗)。這就給我們了啟發,我們在計算機圖片識別的識別的時候,是不是可以借鑒這種機制呢,不一定需要圖片全部的信息,而是識別圖片的特征信息,再由這些特征,我們會將其組合成更大的特征,再組合,最終得出整體的特征信息,如下看一個經典的圖:
我們的人腦在識別人臉的時候,腦神經不同部分也處理的是不同的信息,像素點-線條邊緣-對象部分-對象整體。
上面這種識別方式就與之前我們采用的各種識別方式不同了,我們之前都是將每張圖作為一個整體,去識別其特征,這里更多的分析局部信息,這種方式叫卷積運算。
卷積運算
給了我們一張彩色圖片,我們用長、寬和深度(用於存儲每個像素點 RGB 三種顏色的值)三個維度的張量去表示,用一個小的過濾器分別去取特征值:
這里面的黃色的小框是一種過濾器,我們在做卷積運算的時候,往往需要選擇多種過濾器,這樣就可以得到不同的卷積特征,這里的過濾器是一個權重矩陣,與每個圖片小塊做張量積,得到的就是一維向量,過濾器在卷積神經網絡中的術語叫卷積核。我們還可以看到,原圖的尺寸是 5x5,處理后的尺寸是 3x3,縮小了一些,如果我們每次把黃色的小格子向右移動兩個格子,那我們就會得到 2x2 的輸出,這種移動幾個格子的影響輸出尺寸的術語叫做步幅。
我們這里卷積層可以處理學習圖片特征的問題,但是這些特征如何去進行學習得到這是一只可愛的小狗狗的結論,還是需要以前我們用到的全連接的方法,可是對於圖片來說,全連接的方法要用到的參數太多了,看下圖中密密麻麻的線,這每一層還不到十個節點神經元,換成圖片,動輒幾千個元素,幾百萬幾千萬個參數,這樣訓練出的網絡,只會得出過擬合的結果,需要調整,改變步幅可以,但是效果不好,因此這里我們引入一個新的概念——池化。
池化
池化可以理解為某種情況的采樣。比如最大池化的操作方法是:從輸入的特征值中,提取窗口,輸出每個通道的最大值,這樣說有點不好理解,可以理解為就是卷積層輸出的結果,再用一個上面黃色的 2x2 的小窗口,步幅是 2,每次取窗口中最大值,這樣就可以減少很大一部分數據量。當然,還有很多池化的方法,比如最大池化改為平均值等,當然其目的都是一樣的,減少數據量。
隨后,數據就可以交給全連接層,進行學習,輸出結果了。
這里我們說明幾個問題:我們的網絡一般不會只有一個卷積層和池化層,就像全連接層不會只有一個 Dense 層,往往是:卷積層-池化層-卷積層-池化層-卷積層-全連接層(多個 Dense);我們上面雖然拿圖片識別舉例子,但是卷積神經網絡的應用可不僅僅是圖片識別,還包括目標定位、人臉識別和目標分割等,應用非常廣泛;卷積神經網絡的簡稱是 CNN,平常與他人交流的時候,很多人喜歡用 CNN。
手寫數字圖片識別例子優化
我們之前多分類問題中就提到了手寫圖片數字識別的例子,當時的准確率不到 97.8%,這里我們將其優化為卷積神經網絡的寫法,精度會達到 99.3%,這個提升還是很可觀的呀,具體的代碼解釋上面都已經將理論講清楚了,這里我就不寫那么多代碼的解釋了,直接寫在注釋中,還有很多代碼是之前代碼,不再贅述了,看網絡架構:
看代碼:
#!/usr/bin/env python3
import time
from keras import layers
from keras import models
from keras.datasets import mnist
from keras.utils import to_categorical
def cat():
model = models.Sequential()
# filters 輸出空間的維數
# kernel_size 窗口大小,就是上面黃色窗口的大小
# activation 激活函數
# input_shape 輸入
model.add(layers.Conv2D(filters=32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))
# 最大池化
# pool_size 窗口大小
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
# 展平一個張量
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
# 查看神經網絡架構
# model.summary()
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(test_acc)
if __name__ == "__main__":
time_start = time.time()
cat()
time_end = time.time()
print('Time Used: ', time_end - time_start)