基于Keras+CNN的MNIST數(shù)據(jù)集手寫(xiě)數(shù)字分類(lèi)

2018年9月19日筆記

Keras官方github鏈接:https://github.com/keras-team/keras
官方的口號(hào)是Keras: Deep Learning for humans,中文叫做Keras是給人使用的深度學(xué)習(xí)開(kāi)發(fā)框架,其意義是Keras是一個(gè)高度集成的開(kāi)發(fā)框架,其中的API調(diào)用很簡(jiǎn)單。
Keras用python語(yǔ)言編寫(xiě),在tensorflow、cntk、theano這3種框架的基礎(chǔ)上運(yùn)行。
本文是學(xué)習(xí)github源碼的筆記,源碼鏈接:https://github.com/keras-team/keras/blob/master/examples/cifar10_cnn.py

0.編程環(huán)境

操作系統(tǒng):Win10
python版本:3.6
tensorflow-gpu版本:1.6
keras版本:2.1.5

1.配置環(huán)境

先安裝tenforflow的GPU版本,再安裝keras。
使用卷積神經(jīng)網(wǎng)絡(luò)模型要求有較高的機(jī)器配置,如果使用CPU版tensorflow會(huì)花費(fèi)大量時(shí)間。
讀者在有nvidia顯卡的情況下,安裝GPU版tensorflow會(huì)提高計(jì)算速度50倍。
安裝教程鏈接:https://blog.csdn.net/qq_36556893/article/details/79433298
如果沒(méi)有nvidia顯卡,但有visa信用卡,請(qǐng)閱讀我的另一篇文章《在谷歌云服務(wù)器上搭建深度學(xué)習(xí)平臺(tái)》,鏈接:http://www.itdecent.cn/p/893d622d1b5a

2.完整代碼

此章給讀者能夠直接運(yùn)行的完整代碼,使讀者有編程結(jié)果的感性認(rèn)識(shí)。
如果下面一段代碼運(yùn)行成功,則說(shuō)明安裝tensorflow環(huán)境成功。
想要了解代碼的具體實(shí)現(xiàn)細(xì)節(jié),請(qǐng)閱讀后面的章節(jié)。

from keras.datasets import mnist
from keras.utils import to_categorical

train_X, train_y = mnist.load_data()[0]
train_X = train_X.reshape(-1, 28, 28, 1)
train_X = train_X.astype('float32')
train_X /= 255
train_y = to_categorical(train_y, 10)

from keras.models import Sequential
from keras.layers import Conv2D, MaxPool2D, Flatten, Dropout, Dense
from keras.losses import categorical_crossentropy
from keras.optimizers import Adadelta

