基於Keras的mnist手寫數字識別

首先,在衆多深度學習框架中,我最開始上手的框架(因爲那時候先接觸的caffe,配置使用都太麻煩了)是Keras,什麼叫做搞什麼深度學習,不過是搭積木罷了,這句話真的太適合形容Keras了。Keras是一個高級的深度學習框架,是架設於tensorflow、Theano、CNTK三大深度學習框架之上的,可以設置切換後臺爲其中之一,不過目前比較多的應該是用tensorflow作爲後臺,tensorflow好像也提供了很多Keras的接口。Theano是很老的,現在停止維護了,CNTK是微軟的,受衆相對較窄。

然後說下數據集mnist,tensorflow自帶了mnist數據集,可以通過下面代碼加載數據集:

from tensorflow.examples.tutorials.mnist import *
mnist=input_data.read_data_sets("/tmp/mnist",one_hot=True)

返回的mnist有兩個成員train和test,其下又有images和labels分別表示圖像和標籤,上述one_hot表示用二進制編碼標籤,後面用到categorical_crossentropy作爲損失函數的時候就會用到。mnist數據集包含了55000個訓練樣本和10000個測試樣本。加載了數據集之後要注意數據的維度順序,Keras的數據大部分是默認爲4D張量的,用tensorflow作爲後臺的時候,數據爲張量,其維度順序爲[樣本數,高度,寬度,通道數](用Theano作爲後端的時候,其順序爲[樣本數,通道數,高度,寬度]),所以需要我們調整數據的維度順序:

train_X=train_X.reshape((train_X.shape[0],28,28,1))
test_X=test_X.reshape((test_X.shape[0],28,28,1))

接下來就是構建一個神經網絡了,這下就要充分體現Keras搭積木的特性了,Keras構建一個神經網絡的方式有兩種,一種是序貫模型,一種是函數模型。這裏就先介紹第一種序貫模型。

序貫模型是多個網絡層的線性堆疊,也就是“一條路走到黑”。構建神經網絡的時候通過向Sequential模型傳遞一個layer的list來構造該模型,比如:

from keras.models import Sequential
from keras.layers import Dense, Activation 

model = Sequential([
    Dense(32, units=784), 
    Activation('relu'), 
    Dense(10), 
    Activation('softmax')
])

這樣就構建了一個包含全連接層、激活層、全連接層、激活層的網絡結構。其次,也可以通過add()來添加層,比如:

model = Sequential()
model.add(Dense(32, input_shape=(784,)))
model.add(Activation('relu'))

這裏我們採用add()來添加層:


model=Sequential()
# 28
model.add(Conv2D(32,(3,3),activation='relu', input_shape=(28,28,1),padding="valid")) # 26 
model.add(Conv2D(32,(3,3),activation='relu',padding="valid")) # 24 
model.add(MaxPool2D(pool_size=(2,2))) # 12 
model.add(Dropout(0.5)) # 12 
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 10 
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 8 
model.add(MaxPooling2D(pool_size=(2, 2))) # 4 
model.add(Dropout(0.5)) 
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4 
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4 
model.add(MaxPooling2D(pool_size=(2, 2))) 
model.add(Dropout(0.5)) 
model.add(Flatten()) 
model.add(Dense(64,activation="relu"))
model.add(Dropout(0.5)) 
model.add(Dense(10,activation='softmax')) 
model.compile(loss='categorical_crossentropy', optimizer='adadelta',metrics=['accuracy'])

最後一句compile是用來編譯模型的,這裏需要指定損失函數、優化算法和評估指標等,其中損失函數和優化算法是必須指定的。搭好積木之後就是要讓網絡跑起來訓練,這時候就會用到fit函數了,fit函數參數挺多的,但是隻有兩個是必須指定的,就是輸入數據和標籤,輸入數據的shape必須和模型的輸入的shape一致,標籤則和輸出一致,fit還可以指定訓練的回合數、數據塊的大小等信息,其函數原型如下:


