TensorFlow的MNIST手寫數(shù)字分類問題 基礎(chǔ)篇

2020-11-23 14:11 更新

本章節(jié)的閱讀對象是對機(jī)器學(xué)習(xí)和 TensorFlow 都不太了解的新手.

就像我們學(xué)習(xí)編程的第一步往往是學(xué)習(xí)敲出 "Hello World" 一樣,機(jī)器學(xué)習(xí)的入門就要知道 MNIST.

MNIST 是一個入門級的計算機(jī)視覺數(shù)據(jù)集,它包含各種手寫數(shù)字圖片:


它也包含每一張圖片對應(yīng)的標(biāo)簽,告訴我們這個是數(shù)字幾;比如,上面這四張圖片的標(biāo)簽分別是 5, 0, 4, 1。

在此教程中,我們將訓(xùn)練一個機(jī)器學(xué)習(xí)模型用于預(yù)測圖片里面的數(shù)字.

我們的目的不是要設(shè)計一個世界一流的復(fù)雜模型 —— 盡管我們會在之后給你源代碼去實現(xiàn)一流的預(yù)測模型 —— 而是要介紹下如何使用 TensorFlow 所以,我們這里會從一個很簡單的數(shù)學(xué)模型開始,它叫做 Softmax Regression.

對應(yīng)這個教程的實現(xiàn)代碼很短,而且真正有意思的內(nèi)容只包含在三行代碼里面.但是,去理解包含在這些代碼里面的設(shè)計思想是非常重要的:TensorFlow 工作流程和機(jī)器學(xué)習(xí)的基本概念.因此,這個教程會很詳細(xì)地介紹這些代碼的實現(xiàn)原理.

MNIST 數(shù)據(jù)集

MNIST 數(shù)據(jù)集的官網(wǎng)是 Yann LeCun's website 在這里,我們提供了一份 python 源代碼用于自動下載和安裝這個數(shù)據(jù)集.你可以下載 這份代碼,然后用下面的代碼導(dǎo)入到你的項目里面,也可以直接復(fù)制粘貼到你的代碼文件里面.

import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

下載下來的數(shù)據(jù)集被分成兩部分:60000 行的訓(xùn)練數(shù)據(jù)集 (mnist.train) 和 10000 行的測試數(shù)據(jù)集 (mnist.test) 這樣的切分很重要,在機(jī)器學(xué)習(xí)模型設(shè)計時必須有一個單獨的測試數(shù)據(jù)集不用于訓(xùn)練而是用來評估這個模型的性能,從而更加容易把設(shè)計的模型推廣到其他數(shù)據(jù)集上(泛化).

正如前面提到的一樣,每一個 MNIST 數(shù)據(jù)單元有兩部分組成:一張包含手寫數(shù)字的圖片和一個對應(yīng)的標(biāo)簽我們把這些圖片設(shè)為“xs”,把這些標(biāo)簽設(shè)為“ys”.訓(xùn)練數(shù)據(jù)集和測試數(shù)據(jù)集都包含 xs 和 ys, 比如訓(xùn)練數(shù)據(jù)集的圖片是 mnist.train.images ,訓(xùn)練數(shù)據(jù)集的標(biāo)簽是 mnist.train.labels.

每一張圖片包含 28 像素 X28 像素.我們可以用一個數(shù)字?jǐn)?shù)組來表示這張圖片:


我們把這個數(shù)組展開成一個向量,長度是 28 x 28 = 784.如何展開這個數(shù)組(數(shù)字間的順序)不重要,只要保持各個圖片采用相同的方式展開.從這個角度來看, MNIST 數(shù)據(jù)集的圖片就是在 784 維向量空間里面的點, 并且擁有比較 復(fù)雜的結(jié)構(gòu) (提醒: 此類數(shù)據(jù)的可視化是計算密集型的).