model = Sequential()
model.add(Conv2D(32, (5,5), activation='relu', input_shape=[28, 28, 1]))
model.add(Conv2D(64, (5,5), activation='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))

model.compile(loss=categorical_crossentropy,
             optimizer=Adadelta(),
             metrics=['accuracy'])

batch_size = 100
epochs = 8
model.fit(train_X, train_y,
         batch_size=batch_size,
         epochs=epochs)

test_X, test_y = mnist.load_data()[1]
test_X = test_X.reshape(-1, 28, 28, 1)
test_X = test_X.astype('float32')
test_X /= 255
test_y = to_categorical(test_y, 10)
loss, accuracy = model.evaluate(test_X, test_y, verbose=1)
print('loss:%.4f accuracy:%.4f' %(loss, accuracy))

上面一段代碼的運(yùn)行結(jié)果如下:

Epoch 1/8
60000/60000 [==============================] - 11s 190us/step - loss: 0.2232 - acc: 0.9306
Epoch 2/8
60000/60000 [==============================] - 9s 147us/step - loss: 0.0818 - acc: 0.9756
Epoch 3/8
60000/60000 [==============================] - 9s 148us/step - loss: 0.0633 - acc: 0.9817
Epoch 4/8
60000/60000 [==============================] - 9s 147us/step - loss: 0.0538 - acc: 0.9843
Epoch 5/8
60000/60000 [==============================] - 9s 147us/step - loss: 0.0468 - acc: 0.9861
Epoch 6/8
60000/60000 [==============================] - 9s 148us/step - loss: 0.0428 - acc: 0.9875
Epoch 7/8
60000/60000 [==============================] - 9s 147us/step - loss: 0.0405 - acc: 0.9880
Epoch 8/8
60000/60000 [==============================] - 9s 148us/step - loss: 0.0376 - acc: 0.9888
10000/10000 [==============================] - 1s 111us/step
loss:0.0223 accuracy:0.9930

epoch中文叫做新紀(jì)元,每經(jīng)過(guò)1次epoch,即模型訓(xùn)練遍歷所有樣本1次;
上文中epoch設(shè)置為8,即模型訓(xùn)練遍歷所有樣本8次;
batch_size設(shè)置為100,即每次模型訓(xùn)練使用的樣本數(shù)量為100;
每經(jīng)過(guò)1次epoch,模型遍歷訓(xùn)練集的60000個(gè)樣本,每次訓(xùn)練使用100個(gè)樣本,即模型訓(xùn)練600次,即損失函數(shù)經(jīng)過(guò)600次批量梯度下降。
從上面的運(yùn)行結(jié)果可以看出,經(jīng)過(guò)8次epoch,模型在測(cè)試集的準(zhǔn)確率到達(dá)0.9930。

3.數(shù)據(jù)觀察

3.1 使用keras庫(kù)中的方法加載數(shù)據(jù)

本文使用keras.datasets庫(kù)的mnist.py文件中的load_data方法加載數(shù)據(jù)。
本文作者使用anaconda集成開(kāi)發(fā)環(huán)境,keras.datasets庫(kù)的mnist.py文件路徑:C:\ProgramData\Anaconda3\Lib\site-packages\keras\datasets,如下圖所示:

image.png

mnist.py文件中代碼如下:

from ..utils.data_utils import get_file
import numpy as np

def load_data(path='mnist.npz'):
    path = get_file(path,
                    origin='https://s3.amazonaws.com/img-datasets/mnist.npz',
                    file_hash='8a61469f7ea1b51cbae51d4f78837e45')
    f = np.load(path)
    x_train, y_train = f['x_train'], f['y_train']
    x_test, y_test = f['x_test'], f['y_test']
    f.close()
    return (x_train, y_train), (x_test, y_test)

第1行代碼導(dǎo)入此文件上一級(jí)目錄utils.data_tuils路徑下的get_file方法;
第2行代碼導(dǎo)入numpy庫(kù),起別名np;
第4-12行代碼定義load_data方法;
第5-7行代碼會(huì)檢查keras的緩存文件夾中是否有mnist.npz文件,如果沒(méi)有則下載第6行代碼的url鏈接指向的資源;
keras緩存文件夾是用戶(hù)路徑的.keras文件夾,舉例本文作者的keras緩存文件夾路徑:C:\Users\Administrator\.keras\datasets
在第一次運(yùn)行l(wèi)oad_data方法時(shí),會(huì)從網(wǎng)絡(luò)上下載mnist.npz文件,之后運(yùn)行則不需要下載。
mnist.npz文件在keras緩存文件夾的情況如下圖所示:

image.png

3.2 查看數(shù)據(jù)情況

從3.1節(jié)mnist.py文件的代碼可以看出,load_data方法返回值是一個(gè)元組,其中有2個(gè)元素。
第1個(gè)元素是訓(xùn)練集的數(shù)據(jù),第2個(gè)元素是測(cè)試集的數(shù)據(jù);
訓(xùn)練集的數(shù)據(jù)是1個(gè)元組,里面包括2個(gè)元素,第1個(gè)元素是特征矩陣,第2個(gè)元素是預(yù)測(cè)目標(biāo)值;
測(cè)試集的數(shù)據(jù)是1個(gè)元組,里面包括2個(gè)元素,第1個(gè)元素是特征矩陣,第2個(gè)元素是預(yù)測(cè)目標(biāo)值。
第1種寫(xiě)法:

from keras.datasets import mnist

train_data = mnist.load_data()[0]
test_data = mnist.load_data()[1]
train_X , train_y = train_data
test_X, test_y = test_data
print(train_X.shape, train_y.shape)
print(test_X.shape, test_y.shape)

第2種寫(xiě)法:

from keras.datasets import mnist
(train_X, train_y), (test_X, test_y) = mnist.load_data()
print(train_X.shape, train_y.shape)
print(test_X.shape, test_y.shape)

上面兩種代碼寫(xiě)法的運(yùn)行結(jié)果相同,讀者可以通過(guò)對(duì)比體會(huì)如何使用python中的元組。

(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)

從上面的運(yùn)行結(jié)果可以看出,訓(xùn)練集總共有60000個(gè)樣本,測(cè)試集總共有10000個(gè)樣本,每個(gè)圖片樣本的像素大小是28*28。

3.3 查看手寫(xiě)數(shù)字圖

運(yùn)行下面代碼成功的前提是讀者保持前文代碼中的變量名。
本文作者按照中國(guó)人的思維習(xí)慣,喜歡將變量?jī)?nèi)容的主體放在變量命名的后邊。
例如訓(xùn)練集的特征矩陣,主體是特征矩陣,本文作者將其變量命名為train_X。
外國(guó)人的思維習(xí)慣,習(xí)慣將變量?jī)?nèi)容的主體放在變量命名的前面。
例如訓(xùn)練集的特征矩陣,主體是特征矩陣,外國(guó)人將其變量命名為X_train。
從訓(xùn)練集train_X中選取一部分樣本查看圖片內(nèi)容,即調(diào)用random的sample方法隨機(jī)獲得一部分樣本,代碼如下:

