1. 程式人生 > >【AI實戰】訓練第一個AI模型:MNIST手寫數字識別模型

【AI實戰】訓練第一個AI模型:MNIST手寫數字識別模型

在上篇文章中,我們已經把AI的基礎環境搭建好了(見文章:Ubuntu + conda + tensorflow + GPU + pycharm搭建AI基礎環境),接下來將基於tensorflow訓練第一個AI模型:MNIST手寫數字識別模型
MNIST是一個經典的手寫數字資料集,來自美國國家標準與技術研究所,由不同人手寫的0至9的數字構成,由60000個訓練樣本集和10000個測試樣本集構成,每個樣本的尺寸為28x28,以二進位制格式儲存,如下圖所示:
 
MNIST手寫數字識別模型的主要任務是:輸入一張手寫數字的影象,然後識別影象中手寫的是哪個數字。


該模型的目標明確、任務簡單,資料集規範、統一,資料量大小適中,在普通的PC電腦上都能訓練和識別,堪稱是深度學習領域的“Hello World!”,學習AI的入門必備模型。

0、AI建模主要步驟
在構建AI模型時,一般有以下主要步驟:準備資料、資料預處理、劃分資料集、配置模型、訓練模型、評估優化、模型應用,如下圖所示:
 
下面將按照主要步驟進行介紹。
【注意】由於MNIST資料集太經典了,很多深度學習書籍在介紹該入門模型案例時,基本上就是直接下載獲取資料,然後就進行模型訓練,最後得出一個準確率出來。但這樣的入門案例學習後,當要拿自己的資料來訓練模型,卻往往不知該如何處理資料、如何訓練、如何應用。在本文,將分兩種情況進行介紹:(1)使用MNIST資料(本案例),(2)使用自己的資料。


下面將針對模型訓練的各個主要環節進行介紹,便於讀者快速遷移去訓練自己的資料模型。