展平圖片的數(shù)字?jǐn)?shù)組會丟失圖片的二維結(jié)構(gòu)信息.這顯然是不理想的,最優(yōu)秀的計算機(jī)視覺方法會挖掘并利用這些結(jié)構(gòu)信息,我們會在后續(xù)教程中介紹.但是在這個教程中我們忽略這些結(jié)構(gòu),所介紹的簡單數(shù)學(xué)模型, softmax 回歸 (softmax regression), 不會利用這些結(jié)構(gòu)信息.

因此,在 MNIST 訓(xùn)練數(shù)據(jù)集中,mnist.train.images 是一個形狀為 [60000, 784] 的張量,第一個維度數(shù)字用來索引圖片,第二個維度數(shù)字用來索引每張圖片中的像素點.在此張量里的每一個元素, 都表示某張圖片里的某個像素的強(qiáng)度值,值介于 0 和 1 之間.

mnist-train-xs

相對應(yīng)的 MNIST 數(shù)據(jù)集的標(biāo)簽是介于 0 到 9 的數(shù)字,用來描述給定圖片里表示的數(shù)字。為了用于這個教程,我們使標(biāo)簽數(shù)據(jù)是"one-hot vectors". 一個 one-hot 向量除了某一位的數(shù)字是1以外其余各維度數(shù)字都是 0。所以在此教程中, 數(shù)字 n 將表示成一個只有在第 n 維度(從 0 開始)數(shù)字為 1 的 10 維向量。比如, 標(biāo)簽 0 將表示成([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).因此, mnist.train.labels  是一個 [60000, 10] 的數(shù)字矩陣.

mnist-train-ys

現(xiàn)在,我們準(zhǔn)備好可以開始構(gòu)建我們的模型啦!

Softmax 回歸介紹

我們知道 MNIST 的每一張圖片都表示一個數(shù)字, 從 0 到 9。我們希望得到給定圖片代表每個數(shù)字的概率.比如說,我們的模型可能推測一張包含 9 的圖片代表數(shù)字9的概率是 80% 但是判斷它是 8 的概率是 5% (因為 8 和 9 都有上半部分的小圓),然后給予它代表其他數(shù)字的概率更小的值.

這是一個使用 softmax 回歸(softmax regression)模型的經(jīng)典案例。softmax 模型可以用來給不同的對象分配概率.即使在之后, 我們訓(xùn)練更加精細(xì)的模型時,最后一步也需要用 softmax 來分配概率.

softmax 回歸(softmax regression)分兩步:第一步

為了得到一張給定圖片屬于某個特定數(shù)字類的證據(jù)(evidence), 我們對圖片像素值進(jìn)行加權(quán)求和.如果這個像素具有很強(qiáng)的證據(jù)說明這張圖片不屬于該類,那么相應(yīng)的權(quán)值為負(fù)數(shù),相反如果這個像素?fù)碛杏欣淖C據(jù)支持這張圖片屬于這個類,那么權(quán)值是正數(shù)。

下面的圖片顯示了一個模型學(xué)習(xí)到的圖片上每個像素對于特定數(shù)字類的權(quán)值。紅色代表負(fù)數(shù)權(quán)值, 藍(lán)色代表正數(shù)權(quán)值。

softmax-weights

我們也需要加入一個額外的偏置量 (bias), 因為輸入往往會帶有一些無關(guān)的干擾量。因此對于給定的輸入圖片 x 它代表的是數(shù)字 i 的證據(jù)可以表示為

mnist1

其中  代表權(quán)重, 代表數(shù)字 i 類的偏置量, j 代表給定圖片 x 的像素索引用于像素求和.然后用 softmax 函數(shù)可以把這些證據(jù)轉(zhuǎn)換成概率 y:

mnist4

這里的 softmax 可以看成是一個激勵 (activation) 函數(shù)或者鏈接 (link) 函數(shù),把我們定義的線性函數(shù)的輸出轉(zhuǎn)換成我們想要的格式, 也就是關(guān)于 10 個數(shù)字類的概率分布。 因此,給定一張圖片, 它對于每一個數(shù)字的吻合度可以被 softmax 函數(shù)轉(zhuǎn)換成為一個概率值。softmax 函數(shù)可以定義為:

