在前面的章節(jié)2中,我們使用全連接網(wǎng)絡對手寫MNIST字符集進行分類。我們給圖中的每個對象分配一個神經(jīng)元,總共有784(28*28)個輸入神經(jīng)元。然而,這樣的策略丟失了圖像的空間結構關系。
以下代碼把每個手寫數(shù)字的圖像轉化成扁平向量,導致空間局部性消失:
# X_train是60000行28*28的數(shù)據(jù),變現(xiàn)為60000*784
X_train = X_train.reshape(60000, 784)
X_test = X_test.reshape(10000, 784)
而卷積神經(jīng)網(wǎng)絡(Convolutional Neural Networks, CNN)是一類包含卷積計算且具有深度結構的前饋神經(jīng)網(wǎng)絡(Feedforward Neural Networks),因為其保留了空間信息,也因此可以更好地適用于圖像分類問題。
深度卷積神經(jīng)網(wǎng)絡——DCNN
深度卷積神經(jīng)網(wǎng)絡(Deep Convolutional Neural Networks, CNN)由很多的神經(jīng)網(wǎng)絡層組成。包含交替出現(xiàn)的卷積層和池化層。每個濾波器的深度在網(wǎng)絡中增加。最后一部分通常由一個或多個全連接層組成。

有三個卷積網(wǎng)絡之外的關鍵概念:
- 局部感受野與卷積
- 共享權重和偏差
- 池化
局部感受野與卷積層
在全連接的網(wǎng)絡中,輸入被描繪成縱向排列的神經(jīng)元,但是在卷積網(wǎng)絡中我們把它看成28x28的方形:

輸入神經(jīng)元的一小片區(qū)域會被連接到下一層隱層,這個區(qū)域被稱為局部感受野,然后在輸入圖像中移動局部感受野,每移動一次,對應一個隱層的神經(jīng)元,如此重復構成隱層所有神經(jīng)元。如果局部感受野是5x5的,一次移動一格,輸入圖像是28x28的,那么隱層有24x24個神經(jīng)元。
共享權重和偏置
每個隱層的神經(jīng)元都有一個偏置和連接到它的局部感受野的5x5的權重,并且對這一層的所有神經(jīng)元使用相同的權重和偏置。也就是說,對于隱藏層的第j行第k列的神經(jīng)元,它的輸出為:

其中σ是激活函數(shù),b是共享偏置,Wl,m是共享權重的5x5數(shù)組,用ax,y表示輸入層的第x行第y列的神經(jīng)元的輸出值,即隱層的第j行第k列的神經(jīng)元的若干個輸入。
共享,意味著這一個隱層的所有神經(jīng)元檢測完全相同的特征,在輸入圖像的不同位置。這說明卷積網(wǎng)絡可以很好地適應圖片的平移不變性。共享權重和偏置被稱為卷積核或者濾波器。我們再看一下卷積的過程:

共享權重和偏置的一個很大的優(yōu)點是,大大減少了網(wǎng)絡的參數(shù)數(shù)量。一次卷積我們需要5x5=25個共享權重,加上一個共享偏置共26個參數(shù)。如果我們卷積了20次,那么共有20x26=520個參數(shù)。以全連接對比,輸入神經(jīng)元有28x28=784個,隱層神經(jīng)元設為30個,共有784x30個權重,加上30個偏置,共有23550個參數(shù)。卷積層的平移不變性會減少參數(shù)數(shù)量并加快訓練,有助于建立深度網(wǎng)絡。
池化層
池化層一般在卷積層之后使用,主要是簡化從卷積層輸出的信息。池化層的每個單元概括了前一層的一個小區(qū)域,常見的方法有最大池化,它取前一層那個小區(qū)域里的最大值作為對應池化層的值。
1、最大池化
最簡單的池化方式,簡單地輸出最大激活值作為這個區(qū)域的觀測結果。在Keras中,如果我們要定義一個2x2的最大池化層,我們可以寫成:
model.add(MaxPooling2D(pool_size = (2, 2)))
2、平均池化
另一個選擇是平均池化,就是簡單地把這個區(qū)域觀察到的激活值取平均值。
除了上面兩種,還有更多的池化方式,完整列表請參見Keras官網(wǎng)。簡單地說,所有的池化操作都是對一個給定區(qū)域的匯總操作。
用Keras構建LeNet代碼
from keras import backend as K
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras.datasets import mnist
from keras.utils import np_utils
from keras.optimizers import SGD, RMSprop, Adam
import numpy as np
import matplotlib.pyplot as plt
# define ConvNet
class LeNet:
@staticmethod
def build(input_shape, classes):
model = Sequential()
# CONV => RELU => POOL
model.add(Conv2D(20, kernel_size=5, padding="same", input_shape=input_shape))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
# CONV => RELU => POOL
model.add(Conv2D(50, kernel_size=5, padding="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
# Flatten layer to RELU layer
model.add(Flatten())
model.add(Dense(500))
model.add(Activation("relu"))
# softmax classifier
model.add(Dense(classes))
model.add(Activation("softmax"))
return model
# 網(wǎng)絡和訓練
NB_EPOCH = 20
BATCH_SIZE = 128
VERBOSE = 1
OPTIMIZER = Adam()
VALIDATION_SPLIT = 0.2
IMG_ROWS, IMG_COLS = 28, 28
NB_CLASSES = 10
INPUT_SHAPE = (1, IMG_ROWS, IMG_COLS)
# 混合并劃分訓練集和測試集數(shù)據(jù)
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 設置圖像的維度順序(‘tf’或‘th’)# 當前的維度順序如果為'th'
# 則輸入圖片數(shù)據(jù)時的順序為:channels,rows,cols,否則:rows,cols,channels
K.set_image_dim_ordering("th")
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255
X_test /= 255
# 使用形狀60k*[1*28*28]作為卷積網(wǎng)絡的輸入
X_train = X_train[:, np.newaxis, :, :]
X_test = X_test[:, np.newaxis, :, :]
print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')
# 將類向量轉換成二值類別矩陣
y_train = np_utils.to_categorical(y_train, NB_CLASSES)
y_test = np_utils.to_categorical(y_test, NB_CLASSES)
# 初始化優(yōu)化器和模型
model = LeNet.build(input_shape=INPUT_SHAPE, classes=NB_CLASSES)
model.compile(loss="categorical_crossentropy", optimizer=OPTIMIZER, metrics=["accuracy"])
history = model.fit(X_train, y_train, batch_size=BATCH_SIZE, epochs=NB_EPOCH, verbose=VERBOSE, validation_split=VALIDATION_SPLIT)
score = model.evaluate(X_test, y_test, verbose=VERBOSE)
print("\nTest score:", score[0])
print('Test accuracy:', score[1])
# list all data in history
print(history.history.keys())
# summarize history for accuracy
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
# summarize history for loss
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()