機(jī)器學(xué)習(xí)零基礎(chǔ)?手把手教你用TensorFlow搭建圖像識(shí)別系統(tǒng)(二)
這是Wolfgang Beyer一篇博文。詳細(xì)介紹了如何使用TensorFlow搭建一個(gè)簡(jiǎn)單的圖像識(shí)別系統(tǒng)。本篇將手把手地講解搭建圖像識(shí)別系統(tǒng)的全過(guò)程。
此系列文章主要介紹了不具備機(jī)器學(xué)習(xí)基礎(chǔ)的用戶如何嘗試從零開始在TensorFlow上搭建一個(gè)圖像識(shí)別系統(tǒng)。在文章的***部分中,作者Woflgang Beyer向讀者們介紹了一些簡(jiǎn)單的概念。本文為系列的第二部分,主要介紹了如何實(shí)現(xiàn)簡(jiǎn)單的圖像識(shí)別功能。雷鋒網(wǎng)編譯,未經(jīng)許可不得轉(zhuǎn)載。
現(xiàn)在,我們可以開始建立我們的模型啦。實(shí)際上數(shù)值計(jì)算都是由TensorFlow來(lái)完成,它使用了一個(gè)快速并高效的C++后臺(tái)程序。TensorFlow希望避免頻繁地在Python和C++之間切換,因?yàn)槟菢訒?huì)降低計(jì)算速度。一般的工作流程是,首先為了定義所有的運(yùn)算,先建立一個(gè)TensorFlow圖表。在這個(gè)過(guò)程中沒有計(jì)算,我們只是進(jìn)行設(shè)置操作。之后,我們才針對(duì)輸入數(shù)據(jù)運(yùn)行計(jì)算操作并記錄結(jié)果。
讓我們開始定義我們的圖表。首先通過(guò)創(chuàng)建占位符來(lái)描述TensorFlow輸入數(shù)據(jù)的形式。占位符不包括任何實(shí)際數(shù)據(jù),它們只是定義了數(shù)據(jù)的類型和形狀。
在我們的模型中,我們首先為圖像數(shù)據(jù)定義了占位符,它們包括浮點(diǎn)數(shù)據(jù)(tf.float32)。shape參數(shù)定義了輸入數(shù)據(jù)的大小。我們將同時(shí)輸入多幅圖像(稍后我們將談到這些處理),但是我們希望可以隨時(shí)改變實(shí)際輸入圖像的個(gè)數(shù)。所以***項(xiàng)shape參數(shù)為none,這代表大小可以是任何長(zhǎng)度。第二項(xiàng)參數(shù)是3072,這是每幅圖像的浮點(diǎn)值。
分類標(biāo)簽的占位符包括整型數(shù)據(jù)(tf.int64),每幅圖像都有0到9的一個(gè)值。因?yàn)槲覀儧]有指定輸入圖像的個(gè)數(shù),所以shape參數(shù)為[none]。
weights和biases是我們希望優(yōu)化的變量。但現(xiàn)在還是先談?wù)勎覀兊哪P桶伞?/p>
我們的輸入包括3072個(gè)浮點(diǎn)數(shù)據(jù),而希望實(shí)現(xiàn)的輸出是10個(gè)整型數(shù)據(jù)中的一個(gè)。我們?cè)趺窗?072個(gè)值變成一個(gè)呢?讓我們退后一步,如果不是輸出0到9中的一個(gè)數(shù)字,而是進(jìn)行打分,得到10個(gè)數(shù)字-每個(gè)種類一個(gè)分?jǐn)?shù)-我們挑選出得分***的一個(gè)種類。所以我們最初的問(wèn)題就變成了:如何從將3072個(gè)值變成10個(gè)值。
我們所采取的一種簡(jiǎn)單的方法是單獨(dú)查詢每個(gè)像素。對(duì)每一個(gè)像素(或更準(zhǔn)確點(diǎn),每個(gè)像素的顏色通道)和每個(gè)可能的種類,我們問(wèn)自己是否這個(gè)像素的顏色增加或減少了它屬于某個(gè)種類的可能性。比如說(shuō)像素顏色是紅色。如果汽車圖片的像素通常是紅色,我們希望增加“汽車”這一種類的得分。
我們將像素是紅色通道的值乘以一個(gè)正數(shù)加到“汽車”這一類的的得分里。同樣,如果在位置1的地方,馬的圖像從來(lái)不或很少出現(xiàn)紅色像素,我們希望將分類為“馬”的分?jǐn)?shù)維持在低分或再降低一些。也就是說(shuō)乘以一個(gè)較小的數(shù)或者負(fù)數(shù)后加到分類為“馬”的分?jǐn)?shù)里。對(duì)所有的10個(gè)分類我們都重復(fù)這樣的操作,對(duì)每一個(gè)像素重復(fù)計(jì)算,3072個(gè)值進(jìn)行相加得到一個(gè)總和。3072個(gè)像素的值乘以3072個(gè)加權(quán)參數(shù)值得到這一分類的得分。***我們得到10個(gè)分類的10個(gè)分?jǐn)?shù)。然后我們挑選出得分***的,將圖像打上這一類型的標(biāo)簽。
一幅圖像通過(guò)一個(gè)3072個(gè)值的一維數(shù)組來(lái)表示。每個(gè)值乘以一個(gè)加權(quán)參數(shù),將所有值相加得到一個(gè)數(shù)值-特定種類的分值。
我們可以用矩陣的方法,這樣使用像素值乘以加權(quán)值再相加的過(guò)程大大簡(jiǎn)化。我們的圖像通過(guò)一個(gè)3072維向量表示。如果我們將這個(gè)向量乘以一個(gè)3072×10的加權(quán)矩陣,結(jié)果就是一個(gè)10維向量。它包括了我們需要的加權(quán)和。
通過(guò)矩陣乘法計(jì)算一個(gè)圖像在所有10個(gè)類別中的分?jǐn)?shù)。
3072×10矩陣中的具體值就是我們模型的參數(shù)。如果它沒有規(guī)律或毫無(wú)用處,那我們的輸出也是一樣。這就需要訓(xùn)練數(shù)據(jù)參與工作。通過(guò)查詢訓(xùn)練數(shù)據(jù),我們希望模型能自己計(jì)算出***的參數(shù)。
上面這兩行代碼里,我們告訴TensorFlow,加權(quán)矩陣的大小是3072×10,初始值都被設(shè)置為0。另外,我們定義了第二個(gè)參數(shù),一個(gè)包含偏差值的10維向量。這個(gè)偏差值并不直接作用于圖像數(shù)據(jù),而僅僅是與加權(quán)和相加。這個(gè)偏差值可以被看做是***得分的一個(gè)起始點(diǎn)。想象一下,一副全黑的圖片,所有像素的只都是0。那么不管加權(quán)矩陣的只是多少,所有分類的得分都是0。通過(guò)偏差值,我們則可以保證我們的每一分類的起始值不是0。
下面就要講到預(yù)測(cè)。通過(guò)這一步,我們已經(jīng)確定了多幅圖像向量和矩陣的維度。這個(gè)操作的結(jié)果就是每幅輸入圖像都有一個(gè)10維向量。