fit(self, x, y, batch_size=32, epochs=10, 
        verbose=1, callbacks=None, 
        validation_split=0.0, validation_data=None, 
        shuffle=True, 
        class_weight=None, 
        sample_weight=None, 
        initial_epoch=0)

這裏每個變量的含義是可以在Keras的文檔中查找的,說幾個,verbose是訓練過程中會有信息輸出顯示,這裏定義其顯示的形式,有三種形式:0爲不在標準輸出流輸出日誌信息,1爲輸出進度條記錄,2爲每個epoch輸出一行記錄;callbacks是一個回調函數的list,主要用於訓練過程中一些操作;validation_split是將x和y切成訓練集和驗證集的驗證集佔比,如果設置了validation_data就用validation_data的。

完整的代碼顯示如下:

from tensorflow.examples.tutorials.mnist import *    
from keras.models import *
from keras.layers import *
from keras.callbacks import ModelCheckpoint, LearningRateScheduler,History, BaseLogger,Callback,EarlyStopping, ReduceLROnPlateau
import numpy as np

# os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

# 加載數據集
mnist=input_data.read_data_sets("/tmp/mnist",one_hot=True)
train_X=mnist.train.images
train_Y=mnist.train.labels
test_X=mnist.test.images
test_Y=mnist.test.labels
train_X=train_X.reshape((55000,28,28,1))
test_X=test_X.reshape((test_X.shape[0],28,28,1))
print("type of train_X:",type(train_X))
print("size of train_X:",np.shape(train_X))
print("train_X:",train_X)
print("type of train_Y:",type(train_Y))
print("size of train_Y:",np.shape(train_Y))
print("train_Y:",train_Y)
print("num of test:",test_X.shape[0])

# 配置模型結構
model=Sequential()
model.add(Conv2D(32,(3,3),activation='relu',input_shape=(28,28,1), padding="valid")) # 26
model.add(Conv2D(32,(3,3),activation='relu',padding="valid")) # 24
model.add(MaxPool2D(pool_size=(2,2))) # 12
model.add(Dropout(0.5)) # 12
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 10
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 8
model.add(MaxPooling2D(pool_size=(2, 2))) # 4
model.add(Dropout(0.5))
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(64,activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adadelta',metrics=['accuracy'])

# 訓練模型
epochs=200
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.3, verbose=1, patience=16, min_lr=5e-4)
#save at each epoch if the validation decreased
checkpointer = ModelCheckpoint(filepath= 'best_model_ep{}.h5'.format(epochs), verbose=1, monitor='val_loss', mode='auto', save_best_only=True)
es = EarlyStopping(monitor='val_loss', patience=64)
history = model.fit(train_X, train_Y, epochs=epochs, batch_size=32, verbose=1, shuffle=True, validation_split=0.1, callbacks=[checkpointer, reduce_lr, es])

# 用測試集去評估模型的準確度
accuracy=model.evaluate(test_X,test_Y,batch_size=20)
print('\nTest accuracy:',accuracy[1])
save_model(model,'last_model_ep{}.h5'.format(epochs))

在實驗中,我將訓練集的十分之一用於做驗證集,這裏用ModelCheckpoint回調函數來保存模型,檢測驗證集上的loss,將loss最低的權重保存爲best,將最後一次epoch的權重保存爲last,EarlyStopping則是另一個回調函數,用於檢測驗證集的loss是否下降,如果一直不下降超過一定回合數,那就提前停止訓練;ReduceLROnPlateau適用於調整訓練的學習率的,如果驗證集上的loss持續了幾個回合都沒有下降了就減少學習率。

訓練完成後用model.evaluate()來評估模型的性能,函數原型爲:

evaluate(self, x, y, batch_size=32, verbose=1, sample_weight=None)

最後截個圖顯示下訓練結果:

最終測試集上的準確率是0.9941,應該還是比較可以的分數吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章