1、準確資料
準備資料是訓練模型的第一步,基礎資料可以是網上公開的資料集,也可以是自己的資料集。視覺、語音、語言等各種型別的資料在網上都能找到相應的資料集。
(1)使用MNIST資料(本案例)
MNIST資料集由於非常經典,已整合在tensorflow裡面,可以直接載入使用,也可以從MNIST的官網上(http://yann.lecun.com/exdb/mnist/) 直接下載資料集,程式碼如下:

from tensorflow.examples.tutorials.mnist import input_data

# 資料集路徑
data_dir='/home/roger/data/work/tensorflow/data/mnist'

# 自動下載 MNIST 資料集
mnist = input_data.read_data_sets(data_dir, one_hot=True)

# 如果自動下載失敗,則手工從官網上下載 MNIST 資料集,然後進行載入
# 下載地址  http://yann.lecun.com/exdb/mnist/
#mnist=input_data.read_data_sets(data_dir,one_hot=True)

整合或下載的MNIST資料集已經是打好標籤了,直接使用就行。


(2)使用自己的資料
如果是使用自己的資料集,在準備資料時的重要工作是“標註資料”,也就是對資料進行打標籤,主要的標註方式有:
① 整個檔案打標籤。例如MNIST資料集,每個影象只有1個數字,可以從0至9建10個資料夾,裡面放相應數字的影象;也可以定義一個規則對影象進行命名,如按標籤+序號命名;還可以在資料庫裡面建立一張對應表,儲存檔名與標籤之間的關聯關係。如下圖:
 
② 圈定區域打標籤。例如ImageNet的物體識別資料集,由於每張圖片上有各種物體,這些物體位於不同位置,因此需要圈定某個區域進行標註,目前比較流行的是VOC2007、VOC2012資料格式,這是使用xml檔案儲存圖片中某個物體的名稱(name)和位置資訊(xmin,ymin,xmax,ymax)。
如果圖片很多,一張一張去計算位置資訊,然後編寫xml檔案,實在是太耗時耗力了。所幸,有一位大神開源了一個數據標註工具labelImg(https://github.com/tzutalin/labelImg),只要在介面上畫框標註,就能自動生成VOC格式的xml檔案了,非常方便,如下圖所示:
 
③ 資料截段打標籤。針對語音識別、文字識別等,有些是將資料截成一段一段的語音或句子,然後在另外的檔案中記錄對應的標籤資訊。

2、資料預處理
在準備好基礎資料之後,需要根據模型需要對基礎資料進行相應的預處理。
(1)使用MNIST資料(本案例)
由於MNIST資料集的尺寸統一,只有黑白兩種畫素,無須再進行額外的預處理,直接拿來建模型就行。
(2)使用自己的資料
而如果是要訓練自己的資料,根據模型需要一般要進行以下預處理:
 
a. 統一格式:即統一基礎資料的格式,例如影象資料集,則全部統一為jpg格式;語音資料集,則全部統一為wav格式;文字資料集,則全部統一為UTF-8的純文字格式等,方便模型的處理;
b. 調整尺寸:根據模型的輸入要求,將樣本資料全部調整為統一尺寸。例如LeNet模型是32x32,AlexNet是224x224,VGG是224x224等;
c. 灰度化:根據模型需要,有些要求輸入灰度影象,有些要求輸入RGB彩色影象;
d. 去噪平滑:為提升輸入影象的質量,對影象進行去噪平滑處理,可使用中值濾波器、高斯濾波器等進行影象的去噪處理。如果訓練資料集的影象質量很好了,則無須作去噪處理;
e. 其它處理:根據模型需要進行直方圖均衡化、二值化、腐蝕、膨脹等相關的處理;
f. 樣本增強:有一種觀點認為神經網路是靠資料喂出來的,如果能夠增加訓練資料的樣本量,提供海量資料進行訓練,則能夠有效提升演算法的質量。常見的樣本增強方式有:水平翻轉影象、隨機裁剪、平移變換,顏色、光照變換等,如下圖所示:

3、劃分資料集
在訓練模型之前,需要將樣本資料劃分為訓練集、測試集,有些情況下還會劃分為訓練集、測試集、驗證集。
(1)使用MNIST資料(本案例)
本案例要訓練模型的MNIST資料集,已經提供了訓練集、測試集,程式碼如下:

# 提取訓練集、測試集
train_xdata = mnist.train.images
test_xdata = mnist.test.images

# 提取標籤資料
train_labels = mnist.train.labels
test_labels = mnist.test.labels

(2)使用自己的資料
如果是要劃分自己的資料集,可使用scikit-learn工具進行劃分,程式碼如下:

from sklearn.cross_validation import train_test_split

# 隨機選取75%的資料作為訓練樣本,其餘25%的資料作為測試樣本
# X_data:資料集
# y_labels:資料集對應的標籤
X_train,X_test,y_train,y_test=train_test_split(X_data,y_labels,test_size=0.25,random_state=33)

4、配置模型
接下來是選擇模型、配置模型引數,建議先閱讀深度學習經典模型的文章(見文章:大話卷積神經網路模型),便於快速掌握深度學習模型的相關知識。
(1)選擇模型
本案例將採用LeNet模型來訓練MNIST手寫數字模型,LeNet是一個經典卷積神經網路模型,結構簡單,針對MNIST這種簡單的資料集可達到比較好的效果,LeNet模型的原理介紹請見文章(大話CNN經典模型:LeNet),網路結構圖如下:
 
(2)設定引數
在訓練模型時,一般要設定的引數有:

step_cnt=10000    # 訓練模型的迭代步數
batch_size = 100    # 每次迭代批量取樣本資料的量
learning_rate = 0.001    # 學習率

除此之外還有卷積層權重和偏置、池化層權重、全聯接層權重和偏置、優化函式等等,根據模型需要進行設定。

5、訓練模型
接下來便是根據選擇好的模型,構建網路,然後開始訓練。
(1)構建模型
本案例按照LeNet的網路模型結構,構建網路模型,網路結果如下
 
程式碼如下:

# 訓練資料,佔位符
x = tf.placeholder("float", shape=[None, 784])
# 訓練的標籤資料,佔位符
y_ = tf.placeholder("float", shape=[None, 10])
# 將樣本資料轉為28x28
x_image = tf.reshape(x, [-1, 28, 28, 1])

# 保留概率,用於 dropout 層
keep_prob = tf.placeholder(tf.float32)

# 第一層:卷積層
# 卷積核尺寸為5x5,通道數為1,深度為32,移動步長為1,採用ReLU激勵函式
conv1_weights = tf.get_variable("conv1_weights", [5, 5, 1, 32], initializer=tf.truncated_normal_initializer(stddev=0.1))
conv1_biases = tf.get_variable("conv1_biases", [32], initializer=tf.constant_initializer(0.0))
conv1 = tf.nn.conv2d(x_image, conv1_weights, strides=[1, 1, 1, 1], padding='SAME')
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))

# 第二層:最大池化層
# 池化核的尺寸為2x2,移動步長為2,使用全0填充
pool1 = tf.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

