當咱們開始學習編程的時候,第一件事每每是學習打印"Hello World"。就比如編程入門有Hello World,機器學習入門有MNIST。html
MNIST是一個入門級的計算機視覺數據集,它包含各類手寫數字圖片:python
它也包含每一張圖片對應的標籤,告訴咱們這個是數字幾。好比,上面這四張圖片的標籤分別是5,0,4,1
。web
其實訓練一個簡單的手寫數字識別模型的代碼很短,個人示例代碼總共也就50行,除去註釋、空格之類的估計連30行也沒有,可是去理解包含在代碼中的設計思想是很重要的,所以這篇筆記我會將我對每段代碼的理解都記錄下來。算法
參考:編程
MNIST機器學習入門api
MNIST數據集的官網是Yann LeCun's website。 雖然python提供了直接下載這個數據集的代碼,可是考慮到國內網絡的緣由,建議點這下載數據集,而後導入到項目根目錄下就能夠了。 bash
下載下來的數據集被分紅兩部分:60000行的訓練數據集(mnist.train)和10000行的測試數據集(mnist.test)。這樣的劃分很重要,在機器學習模型設計時必須有一個單獨的測試數據集不用於訓練而是用來評估這個模型的性能,從而更加容易把設計的模型推廣到其餘數據集上(泛化)。正如前面提到的同樣,每個MNIST數據單元有兩部分組成:一張包含手寫數字的圖片和一個對應的標籤。咱們把這些圖片設爲xs
,把這些標籤設爲ys
。訓練數據集和測試數據集都包含xs
和ys
,好比訓練數據集的圖片是 mnist.train.images
,訓練數據集的標籤是 mnist.train.labels
。網絡
每一張圖片包含28像素X28像素。咱們能夠用一個數字數組來表示這張圖片:機器學習
咱們把這個數組展開成一個向量,長度是 28x28 = 784。如何展開這個數組(數字間的順序)不重要,只要保持各個圖片採用相同的方式展開就能夠了。
所以,在MNIST訓練數據集中,mnist.train.images
是一個形狀爲 [60000, 784]
的張量,第一個維度數字用來索引圖片,第二個維度數字用來索引每張圖片中的像素點。在此張量裏的每個元素,都表示某張圖片裏的某個像素的強度值,值介於0和1之間。
相對應的MNIST數據集的標籤是介於0到9的數字,用來描述給定圖片裏表示的數字。爲了用於這個教程,咱們使標籤數據是"one-hot vectors"。 一個one-hot
向量除了某一位的數字是1之外其他各維度數字都是0。因此在此教程中,數字n將表示成一個只有在第n維度(從0開始)數字爲1的10維向量。好比,標籤0將表示成([1,0,0,0,0,0,0,0,0,0,0])。所以, mnist.train.labels
是一個 [60000, 10]
的數字矩陣。
如今,咱們準備好能夠開始構建咱們的模型啦!
(由於這段很枯燥,並且我也解釋不太好,因此乾脆直接從Tensorflow的網站上覆制粘貼來了,若是不想看的能夠直接跳過到模型實現,最後寫代碼的時候只要知道用softmax函數就能夠了)
咱們知道MNIST的每一張圖片都表示一個數字,從0到9。咱們但願獲得給定圖片表明每一個數字的機率。好比說,咱們的模型可能推測一張包含9的圖片表明數字9的機率是80%可是判斷它是8的機率是5%(由於8和9都有上半部分的小圓),而後給予它表明其餘數字的機率更小的值。
這是一個使用softmax迴歸(softmax regression)模型的經典案例。softmax模型能夠用來給不一樣的對象分配機率。即便在以後,咱們訓練更加精細的模型時,最後一步也須要用softmax來分配機率。
softmax迴歸(softmax regression)分兩步:第一步
爲了獲得一張給定圖片屬於某個特定數字類的證據(evidence),咱們對圖片像素值進行加權求和。若是這個像素具備很強的證聽說明這張圖片不屬於該類,那麼相應的權值爲負數,相反若是這個像素擁有有利的證據支持這張圖片屬於這個類,那麼權值是正數。
下面的圖片顯示了一個模型學習到的圖片上每一個像素對於特定數字類的權值。紅色表明負數權值,藍色表明正數權值。
咱們也須要加入一個額外的偏置量(bias),由於輸入每每會帶有一些無關的干擾量。所以對於給定的輸入圖片 x 它表明的是數字 i 的證據能夠表示爲
其中 表明權重,表明數字 i 類的偏置量,j 表明給定圖片 x 的像素索引用於像素求和。而後用softmax函數能夠把這些證據轉換成機率 y:
這裏的softmax能夠當作是一個激勵(activation)函數或者連接(link)函數,把咱們定義的線性函數的輸出轉換成咱們想要的格式,也就是關於10個數字類的機率分佈。所以,給定一張圖片,它對於每個數字的吻合度能夠被softmax函數轉換成爲一個機率值。softmax函數能夠定義爲:
展開等式右邊的子式,能夠獲得:
可是更多的時候把softmax模型函數定義爲前一種形式:把輸入值當成冪指數求值,再正則化這些結果值。這個冪運算表示,更大的證據對應更大的假設模型(hypothesis)裏面的乘數權重值。反之,擁有更少的證據意味着在假設模型裏面擁有更小的乘數係數。假設模型裏的權值不能夠是0值或者負值。Softmax而後會正則化這些權重值,使它們的總和等於1,以此構造一個有效的機率分佈。(更多的關於Softmax函數的信息,能夠參考Michael Nieslen的書裏面的這個部分,其中有關於softmax的可交互式的可視化解釋。)
對於softmax迴歸模型能夠用下面的圖解釋,對於輸入的xs
加權求和,再分別加上一個偏置量,最後再輸入到softmax函數中:
若是把它寫成一個等式,咱們能夠獲得:
咱們也能夠用向量表示這個計算過程:用矩陣乘法和向量相加。這有助於提升計算效率。(也是一種更有效的思考方式)
更進一步,能夠寫成更加緊湊的方式:
在使用TensorFlow以前,首先導入它:
import tensorflow as tf
複製代碼
而後導入數據集並載入數據
from tensorflow.examples.tutorials.mnist import input_data
# 載入數據
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
複製代碼
咱們經過操做符號變量來描述這些可交互的操做單元,能夠用下面的方式建立一個:
x = tf.placeholder(tf.float32, [None, 784])
複製代碼
x
不是一個特定的值,而是一個佔位符placeholder
,咱們在TensorFlow運行計算時輸入這個值。咱們但願可以輸入任意數量的MNIST圖像,每一張圖展平成784維的向量。咱們用2維的浮點數張量來表示這些圖,這個張量的形狀是[None,784 ]
。(這裏的None
表示此張量的第一個維度能夠是任何長度的。)
咱們的模型也須要權重和偏量,固然咱們能夠把它們當作是另外的輸入(使用佔位符),但TensorFlow有一個更好的方法來表示它們:Variable
。 一個Variable
表明一個可修改的張量,存在在TensorFlow的用於描述交互性操做的圖中。它們能夠用於計算輸入值,也能夠在計算中被修改。對於各類機器學習應用,通常都會有模型參數,能夠用Variable
表示。
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
複製代碼
咱們賦予tf.Variable
不一樣的初值來建立不一樣的Variable
:在這裏,咱們都用全爲零的張量來初始化W
和b
。由於咱們要學習W
和b
的值,它們的初值能夠隨意設置。
注意,W
的維度是[784,10],由於咱們想要用784維的圖片向量乘以它以獲得一個10維的證據值向量,每一位對應不一樣數字類。b
的形狀是[10],因此咱們能夠直接把它加到輸出上面。
如今,咱們能夠實現咱們的模型啦。只須要一行代碼!
prediction = tf.nn.softmax(tf.matmul(x, W)+b)
複製代碼
首先,咱們用tf.matmul(X,W)
表示x
乘以W
,對應以前等式裏面的Wx,這裏x
是一個2維張量擁有多個輸入。而後再加上b
,把和輸入到tf.nn.softmax
函數裏面。
爲了訓練咱們的模型,咱們首先須要定義一個指標來評估這個模型是好的。其實,在機器學習,咱們一般定義指標來表示一個模型是壞的,這個指標稱爲成本(cost)或損失(loss),而後儘可能最小化這個指標。可是,這兩種方式是相同的。損失函數有不少種,在這裏咱們採用平方損失函數,一般咱們會用均方差(MSE)做爲衡量指標,公式以下:
爲了計算損失函數,咱們首先須要添加一個新的佔位符用於輸入正確值:
y = tf.placeholder(tf.float32, [None, 10])
複製代碼
而後定義損失函數(loss):
# 二次代價函數
loss = tf.reduce_mean(tf.square(y-prediction))
複製代碼
這段代碼的含義我在上一篇筆記中已經介紹過了,不清楚的推薦閱讀TensorFlow筆記(2)——利用TensorFlow訓練一個最簡單的一元線性模型
而後使用優化算法來不斷的修改變量來下降損失值:
# 使用梯度降低法
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
複製代碼
在這裏,咱們要求TensorFlow用梯度降低算法(gradient descent algorithm)以0.01的學習速率最小化交叉熵。梯度降低算法(gradient descent algorithm)是一個簡單的學習過程,TensorFlow只需將每一個變量一點點地往使成本不斷下降的方向移動。固然TensorFlow也提供了其餘許多優化算法:只要簡單地調整一行代碼就可使用其餘的算法。
TensorFlow在這裏實際上所作的是,它會在後臺給描述你的計算的那張圖裏面增長一系列新的計算操做單元用於實現反向傳播算法和梯度降低算法。而後,它返回給你的只是一個單一的操做,當運行這個操做時,它用梯度降低算法訓練你的模型,微調你的變量,不斷減小成本。
如今,咱們已經設置好了咱們的模型。在運行計算以前,咱們須要添加一個操做來初始化咱們建立的變量:
# 初始化變量
init = tf.global_variables_initializer()
複製代碼
接下來咱們就能夠定義一個會話了,並在該會話中執行初始化變量的操做:
with tf.Session() as sess:
sess.run(init)
複製代碼
而後開始訓練模型,咱們須要先定義一個批次batch_size
,由於咱們在訓練的時候不可能每次都只放一張圖片進入神經網絡(由於這樣太慢了),批次爲100在這表示的就是咱們一次放入100張圖片進入神經網絡,而後咱們須要計算一共會有多少個批次:
# 每一個批次的大小
batch_size = 100
# 計算一共有多少個批次
n_batch = mnist.train.num_examples // batch_size
複製代碼
而後咱們讓模型循環訓練30次:
with tf.Session() as sess:
sess.run(init)
for epoch in range(30):
for batch in range(n_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
複製代碼
該循環的每一個步驟中,咱們都會隨機抓取訓練數據中的n_batch
個批處理數據點,而後咱們用這些數據點做爲參數替換以前的佔位符來運行train_step
。
使用一小部分的隨機數據來進行訓練被稱爲隨機訓練(stochastic training)- 在這裏更確切的說是隨機梯度降低訓練。在理想狀況下,咱們但願用咱們全部的數據來進行每一步的訓練,由於這能給咱們更好的訓練結果,但顯然這須要很大的計算開銷。因此,每一次訓練咱們可使用不一樣的數據子集,這樣作既能夠減小計算開銷,又能夠最大化地學習到數據集的整體特性。
那麼咱們的模型性能如何呢?
首先讓咱們找出那些預測正確的標籤。tf.argmax
是一個很是有用的函數,它能給出某個tensor對象在某一維上的其數據最大值所在的索引值。因爲標籤向量是由0,1組成,所以最大值1所在的索引位置就是類別標籤,好比tf.argmax(y,1)
返回的是模型對於任一輸入x預測到的標籤值,而 tf.argmax(prediction,1)
表明正確的標籤,咱們能夠用 tf.equal
來檢測咱們的預測是否真實標籤匹配(索引位置同樣表示匹配)。
# 結果存放在一個布爾型列表中
# argmax返回一維張量中最大的值所在的位置
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
複製代碼
這行代碼會給咱們一組布爾值。爲了肯定正確預測項的比例,咱們能夠把布爾值轉換成浮點數,而後取平均值。例如,[True, False, True, True]
會變成 [1,0,1,1]
,取平均值後獲得 0.75
.
# 求準確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
複製代碼
最後,咱們計算所學習到的模型在測試數據集上面的正確率。
with tf.Session() as sess:
sess.run(init)
for epoch in range(30):
for batch in range(n_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
acc = sess.run(accuracy, feed_dict={
x: mnist.test.images, y: mnist.test.labels})
print("Iter "+str(epoch)+",Testing Accuracy "+str(acc))
複製代碼
最終結果以下圖所示,精確度大約在90%
我加了datetime
這個包,目的是爲了計算代碼的執行時間,不影響閱讀。
import datetime
# 3.2 MNIST數據集分類簡單版本
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
start = datetime.datetime.now()
# 載入數據
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# 每一個批次的大小
batch_size = 100
# 計算一共有多少個批次
n_batch = mnist.train.num_examples // batch_size
# 定義兩個placeholder
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
# 建立一個簡單的神經網絡
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
prediction = tf.nn.softmax(tf.matmul(x, W)+b)
# 二次代價函數
loss = tf.reduce_mean(tf.square(y-prediction))
# 使用梯度降低法
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
# 初始化變量
init = tf.global_variables_initializer()
# 結果存放在一個布爾型列表中
# argmax返回一維張量中最大的值所在的位置
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
# 求準確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess:
sess.run(init)
for epoch in range(30):
for batch in range(n_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
acc = sess.run(accuracy, feed_dict={
x: mnist.test.images, y: mnist.test.labels})
print("Iter "+str(epoch)+",Testing Accuracy "+str(acc))
end = datetime.datetime.now()
print((end-start).seconds)
複製代碼