轉載自: https://my.oschina.net/u/876354/blog/1926060
在上篇文章中,我們已經把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])
使用模型進行測試的結果如下圖:
至此,一個完整的模型訓練和應用的過程就介紹完了。