天天看點

機器學習零基礎?手把手教你用TensorFlow搭建圖像識别系統(二)| 幹貨

現在,我們可以開始建立我們的模型啦。實際上數值計算都是由tensorflow來完成,它使用了一個快速并高效的c++背景程式。tensorflow希望避免頻繁地在python和c++之間切換,因為那樣會降低計算速度。一般的工作流程是,首先為了定義所有的運算,先建立一個tensorflow圖表。在這個過程中沒有計算,我們隻是進行設定操作。之後,我們才針對輸入資料運作計算操作并記錄結果。

讓我們開始定義我們的圖表。首先通過建立占位符來描述tensorflow輸入資料的形式。占位符不包括任何實際資料,它們隻是定義了資料的類型和形狀。

在我們的模型中,我們首先為圖像資料定義了占位符,它們包括浮點資料(tf.float32)。shape參數定義了輸入資料的大小。我們将同時輸入多幅圖像(稍後我們将談到這些處理),但是我們希望可以随時改變實際輸入圖像的個數。是以第一項shape參數為none,這代表大小可以是任何長度。第二項參數是3072,這是每幅圖像的浮點值。

分類标簽的占位符包括整型資料(tf.int64),每幅圖像都有0到9的一個值。因為我們沒有指定輸入圖像的個數,是以shape參數為[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是我們希望優化的變量。但現在還是先談談我們的模型吧。

我們的輸入包括3072個浮點資料,而希望實作的輸出是10個整型資料中的一個。我們怎麼把3072個值變成一個呢?讓我們退後一步,如果不是輸出0到9中的一個數字,而是進行打分,得到10個數字-每個種類一個分數-我們挑選出得分最高的一個種類。是以我們最初的問題就變成了:如何從将3072個值變成10個值。

我們所采取的一種簡單的方法是單獨查詢每個像素。對每一個像素(或更準确點,每個像素的顔色通道)和每個可能的種類,我們問自己是否這個像素的顔色增加或減少了它屬于某個種類的可能性。比如說像素顔色是紅色。如果汽車圖檔的像素通常是紅色,我們希望增加“汽車”這一種類的得分。我們将像素是紅色通道的值乘以一個正數加到“汽車”這一類的的得分裡。同樣,如果在位置1的地方,馬的圖像從來不或很少出現紅色像素,我們希望将分類為“馬”的分數維持在低分或再降低一些。也就是說乘以一個較小的數或者負數後加到分類為“馬”的分數裡。對所有的10個分類我們都重複這樣的操作,對每一個像素重複計算,3072個值進行相加得到一個總和。3072個像素的值乘以3072個權重參數值得到這一分類的得分。最後我們得到10個分類的10個分數。然後我們挑選出得分最高的,将圖像打上這一類型的标簽。

機器學習零基礎?手把手教你用TensorFlow搭建圖像識别系統(二)| 幹貨

一幅圖像通過一個3072個值的一維數組來表示。每個值乘以一個權重參數,将所有值相加得到一個數值-特定種類的分值。

我們可以用矩陣的方法,這樣使用像素值乘以權重值再相加的過程大大簡化。我們的圖像通過一個3072維向量表示。如果我們将這個向量乘以一個3072x10的權重矩陣,結果就是一個10維向量。它包括了我們需要的權重和。

機器學習零基礎?手把手教你用TensorFlow搭建圖像識别系統(二)| 幹貨

通過矩陣乘法計算一個圖像在所有10個類别中的分數。

3072x10矩陣中的具體值就是我們模型的參數。如果它沒有規律或毫無用處,那我們的輸出也是一樣。這就需要訓練資料參與工作。通過查詢訓練資料,我們希望模型能自己計算出最後的參數。

上面這兩行代碼裡,我們告訴tensorflow,權重矩陣的大小是3072x10,初始值都被設定為0。另外,我們定義了第二個參數,一個包含偏內插補點的10維向量。這個偏內插補點并不直接作用于圖像資料,而僅僅是與權重和相加。這個偏內插補點可以被看做是最後得分的一個起始點。想象一下,一副全黑的圖檔,所有像素的隻都是0。那麼不管權重矩陣的隻是多少,所有分類的得分都是0。通過偏內插補點,我們則可以保證我們的每一分類的起始值不是0。

# define the classifier's result logits = tf.matmul(images_placeholder, weights) + biases

下面就要講到預測。通過這一步,我們已經确定了多幅圖像向量和矩陣的次元。這個操作的結果就是每幅輸入圖像都有一個10維向量。

機器學習零基礎?手把手教你用TensorFlow搭建圖像識别系統(二)| 幹貨

通過矩陣乘法,計算多幅圖像的所有10個分類的分數。

# define the loss function loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits,labels_placeholder))

weights和bias參數逐漸優化的過程叫做訓練,它包括以下步驟:第一,我們輸入訓練資料讓模型根據目前參數進行預測。将預測值與正确的分類标簽進行比較。比較的數值結果被叫做損失。越小的損失值表示預測值與正确标簽越接近,反之亦然。我們希望将模型的損失值降到最小,讓預測值與真實标簽更接近。但是在我們将損失最小化之前,先來看看損失是怎麼計算出來的。

幸運的是tensorflow提供了一個函數幫我們完成了這一系列的操作。我們比較模型預測值logits和正确分類值labels_placeholder。sparse_softmax_cross_entropy_with_logits()函數的輸出就是每幅輸入圖像的損失值。然後我們隻需計算輸入圖像的平均損失值。

# define the training operation train_step = tf.train.gradientdescentoptimizer(learning_rate).minimize(loss)

但是我們如何調整參數來将損失最小化呢?tensorflow這時就大發神威了。通過被稱作自動分化(auto-differentiation)的技術,它可以計算出相對于參數值,損失值的梯度。這就是說它可以知道每個參數對總的損失的影響,小幅度的加或減參數是否可以降低損失。然後依此調整所有參數值,增加模型的準确性。在完成參數調整之後,整個過程重新開始,新的一組圖檔被輸入到模型中。

tensorflow知道不同的優化技術可以将梯度資訊用于更新參數值。這裡我們使用梯度下降算法。在決定參數是,它隻關心模型目前的狀态,而不去考慮以前的參數值。參數下降算法隻需要一個單一的參數,學習率,它是參數更新的一個比例因子。學習率越大,表示每一步參數值的調整越大。如果學習率過大,參數值可能超過正确值導緻模型不能收斂。如果學習率過小,模型的學習速度會非常緩慢,需要花很長時間才能找到一個好的參數值。

輸入圖像分類,比較預測結果和真實值,計算損失和調整參數的過程需要重複多次。對于更大,更複雜的模型,這個計算量将迅速上升。但是對于我們的簡單模型,我們既不需要考驗耐心也不需要專門的硬體裝置就可以得到結果。

# 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傳回分數最高的分類。這就是預測的分類标簽。tf.equal()将這個标簽與正确的分類标簽相比較,然後傳回布爾向量。布爾數轉換為浮點數(每個值不是0就是1),這些數求平均得到的分數就是正确預測圖像的比例。

# ----------------------------------------------------------------------------- # 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變量對它進行通路。運作這個會話控制的第一步就是初始化我們早先建立的變量。在變量定義中我們指定了初始值,這時就需要把這些初始值賦給變量。

然後我們開始疊代訓練過程。它會重複進行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]