mnist5

展開等式右邊的子式, 可以得到:

mnist6

但是更多的時候把 softmax 模型函數(shù)定義為前一種形式:把輸入值當(dāng)成冪指數(shù)求值,再正則化這些結(jié)果值。這個冪運(yùn)算表示, 更大的證據(jù)對應(yīng)更大的假設(shè)模型 (hypothesis) 里面的乘數(shù)權(quán)重值。反之,擁有更少的證據(jù)意味著在假設(shè)模型里面擁有更小的乘數(shù)系數(shù).假設(shè)模型里的權(quán)值不可以是 0 值或者負(fù)值。Softmax 然后會正則化這些權(quán)重值,使它們的總和等于1,以此構(gòu)造一個有效的概率分布(更多的關(guān)于 Softmax 函數(shù)的信息,可以參考 Michael Nieslen 的書里面的這個部分,其中有關(guān)于 softmax 的可交互式的可視化解釋)。

對于 softmax 回歸模型可以用下面的圖解釋,對于輸入的 xs 加權(quán)求和,再分別加上一個偏置量,最后再輸入到 softmax 函數(shù)中:

softmax-regression-scalargraph

如果把它寫成一個等式,我們可以得到:

softmax-regression-scalarequation

我們也可以用向量表示這個計算過程:用矩陣乘法和向量相加.這有助于提高計算效率.(也是一種更有效的思考方式)

softmax-regression-vectorequation

更進(jìn)一步,可以寫成更加緊湊的方式:

mnist7

實現(xiàn)回歸模型

為了用 python 實現(xiàn)高效的數(shù)值計算,我們通常會使用函數(shù)庫,比如 NumPy,會把類似矩陣乘法這樣的復(fù)雜運(yùn)算使用其他外部語言實現(xiàn).不幸的是,從外部計算切換回 Python 的每一個操作,仍然是一個很大的開銷.如果你用 GPU 來進(jìn)行外部計算,這樣的開銷會更大.用分布式的計算方式,也會花費(fèi)更多的資源用來傳輸數(shù)據(jù).

TensorFlow 也把復(fù)雜的計算放在 python 之外完成,但是為了避免前面說的那些開銷,它做了進(jìn)一步完善。Tensorflow 不單獨地運(yùn)行單一的復(fù)雜計算,而是讓我們可以先用圖描述一系列可交互的計算操作,然后全部一起在 Python 之外運(yùn)行(這樣類似的運(yùn)行方式,可以在不少的機(jī)器學(xué)習(xí)庫中看到)。

使用 TensorFlow 之前,首先導(dǎo)入它:

import tensorflow as tf

我們通過操作符號變量來描述這些可交互的操作單元,可以用下面的方式創(chuàng)建一個:

x = tf.placeholder("float", [None, 784])

x 不是一個特定的值,而是一個占位符 placeholder,我們在 TensorFlow 運(yùn)行計算時輸入這個值.我們希望能夠輸入任意數(shù)量的 MNIST 圖像,每一張圖展平成 784 維的向量。我們用 2 維的浮點數(shù)張量來表示這些圖,這個張量的形狀是 [None,784 ] (這里的 None 表示此張量的第一個維度可以是任何長度的)。

我們的模型也需要權(quán)重值和偏置量,當(dāng)然我們可以把它們當(dāng)做是另外的輸入(使用占位符),但 TensorFlow 有一個更好的方法來表示它們:Variable 一個 Variable 代表一個可修改的張量,存在在 TensorFlow 的用于描述交互性操作的圖中。它們可以用于計算輸入值,也可以在計算中被修改。對于各種機(jī)器學(xué)習(xí)應(yīng)用,一般都會有模型參數(shù),可以用 Variable 表示。

W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

