2. 深度學(xué)習(xí)的helloworld! 用Keras實(shí)現(xiàn)手寫數(shù)字識(shí)別

經(jīng)過上一章的1. Keras神經(jīng)網(wǎng)絡(luò)基礎(chǔ)知識(shí)的鋪墊,我們今天就利用Keras構(gòu)建一個(gè)可以識(shí)別手寫數(shù)字的網(wǎng)絡(luò)。

1. MNIST數(shù)據(jù)集介紹

MNIST數(shù)據(jù)集是一個(gè)手寫體數(shù)據(jù)集,由600000個(gè)訓(xùn)練樣例和10000個(gè)測(cè)試樣例組成的手寫數(shù)字?jǐn)?shù)據(jù)庫。每個(gè)MNIST數(shù)據(jù)集都是灰度的,由28*28像素組成。


簡單說就是一堆這樣?xùn)|西

我們的任務(wù)就是通過輸入的手寫數(shù)字圖像信息來識(shí)別出相應(yīng)的數(shù)字。

2. 程序示例

8說了,上代碼

import numpy as np
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.optimizers import SGD
from keras.utils import np_utils
np.random.seed(2019)             # 重復(fù)性設(shè)置

# 網(wǎng)絡(luò)和訓(xùn)練
NB_EPOCH = 200
BATCH_SIZE = 128
VERBOSE = 1
NB_CLASSES = 10           # 輸出個(gè)數(shù)等于數(shù)字個(gè)數(shù)
OPTIMIZER = SGD()         # SGD 優(yōu)化器
N_HIDDEN = 128
VALIDATION_SPLIT = 0.2    # 訓(xùn)練集中用座驗(yàn)證集的比例

# 數(shù)據(jù):混合并劃分訓(xùn)練集和測(cè)試集數(shù)據(jù)
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# X_trian是60000行28*28的書據(jù),變形為60000*784
RESHAPED = 784
X_train = X_train.reshape(60000, RESHAPED)
X_test = X_test.reshape(10000, RESHAPED)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')

# 歸一化
X_train /= 255
X_test /= 255
print(X_train.shape[0], 'train sample')
print(X_test.shape[0], 'test sample')

# 將類向量轉(zhuǎn)換為二值類別矩陣
Y_train = np_utils.to_categorical(y_train, NB_CLASSES)
Y_test = np_utils.to_categorical(y_test, NB_CLASSES)

# 10個(gè)輸出
# 最后是softmax激活函數(shù)
model = Sequential()
model.add(Dense(NB_CLASSES, input_shape=(RESHAPED,)))
model.add(Activation('softmax'))
model.summary()

# 編譯模型
# loss: 損失函數(shù); 這里選擇多分類對(duì)數(shù)損失函數(shù)(categorical_crossentropy)
# optimizer: 優(yōu)化器; 這里選擇SGD()
# metrics: 性能評(píng)估,該性能的評(píng)估結(jié)果講不會(huì)用于訓(xùn)練; 這里選擇accuracy準(zhǔn)確率
model.compile(loss='categorical_crossentropy', optimizer=OPTIMIZER, metrics=['accuracy'])

# fit() 模型訓(xùn)練
# epochs 訓(xùn)練輪數(shù)
# batch_size 優(yōu)化器進(jìn)行權(quán)重更新前要觀察的訓(xùn)練實(shí)例數(shù)
history = model.fit(X_train, Y_train, batch_size=BATCH_SIZE, epochs=NB_EPOCH,
                    verbose=VERBOSE, validation_split=VALIDATION_SPLIT)