# 第三層:卷積層
# 卷積核尺寸為5x5,通道數為32,深度為64,移動步長為1,採用ReLU激勵函式
conv2_weights = tf.get_variable("conv2_weights", [5, 5, 32, 64], initializer=tf.truncated_normal_initializer(stddev=0.1))
conv2_biases = tf.get_variable("conv2_biases", [64], initializer=tf.constant_initializer(0.0))
conv2 = tf.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding='SAME')
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_biases))

# 第四層:最大池化層
# 池化核尺寸為2x2, 移動步長為2,使用全0填充
pool2 = tf.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

# 第五層:全連線層
fc1_weights = tf.get_variable("fc1_weights", [7 * 7 * 64, 1024],
                              initializer=tf.truncated_normal_initializer(stddev=0.1))
fc1_baises = tf.get_variable("fc1_baises", [1024], initializer=tf.constant_initializer(0.1))
pool2_vector = tf.reshape(pool2, [-1, 7 * 7 * 64])
fc1 = tf.nn.relu(tf.matmul(pool2_vector, fc1_weights) + fc1_baises)

# Dropout層(即按keep_prob的概率保留資料,其它丟棄),以防止過擬合
fc1_dropout = tf.nn.dropout(fc1, keep_prob)

# 第六層:全連線層
fc2_weights = tf.get_variable("fc2_weights", [1024, 10],
                              initializer=tf.truncated_normal_initializer(stddev=0.1))  # 神經元節點數1024, 分類節點10
fc2_biases = tf.get_variable("fc2_biases", [10], initializer=tf.constant_initializer(0.1))
fc2 = tf.matmul(fc1_dropout, fc2_weights) + fc2_biases

# 第七層:輸出層
y_conv = tf.nn.softmax(fc2)

(2)訓練模型
在訓練模型時,需要選擇優化器,也就是說要告訴模型以什麼策略來提升模型的準確率,一般是選擇交叉熵損失函式,然後使用優化器在反向傳播時最小化損失函式,從而使模型的質量在不斷迭代中逐步提升。
程式碼如下:

# 定義交叉熵損失函式
# y_ 為真實標籤
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1]))

# 選擇優化器,使優化器最小化損失函式
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

# 返回模型預測的最大概率的結果,並與真實值作比較
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))

# 用平均值來統計測試準確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 訓練模型
saver=tf.train.Saver()
with tf.Session() as sess:
    tf.global_variables_initializer().run()

    for step in range(step_cnt):
        batch = mnist.train.next_batch(batch_size)
        if step % 100 == 0:
            # 每迭代100步進行一次評估,輸出結果,儲存模型,便於及時瞭解模型訓練進展
            train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
            print("step %d, training accuracy %g" % (step, train_accuracy))
            saver.save(sess,model_dir+'/my_mnist_model.ctpk',global_step=step)
        train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.8})

    # 使用測試資料測試準確率
    print("test accuracy %g" % accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

訓練的結果如下,由於MNIST資料集比較簡單,模型訓練很快就達到99%的準確率,如下圖所示:
 
模型訓練後儲存的結果如下圖所示:

6、評估優化
在使用訓練資料完成模型的訓練之後,再使用測試資料進行測試,瞭解模型的泛化能力,程式碼如下

# 使用測試資料測試準確率
test_acc=accuracy.eval(feed_dict={x: test_xdata, y_: test_labels, keep_prob: 1.0})
print("test accuracy %g" %test_acc)

模型測試結果如下:
 
7、模型應用
模型訓練完成後,將模型儲存起來,當要實際應用時,則通過載入模型,輸入影象進行應用。程式碼如下:

# 載入 MNIST 模型
saver = tf.train.Saver()
with tf.Session() as sess:
    saver.restore(sess, tf.train.latest_checkpoint(model_dir))

    # 隨機提取 MNIST 測試集的一個樣本資料和標籤
    test_len=len(mnist.test.images)
    test_idx=random.randint(0,test_len-1)
    x_image=mnist.test.images[test_idx]
    y=np.argmax(mnist.test.labels[test_idx])

    # 跑模型進行識別
    y_conv = tf.argmax(y_conv,1)
    pred=sess.run(y_conv,feed_dict={x:[x_image], keep_prob: 1.0})

    print('正確:',y,',預測:',pred[0])

使用模型進行測試的結果如下圖:

至此,一個完整的模型訓練和應用的過程就介紹完了。
接下來還會有更多AI實戰的精彩內容,敬請期待!

 

獲取完整原始碼

想要閱讀本案例的 完整程式碼,請關注本人公眾號“大資料與人工智慧Lab”(BigdataAILab),然後回覆“程式碼”關鍵字可檢視完整的原始碼。

 

 

推薦相關閱讀

  •  

關注本人公眾號“大資料與人工智慧Lab”(BigdataAILab),獲取更多資訊