一、簡介
本文實作基礎Convolutional Neural Network (CNN),資料集使用 CIFAR images。
卷積神經網絡是一種多層神經網絡,擅長處理圖像特别是大圖像的相關機器學習問題,它通過一系列方法,将資料量龐大的圖像識别問題不斷降維,最終使其能夠被訓練。CNN最早由Yann LeCun提出并應用在手寫字型識别上(MINST)。LeCun提出的網絡稱為LeNet,其網絡結構如下:
典型的卷積網絡,由卷積層、池化層、全連接配接層組成。其中卷積層與池化層配合,組成多個卷積組,逐層提取特征,最終通過若幹個全連接配接層完成分類。
1. 卷積
如圖所示,我們有一個5x5的圖像,我們用一個3x3的卷積核:
1 0 1
0 1 0
1 0 1
來對圖像進行卷積操作(可以了解為有一個滑動視窗,把卷積核與對應的圖像像素做乘積然後求和),得到了3x3的卷積結果。
這個過程我們可以了解為我們使用一個過濾器(卷積核)來過濾圖像的各個小區域,進而得到這些小區域的特征值。
在實際訓練過程中,卷積核的值是在學習過程中學到的。
2. 池化
池化簡單的說就是下采樣。池化的過程如下圖所示:
上圖中,我們可以看到,原始圖檔是20x20的,我們對其進行下采樣,采樣視窗為10x10,最終将其下采樣成為一個2x2大小的特征圖。
之是以這麼做的原因,是因為即使做完了卷積,圖像仍然很大(因為卷積核比較小),是以為了降低資料次元,就進行下采樣。
之是以能這麼做,是因為即使減少了許多資料,特征的統計屬性仍能夠描述圖像,而且由于降低了資料次元,有效地避免了過拟合。
在實際應用中,池化根據下采樣的方法,分為最大值下采樣(Max-Pooling)與平均值下采樣(Mean-Pooling)。
3. 全連接配接層 (fully connected layers,FC)
在整個卷積神經網絡中起到“分類器”的作用。如果說卷積層、池化層和激活函數層等操作是将原始資料映射到隐層特征空間的話,全連接配接層則起到将學到的“分布式特征表示”映射到樣本标記空間的作用。在實際使用中,全連接配接層可由卷積操作實作:對前層是全連接配接的全連接配接層可以轉化為卷積核為1x1的卷積;而前層是卷積層的全連接配接層可以轉化為卷積核為hxw的全局卷積,h和w分别為前層卷積結果的高和寬。
4. CIFAR10介紹
該資料集共有60000張彩色圖像,這些圖像是32*32,分為10個類,每類6000張圖。這裡面有50000張用于訓練,構成了5個訓練批,每一批10000張圖;另外10000用于測試,單獨構成一批。測試批的資料裡,取自10類中的每一類,每一類随機取1000張。抽剩下的就随機排列組成了訓練批。注意一個訓練批中的各類圖像并不一定數量相同,總的來看訓練批,每一類都有5000張圖。
二、TensorFlow2.0實作的基礎cnn
1. 導入tensorflow
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
2. 下載下傳CIFAR10資料集
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
# Normalize pixel values to be between 0 and 1
train_images, test_images = train_images / 255.0, test_images / 255.0
3. 檢視資料
這裡顯示25張圖檔和其分類,看看分類是否正确:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer','dog', 'frog', 'horse', 'ship', 'truck']
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(train_images[i], cmap=plt.cm.binary)
# The CIFAR labels happen to be arrays,
# which is why you need the extra index
plt.xlabel(class_names[train_labels[i][0]])
plt.show()
4. 建立模型和池化層
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
# 顯示模型資訊
model.summary()
上面可以看到每個池化層輸出是3維張量(高, 寬,通道數). 每個池化層輸出的通道數取悶在于第一個變量值(如32,64). 如果算力足夠,可以為每個池化層添加更多的通道數.
5. 全連接配接層
model.add(layers.Flatten()) # 3維 轉為 1維
model.add(layers.Dense(64, activation='relu')) # 激活函數relu
model.add(layers.Dense(10, activation='softmax')) # 激活函數softmax CIFAR有10個類别輸出,是以softmax這裡參數設定為10
# 再看看模型情況
model.summary()
6. 編譯和訓練模型
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(train_images, train_labels, epochs=10,
validation_data=(test_images, test_labels))
7. 評估模型
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0.5, 1])
plt.legend(loc='lower right')
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(test_acc)