我們賦予 tf.Variable 不同的初值來創(chuàng)建不同的 Variable:在這里,我們都用全為零的張量來初始化 W 和 b。因為我們要學(xué)習(xí) W 和 b 的值,它們的初值可以隨意設(shè)置。

注意,W 的維度是 [784,10],因為我們想要用 784 維的圖片向量乘以它以得到一個 10 維的證據(jù)值向量,每一位對應(yīng)不同數(shù)字類。b 的形狀是 [10],所以我們可以直接把它加到輸出上面。

現(xiàn)在,我們可以實現(xiàn)我們的模型啦。只需要一行代碼!

y = tf.nn.softmax(tf.matmul(x,W) + b)

首先,我們用 tf.matmul(??X,W) 表示 x 乘以 W,對應(yīng)之前等式里面的,這里 x 是一個 2 維張量擁有多個輸入。然后再加上 b,把和輸入到 tf.nn.softmax 函數(shù)里面。

至此,我們先用了幾行簡短的代碼來設(shè)置變量,然后只用了一行代碼來定義我們的模型。TensorFlow 不僅僅可以使 softmax 回歸模型計算變得特別簡單,它也用這種非常靈活的方式來描述其他各種數(shù)值計算,從機(jī)器學(xué)習(xí)模型對物理學(xué)模擬仿真模型。一旦被定義好之后,我們的模型就可以在不同的設(shè)備上運(yùn)行:計算機(jī)的 CPU, GPU, 甚至是手機(jī)!

訓(xùn)練模型

為了訓(xùn)練我們的模型,我們首先需要定義一個指標(biāo)來評估這個模型是好的.其實,在機(jī)器學(xué)習(xí),我們通常定義指標(biāo)來表示一個模型是壞的,這個指標(biāo)稱為成本 (cost) 或損失 (loss),然后盡量最小化這個指標(biāo)。但是,這兩種方式是相同的。

一個非常常見的,非常漂亮的成本函數(shù)是“交叉熵”(cross-entropy)。交叉熵產(chǎn)生于信息論里面的信息壓縮編碼技術(shù),但是它后來演變成為從博弈論到機(jī)器學(xué)習(xí)等其他領(lǐng)域里的重要技術(shù)手段。它的定義如下:

mnist10

y 是我們預(yù)測的概率分布,y' 是實際的分布(我們輸入的 one-hot vector)。比較粗糙的理解是,交叉熵是用來衡量我們的預(yù)測用于描述真相的低效性。更詳細(xì)的關(guān)于交叉熵的解釋超出本教程的范疇,但是你很有必要好好理解它.

為了計算交叉熵,我們首先需要添加一個新的占位符用于輸入正確值:

y_ = tf.placeholder("float", [None,10])

然后我們可以用計算交叉熵:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