通過(guò)矩陣乘法,計(jì)算多幅圖像的所有10個(gè)分類的分?jǐn)?shù)。
weights和bias參數(shù)逐漸優(yōu)化的過(guò)程叫做訓(xùn)練,它包括以下步驟:***,我們輸入訓(xùn)練數(shù)據(jù)讓模型根據(jù)當(dāng)前參數(shù)進(jìn)行預(yù)測(cè)。將預(yù)測(cè)值與正確的分類標(biāo)簽進(jìn)行比較。比較的數(shù)值結(jié)果被叫做損失。越小的損失值表示預(yù)測(cè)值與正確標(biāo)簽越接近,反之亦然。我們希望將模型的損失值降到最小,讓預(yù)測(cè)值與真實(shí)標(biāo)簽更接近。但是在我們將損失最小化之前,先來(lái)看看損失是怎么計(jì)算出來(lái)的。
前一步計(jì)算出來(lái)的分?jǐn)?shù)被存儲(chǔ)在logits變量里,包含任意實(shí)數(shù)。我們可以調(diào)用softmax函數(shù)將這些值轉(zhuǎn)化成概率值(0到1之間的實(shí)數(shù),總和為1),這樣將輸入轉(zhuǎn)變成能表示其特征的輸出。相對(duì)應(yīng)的輸入排列保持不變,原本得分***的分類擁有***的概率。
softmax函數(shù)輸出的概率分布與真實(shí)的概率分布相比較。在真實(shí)的概率分布中正確的類別概率為1,其他類別的概率為0。我們使用交叉熵來(lái)比較兩種概率分布(更多技術(shù)性的解釋可以在這里找到)。交叉熵越小,預(yù)測(cè)值的概率分布與正確值的概率分布的差別就越小。這個(gè)值代表了我們模型的損失。
幸運(yùn)的是TensorFlow提供了一個(gè)函數(shù)幫我們完成了這一系列的操作。我們比較模型預(yù)測(cè)值logits和正確分類值labels_placeholder。sparse_softmax_cross_entropy_with_logits()函數(shù)的輸出就是每幅輸入圖像的損失值。然后我們只需計(jì)算輸入圖像的平均損失值。
但是我們?nèi)绾握{(diào)整參數(shù)來(lái)將損失最小化呢?TensorFlow這時(shí)就大發(fā)神威了。通過(guò)被稱作自動(dòng)分化(auto-differentiation)的技術(shù),它可以計(jì)算出相對(duì)于參數(shù)值,損失值的梯度。這就是說(shuō)它可以知道每個(gè)參數(shù)對(duì)總的損失的影響,小幅度的加或減參數(shù)是否可以降低損失。然后依此調(diào)整所有參數(shù)值,增加模型的準(zhǔn)確性。在完成參數(shù)調(diào)整之后,整個(gè)過(guò)程重新開始,新的一組圖片被輸入到模型中。
TensorFlow知道不同的優(yōu)化技術(shù)可以將梯度信息用于更新參數(shù)值。這里我們使用梯度下降算法。在決定參數(shù)是,它只關(guān)心模型當(dāng)前的狀態(tài),而不去考慮以前的參數(shù)值。參數(shù)下降算法只需要一個(gè)單一的參數(shù),學(xué)習(xí)率,它是參數(shù)更新的一個(gè)比例因子。學(xué)習(xí)率越大,表示每一步參數(shù)值的調(diào)整越大。如果學(xué)習(xí)率過(guò)大,參數(shù)值可能超過(guò)正確值導(dǎo)致模型不能收斂。如果學(xué)習(xí)率過(guò)小,模型的學(xué)習(xí)速度會(huì)非常緩慢,需要花很長(zhǎng)時(shí)間才能找到一個(gè)好的參數(shù)值。
輸入圖像分類,比較預(yù)測(cè)結(jié)果和真實(shí)值,計(jì)算損失和調(diào)整參數(shù)的過(guò)程需要重復(fù)多次。對(duì)于更大,更復(fù)雜的模型,這個(gè)計(jì)算量將迅速上升。但是對(duì)于我們的簡(jiǎn)單模型,我們既不需要考驗(yàn)?zāi)托囊膊恍枰獙iT的硬件設(shè)備就可以得到結(jié)果。
這兩行代碼用于檢驗(yàn)?zāi)P偷木_度。logits的argmax返回分?jǐn)?shù)***的分類。這就是預(yù)測(cè)的分類標(biāo)簽。tf.equal()將這個(gè)標(biāo)簽與正確的分類標(biāo)簽相比較,然后返回布爾向量。布爾數(shù)轉(zhuǎn)換為浮點(diǎn)數(shù)(每個(gè)值不是0就是1),這些數(shù)求平均得到的分?jǐn)?shù)就是正確預(yù)測(cè)圖像的比例。
***,我們定義了TensorFlow圖表并準(zhǔn)備好運(yùn)行它。在一個(gè)會(huì)話控制中運(yùn)行這個(gè)圖表,可以通過(guò)sess變量對(duì)它進(jìn)行訪問(wèn)。運(yùn)行這個(gè)會(huì)話控制的***步就是初始化我們?cè)缦葎?chuàng)建的變量。在變量定義中我們指定了初始值,這時(shí)就需要把這些初始值賦給變量。
然后我們開始迭代訓(xùn)練過(guò)程。它會(huì)重復(fù)進(jìn)行max_steps次。
這幾行代碼隨機(jī)抽取了訓(xùn)練數(shù)據(jù)的幾幅圖像。從訓(xùn)練數(shù)據(jù)中抽取的幾幅圖像和標(biāo)簽被稱作批。批的大小(單個(gè)批中圖像的數(shù)量)告訴我們參數(shù)更新的頻率。我們首先對(duì)批中所有圖像的損失值求平均。然后根據(jù)梯度下降算法更新參數(shù)。
如果我們先就對(duì)訓(xùn)練集中的所有圖像進(jìn)行分類,而不是在批處理完之后這樣做,我們能夠計(jì)算出初始平均損失和初始梯度,用它們來(lái)取代批運(yùn)行時(shí)使用的估計(jì)值。但是這樣的話,對(duì)每個(gè)迭代參數(shù)的更新都需要進(jìn)行更多的計(jì)算。在另一種極端情況下,我們可以設(shè)置批的大小為1,然后更新單幅圖像的參數(shù)。這會(huì)造成更高頻率的參數(shù)更新,但是更有可能出現(xiàn)錯(cuò)誤。從而向錯(cuò)誤的方向頻繁修正。
通常在這兩種極端情況的中間位置我們能得到最快的改進(jìn)結(jié)果。對(duì)于更大的模型,對(duì)內(nèi)存的考慮也至關(guān)重要。批的大小***盡可能大,同時(shí)又能使所有變量和中間結(jié)果能寫入內(nèi)存。
這里***行代碼batch_size在從0到整個(gè)訓(xùn)練集的大小之間隨機(jī)指定一個(gè)值。然后根據(jù)這個(gè)值,批處理選取相應(yīng)個(gè)數(shù)的圖像和標(biāo)簽。
每100次迭代,我們對(duì)模型訓(xùn)練數(shù)據(jù)批的當(dāng)前精確率進(jìn)行檢查。我們只需要調(diào)用我們之前定義的精確率操作來(lái)完成。
這是整個(gè)訓(xùn)練循環(huán)中最重要的一行代碼。我們告訴模型執(zhí)行一個(gè)單獨(dú)的訓(xùn)練步驟。我們沒有必要為了參數(shù)更新再次聲明模型需要做什么。所有的信息都是由TensorFlow圖表中的定義提供的。TensorFlow知道根據(jù)損失使用梯度下降算法更新參數(shù)。而損失依賴logits。Logits又依靠weights,biases和具體的輸入批。
因此我們只需要向模型輸入訓(xùn)練數(shù)據(jù)批。這些通過(guò)提供查找表來(lái)完成。訓(xùn)練數(shù)據(jù)批已經(jīng)在我們?cè)缦榷x的占位符中完成了賦值。
訓(xùn)練結(jié)束后,我們用測(cè)試集對(duì)模型進(jìn)行評(píng)估。這是模型***次見到測(cè)試集。所以測(cè)試集中的圖像對(duì)模型來(lái)時(shí)是全新的。我們會(huì)評(píng)估訓(xùn)練后的模型在處理從未見過(guò)的數(shù)據(jù)時(shí)表現(xiàn)如何。
***一行代碼打印出訓(xùn)練和運(yùn)行模型用了多長(zhǎng)時(shí)間。
結(jié)果
讓我們用“python softmax.py”命令運(yùn)行這個(gè)模型。這里是我得到的輸出:
這意味著什么?在這個(gè)測(cè)試集中訓(xùn)練模型的估計(jì)精度為31%左右。如果你運(yùn)行自己的代碼,你的結(jié)果可能在25-30%。所以我們的模型能夠?qū)奈匆娺^(guò)的圖像正確標(biāo)簽的比率為25%-30%。還不算壞!這里有10個(gè)不同的標(biāo)簽,如果隨機(jī)猜測(cè),結(jié)果的準(zhǔn)確率只有10%。我們這個(gè)非常簡(jiǎn)單的方法已經(jīng)優(yōu)于隨機(jī)猜測(cè)。如果你覺得25%仍然有點(diǎn)低,別忘了這個(gè)模型其實(shí)還比較原始。它對(duì)具體圖像的比如線和形狀等特征毫無(wú)概念。它只是單獨(dú)檢測(cè)每個(gè)像素的顏色,完全不考慮與其他像素的關(guān)聯(lián)。對(duì)一幅圖像某一個(gè)像素的修改對(duì)模型來(lái)說(shuō)意味著完全不同的輸入??紤]到這些,25%的準(zhǔn)確率看起來(lái)也不是那么差勁了。
如果我們多進(jìn)行幾次迭代,結(jié)果又會(huì)如何呢?這可能并不會(huì)改善模型的準(zhǔn)確率。如果看看結(jié)果,你就會(huì)發(fā)現(xiàn),訓(xùn)練的準(zhǔn)確率并不是穩(wěn)定上升的,而是在0.23至0.44之間波動(dòng)??雌饋?lái)我們已經(jīng)到達(dá)了模型的極限,再進(jìn)行更多的訓(xùn)練于事無(wú)補(bǔ)。這個(gè)模型無(wú)法再提供更好的結(jié)果。事實(shí)上,比起進(jìn)行1000次迭代的訓(xùn)練,我們進(jìn)行少得多的迭代次數(shù)也能得到相似的準(zhǔn)確率。
你可能注意到的***一件事就是:測(cè)試的精確度大大低于訓(xùn)練的精確度。如果這個(gè)差距非常巨大,這也意味著過(guò)度擬合。模型針對(duì)已經(jīng)見過(guò)的訓(xùn)練數(shù)據(jù)進(jìn)行了精細(xì)的調(diào)整,而對(duì)于以前從未見過(guò)的數(shù)據(jù)則無(wú)法做到這點(diǎn)。
這篇文章已經(jīng)寫了很長(zhǎng)時(shí)間了。很感謝你看完了全文(或直接跳到了文末)!無(wú)論是對(duì)機(jī)器學(xué)習(xí)分類器如何工作或是如何使用TensorFlow建立和運(yùn)行簡(jiǎn)單的圖表,我希望你找到了一些你感興趣的東西。當(dāng)然,我還有很多材料希望添加進(jìn)來(lái)。目前為止,我們只是用到了softmax分類器,它甚至都沒應(yīng)用任何一種神經(jīng)網(wǎng)絡(luò)。我的下一篇博文進(jìn)行完善:一個(gè)小型神經(jīng)網(wǎng)絡(luò)模型能夠怎樣***程度地改進(jìn)結(jié)果。