import matplotlib.pyplot as plt
import math
import random 

def drawDigit(position, image, title):
    plt.subplot(*position)
    plt.imshow(image.reshape(-1, 28), cmap='gray_r')
    plt.axis('off')
    plt.title(title)
    
def batchDraw(batch_size):
    selected_index = random.sample(range(len(train_y)), k=batch_size)
    images,labels = train_X[selected_index], train_y[selected_index]
    image_number = images.shape[0]
    row_number = math.ceil(image_number ** 0.5)
    column_number = row_number
    plt.figure(figsize=(row_number, column_number))
    for i in range(row_number):
        for j in range(column_number):
            index = i * column_number + j
            if index < image_number:
                position = (row_number, column_number, index+1)
                image = images[index]
                title = 'actual:%d' %(labels[index])
                drawDigit(position, image, title)

batchDraw(100)
plt.show()

上面一段代碼的運(yùn)行結(jié)果如下圖所示,本文作者對(duì)難以辨認(rèn)的數(shù)字做了紅色方框標(biāo)注:


image.png

4.數(shù)據(jù)準(zhǔn)備

from keras.datasets import mnist
from keras.utils import to_categorical

train_X, train_y = mnist.load_data()[0]
train_X = train_X.reshape(-1, 28, 28, 1)
train_X = train_X.astype('float32')
train_X /= 255
train_y = to_categorical(train_y, 10)

第1行代碼從keras.datasets庫(kù)中導(dǎo)入mnist.py文件;
第2行代碼從keras.utils庫(kù)中導(dǎo)入to_categorical方法;
第4行代碼獲取訓(xùn)練集的特征矩陣賦值給變量train_X,獲取訓(xùn)練集的預(yù)測(cè)目標(biāo)值賦值給變量train_y;
第5-7行代碼將原始的特征矩陣做數(shù)據(jù)處理形成模型需要的數(shù)據(jù);
第8行代碼使用keras中的方法對(duì)數(shù)字的標(biāo)簽分類(lèi)做One-Hot編碼。

