在這篇文章裡, 我們將通過解決一個現實世界的問題來練手深度學習。 我們要使用的圖像是德國交通標誌識別基準(GTSRB)資料集。問題是要識別圖像中不同種類的交通標誌。解決這個問題對於自駕車在道路上行駛至關重要。
這個數據集裡面的圖像具有不同大小,光照條件,遮擋情況下的43種不同交通標誌符號,圖像的成像情況與真實你實際在不同時間點下去 路邊開車走路時看到的交通標誌的情形非常相似。訓練集包括大約39,000個圖像,而測試集大約有12,000個圖像。圖像不能保證是固定 的尺寸,標誌不一定在每個圖像中都是居中。每個圖像包含實際交通標誌周圍10%左右的邊界。
在進行後面的相關動作之前, 你需要從GTSRB網站下載"Images and annotations”(圖像資料與標籤資訊)"以便進行模型的訓練和測試,並將其解壓縮到一個文件夾中。 同時再另外下載'Extended annotations including class ids'資料集來作為模型的測試集。 我們在這個範列中會將以上的資料組織鳥戮如下標示的目錄結構:
GTSRB
├── GT-final_test.csv
├── Final_Test
│ └── Images
└── Final_Training
└── Images
├── 00000
├── 00001
├── ...
├── 00041
└── 00042
import warnings
warnings.filterwarnings('ignore') # 把一些惱人的警示訊息暫時忽略
import numpy as np
from skimage import io, color, exposure, transform
from sklearn.model_selection import train_test_split
import os
import glob
import h5py
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential, model_from_json
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPooling2D
from keras.optimizers import SGD
import keras.utils as np_utils
from keras.callbacks import LearningRateScheduler, ModelCheckpoint
from matplotlib import pyplot as plt
%matplotlib inline
NUM_CLASSES = 43 # 共有43種要辨識的交通標誌
IMG_SIZE = 48 # 每張圖像最後都要整理成 48x48的大小
Using TensorFlow backend.
從上面的代表性圖像可以看出,圖像在亮度上(illumination)有很大的差異不同。他們也有不同的大小。所以,我們來編寫一個函數,在HSV顏色空間中進行直方圖均衡,並將圖像調整為成一樣的標準大小。
"直方圖均衡化"是圖像處理領域中利用圖像直方圖
對對比度
進行調整的方法。
這種方法通常用來增加許多圖像的全局對比度,尤其是當圖像的有用數據的對比度相當接近的時候。通過這種方法,亮度可以更好地在直方圖上分布。這樣就可以用於增強局部的對比度而不影響整體的對比度,直方圖均衡化通過有效地擴展常用的亮度來實現這種功能。
這種方法對於背景和前景都太亮或者太暗的圖像非常有用, 而且它是一個相當直觀的技術並且是可逆操作,如果已知均衡化函數,那麼就可以恢復原始的直方圖,並且計算量也不大。 但是這種方法的一個缺點是它它可能會增加背景雜訊的對比度並且降低有用訊號的對比度。
詳細說明請見: Wikipedia-直方圖均衡化
由於圖像具有不同的形狀和大小,因此我們必須對圖像進行歸一化(normalize)處理以便可以決定模型的輸入。 在這個範例我們選擇使用48x48的圖像大小。你也可以嘗試使用比較小的圖像比如32x32或24x24看看是不是也可得到類似的準確率。
# 用來對圖像進行處理的函式
import os
from pathlib import PurePath # 處理不同作業系統file path的解析問題 (*nix vs windows)
# 圖像標高度均衡、置中及大小調整
def preprocess_img(img):
# 進行"直方圖均衡化"處理
hsv = color.rgb2hsv(img) # 對彩色分量rgb分別做均衡化,會產生奇異的點,圖像不和諧。一般採用的是用hsv空間進行亮度的均衡
hsv[:,:,2] = exposure.equalize_hist(hsv[:,:,2])
img = color.hsv2rgb(hsv) # 再把圖像從hsv轉回rgb
# 進行圖像置中
min_side = min(img.shape[:-1])
centre = img.shape[0]//2, img.shape[1]//2
img = img[centre[0]-min_side//2:centre[0]+min_side//2,
centre[1]-min_side//2:centre[1]+min_side//2,
:]
# 改變大小
img = transform.resize(img, (IMG_SIZE, IMG_SIZE))
return img
# 取得圖像檔的分類標籤
def get_class(img_path):
# 圖像檔所在的檔案夾名稱就是該圖像檔的分類標籤
return int(PurePath(img_path).parts[-2])
讓我們預處理所有的訓練圖像並以numpy數組來存儲到檔案系統中。過程中我們還會從圖像檔的檔案路徑中獲取圖片的標籤。 我們會將標籤(label)資料進行one-hot編碼:
# 從檔案系統中把保留的資料載入(如果沒有就進行第一次的資料轉換處理)
try:
with h5py.File('X.h5') as hf:
X, Y = hf['imgs'][:], hf['labels'][:]
print("Loaded images from X.h5")
except(IOError, OSError, KeyError):
print("Error in reading X.h5. Processing all images...")
root_dir = 'GTSRB/Final_Training/Images/'
imgs = []
labels = []
all_img_paths = glob.glob(os.path.join(root_dir, '*/*.ppm')) # 我們有 Test與Traing兩個檔案夾的資料要處理
np.random.shuffle(all_img_paths) # 進行打散
for img_path in all_img_paths:
try:
img = preprocess_img(io.imread(img_path))
label = get_class(img_path)
imgs.append(img) # 保留圖像資料
labels.append(label) # 保留圖像標籤
if len(imgs)%1000 == 0:
print("Processed {}/{}".format(len(imgs), len(all_img_paths))) # 每1000筆秀一下進度
except(IOError, OSError):
print('missed', img_path)
pass
X = np.array(imgs, dtype='float32') # 將資料轉換成numpy的ndarray, 資料型別為float32
Y = np.eye(NUM_CLASSES, dtype='uint8')[labels] # 對labels的資料進行one-hot (使用numpy.eye的函式)
# 將處理過圖像資料與標籤保持在檔案系統, 下次可以加速載入與處理
with h5py.File('X.h5', 'w') as hf:
hf.create_dataset('imgs', data=X)
hf.create_dataset('labels', data=Y)
Loaded images from X.h5
現在我們來定義我們的模型架構。我們將使用具有6個卷積層的前饋網絡,然後是完全連接的隱藏層。 我們也將在兩者之間使用Dropout層來防止網絡"過擬合(overfitting)"。
# 產生一個Keras序貫模型
def cnn_model():
model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(IMG_SIZE, IMG_SIZE, 3)))
model.add(Conv2D(32, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(64, (3, 3), padding='same', activation='relu'))
model.add(Conv2D(64, (3, 3), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(128, (3, 3), padding='same', activation='relu'))
model.add(Conv2D(128, (3, 3), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(NUM_CLASSES, activation='softmax'))
return model;
model = cnn_model() # 初始化一個模型
model.summary() # 秀出模型架構
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 48, 48, 32) 896 _________________________________________________________________ conv2d_2 (Conv2D) (None, 46, 46, 32) 9248 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 23, 23, 32) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 23, 23, 32) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 23, 23, 64) 18496 _________________________________________________________________ conv2d_4 (Conv2D) (None, 23, 23, 64) 36928 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 11, 11, 64) 0 _________________________________________________________________ dropout_2 (Dropout) (None, 11, 11, 64) 0 _________________________________________________________________ conv2d_5 (Conv2D) (None, 11, 11, 128) 73856 _________________________________________________________________ conv2d_6 (Conv2D) (None, 11, 11, 128) 147584 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 5, 5, 128) 0 _________________________________________________________________ dropout_3 (Dropout) (None, 5, 5, 128) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 3200) 0 _________________________________________________________________ dense_1 (Dense) (None, 512) 1638912 _________________________________________________________________ dropout_4 (Dropout) (None, 512) 0 _________________________________________________________________ dense_2 (Dense) (None, 43) 22059 ================================================================= Total params: 1,947,979 Trainable params: 1,947,979 Non-trainable params: 0 _________________________________________________________________
在訓練模型之前,我們需要將模型配置為學習算法並進行編譯。我們需要指定:
loss
: 損失函數,我們要優化。我們不能使用MSE
,因為它是不連續的數值。因此,我們使用:categorical_crossentropy
optimizer
: 我們使用標準隨機梯度下降(Stochastic gradient descent)與涅斯捷羅夫動量(Nesterov momentum)metric
: 由於我們正在處理一個分類問題,我們用度量是accuracy
。# 讓我們先配置一個常用的組合來作為後續優化的基準點
lr = 0.01
sgd = SGD(lr=lr, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy'])
現在,我們的模型已經準備好了。在訓練期間,我們的模型將進行迭代批量訓練,每個次的訓練資料的大小為batch_size。對於每批次,模型將會計算出梯度(gradient),並自動更新網絡的權重。對所有訓練集的一次迭代被稱為一次的循環(epoch)。訓練通常會一直進行到損失收斂於一個常數。
我們將增加一些功能到我們的訓練設定:
Learning rate scheduler
: 隨著訓練循環的次數逐漸增加的過程中對權重調整的學習率進行衰減通常有助於讓模型學習更好Model checkpoint
: 我們將比對每個訓練循環的驗證準確度並只保存模型表現最好的模型。這對深度學度來說是很有用的設定,因為我們的網絡可能在一定數量的訓練循環後開始過擬合(overfitting),但是我們需要在整過訓練過程中表現最好的模型留下來。這些設定不是必須的,但它們的確可以提高模型的準確性。這些功能是通過Keras的callback
功能來實現的。callback
是一組函式,將在訓練過程的特定階段被應用,比如將訓練結束。 Keras提供內置的學習速率調度(learning rate scheduling )和模型檢查點功能(model checkpointing)。
def lr_schedule(epoch):
return lr*(0.1**int(epoch/10))
batch_size = 32
nb_epoch = 30
history = model.fit(X, Y,
batch_size=batch_size,
epochs=nb_epoch,
validation_split=0.2,
shuffle=True,
callbacks=[LearningRateScheduler(lr_schedule),
ModelCheckpoint('model.h5', save_best_only=True)
])
Train on 31367 samples, validate on 7842 samples Epoch 1/30 31367/31367 [==============================] - 11s 339us/step - loss: 1.7234 - acc: 0.5070 - val_loss: 0.2577 - val_acc: 0.9204 Epoch 2/30 31367/31367 [==============================] - 10s 326us/step - loss: 0.2787 - acc: 0.9119 - val_loss: 0.1255 - val_acc: 0.9661 Epoch 3/30 31367/31367 [==============================] - 11s 335us/step - loss: 0.1571 - acc: 0.9509 - val_loss: 0.0638 - val_acc: 0.9806 Epoch 4/30 31367/31367 [==============================] - 11s 340us/step - loss: 0.1060 - acc: 0.9671 - val_loss: 0.0602 - val_acc: 0.9800 Epoch 5/30 31367/31367 [==============================] - 11s 344us/step - loss: 0.0875 - acc: 0.9729 - val_loss: 0.0489 - val_acc: 0.9856 Epoch 6/30 31367/31367 [==============================] - 10s 325us/step - loss: 0.0686 - acc: 0.9786 - val_loss: 0.0429 - val_acc: 0.9869 Epoch 7/30 31367/31367 [==============================] - 10s 328us/step - loss: 0.0616 - acc: 0.9811 - val_loss: 0.0213 - val_acc: 0.9934 Epoch 8/30 31367/31367 [==============================] - 11s 335us/step - loss: 0.0487 - acc: 0.9843 - val_loss: 0.0319 - val_acc: 0.9913 Epoch 9/30 31367/31367 [==============================] - 11s 341us/step - loss: 0.0480 - acc: 0.9851 - val_loss: 0.0227 - val_acc: 0.9938 Epoch 10/30 31367/31367 [==============================] - 10s 327us/step - loss: 0.0418 - acc: 0.9871 - val_loss: 0.0222 - val_acc: 0.9930 Epoch 11/30 31367/31367 [==============================] - 11s 342us/step - loss: 0.0197 - acc: 0.9939 - val_loss: 0.0131 - val_acc: 0.9958 Epoch 12/30 31367/31367 [==============================] - 11s 346us/step - loss: 0.0133 - acc: 0.9957 - val_loss: 0.0114 - val_acc: 0.9963 Epoch 13/30 31367/31367 [==============================] - 11s 336us/step - loss: 0.0101 - acc: 0.9968 - val_loss: 0.0111 - val_acc: 0.9962 Epoch 14/30 31367/31367 [==============================] - 11s 349us/step - loss: 0.0100 - acc: 0.9969 - val_loss: 0.0115 - val_acc: 0.9966 Epoch 15/30 31367/31367 [==============================] - 11s 350us/step - loss: 0.0082 - acc: 0.9977 - val_loss: 0.0113 - val_acc: 0.9966 Epoch 16/30 31367/31367 [==============================] - 11s 339us/step - loss: 0.0077 - acc: 0.9974 - val_loss: 0.0109 - val_acc: 0.9964 Epoch 17/30 31367/31367 [==============================] - 10s 329us/step - loss: 0.0071 - acc: 0.9974 - val_loss: 0.0114 - val_acc: 0.9966 Epoch 18/30 31367/31367 [==============================] - 10s 333us/step - loss: 0.0062 - acc: 0.9977 - val_loss: 0.0116 - val_acc: 0.9964 Epoch 19/30 31367/31367 [==============================] - 10s 326us/step - loss: 0.0066 - acc: 0.9976 - val_loss: 0.0105 - val_acc: 0.9968 Epoch 20/30 31367/31367 [==============================] - 11s 346us/step - loss: 0.0056 - acc: 0.9981 - val_loss: 0.0105 - val_acc: 0.9964 Epoch 21/30 31367/31367 [==============================] - 10s 335us/step - loss: 0.0052 - acc: 0.9982 - val_loss: 0.0105 - val_acc: 0.9964 Epoch 22/30 31367/31367 [==============================] - 11s 349us/step - loss: 0.0058 - acc: 0.9979 - val_loss: 0.0105 - val_acc: 0.9963 Epoch 23/30 31367/31367 [==============================] - 10s 328us/step - loss: 0.0058 - acc: 0.9982 - val_loss: 0.0106 - val_acc: 0.9963 Epoch 24/30 31367/31367 [==============================] - 10s 318us/step - loss: 0.0054 - acc: 0.9983 - val_loss: 0.0105 - val_acc: 0.9966 Epoch 25/30 31367/31367 [==============================] - 10s 329us/step - loss: 0.0048 - acc: 0.9984 - val_loss: 0.0104 - val_acc: 0.9967 Epoch 26/30 31367/31367 [==============================] - 11s 358us/step - loss: 0.0055 - acc: 0.9981 - val_loss: 0.0105 - val_acc: 0.9967 Epoch 27/30 31367/31367 [==============================] - 10s 316us/step - loss: 0.0060 - acc: 0.9979 - val_loss: 0.0106 - val_acc: 0.9966 Epoch 28/30 31367/31367 [==============================] - 11s 337us/step - loss: 0.0050 - acc: 0.9984 - val_loss: 0.0105 - val_acc: 0.9967 Epoch 29/30 31367/31367 [==============================] - 10s 319us/step - loss: 0.0054 - acc: 0.9983 - val_loss: 0.0105 - val_acc: 0.9968 Epoch 30/30 31367/31367 [==============================] - 10s 330us/step - loss: 0.0043 - acc: 0.9986 - val_loss: 0.0104 - val_acc: 0.9967
# 透過趨勢圖來觀察訓練與驗證的走向 (特別去觀察是否有"過擬合(overfitting)"的現象)
import matplotlib.pyplot as plt
def plot_train_history(history, train_metrics, val_metrics):
plt.plot(history.history.get(train_metrics),'-o')
plt.plot(history.history.get(val_metrics),'-o')
plt.ylabel(train_metrics)
plt.xlabel('Epochs')
plt.legend(['train', 'validation'])
plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plot_train_history(history, 'loss','val_loss')
plt.subplot(1,2,2)
plot_train_history(history, 'acc','val_acc')
plt.show()
讓我們加載測試數據並評估我們的模型
# 載入測試資料
import pandas as pd
test = pd.read_csv('GTSRB/GT-final_test.csv', sep=';') # 裡面包含了六個欄位 Filename;Width;Height;Roi.X1;Roi.Y1;Roi.X2;Roi.Y2;ClassId
X_test = []
y_test = []
# 迭代處理每一筆要測試的圖像檔
i=0
for file_name, class_id in zip(list(test['Filename']),list(test['ClassId'])):
img_path = os.path.join('GTSRB/Final_Test/Images/',file_name)
X_test.append(preprocess_img(io.imread(img_path)))
y_test.append(class_id)
# 轉換成numpy ndarray
X_test = np.array(X_test)
y_test = np.array(y_test)
print("X_test.shape: ", X_test.shape)
print("y_test.shape: ", y_test.shape)
X_test.shape: (12630, 48, 48, 3) y_test.shape: (12630,)
# 預測與比對
y_pred = model.predict_classes(X_test)
acc = np.sum(y_pred==y_test)/np.size(y_pred)
print("Test accuracy = {}".format(acc))
12630/12630 [==============================] - 1s 116us/step Test accuracy = 0.9707046714172605
你可能會認為40,000圖像是很多的圖像。再想一想,我們的模型裡有1,358,155個參數。這是訓練圖像的數量的40多倍。 如果我們可以從現有的圖像生成新的訓練圖像,這將是一個很好的方式來增加訓練數據集的大小。
讓我們直接使用keras的內置功能來完成圖像增強 (Data Augmentation)。
from sklearn.model_selection import train_test_split
X_train, X_val, Y_train, Y_val = train_test_split(X, Y, test_size=0.2, random_state=42)
datagen = ImageDataGenerator(featurewise_center=False,
featurewise_std_normalization=False,
rotation_range=10.,
width_shift_range=0.1,
height_shift_range=0.1,
shear_range=0.1,
zoom_range=0.2,
)
datagen.fit(X_train)
# 重新重置一個新的模型
model = cnn_model()
# 讓我們用相同的組合
lr = 0.01
sgd = SGD(lr=lr, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy'])
def lr_schedule(epoch):
return lr*(0.1**int(epoch/10))
batch_size = 32
nb_epoch = 30
# 透過data generator來產生訓練資料, 由於資料是可持續產生, 我們可以透過設定'steps_per_epoch'的數量來讓模型可以有更多的訓練批次
history = model.fit_generator(datagen.flow(X_train, Y_train, batch_size=batch_size),
steps_per_epoch=X_train.shape[0]/batch_size,
epochs=nb_epoch,
validation_data=(X_val, Y_val),
callbacks=[LearningRateScheduler(lr_schedule),
ModelCheckpoint('model2.h5',save_best_only=True)]
)
Epoch 1/30 981/980 [==============================] - 14s 14ms/step - loss: 2.5697 - acc: 0.2650 - val_loss: 1.1405 - val_acc: 0.6341 Epoch 2/30 981/980 [==============================] - 14s 14ms/step - loss: 0.9100 - acc: 0.7102 - val_loss: 0.1976 - val_acc: 0.9338 Epoch 3/30 981/980 [==============================] - 14s 14ms/step - loss: 0.4247 - acc: 0.8645 - val_loss: 0.0777 - val_acc: 0.9748 Epoch 4/30 981/980 [==============================] - 14s 14ms/step - loss: 0.2750 - acc: 0.9142 - val_loss: 0.0568 - val_acc: 0.9811 Epoch 5/30 981/980 [==============================] - 14s 14ms/step - loss: 0.2022 - acc: 0.9380 - val_loss: 0.0382 - val_acc: 0.9878 Epoch 6/30 981/980 [==============================] - 14s 14ms/step - loss: 0.1674 - acc: 0.9508 - val_loss: 0.0206 - val_acc: 0.9936 Epoch 7/30 981/980 [==============================] - 14s 14ms/step - loss: 0.1443 - acc: 0.9556 - val_loss: 0.0244 - val_acc: 0.9929 Epoch 8/30 981/980 [==============================] - 14s 14ms/step - loss: 0.1338 - acc: 0.9591 - val_loss: 0.0170 - val_acc: 0.9944 Epoch 9/30 981/980 [==============================] - 14s 14ms/step - loss: 0.1177 - acc: 0.9646 - val_loss: 0.0148 - val_acc: 0.9959 Epoch 10/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0995 - acc: 0.9700 - val_loss: 0.0108 - val_acc: 0.9960 Epoch 11/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0568 - acc: 0.9825 - val_loss: 0.0065 - val_acc: 0.9978 Epoch 12/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0431 - acc: 0.9877 - val_loss: 0.0062 - val_acc: 0.9972 Epoch 13/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0413 - acc: 0.9870 - val_loss: 0.0054 - val_acc: 0.9982 Epoch 14/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0366 - acc: 0.9887 - val_loss: 0.0064 - val_acc: 0.9981 Epoch 15/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0349 - acc: 0.9895 - val_loss: 0.0054 - val_acc: 0.9980 Epoch 16/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0368 - acc: 0.9894 - val_loss: 0.0056 - val_acc: 0.9983 Epoch 17/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0302 - acc: 0.9906 - val_loss: 0.0057 - val_acc: 0.9983 Epoch 18/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0291 - acc: 0.9913 - val_loss: 0.0047 - val_acc: 0.9982 Epoch 19/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0288 - acc: 0.9914 - val_loss: 0.0052 - val_acc: 0.9983 Epoch 20/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0309 - acc: 0.9905 - val_loss: 0.0051 - val_acc: 0.9985 Epoch 21/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0256 - acc: 0.9912 - val_loss: 0.0052 - val_acc: 0.9986 Epoch 22/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0276 - acc: 0.9912 - val_loss: 0.0048 - val_acc: 0.9987 Epoch 23/30 981/980 [==============================] - 13s 14ms/step - loss: 0.0267 - acc: 0.9915 - val_loss: 0.0046 - val_acc: 0.9986 Epoch 24/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0231 - acc: 0.9928 - val_loss: 0.0045 - val_acc: 0.9986 Epoch 25/30 981/980 [==============================] - 13s 14ms/step - loss: 0.0246 - acc: 0.9918 - val_loss: 0.0046 - val_acc: 0.9986 Epoch 26/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0263 - acc: 0.9922 - val_loss: 0.0048 - val_acc: 0.9986 Epoch 27/30 981/980 [==============================] - 13s 14ms/step - loss: 0.0231 - acc: 0.9930 - val_loss: 0.0046 - val_acc: 0.9986 Epoch 28/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0253 - acc: 0.9924 - val_loss: 0.0045 - val_acc: 0.9985 Epoch 29/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0232 - acc: 0.9931 - val_loss: 0.0044 - val_acc: 0.9987 Epoch 30/30 981/980 [==============================] - 14s 14ms/step - loss: 0.0247 - acc: 0.9924 - val_loss: 0.0045 - val_acc: 0.9986
# 透過趨勢圖來觀察訓練與驗證的走向 (特別去觀察是否有"過擬合(overfitting)"的現象)
import matplotlib.pyplot as plt
def plot_train_history(history, train_metrics, val_metrics):
plt.plot(history.history.get(train_metrics),'-o')
plt.plot(history.history.get(val_metrics),'-o')
plt.ylabel(train_metrics)
plt.xlabel('Epochs')
plt.legend(['train', 'validation'])
plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plot_train_history(history, 'loss','val_loss')
plt.subplot(1,2,2)
plot_train_history(history, 'acc','val_acc')
plt.show()
# 預測與比對
y_pred = model.predict_classes(X_test)
acc = np.sum(y_pred==y_test)/np.size(y_pred)
print("Test accuracy = {}".format(acc))
12630/12630 [==============================] - 2s 119us/step Test accuracy = 0.9870942201108472
透過圖像增強 (Data Augmentation)後再訓練的這個模型,我在測試集上獲得了98.7%
的準確度(灑花! 灑花!)。
說正格的,我並沒有做太多的參數調校。以下列出一些可以嘗試改進模型的東西:
BatchNormalization
層到網絡。這只是初學者的一種快速入門模型。對於這個問題的還有一些先進的解決方案,你可以看看這個,作者用一個稱為Spatial Transformer
層的專門層
達到99.61%
的準確度。
在這篇文章中有一些個人學習到的一些有趣的重點:
ModelCheckpoint
真的很好用