首先,用 tf.log 計算 y 的每個元素的對數(shù)。接下來,我們把 y_ 的每一個元素和 tf.log(y_) 的對應(yīng)元素相乘。最后,用 tf.reduce_sum 計算張量的所有元素的總和。(注意,這里的交叉熵不僅僅用來衡量單一的一對預(yù)測和真實值,而是所有 100 幅圖片的交叉熵的總和。對于 100 個數(shù)據(jù)點的預(yù)測表現(xiàn)比單一數(shù)據(jù)點的表現(xiàn)能更好地描述我們的模型的性能。

現(xiàn)在我們知道我們需要我們的模型做什么啦,用 TensorFlow 來訓(xùn)練它是非常容易的。因為 TensorFlow 擁有一張描述你各個計算單元的圖,它可以自動地使用反向傳播算法(backpropagation algorithm)來有效地確定你的變量是如何影響你想要最小化的那個成本值的。然后,TensorFlow 會用你選擇的優(yōu)化算法來不斷地修改變量以降低成本。

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

在這里,我們要求 TensorFlow 用梯度下降算法(gradient descent algorithm)以 0.01 的學(xué)習(xí)速率最小化交叉熵。梯度下降算法(gradient descent algorithm)是一個簡單的學(xué)習(xí)過程,TensorFlow 只需將每個變量一點點地往使成本不斷降低的方向移動。當(dāng)然 TensorFlow 也提供了其他許多優(yōu)化算法:只要簡單地調(diào)整一行代碼就可以使用其他的算法。

TensorFlow 在這里實際上所做的是,它會在后臺給描述你的計算的那張圖里面增加一系列新的計算操作單元用于實現(xiàn)反向傳播算法和梯度下降算法。然后,它返回給你的只是一個單一的操作,當(dāng)運(yùn)行這個操作時,它用梯度下降算法訓(xùn)練你的模型,微調(diào)你的變量,不斷減少成本。

現(xiàn)在,我們已經(jīng)設(shè)置好了我們的模型。在運(yùn)行計算之前,我們需要添加一個操作來初始化我們創(chuàng)建的變量:

init = tf.initialize_all_variables()

現(xiàn)在我們可以在一個 Session 里面啟動我們的模型,并且初始化變量:

sess = tf.Session()
sess.run(init)

然后開始訓(xùn)練模型,這里我們讓模型循環(huán)訓(xùn)練 1000 次!

for i in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

該循環(huán)的每個步驟中,我們都會隨機(jī)抓取訓(xùn)練數(shù)據(jù)中的 100 個批處理數(shù)據(jù)點,然后我們用這些數(shù)據(jù)點作為參數(shù)替換之前的占位符來運(yùn)行 train_step。

使用一小部分的隨機(jī)數(shù)據(jù)來進(jìn)行訓(xùn)練被稱為隨機(jī)訓(xùn)練 (stochastic training) - 在這里更確切的說是隨機(jī)梯度下降訓(xùn)練。在理想情況下,我們希望用我們所有的數(shù)據(jù)來進(jìn)行每一步的訓(xùn)練,因為這能給我們更好的訓(xùn)練結(jié)果,但顯然這需要很大的計算開銷。所以,每一次訓(xùn)練我們可以使用不同的數(shù)據(jù)子集,這樣做既可以減少計算開銷,又可以最大化地學(xué)習(xí)到數(shù)據(jù)集的總體特性。

評估我們的模型

那么我們的模型性能如何呢?

首先,讓我們找出那些預(yù)測正確的標(biāo)簽。tf.argmax 是一個非常有用的函數(shù),它能給出某個 tensor 對象在某一維上的其數(shù)據(jù)最大值所在的索引值。由于標(biāo)簽向量是由 0, 1 組成,因此最大值 1 所在的索引位置就是類別標(biāo)簽,比如 tf.argmax(y,1) 返回的是模型對于任一輸入 x 預(yù)測到的標(biāo)簽值,而 tf.argmax(y_,1) 代表正確的標(biāo)簽,我們可以用 tf.equal 來檢測我們的預(yù)測是否真實標(biāo)簽匹配(索引位置一樣表示匹配)。

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

這行代碼會給我們一組布爾值。為了確定正確預(yù)測項的比例,我們可以把布爾值轉(zhuǎn)換成浮點數(shù),然后取平均值。例如,[True, False, True, True] 會變成 [1, 0, 1, 1] ,取平均值后得到 0.75。

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

最后,我們計算所學(xué)習(xí)到的模型在測試數(shù)據(jù)集上面的正確率。

print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

這個最終結(jié)果值應(yīng)該大約是 91%。

這個結(jié)果好嗎?嗯,并不太好。事實上,這個結(jié)果是很差的。這是因為我們僅僅使用了一個非常簡單的模型。不過,做一些小小的改進(jìn),我們就可以得到 97% 的正確率。最好的模型甚至可以獲得超過 99.7% 的準(zhǔn)確率!(想了解更多信息,可以看看這個關(guān)于各種模型的性能對比列表)。

比結(jié)果更重要的是,我們從這個模型中學(xué)習(xí)到的設(shè)計思想。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號