5.搭建神經(jīng)網(wǎng)絡(luò)

from keras.models import Sequential
from keras.layers import Conv2D, MaxPool2D, Flatten, Dropout, Dense
from keras.losses import categorical_crossentropy
from keras.optimizers import Adadelta

model = Sequential()
model.add(Conv2D(32, (5,5), activation='relu', input_shape=[28, 28, 1]))
model.add(Conv2D(64, (5,5), activation='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))

model.compile(loss=categorical_crossentropy,
             optimizer=Adadelta(),
             metrics=['accuracy'])

第1-4行代碼導(dǎo)入keras中的模型、、損失函數(shù)、優(yōu)化器。
第6行代碼使用keras.model庫(kù)的Sequential方法實(shí)例化模型對(duì)象;
第7、8行代碼是模型中添加卷積層;
第9行代碼是模型中添加最大池化層;
第10行代碼是模型中的數(shù)據(jù)矩陣展平;
第11行代碼是模型中添加dropout操作;
第12行代碼是模型中添加全連接層
第13行代碼是模型中添加dropout操作;
第14行代碼是模型中添加全連接層,且使用relu作為激活函數(shù),即最終分類(lèi)結(jié)果;
第16-18行代碼為模型指定損失函數(shù),優(yōu)化器,評(píng)判指標(biāo)。

6.模型訓(xùn)練

batch_size = 100
epochs = 8
model.fit(train_X, train_y,
         batch_size=batch_size,
         epochs=epochs)

第1行代碼設(shè)置批量梯度下降時(shí)的batch_size為100;
第2行代碼設(shè)置遍歷所有樣本的次數(shù)epoch為8,讀者可以自行嘗試不同的值,本文作者在設(shè)置為8時(shí)取得較好的收斂效果;
第3-5行代碼調(diào)用模型對(duì)象的fit方法開(kāi)始模型訓(xùn)練,fit方法需要4個(gè)參數(shù),第1個(gè)參數(shù)是特征矩陣,第2個(gè)參數(shù)是預(yù)測(cè)目標(biāo)值,第3個(gè)關(guān)鍵字參數(shù)batch_size,第4個(gè)關(guān)鍵字參數(shù)epochs。
上面一段代碼的運(yùn)行結(jié)果如下圖所示:

Epoch 1/8
60000/60000 [==============================] - 12s 192us/step - loss: 0.2178 - acc: 0.9330
Epoch 2/8
60000/60000 [==============================] - 9s 150us/step - loss: 0.0810 - acc: 0.9760
Epoch 3/8
60000/60000 [==============================] - 9s 150us/step - loss: 0.0628 - acc: 0.9813
Epoch 4/8
60000/60000 [==============================] - 9s 151us/step - loss: 0.0531 - acc: 0.9838
Epoch 5/8
60000/60000 [==============================] - 9s 150us/step - loss: 0.0475 - acc: 0.9858
Epoch 6/8
60000/60000 [==============================] - 9s 151us/step - loss: 0.0435 - acc: 0.9873
Epoch 7/8
60000/60000 [==============================] - 9s 151us/step - loss: 0.0386 - acc: 0.9887
Epoch 8/8
60000/60000 [==============================] - 9s 151us/step - loss: 0.0366 - acc: 0.9895

7.模型評(píng)估

test_X, test_y = mnist.load_data()[1]
test_X = test_X.reshape(-1, 28, 28, 1)
test_X = test_X.astype('float32')
test_X /= 255
test_y = to_categorical(test_y, 10)

loss, accuracy = model.evaluate(test_X, test_y, verbose=1)
print('train data loss:%.4f accuracy:%.4f' %(loss, accuracy))
loss, accuracy = model.evaluate(train_X, train_y, verbose=1)
print('test data loss:%.4f accuracy:%.4f' %(loss, accuracy))

第1行代碼獲取測(cè)試集的數(shù)據(jù);
第2-4行代碼將原始的特征矩陣做數(shù)據(jù)處理形成模型需要的數(shù)據(jù);
第5行代碼使用keras中的方法對(duì)數(shù)字的標(biāo)簽分類(lèi)做One-Hot編碼。
上面一段代碼的運(yùn)行結(jié)果如下:
第7-8行代碼使用測(cè)試集的數(shù)據(jù)做模型評(píng)估,打印損失函數(shù)值和準(zhǔn)確率;
第9-10行代碼使用訓(xùn)練集的數(shù)據(jù)做模型評(píng)估,打印損失函數(shù)值和準(zhǔn)確率。

10000/10000 [==============================] - 1s 110us/step
train data loss:0.0215 accuracy:0.9931
60000/60000 [==============================] - 6s 107us/step
test data loss:0.0153 accuracy:0.9957

8.模型測(cè)試

import math
import matplotlib.pyplot as plt
import numpy as np
import random

def drawDigit3(position, image, title, isTrue):
    plt.subplot(*position)
    plt.imshow(image.reshape(-1, 28), cmap='gray_r')
    plt.axis('off')
    if not isTrue:
        plt.title(title, color='red')
    else:
        plt.title(title)
        
def batchDraw3(batch_size, test_X, test_y):
    selected_index = random.sample(range(len(test_y)), k=batch_size)
    images = test_X[selected_index]
    labels = test_y[selected_index]
    predict_labels = model.predict(images)
    image_number = images.shape[0]
    row_number = math.ceil(image_number ** 0.5)
    column_number = row_number
    plt.figure(figsize=(row_number+8, column_number+8))
    for i in range(row_number):
        for j in range(column_number):
            index = i * column_number + j
            if index < image_number:
                position = (row_number, column_number, index+1)
                image = images[index]
                actual = np.argmax(labels[index])
                predict = np.argmax(predict_labels[index])
                isTrue = actual==predict
                title = 'actual:%d\npredict:%d' %(actual,predict)
                drawDigit3(position, image, title, isTrue)

batchDraw3(100, test_X, test_y)
plt.show()

第6-13行定義drawDigit3函數(shù)畫(huà)出單個(gè)數(shù)字;
第7行代碼調(diào)用matplotlib.pyplot庫(kù)的subplot方法指定子圖位置;
第8行代碼調(diào)用matplotlib.pyplot庫(kù)的imshow方法把數(shù)字矩陣?yán)L制成圖;
第9行代碼設(shè)置不顯示坐標(biāo)軸;
第10-13行代碼如果函數(shù)的參數(shù)isTrue為真,則標(biāo)題為黑色,否則為紅色。
第15-34行代碼定義batchDraw函數(shù),根據(jù)參數(shù)batch_size選出此數(shù)量的樣本并畫(huà)圖。
第21行代碼調(diào)用math庫(kù)的ceil函數(shù)對(duì)小數(shù)向上取整,例如math.ceil(2.01)=3
上面一段代碼的運(yùn)行結(jié)果如下圖所示:

image.png

從上面的運(yùn)行結(jié)果可以看出,只有最后1行中的1個(gè)數(shù)被判斷錯(cuò)誤,符合前一章模型評(píng)估中99.3%的結(jié)果。

9.總結(jié)

1.keras基于tensorflow封裝,代碼更直觀,容易理解;
2.根據(jù)本文作者的經(jīng)驗(yàn),在MNIST數(shù)據(jù)集上,基于tensorflow編寫(xiě)代碼需要53行代碼,基于keras編寫(xiě)代碼需要38行,38/53=0.7170,即可以減少30%的代碼量。
3.keras在訓(xùn)練過(guò)程中會(huì)動(dòng)態(tài)顯示訓(xùn)練進(jìn)度,是友好的用戶(hù)界面設(shè)計(jì)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容