這幾行代碼随機抽取了訓練資料的幾幅圖像。從訓練資料中抽取的幾幅圖像和标簽被稱作批。批的大小(單個批中圖像的數量)告訴我們參數更新的頻率。我們首先對批中所有圖像的損失值求平均。然後根據梯度下降算法更新參數。

如果我們先就對訓練集中的所有圖像進行分類,而不是在批處理完之後這樣做,我們能夠計算出初始平均損失和初始梯度,用它們來取代批運作時使用的估計值。但是這樣的話,對每個疊代參數的更新都需要進行更多的計算。在另一種極端情況下,我們可以設定批的大小為1,然後更新單幅圖像的參數。這會造成更高頻率的參數更新,但是更有可能出現錯誤。進而向錯誤的方向頻繁修正。

通常在這兩種極端情況的中間位置我們能得到最快的改進結果。對于更大的模型,對記憶體的考慮也至關重要。批的大小最好盡可能大,同時又能使所有變量和中間結果能寫入記憶體。

這裡第一行代碼batch_size在從0到整個訓練集的大小之間随機指定一個值。然後根據這個值,批處理選取相應個數的圖像和标簽。

# 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次疊代,我們對模型訓練資料批的目前精确率進行檢查。我們隻需要調用我們之前定義的精确率操作來完成。

# perform a single training step sess.run(train_step, feed_dict={images_placeholder: images_batch,labels_placeholder: labels_batch})

這是整個訓練循環中最重要的一行代碼。我們告訴模型執行一個單獨的訓練步驟。我們沒有必要為了參數更新再次聲明模型需要做什麼。所有的資訊都是由tensorflow圖表中的定義提供的。tensorflow知道根據損失使用梯度下降算法更新參數。而損失依賴logits。logits又依靠weights,biases和具體的輸入批。

是以我們隻需要向模型輸入訓練資料批。這些通過提供查找表來完成。訓練資料批已經在我們早先定義的占位符中完成了指派。

# 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))

訓練結束後,我們用測試集對模型進行評估。這是模型第一次見到測試集。是以測試集中的圖像對模型來時是全新的。我們會評估訓練後的模型在處理從未見過的資料時表現如何。

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%。我們這個非常簡單的方法已經優于随機猜測。如果你覺得25%仍然有點低,别忘了這個模型其實還比較原始。它對具體圖像的比如線和形狀等特征毫無概念。它隻是單獨檢測每個像素的顔色,完全不考慮與其他像素的關聯。對一幅圖像某一個像素的修改對模型來說意味着完全不同的輸入。考慮到這些,25%的準确率看起來也不是那麼差勁了。

如果我們多進行幾次疊代,結果又會如何呢?這可能并不會改善模型的準确率。如果看看結果,你就會發現,訓練的準确率并不是穩定上升的,而是在0.23至0.44之間波動。看起來我們已經到達了模型的極限,再進行更多的訓練于事無補。這個模型無法再提供更好的結果。事實上,比起進行1000次疊代的訓練,我們進行少得多的疊代次數也能得到相似的準确率。

你可能注意到的最後一件事就是:測試的精确度大大低于訓練的精确度。如果這個差距非常巨大,這也意味着過度拟合。模型針對已經見過的訓練資料進行了精細的調整,而對于以前從未見過的資料則無法做到這點。

繼續閱讀