#對(duì)模型訓(xùn)練的結(jié)果進(jìn)行評(píng)估
score = model.evaluate(X_test, Y_test, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

3. 訓(xùn)練結(jié)果

訓(xùn)練結(jié)束后,我們用測(cè)試數(shù)據(jù)對(duì)模型對(duì)模型進(jìn)行測(cè)試,其中訓(xùn)練集上的準(zhǔn)確率為92.31%,驗(yàn)證集上的準(zhǔn)確率為92.32%,測(cè)試集上的準(zhǔn)確率為92.25%


4. 進(jìn)一步的改進(jìn)

以上的測(cè)試結(jié)果表明10個(gè)手寫數(shù)字里只有不到一個(gè)沒有被正確識(shí)別。當(dāng)然我們可以做的更好。我們看一下如何改進(jìn)。

4.1 用隱藏層改進(jìn)簡單網(wǎng)絡(luò)

第一個(gè)改進(jìn)的方法是為我們的網(wǎng)絡(luò)添加更多的層。所以在輸入層之后,我們加入了兩個(gè)具有N_HIDDEN個(gè)神經(jīng)元并將ReLU作為激活函數(shù)的dense層。

model = Sequential()
model.add(Dense(N_HIDDEN, input_shape=(RESHAPED,)))  # 加入的第一個(gè)隱藏層
model.add(Activation('relu'))
model.add(Dense(N_HIDDEN))                           # 加入的第二個(gè)隱藏層
model.add(Activation('relu'))
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()

結(jié)果:
測(cè)試集精度達(dá)到了97%

4.2 用dropout進(jìn)一步改進(jìn)簡單網(wǎng)絡(luò)

dropout就是我們決定在內(nèi)部全連接的隱藏層上傳播的值里,按dropout概率隨機(jī)丟棄某些值。在機(jī)器學(xué)習(xí)中,這是一種眾所周知的正則化形式。這看起來很荒唐,但它確實(shí)能提高我們的性能。


dropout層的效果

它的效果是讓網(wǎng)絡(luò)對(duì)神經(jīng)元的特定權(quán)重變得不那么敏感。讓網(wǎng)絡(luò)能夠更好地泛化,并且很少過擬合訓(xùn)練數(shù)據(jù)。

model = Sequential()
model.add(Dense(N_HIDDEN, input_shape=(RESHAPED,)))  # 加入的第一個(gè)隱藏層
model.add(Activation('relu'))                        # 激勵(lì)函數(shù)為RELU
model.add(Dropout(DROPOUT))                          # 加在隱藏層上的dropout層
model.add(Dense(N_HIDDEN))                           # 加入的第二個(gè)隱藏層
model.add(Activation('relu'))
model.add(Dropout(DROPOUT))                          # 加在隱藏層上的dropout層
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()

4.3 增加迭代次數(shù)

注意,訓(xùn)練集上的準(zhǔn)確率應(yīng)高于測(cè)試集上的準(zhǔn)確率,否則說明我們的訓(xùn)練時(shí)間不夠長。所以我們?cè)囍鴮⒂?xùn)練輪數(shù)大幅增加至250。但注意,增加訓(xùn)練輪數(shù)不一定會(huì)使網(wǎng)絡(luò)提高!

4.4 更換不同的優(yōu)化器

我們一開始就使用叫做隨機(jī)梯度下降 (Stochastic Gradient Descent, SGD) 優(yōu)化器,關(guān)于梯度下降的原理,大家自行百度吧。除了SGD具有的加速度分量之外,RMSSpropAdam還包括了動(dòng)量的概念(速度分量)。這樣可以通過更多的計(jì)算代價(jià)來實(shí)現(xiàn)更快的收斂。我們選用Adam作為優(yōu)化器。

4.5 采用正則化方式避免過擬合

在模型的訓(xùn)練過程中,模型可能會(huì)變得過度復(fù)雜,因其所有內(nèi)在關(guān)系都被記憶下來,復(fù)雜的模型可能在訓(xùn)練數(shù)據(jù)上會(huì)取得優(yōu)秀的性能,但在驗(yàn)證數(shù)據(jù)上的性能卻不好,因?yàn)閷?duì)于全新的數(shù)據(jù),模型不能很好地泛化。
如果在訓(xùn)練期間,我們看到損失函數(shù)在驗(yàn)證集上初始下降后轉(zhuǎn)為增長,那就是一個(gè)過度訓(xùn)練的模型復(fù)雜度問題。我們稱為過擬合。為了解決過擬合問題,我們需要用到正則化。
機(jī)器學(xué)習(xí)中用到了三種不同的正則化方法:

  • L1正則化(也稱為lasso):模型復(fù)雜度表示為權(quán)重的絕對(duì)值之和
  • L2正則化(也稱為ridge):模型復(fù)雜度表示為權(quán)重的平方和
  • 彈性網(wǎng)絡(luò)正則化:模型復(fù)雜度通過聯(lián)合兩種技術(shù)捕捉。

這里我們?cè)趦?nèi)核(權(quán)重w)上使用了L2正則化方法:

from keras import regularizers
model.add(Dense(64, input_dim=64, kernel_regularizer=regularizers.l2(0.01)))

由于本實(shí)驗(yàn)并沒有出現(xiàn)過擬合,所以并沒有加入到最終代碼中去。

4.6 其他的提高手段

  • 控制優(yōu)化器的學(xué)習(xí)率
  • 增加內(nèi)部隱藏神經(jīng)元的數(shù)量N_HIDDEN
  • 增加批處理的大小BATCH_SIZE
    以上幾個(gè)提高模型準(zhǔn)確率的方法都可以通過繪圖來確定最佳參數(shù),若參數(shù)設(shè)置不合理
    會(huì)出現(xiàn)計(jì)算變得更復(fù)雜但收益沒有增加的情況

4.7 超參數(shù)調(diào)優(yōu)

上述實(shí)驗(yàn)讓我們了解了微調(diào)網(wǎng)絡(luò)的可能方式。實(shí)際上有很多可以優(yōu)化的參數(shù)(如隱藏神經(jīng)元的數(shù)量、BATCH_SIZE、訓(xùn)練輪數(shù),以及關(guān)于網(wǎng)絡(luò)本身復(fù)雜度的參數(shù)等)
超參數(shù)調(diào)優(yōu)是找到使成本函數(shù)最小化的那些參數(shù)組合的過程。

5. 輸出預(yù)測(cè)

當(dāng)模型訓(xùn)練好后,就可以用于預(yù)測(cè)。在Keras中這很簡單:

prediction = model.predict(X)

