0
本文作者: 晟煒 | 2017-01-16 14:40 |
雷鋒網(wǎng)按:此系列文章主要介紹了不具備機器學習基礎的用戶如何嘗試從零開始在TensorFlow上搭建一個圖像識別系統(tǒng)。在文章的第一部分中,作者Woflgang Beyer向讀者們介紹了一些簡單的概念。本文為系列的第二部分,主要介紹了如何實現(xiàn)簡單的圖像識別功能。雷鋒網(wǎng)編譯,未經(jīng)許可不得轉載。
現(xiàn)在,我們可以開始建立我們的模型啦。實際上數(shù)值計算都是由TensorFlow來完成,它使用了一個快速并高效的C++后臺程序。TensorFlow希望避免頻繁地在Python和C++之間切換,因為那樣會降低計算速度。一般的工作流程是,首先為了定義所有的運算,先建立一個TensorFlow圖表。在這個過程中沒有計算,我們只是進行設置操作。之后,我們才針對輸入數(shù)據(jù)運行計算操作并記錄結果。
讓我們開始定義我們的圖表。首先通過創(chuàng)建占位符來描述TensorFlow輸入數(shù)據(jù)的形式。占位符不包括任何實際數(shù)據(jù),它們只是定義了數(shù)據(jù)的類型和形狀。
在我們的模型中,我們首先為圖像數(shù)據(jù)定義了占位符,它們包括浮點數(shù)據(jù)(tf.float32)。shape參數(shù)定義了輸入數(shù)據(jù)的大小。我們將同時輸入多幅圖像(稍后我們將談到這些處理),但是我們希望可以隨時改變實際輸入圖像的個數(shù)。所以第一項shape參數(shù)為none,這代表大小可以是任何長度。第二項參數(shù)是3072,這是每幅圖像的浮點值。
分類標簽的占位符包括整型數(shù)據(jù)(tf.int64),每幅圖像都有0到9的一個值。因為我們沒有指定輸入圖像的個數(shù),所以shape參數(shù)為[none]。
# Define variables (these are the values we want to optimize)
weights = tf.Variable(tf.zeros([3072, 10]))
biases = tf.Variable(tf.zeros([10]))
weights和biases是我們希望優(yōu)化的變量。但現(xiàn)在還是先談談我們的模型吧。
我們的輸入包括3072個浮點數(shù)據(jù),而希望實現(xiàn)的輸出是10個整型數(shù)據(jù)中的一個。我們怎么把3072個值變成一個呢?讓我們退后一步,如果不是輸出0到9中的一個數(shù)字,而是進行打分,得到10個數(shù)字-每個種類一個分數(shù)-我們挑選出得分最高的一個種類。所以我們最初的問題就變成了:如何從將3072個值變成10個值。
我們所采取的一種簡單的方法是單獨查詢每個像素。對每一個像素(或更準確點,每個像素的顏色通道)和每個可能的種類,我們問自己是否這個像素的顏色增加或減少了它屬于某個種類的可能性。比如說像素顏色是紅色。如果汽車圖片的像素通常是紅色,我們希望增加“汽車”這一種類的得分。我們將像素是紅色通道的值乘以一個正數(shù)加到“汽車”這一類的的得分里。同樣,如果在位置1的地方,馬的圖像從來不或很少出現(xiàn)紅色像素,我們希望將分類為“馬”的分數(shù)維持在低分或再降低一些。也就是說乘以一個較小的數(shù)或者負數(shù)后加到分類為“馬”的分數(shù)里。對所有的10個分類我們都重復這樣的操作,對每一個像素重復計算,3072個值進行相加得到一個總和。3072個像素的值乘以3072個加權參數(shù)值得到這一分類的得分。最后我們得到10個分類的10個分數(shù)。然后我們挑選出得分最高的,將圖像打上這一類型的標簽。
一幅圖像通過一個3072個值的一維數(shù)組來表示。每個值乘以一個加權參數(shù),將所有值相加得到一個數(shù)值-特定種類的分值。
我們可以用矩陣的方法,這樣使用像素值乘以加權值再相加的過程大大簡化。我們的圖像通過一個3072維向量表示。如果我們將這個向量乘以一個3072x10的加權矩陣,結果就是一個10維向量。它包括了我們需要的加權和。
通過矩陣乘法計算一個圖像在所有10個類別中的分數(shù)。
3072x10矩陣中的具體值就是我們模型的參數(shù)。如果它沒有規(guī)律或毫無用處,那我們的輸出也是一樣。這就需要訓練數(shù)據(jù)參與工作。通過查詢訓練數(shù)據(jù),我們希望模型能自己計算出最后的參數(shù)。
上面這兩行代碼里,我們告訴TensorFlow,加權矩陣的大小是3072x10,初始值都被設置為0。另外,我們定義了第二個參數(shù),一個包含偏差值的10維向量。這個偏差值并不直接作用于圖像數(shù)據(jù),而僅僅是與加權和相加。這個偏差值可以被看做是最后得分的一個起始點。想象一下,一副全黑的圖片,所有像素的只都是0。那么不管加權矩陣的只是多少,所有分類的得分都是0。通過偏差值,我們則可以保證我們的每一分類的起始值不是0。
# Define the classifier's result
logits = tf.matmul(images_placeholder, weights) + biases
下面就要講到預測。通過這一步,我們已經(jīng)確定了多幅圖像向量和矩陣的維度。這個操作的結果就是每幅輸入圖像都有一個10維向量。
通過矩陣乘法,計算多幅圖像的所有10個分類的分數(shù)。
# Define the loss function
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits,labels_placeholder))
weights和bias參數(shù)逐漸優(yōu)化的過程叫做訓練,它包括以下步驟:第一,我們輸入訓練數(shù)據(jù)讓模型根據(jù)當前參數(shù)進行預測。將預測值與正確的分類標簽進行比較。比較的數(shù)值結果被叫做損失。越小的損失值表示預測值與正確標簽越接近,反之亦然。我們希望將模型的損失值降到最小,讓預測值與真實標簽更接近。但是在我們將損失最小化之前,先來看看損失是怎么計算出來的。
前一步計算出來的分數(shù)被存儲在logits變量里,包含任意實數(shù)。我們可以調用softmax函數(shù)將這些值轉化成概率值(0到1之間的實數(shù),總和為1),這樣將輸入轉變成能表示其特征的輸出。相對應的輸入排列保持不變,原本得分最好的分類擁有最大的概率。softmax函數(shù)輸出的概率分布與真實的概率分布相比較。在真實的概率分布中正確的類別概率為1,其他類別的概率為0。我們使用交叉熵來比較兩種概率分布(更多技術性的解釋可以在這里找到)。交叉熵越小,預測值的概率分布與正確值的概率分布的差別就越小。這個值代表了我們模型的損失。
幸運的是TensorFlow提供了一個函數(shù)幫我們完成了這一系列的操作。我們比較模型預測值logits和正確分類值labels_placeholder。sparse_softmax_cross_entropy_with_logits()函數(shù)的輸出就是每幅輸入圖像的損失值。然后我們只需計算輸入圖像的平均損失值。
# Define the training operation
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
但是我們如何調整參數(shù)來將損失最小化呢?TensorFlow這時就大發(fā)神威了。通過被稱作自動分化(auto-differentiation)的技術,它可以計算出相對于參數(shù)值,損失值的梯度。這就是說它可以知道每個參數(shù)對總的損失的影響,小幅度的加或減參數(shù)是否可以降低損失。然后依此調整所有參數(shù)值,增加模型的準確性。在完成參數(shù)調整之后,整個過程重新開始,新的一組圖片被輸入到模型中。
TensorFlow知道不同的優(yōu)化技術可以將梯度信息用于更新參數(shù)值。這里我們使用梯度下降算法。在決定參數(shù)是,它只關心模型當前的狀態(tài),而不去考慮以前的參數(shù)值。參數(shù)下降算法只需要一個單一的參數(shù),學習率,它是參數(shù)更新的一個比例因子。學習率越大,表示每一步參數(shù)值的調整越大。如果學習率過大,參數(shù)值可能超過正確值導致模型不能收斂。如果學習率過小,模型的學習速度會非常緩慢,需要花很長時間才能找到一個好的參數(shù)值。
輸入圖像分類,比較預測結果和真實值,計算損失和調整參數(shù)的過程需要重復多次。對于更大,更復雜的模型,這個計算量將迅速上升。但是對于我們的簡單模型,我們既不需要考驗耐心也不需要專門的硬件設備就可以得到結果。
# Operation comparing prediction with true label
correct_prediction = tf.equal(tf.argmax(logits, 1), labels_placeholder)
# Operation calculating the accuracy of our predictions
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
這兩行代碼用于檢驗模型的精確度。logits的argmax返回分數(shù)最高的分類。這就是預測的分類標簽。tf.equal()將這個標簽與正確的分類標簽相比較,然后返回布爾向量。布爾數(shù)轉換為浮點數(shù)(每個值不是0就是1),這些數(shù)求平均得到的分數(shù)就是正確預測圖像的比例。
# -----------------------------------------------------------------------------
# Run the TensorFlow graph
# -----------------------------------------------------------------------------
with tf.Session() as sess:
# Initialize variables
sess.run(tf.initialize_all_variables())
# Repeat max_steps times
for i in range(max_steps):
最后,我們定義了TensorFlow圖表并準備好運行它。在一個會話控制中運行這個圖表,可以通過sess變量對它進行訪問。運行這個會話控制的第一步就是初始化我們早先創(chuàng)建的變量。在變量定義中我們指定了初始值,這時就需要把這些初始值賦給變量。
然后我們開始迭代訓練過程。它會重復進行max_steps次。
# Generate input data batch
indices = np.random.choice(data_sets['images_train'].shape[0], batch_size)
images_batch = data_sets['images_train'][indices]
labels_batch = data_sets['labels_train'][indices]
這幾行代碼隨機抽取了訓練數(shù)據(jù)的幾幅圖像。從訓練數(shù)據(jù)中抽取的幾幅圖像和標簽被稱作批。批的大小(單個批中圖像的數(shù)量)告訴我們參數(shù)更新的頻率。我們首先對批中所有圖像的損失值求平均。然后根據(jù)梯度下降算法更新參數(shù)。
如果我們先就對訓練集中的所有圖像進行分類,而不是在批處理完之后這樣做,我們能夠計算出初始平均損失和初始梯度,用它們來取代批運行時使用的估計值。但是這樣的話,對每個迭代參數(shù)的更新都需要進行更多的計算。在另一種極端情況下,我們可以設置批的大小為1,然后更新單幅圖像的參數(shù)。這會造成更高頻率的參數(shù)更新,但是更有可能出現(xiàn)錯誤。從而向錯誤的方向頻繁修正。
通常在這兩種極端情況的中間位置我們能得到最快的改進結果。對于更大的模型,對內存的考慮也至關重要。批的大小最好盡可能大,同時又能使所有變量和中間結果能寫入內存。
這里第一行代碼batch_size在從0到整個訓練集的大小之間隨機指定一個值。然后根據(jù)這個值,批處理選取相應個數(shù)的圖像和標簽。
# Periodically print out the model's current accuracy
if i % 100 == 0:
train_accuracy = sess.run(accuracy, feed_dict={
images_placeholder: images_batch, labels_placeholder: labels_batch})
print('Step {:5d}: training accuracy {:g}'.format(i, train_accuracy))
每100次迭代,我們對模型訓練數(shù)據(jù)批的當前精確率進行檢查。我們只需要調用我們之前定義的精確率操作來完成。
# Perform a single training step
sess.run(train_step, feed_dict={images_placeholder: images_batch,labels_placeholder: labels_batch})
這是整個訓練循環(huán)中最重要的一行代碼。我們告訴模型執(zhí)行一個單獨的訓練步驟。我們沒有必要為了參數(shù)更新再次聲明模型需要做什么。所有的信息都是由TensorFlow圖表中的定義提供的。TensorFlow知道根據(jù)損失使用梯度下降算法更新參數(shù)。而損失依賴logits。Logits又依靠weights,biases和具體的輸入批。
因此我們只需要向模型輸入訓練數(shù)據(jù)批。這些通過提供查找表來完成。訓練數(shù)據(jù)批已經(jīng)在我們早先定義的占位符中完成了賦值。
# After finishing the training, evaluate on the test set
test_accuracy = sess.run(accuracy, feed_dict={
images_placeholder: data_sets['images_test'],
labels_placeholder: data_sets['labels_test']})
print('Test accuracy {:g}'.format(test_accuracy))
訓練結束后,我們用測試集對模型進行評估。這是模型第一次見到測試集。所以測試集中的圖像對模型來時是全新的。我們會評估訓練后的模型在處理從未見過的數(shù)據(jù)時表現(xiàn)如何。
endTime = time.time()
print('Total time: {:5.2f}s'.format(endTime - beginTime))
最后一行代碼打印出訓練和運行模型用了多長時間。
讓我們用“python softmax.py”命令運行這個模型。這里是我得到的輸出:
Step 0: training accuracy 0.14
Step 100: training accuracy 0.32
Step 200: training accuracy 0.3
Step 300: training accuracy 0.23
Step 400: training accuracy 0.26
Step 500: training accuracy 0.31
Step 600: training accuracy 0.44
Step 700: training accuracy 0.33
Step 800: training accuracy 0.23
Step 900: training accuracy 0.31
Test accuracy 0.3066
Total time: 12.42s
這意味著什么?在這個測試集中訓練模型的估計精度為31%左右。如果你運行自己的代碼,你的結果可能在25-30%。所以我們的模型能夠對從未見過的圖像正確標簽的比率為25%-30%。還不算壞!這里有10個不同的標簽,如果隨機猜測,結果的準確率只有10%。我們這個非常簡單的方法已經(jīng)優(yōu)于隨機猜測。如果你覺得25%仍然有點低,別忘了這個模型其實還比較原始。它對具體圖像的比如線和形狀等特征毫無概念。它只是單獨檢測每個像素的顏色,完全不考慮與其他像素的關聯(lián)。對一幅圖像某一個像素的修改對模型來說意味著完全不同的輸入??紤]到這些,25%的準確率看起來也不是那么差勁了。
如果我們多進行幾次迭代,結果又會如何呢?這可能并不會改善模型的準確率。如果看看結果,你就會發(fā)現(xiàn),訓練的準確率并不是穩(wěn)定上升的,而是在0.23至0.44之間波動??雌饋砦覀円呀?jīng)到達了模型的極限,再進行更多的訓練于事無補。這個模型無法再提供更好的結果。事實上,比起進行1000次迭代的訓練,我們進行少得多的迭代次數(shù)也能得到相似的準確率。
你可能注意到的最后一件事就是:測試的精確度大大低于訓練的精確度。如果這個差距非常巨大,這也意味著過度擬合。模型針對已經(jīng)見過的訓練數(shù)據(jù)進行了精細的調整,而對于以前從未見過的數(shù)據(jù)則無法做到這點。
這篇文章已經(jīng)寫了很長時間了。很感謝你看完了全文(或直接跳到了文末)!無論是對機器學習分類器如何工作或是如何使用TensorFlow建立和運行簡單的圖表,我希望你找到了一些你感興趣的東西。當然,我還有很多材料希望添加進來。目前為止,我們只是用到了softmax分類器,它甚至都沒應用任何一種神經(jīng)網(wǎng)絡。我的下一篇博文進行完善:一個小型神經(jīng)網(wǎng)絡模型能夠怎樣最大程度地改進結果。雷鋒網(wǎng)將繼續(xù)對下一篇文章進行編譯,敬請期待。
via wolfib
相關文章:
機器學習零基礎?手把手教你用TensorFlow搭建圖像識別系統(tǒng)(一)| 干貨
雷峰網(wǎng)原創(chuàng)文章,未經(jīng)授權禁止轉載。詳情見轉載須知。