写给初学者的深度学习教程之 MNIST 数字识别
一般而言,MNIST 數據集測試就是機器學習和深度學習當中的"Hello World"工程,幾乎是所有的教程都會把它放在最開始的地方.這是因為,這個簡單的工程包含了大致的機器學習流程,通過練習這個工程有助于讀者加深理解機器學習或者是深度學習的大致流程.
但恰恰有那么一部分同學,由于初入深度學習這個領域,腦海中還沒有清晰的概念,所以即使是 MNIST 數字識別這樣簡單的例子,我覺得也應該有人稍微詳細地講解一下。
本文的目的就是用更耐心的方式去引導初學者理解深度學習的大致流程和操作技巧.
最核心的模型
無論是機器學習還是深度學習,都繞不過模型.深度學習中的模型主要是各種神經網絡.
但只有模型是不夠的,前提條件其實是數據,然后,后置的操作是訓練,再之后是測試.
這里寫圖片描述
模型通過不斷的訓練從數據中學習,然后通過測試去驗證模型的正確性.
MNIST 數字識別工程,也是為了確定一個模型,然后進行訓練,訓練過程中這個模型從大量的數字圖片中學習得到識別手寫數字的能力,最后,需要測試驗證這個模型是否足夠理想和優秀.
MNIST 數字識別項目,模型可以是傳統的機器學習中的模型,也可以使用深度學習中的神經網絡.在本文中,我使用的是 CNN,然后用的是 Python 和 Tensorflow.
MNIST 是什么?
MNIST 是一個小型的手寫數字圖片庫,它總共有 60000 張圖片,其中 50000 張訓練圖片,10000 張測試圖片.每張圖片的像素都是 28 * 28
這里寫圖片描述
MNIST 對應上圖數據這一塊,它需要導入到模型當中.
從數據到模型一般而言是需要轉化的,這一步叫做數據預處理。Tensorflow 接受 Numpy 中的 ndarray ,所以要想辦法進行轉換.
它的官網地址如下:
http://yann.lecun.com/exdb/mnist/
數據庫其實只由 4 個文件組成.
這里寫圖片描述
下載下來,然后分別解壓縮,可以發現其實只是 4 個 bin 文件.
這里寫圖片描述
train-images.idx3-ubyte 包含了 50000 張訓練圖片.
train-labels.idx1-ubyte 包含了 50000 個標簽.
t10k-images.idx3-ubyte 包含了 10000 張訓練圖片.
t10k-labels.idx1-ubyte 包含了 10000 個標簽.
在這里有一點,非常重要,MNIST 將需要圖片或者標簽全部寫入到一個 bin 文件當中去了,如果要讀取某張圖片和對應的標簽值就需要按照一定的方法從 bin 文件中分割.
不過,MNIST 官網有 bin 文件的結果說明,所以根據結構很容易編寫代碼實現.
這里寫圖片描述
我們先看,訓練用的標簽文件.
0000 起始位置是一個魔數 數值為 2049
0004 文件這個地方存放的數值是 6000 代表 6000 個標簽
0008 文件這個地方開始按順序存放與訓練圖片對應的數字標簽 數值 0~9 我想大家都知道是什么吧
??? 1
??? 2
??? 3
所以,如果我們要讀取標簽的話,從標簽文件開始偏移8個ubyte就能讀取所有的標簽數值了.
再看看訓練用的圖片集文件
0000 位置也是一個魔數
0004 代表了本文件中的圖片數量
0008 文件這個位置存放的是一張圖片的高
0012 文件這個位置存放的是一張圖片的寬
0016 從這里起,代表的是圖像中的每一個像素點
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
如果我們想要讀取第一張圖片怎么辦?
從文件起始位置偏移16個byte,然后讀取后面的28*28也就是 784 個字節.
如果要讀取第二張圖片能?
從文件起始位置偏移16+(2-1)*784個byte,然后讀取后面的28*28個字節.
讀取第 n 張圖片時
從文件起始位置偏移 16+(n-1)*784 個byte,然后讀取后面的28*28個字節.
一切都是有套路可以循的.
至于測試圖片集文件和測試標簽集文件,跟上面的類似,就不繼續分析了.
我們可以自己按照bin文件的格式提取圖片和標簽,但考慮到這個沒有技術含量又枯燥無畏,常見的機器學習框架都預置了對MNIST的處理,如scklean和Tensorflow,并不需要我們動手.極大減低了我們的痛苦
人生苦短,我用python大概就是這個意思.
接下來的內容,我們可以看到 Tensorflow 可以很輕松地實現對 MNIST 中數據的讀取.
Tensorflow 讀取MNIST圖片數據
前面說過 Tensorflow 能很容易對 MNIST 進行讀取和格式轉換,其實是因為 Tensorflow 示例教程替我們做了這一部分的工作.
from tensorflow.examples.tutorials.mnist import input_data
??? 1
從mnist這個模塊中引入 input_data 這個類.
# MNIST_data 代表當前程序文件所在的目錄中,用于存放MNIST數據的文件夾,如果沒有則新建,然后下載.
mnist = input_data.read_data_sets("MNIST_data",one_hot=True)
??? 1
??? 2
只需要調用 input_data 的 read_data_sets() 方法就好了。
如果當前文件所在目錄中,不存在 MNIST_data 這個目錄的話,程序會自動下載 MNIST 數據到這個位置,如果已經存在了的話,就直接讀取數據文件。
把所有的圖片讀取出來后,創建一個 mnist,mnist 是一個 dataset 類實例,里面有許多 numpy 數組,存放圖片和標簽.
需要注意的是 MNIST 本身數據集分為兩個部分.
訓練集 和測試集
但在 input_data 中,人為增加了驗證集,默認 5000 張圖片.
validation_images = train_images[:validation_size]
validation_labels = train_labels[:validation_size]
train_images = train_images[validation_size:]
train_labels = train_labels[validation_size:]
??? 1
??? 2
??? 3
??? 4
所以,最終有3個數據集:
訓練集、測試集、驗證集
通過 mnist 對象可以輕松訪問它們,如下面代碼所示
mnist.train.images
mnist.train.labels
mnist.test.images
mnist.test.labels
mnist.validation.images
mnist.validation.labels
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
需要注意的是,讀取后的圖片數據,每張圖片就是numpy 數組中一行的數據.
我們簡單打印一下
print(mnist.train.images.shape)
print(mnist.train.labels.shape)
??? 1
??? 2
打印的結果如下:
(55000, 784)
(55000, 10)
??? 1
??? 2
可以看到,train.images 數組行數為55000 列數為 784,代表了 55000 張測試圖片.
好奇的同學也可以將測試圖片可視化的方式呈現.
#獲取第二張圖片
image = mnist.train.images[1,:]
#將圖像數據還原成28*28的分辨率
image = image.reshape(28,28)
#打印對應的標簽
print(mnist.train.labels[1])
plt.figure()
plt.imshow(image)
plt.show()
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
??? 9
??? 10
這里寫圖片描述
可以看到圖片其實是數字3,標簽內容如下.
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
??? 1
這是 one-hot 的形式,它代表標簽值是 3.
Tensorflow 設置 CNN 結構
上面的內容介紹了如何在 Tensorflow 中讀取 MNIST 數據集的圖片和標簽,接下來要做的事情就是搞定模塊這一環節.
這里寫圖片描述
模型我選定的是 CNN,也就是卷積神經網絡,在這里我假設大家都明白 CNN 的概念,我要確定一個 CNN 來學習如何識別手寫數字的能力.
為了簡單起見,我確定了一個 4 層的神經網絡.
這里寫圖片描述
從左到右,分別是輸入層、卷積層、全連接層、輸出層.
??? 卷積層我用了 3x3 的卷積核,數量為 32 stride為 1
??? 激活方法用了 relu
??? 然后用了池化層 2x2 的核 stride 為 2
??? fc1 層用了 784 個神經元
??? output 層 10 個神經元,用于預測一張測試圖片中每個數字的概率,其中的概率經 softmax 處理過
本文想測試一下,就是這個再簡單不過的卷積神經網絡,它對 MNIST 中數字的識別效果如何.
下面就是代碼
# None 代表圖片數量未知
input = tf.placeholder(tf.float32,[None,784])
# 將input 重新調整結構,適用于CNN的特征提取
input_image = tf.reshape(input,[-1,28,28,1])
# y是最終預測的結果
y = tf.placeholder(tf.float32,[None,10])
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
因為 Tensorflow 一次可以訓練多張圖片,所以要用一個占位符 placeholder 這樣具體數值可以在后面訓練時動態分配.
# input 代表輸入,filter 代表卷積核
def conv2d(input,filter):
??? return tf.nn.conv2d(input,filter,strides=[1,1,1,1],padding='SAME')
# 池化層
def max_pool(input):
??? return tf.nn.max_pool(input,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
# 初始化卷積核或者是權重數組的值
def weight_variable(shape):
??? initial = tf.truncated_normal(shape,stddev=0.1)
??? return tf.Variable(initial)
# 初始化bias的值
def bias_variable(shape):
??? return tf.Variable(tf.zeros(shape))
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
??? 9
??? 10
??? 11
??? 12
??? 13
??? 14
??? 15
??? 16
上面4個方法都是工具方法,為了幫助我們創造神經網絡的.
??? conv2d() 是創造卷積層的方法.
??? max_pool() 是池化層.
??? 然后剩下的兩個方法都是為了初始化超參數的.
#[filter_height, filter_width, in_channels, out_channels]
#定義了卷積核
filter = [3,3,1,32]
filter_conv1 = weight_variable(filter)
b_conv1 = bias_variable([32])
# 創建卷積層,進行卷積操作,并通過Relu激活,然后池化
h_conv1 = tf.nn.relu(conv2d(input_image,filter_conv1)+b_conv1)
h_pool1 = max_pool(h_conv1)
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
??? 9
定義了卷積層的結構.
h_flat = tf.reshape(h_pool1,[-1,14*14*32])
W_fc1 = weight_variable([14*14*32,784])
b_fc1 = bias_variable([784])
h_fc1 = tf.matmul(h_flat,W_fc1) + b_fc1
W_fc2 = weight_variable([784,10])
b_fc2 = bias_variable([10])
y_hat = tf.matmul(h_fc1,W_fc2) + b_fc2
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
??? 9
??? 10
??? 11
h_flat 是將 pool 后的卷積核全部拉平成一行數據,便于和后面的全連接層進行數據運算.
y_hat 是整個神經網絡的輸出層,包含 10 個結點.
cross_entropy = tf.reduce_mean(
??? tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=y_hat ))
??? 1
??? 2
代價函數采用了 cross_entropy,顯然,整個模型輸出的值經過了 softmax 處理,將輸出的值換算成每個類別的概率.
到這里,神經網絡結構我們就確定了,下面要做的就是訓練神經網絡和測試神經網絡了.
訓練神經網絡
這里寫圖片描述
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
??? 1
在這里,定義了一個梯度下降的訓練器,學習率是0.01.
train_step 其實就是一個黑盒子,它隱去了很多的技術細節,但同時也極大方便了我們的開發.
我們只需要知道,train_step在每一次訓練后都會調整神經網絡中參數的值,以便 cross_entropy 這個代價函數的值最低,也就是為了神經網絡的表現越來越好.
correct_prediction = tf.equal(tf.argmax(y_hat,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
??? 1
??? 2
??? 3
上面代碼的目的是定義準確率,我們會在后面的代碼中周期性地打印準確率,訓練測試后,我們還要打印測試集下面神經網絡的準確率.
with tf.Session() as sess:
??? sess.run(tf.global_variables_initializer())
??? for i in range(10000):
??????? batch_x,batch_y = mnist.train.next_batch(50)
??????? if i % 100 == 0:
??????????? train_accuracy = accuracy.eval(feed_dict={input:batch_x,y:batch_y})
??????????? print("step %d,train accuracy %g " %(i,train_accuracy))
??????? train_step.run(feed_dict={input:batch_x,y:batch_y})
??? print("test accuracy %g " % accuracy.eval(feed_dict={input:mnist.test.images,y:mnist.test.labels}))
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
??? 9
??? 10
??? 11
??? 12
??? 13
??? 14
我們的 epoch 是 10000 次,也就是說需要訓練10000個周期.每個周期訓練都是小批量訓練 50 張,然后每隔 100 個訓練周期打印階段性的準確率.
訓練完成后,還需要驗證測試集下的準確度
step 9600,train accuracy 0.98
step 9700,train accuracy 0.96
step 9800,train accuracy 1
step 9900,train accuracy 1
test accuracy 0.9766
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
最終的測試成績,準確率 97.66%.
那么準確率為 97.66 % 算不算高呢?
其實,非常不錯了.我們文章采取的模型是我自己設置的最簡單的模型.但即使這樣,相比于傳統的機器學習方法,它的確不錯了.大家可以去官網看看不同的模型,在 MNIST 測試時的表現.
下面是完整代碼,我是 Python3.5 + Tensorflow1.7
mnist_conv.py
# coding:utf-8
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data",one_hot=True)
input = tf.placeholder(tf.float32,[None,784])
input_image = tf.reshape(input,[-1,28,28,1])
y = tf.placeholder(tf.float32,[None,10])
# input 代表輸入,filter 代表卷積核
def conv2d(input,filter):
??? return tf.nn.conv2d(input,filter,strides=[1,1,1,1],padding='SAME')
# 池化層
def max_pool(input):
??? return tf.nn.max_pool(input,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
# 初始化卷積核或者是權重數組的值
def weight_variable(shape):
??? initial = tf.truncated_normal(shape,stddev=0.1)
??? return tf.Variable(initial)
# 初始化bias的值
def bias_variable(shape):
??? return tf.Variable(tf.zeros(shape))
#[filter_height, filter_width, in_channels, out_channels]
#定義了卷積核
filter = [3,3,1,32]
filter_conv1 = weight_variable(filter)
b_conv1 = bias_variable([32])
# 創建卷積層,進行卷積操作,并通過Relu激活,然后池化
h_conv1 = tf.nn.relu(conv2d(input_image,filter_conv1)+b_conv1)
h_pool1 = max_pool(h_conv1)
h_flat = tf.reshape(h_pool1,[-1,14*14*32])
W_fc1 = weight_variable([14*14*32,768])
b_fc1 = bias_variable([768])
h_fc1 = tf.matmul(h_flat,W_fc1) + b_fc1
W_fc2 = weight_variable([768,10])
b_fc2 = bias_variable([10])
y_hat = tf.matmul(h_fc1,W_fc2) + b_fc2
?
cross_entropy = tf.reduce_mean(
??? tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=y_hat ))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
#train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_hat,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
with tf.Session() as sess:
??? sess.run(tf.global_variables_initializer())
??? for i in range(10000):
??????? batch_x,batch_y = mnist.train.next_batch(50)
??????? if i % 100 == 0:
??????????? train_accuracy = accuracy.eval(feed_dict={input:batch_x,y:batch_y})
??????????? print("step %d,train accuracy %g " %(i,train_accuracy))
??????? train_step.run(feed_dict={input:batch_x,y:batch_y})
??????? # sess.run(train_step,feed_dict={x:batch_x,y:batch_y})
??? print("test accuracy %g " % accuracy.eval(feed_dict={input:mnist.test.images,y:mnist.test.labels}))
??? 1
??? 2
??? 3
??? 4
??? 5
??? 6
??? 7
??? 8
??? 9
??? 10
??? 11
??? 12
??? 13
??? 14
??? 15
??? 16
??? 17
??? 18
??? 19
??? 20
??? 21
??? 22
??? 23
??? 24
??? 25
??? 26
??? 27
??? 28
??? 29
??? 30
??? 31
??? 32
??? 33
??? 34
??? 35
??? 36
??? 37
??? 38
??? 39
??? 40
??? 41
??? 42
??? 43
??? 44
??? 45
??? 46
??? 47
??? 48
??? 49
??? 50
??? 51
??? 52
??? 53
??? 54
??? 55
??? 56
??? 57
??? 58
??? 59
??? 60
??? 61
??? 62
??? 63
??? 64
??? 65
??? 66
??? 67
??? 68
??? 69
??? 70
??? 71
??? 72
??? 73
??? 74
??? 75
??? 76
??? 77
??? 78
??? 79
擴展
本文中的神經網絡,麻雀雖小,但五臟俱全.
不過,同學們可以持續優化它,畢竟有的神經網絡能夠達到 99.67% 的準確率.
??? 設計更深的層次的神經網絡,本文只有4層,并且這4層還包括輸入輸出層,同學們可以擴展更多的層,變現效果肯定更好.
??? 使用其它的優化器,比如 AdamOptimizer
??? 使用 dropout 優化手段
??? 使用數據增強技術,讓 MNIST 可供訓練的圖片更多,這樣神經網絡學習也更充分
??? 用 Tensorboard 記錄訓練過程的準確率或者 cross_entropy 的數值,最后生成可視化的報表
最終,還是要建議同學們自己動手敲一遍代碼,敲完然后思考一下,為什么要這樣寫,等你能夠比較流利敲出代碼時,你就通過 MNIST 基本掌握了深度學習的一些套路,這會提高你在后續學習中的興致.如果你不親手敲代碼的化,那么深度學習的很多概念,你沒有辦法讓它直觀起來,并且你會把它們忘掉.
最后,如果應對了 MNIST 之后,我們就可以將目光放到更復雜的數據集上去。比如 CIFAR10,比如自動駕駛中的行人識別。
光看書是不行的,真的要親手實踐。
總結
以上是生活随笔為你收集整理的写给初学者的深度学习教程之 MNIST 数字识别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用上微软Bosque 困扰程序员30年的
- 下一篇: 使用Tensorflow构建和训练自己的