對(duì)于給定的輸入,可以計(jì)算出幾種類型的輸出,包括以下方法:

model.evaluate()          #用于計(jì)算損失值
model.predict_classes()   #用于計(jì)算輸出類別
model.predict_proba()     #用于計(jì)算類別概率

6. 貼上最后的代碼和結(jié)果

import numpy as np
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.optimizers import SGD, RMSprop, Adam
from keras.utils import np_utils
np.random.seed(2019)             # 重復(fù)性設(shè)置

# 網(wǎng)絡(luò)和訓(xùn)練
NB_EPOCH = 250
BATCH_SIZE = 128
VERBOSE = 1
NB_CLASSES = 10           # 輸出個(gè)數(shù)等于數(shù)字個(gè)數(shù)
# OPTIMIZER = SGD()         # SGD 優(yōu)化器
OPTIMIZER = Adam()        # Adam 優(yōu)化器
N_HIDDEN = 128            # 隱藏層中輸出個(gè)數(shù)
VALIDATION_SPLIT = 0.2    # 訓(xùn)練集中用作驗(yàn)證集的比例
DROPOUT = 0.3             # dropout丟棄神經(jīng)元的概率

# 數(shù)據(jù):混合并劃分訓(xùn)練集和測(cè)試集數(shù)據(jù)
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# X_trian是60000行28*28的書據(jù),變形為60000*784
RESHAPED = 784
X_train = X_train.reshape(60000, RESHAPED)
X_test = X_test.reshape(10000, RESHAPED)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')

# 歸一化
X_train /= 255
X_test /= 255
print(X_train.shape[0], 'train sample')
print(X_test.shape[0], 'test sample')

# 將類向量轉(zhuǎn)換為二值類別矩陣(One-Hot編碼)
Y_train = np_utils.to_categorical(y_train, NB_CLASSES)
Y_test = np_utils.to_categorical(y_test, NB_CLASSES)

# 10個(gè)輸出
# 最后是softmax激活函數(shù)
model = Sequential()
model.add(Dense(N_HIDDEN, input_shape=(RESHAPED,)))  # 加入的第一個(gè)隱藏層
model.add(Activation('relu'))                        # 激勵(lì)函數(shù)為RELU
model.add(Dropout(DROPOUT))                          # 加在隱藏層上的dropout層
model.add(Dense(N_HIDDEN))                           # 加入的第二個(gè)隱藏層
model.add(Activation('relu'))
model.add(Dropout(DROPOUT))                          # 加在隱藏層上的dropout層
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()

# 編譯模型
# loss: 損失函數(shù); 這里選擇多分類對(duì)數(shù)損失函數(shù)(categorical_crossentropy)
# optimizer: 優(yōu)化器; 這里選擇SGD()
# metrics: 性能評(píng)估,該性能的評(píng)估結(jié)果講不會(huì)用于訓(xùn)練; 這里選擇accuracy準(zhǔn)確率
model.compile(loss='categorical_crossentropy', optimizer=OPTIMIZER, metrics=['accuracy'])

# fit() 模型訓(xùn)練
# epochs 訓(xùn)練輪數(shù)
# batch_size 優(yōu)化器進(jìn)行權(quán)重更新前要觀察的訓(xùn)練實(shí)例數(shù)
# verbose 日志顯示
# validation_split 驗(yàn)證集劃分
history = model.fit(X_train, Y_train, batch_size=BATCH_SIZE, epochs=NB_EPOCH,
                    verbose=VERBOSE, validation_split=VALIDATION_SPLIT)

# 對(duì)模型訓(xùn)練的結(jié)果進(jìn)行評(píng)估
score = model.evaluate(X_test, Y_test, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

可以看出,最后的幾個(gè)改進(jìn)并沒有顯著提高。增加內(nèi)部神經(jīng)元的數(shù)量會(huì)產(chǎn)生更復(fù)雜的模型,并需要更昂貴的計(jì)算,但它只有微小的收益。即使增加訓(xùn)練輪數(shù),也是同樣的結(jié)果。

7. 開始你的深度學(xué)習(xí)之路

恭喜你完成了你的第一個(gè)神經(jīng)網(wǎng)絡(luò)模型,并且嘗試對(duì)它進(jìn)行了優(yōu)化。
在優(yōu)化的過程中,我們發(fā)現(xiàn),越接近99%,優(yōu)化就越困難。如果要更多的改進(jìn),我們需要一個(gè)全新的思路。想一想,我們錯(cuò)過了什么?
我們丟失了所有與圖像的局部空間相關(guān)的信息!特別是這段代碼將位圖轉(zhuǎn)換為空間局部性消失的平面向量:

X_train = X_train.reshape(60000, RESHAPED)
X_test = X_test.reshape(10000, RESHAPED)

為了利用這種空間局部信息,這就需要一種特殊的深度學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)——卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural Network, CNN),它既保留了圖像的空間局部性信息,也保留了層次漸進(jìn)的抽象學(xué)習(xí)的思想。如果你感興趣的話,那就開始你的深度學(xué)習(xí)之路吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容