獨家 手把手教TensorFlow
上一期我們發布了「一文讀懂TensorFlow(附代碼、學習資料)」,帶領大家對TensorFlow進行了全面了解,並分享了入門所需的網站、圖書、視頻等資料,本期文章就來帶你一步步上手TensorFlow。
1. 前言
深度學習演算法的成功使人工智慧的研究和應用取得了突破性進展,並極大地改變了我們的生活。越來越多的開發人員都在學習深度學習方面的開發技術。Google推出的TensorFlow是目前最為流行的開源深度學習框架,在圖形分類、音頻處理、推薦系統和自然語言處理等場景下都有豐富的應用。儘管功能強大,該框架學習門檻並不高,只要掌握Python安裝和使用,並對機器學習和神經網路方面的知識有所了解就可以上手。本文就帶你來一趟TensorFlow的啟蒙之旅。
2. 初識TensorFlow
2.1. TensorFlow安裝說明
我們先來安裝TensorFlow。TensorFlow對環境不算挑剔,在Python 2.7和Python3下面均可運行,操作系統Linux、MAC、Windows均可(注意新版本剛出來時可能只支持部分操作系統),只要是64位。安裝TensorFlow主要不同之處是TensorFlow安裝包分支持GPU和不支持GPU兩種版本,名稱分別為tensorflow-gpu和tensorflow。實際生產環境最好安裝支持GPU的版本,以利於GPU強大的計算能力,不過這需要先安裝相應的CUDA ToolKit和CuDNN。相比之下,安裝不支持GPU的TensorFlow包容易些,順利的話執行一句pip install tensorflow就OK。如果讀者在安裝中遇到問題,可根據錯誤提示在網上搜索解決辦法。
安裝後,可在命令行下啟動Python或打開Jupyter Notebook,執行下面的語句驗證TensorFlow是否安裝成功。
>>>import tensorflow as tf
用tf引用TensorFlow包已成為一種約定。在本文的所有示例代碼中,均假定已事先執行該語句。
2.2. TensorFlow計算模型
我們先來看在TensorFlow中如何計算c = a + b。這裡a = 3,b = 2。
>>>a = tf.constant (3)
>>>b = tf.constant (2)
>>>c = a + b
>>>sess = tf.Session ()
>>>print (sess.run(c))
5
從上面的代碼可以看出,比起Python中的一句print (3+2),TensorFlow中實現同樣的功能需要更多的步驟。首先得把參數打包,再交給Session對象執行才能輸出結果。
現在我們對代碼稍作修改,讓程序輸出更多的調試信息。
>>>a = tf.constant (3)
>>>b = tf.constant (2)
>>>print (a, b)
Tensor("Const:0", shape=(), dtype=int32) Tensor("Const_1:0", shape=(), dtype=int32)
>>>c = a + b
>>>print(c)
Tensor("add:0", shape=(), dtype=int32)
>>>sess = tf.Session ()
>>>sess.run ((a,b))
(3,2)
>>>print(sess.run(c))
5
從上面可以看出,a、b、c都是張量(Tensor)而不是數字。張量的數學含義是多維數組。我們把1維數組稱為向量,2維數組稱為矩陣。而不管1維、2維、3維、4維,都可以稱作張量,甚至標量(數字)也可以看作是0維的張量。在深度學習中,幾乎所有數據都可以看作張量,如神經網路的權重、偏置等。一張黑白圖片可以用2維張量表示,其中的每個元素表示圖片上一個像素的灰度值。一張彩色圖片則需要用3維張量表示,其中兩個維度為寬和高,另一個維度為顏色通道。TensorFlow的名字中就含有張量(Tensor)這個詞。另一個詞Flow的意思是「流」,表示通過張量的流動來表達計算。TensorFlow是一個通過圖(Graph)的形式來表述計算的編程系統,圖中每個節點為一種操作(Operation),包括計算、初始化、賦值等。張量則為操作的輸入和輸出。如上面的c = a + b為張量的加法操作,等效於c = tf.add (a, b),a和b是加法操作的輸入,c是加法操作的輸出。
把張量提交給會話對象(Session)執行,就可以得到具體的數值。即在TensorFlow中包含兩個階段,先以計算圖的方式定義計算過程,再提交給會話對象,執行計算並返回計算結果。這是由於,TensorFlow的核心不是用Python語言實現的,每一步調用都需要函數庫與Python之間的切換,存在很大開銷。而且TensorFlow通常在GPU上執行,如果每一步都自動執行的話,則GPU把大量資源浪費在多次接收和返回數據上,遠不如一次性接收返回數據高效。我們可以把TensorFlow的計算過程設想為叫外賣。如果我們到館子里用餐,可以邊吃邊上菜。如果叫外賣的話,就得先一次性點好菜譜,再讓對方把飯菜做好後打包送來,讓送餐的多次跑路不太合適。
與sess.run (c)的等效的語句是c.eval (session = sess)。作為對象和參數,張量和會話剛好調了個位置。如果上下文中只用到一個會話,則可用tf.InteractiveSession()創建默認的會話對象,後面執行計算時無需再指定。即:
>>>a = tf.constant (3)
>>>b = tf.constant (2)
>>>c = a + b
>>>sess = tf.InteractiveSession ()
>>>print (c.eval())
5
另外,在先前的代碼中,參數3和2被固化在代碼中。如果要多次執行加法運算,我們可以用tf.placeholder代替tf.constant,而在執行時再給參數賦值。如下面的代碼所示:
>>>a = tf.placeholder(tf.int32)
>>>b = tf.placeholder(tf.int32)
>>>c = a + b
>>> sess = tf.InteractiveSession()
#下面的語句也可寫成print (sess.run (c, ))
>>>print (c.eval ())
5
>>>print (c.eval ())
[5 7 9]
另一種存儲參數的方式是使用變數對象(tf.Variable)。與tf.constant函數創建的張量不同,變數對象支持參數的更新,不過這也意味著依賴更多的資源,與會話綁定得更緊。變數對象必須在會話對象中明確地被初始化,通常調用tf.global_variables_initializer函數一次性初始化所有變數。
>>>a = tf.Variable (3)
>>>b = tf.Variable (2)
>>>c = a + b
>>>init = tf.global_variables_initializer()
>>>sess = tf.InteractiveSession()
>>>init.run()
>>>print(c.eval())
5
>>>a.load (7)
>>>b.load (8)
>>>print (c.eval())
15
在深度學習中,變數對象通常用於表示待優化的模型參數如權重、偏置等,其數值在訓練過程中自動調整。這在本文後面的例子中可以看到。
3. TensorFlow機器學習入門
3.1. 導入數據
MNIST是一個非常有名的手寫體數字識別數據集,常常被用作機器學習的入門樣例。TensorFlow的封裝讓使用MNIST更加方便。現在我們就以MINIST數字識別問題為例探討如何使用TensorFlow進行機器學習。
MNIST是一個圖片集,包含70000張手寫數字圖片:
它也包含每一張圖片對應的標籤,告訴我們這個是數字幾。比如,上面這四張圖片的標籤分別是5,0,4,1。
在下面的代碼中,input_data.read_data_sets()函數下載數據並解壓。
# MNIST_data為隨意指定的存儲數據的臨時目錄
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
下載下來的數據集被分成3部分:55000張訓練數據(minist.train);5000張驗證數據(mnist.validation);10000張測試數據(mnist.test)。切分的目的是確保模型設計時有一個單獨的測試數據集不用於訓練而是用來評估這個模型的性能,從而更加容易把設計的模型推廣到其他數據集上。
每一張圖片包含個像素點。我們可以用一個數字數組來表示一張圖片:
3.2. 設計模型
現在我們通過訓練一個叫做Softmax的機器學習模型來預測圖片里的數字。回顧一下,分類和回歸(數值預測)是最基本的機器學習問題。線性回歸是針對回歸問題最基本的機器學習模型,其基本思想是為各個影響因素分配合適的權重,預測的結果是各影響因素的加權和。邏輯(Logistic)回歸則常用來處理分類問題,它在線性回歸的基礎上,通過Logistic函數(也稱Sigmoid函數)把低於和高於參照值的結果分別轉換為接近0和1的數值。不過邏輯回歸只能處理二分問題。Softmax回歸則是邏輯回歸在多分類問題上的推廣。整個模型如下圖所示:
或者用線性代數公式表示為:
其中,x為輸入數據的特徵向量,向量的長度為圖片的像素(),向量中的每個元素為圖片上各點的灰度值,W為的權重矩陣,其中784對應於圖片的像素,10對應於0 - 9這10個數字,b為長度為10的向量,向量中的每個元素為0 - 9各個數字的偏置,得到各個數字的權重,最後softmax函數把權重轉換為概率分布。通常我們最後只保留概率最高的那個數字,不過有時也關注概率較高的其他數字。
下面是TensorFlow中實現該公式的代碼,核心代碼為最後一句,其中tf.matmul函數表示Tensor中的矩陣乘法。注意與公式中略有不同的是,這裡把x聲明為2維的張量,其中第1維為任意長度,這樣我們就可以批量輸入圖片進行處理。另外,為了簡單起見,我們用0填充W和b。
x = tf.placeholder (tf.float32, [None, 784])
W = tf.Variable (tf.zeros([784, 10]))
b = tf.Variable (tf.zeros([10]))
除了模型外,我們還需要定義一個指標來指示如何優化模型中的參數。我們通常定義指標來表示一個模型不盡人意的程度,然後盡量最小化這個指標。這個指標稱為成本函數。成本函數與模型是密切相關的。回歸問題一般用均方誤差作成本函數,而對於分類問題,常用的成本函數是交叉熵(cross-entropy),定義為
其中y是我們預測的概率分布,y』是實際的分布。對交叉熵的理解涉及資訊理論方面的知識,這裡我們可以把它看作反映預測不匹配的指標,或者說該指標反映實際情況出乎預料的程度。注意交叉熵是非對稱的。在TensorFlow中,交叉熵表示為下面的代碼:
cross_entropy = -tf.reduce_sum (y_ * tf.log (y))
y = tf.matmul (x, W) + b
cross_entropy = tf.reduce_mean (
3.3. 設計優化演算法
現在我們需要考慮如何調整參數使成本函數最小,這在機器學習中稱為優化演算法的設計問題。筆者這裡對TensorFlow實現優化的過程作一個簡要的介紹,要知道優化演算法從某種意義上講比模型更重要。
TensorFlow是一個基於神經網路的深度學習框架。對於Softmax這樣的模型,被當作是不含隱藏層的全連接神經網路。通過調整神經網路中的參數對訓練數據進行擬合,可以使得模型對未知的樣本提供預測的能力,表現為前向傳播和反向傳播(Backpropagation)的迭代過程。在每次迭代的開始,首先需要選取全部或部分訓練數據,通過前向傳播演算法得到神經網路模型的預測結果。
因為訓練數據都是有正確答案標註的,所以可以計算出當前神經網路模型的預測答案與正確答案之間的差距。最後,基於預測值和真實值之間的差距,反向傳播演算法會相應更新神經網路參數的取值,使得在這批數據上神經網路模型的預測結果和真實答案更加接近。如下圖所示:
TensorFlow支持多種不同的優化器,讀者可以根據具體的應用選擇不同的優化演算法。比較常用的優化方法有三種:
tf.train.GradientDescentOptimizer
tf.train.AdamOptimizer
tf.train.MomentumOptimizer
train_step = tf.train.GradientDescentOptimizer (0.01).minimize (cross_entropy)
在這裡,我們要求TensorFlow用梯度下降演算法(Gradient Descent)以0.01的學習速率最小化交叉熵。梯度下降演算法是一個簡單的學習過程,TensorFlow只需將每個變數一點點地往使成本不斷降低的方向移動。語句返回的train_step表示執行優化的操作(Operation),可以提交給會話對象運行。
3.4. 訓練模型
現在我們開始訓練模型,迭代1000次。注意會話對象執行的不是W、b也不是y,而是train_step。
for i in range(1000):
sess.run (train_step, feed_dict = )
該循環的每個步驟中,我們都會隨機抓取訓練數據中的100個批處理數據點,然後我們用這些數據點作為參數替換之前的佔位符來運行train_step操作。
使用一小部分的隨機數據來進行訓練被稱為隨機訓練(stochastic training)- 在這裡更確切的說是隨機梯度下降訓練。在理想情況下,我們希望用我們所有的數據來進行每一步的訓練,因為這能給我們更好的訓練結果,但顯然這需要很大的計算開銷。所以,每一次訓練我們可以使用不同的數據子集,這樣做既可以減少計算開銷,又可以最大化地學習到數據集的總體特性。
3.5. 評估模型
到驗證我們的模型是否有效的時候了。我們可以基於訓練好的W和b,用測試圖片計算出y,並取預測的數字與測試圖片的實際標籤進行對比。在Numpy中有個非常有用的函數argmax,它能給出數組中最大元素所在的索引值。由於標籤向量是由0, 1組成,因此最大值1所在的索引位置就是類別標籤。對y而言,最大權重的索引位置就是預測的數字,因為softmax函數是單調遞增的。下面代碼比較各個測試圖片的預測與實際是否匹配,並通過均值函數計算正確率。
import numpy as np
output = sess.run (y, feed_dict = )
我們也可以讓TensorFlow來執行比較,這在很多時候更為方便和高效。TensorFlow中也有類似的argmax函數。
correct_prediction = tf.equal (tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean (tf.cast(correct_prediction, "float"))
print (sess.run (accuracy, feed_dict = ))
這個最終結果值應該大約是91%。完整的代碼請參考https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/mnist/mnist_softmax.py,有少量修改。
4. TensorFlow深度學習入門
4.1. 卷積神經網路介紹
前面我們使用了單層神經網路。如果增加神經網路的層數,可以進一步提高正確率。不過,增加層數會使需要訓練的參數增多,這除了導致計算速度減慢,還很容易引發過擬合問題。所以需要一個更合理的神經網路結構來有效地減少神經網路中參數個數。對於圖像識別這類問題,卷積神經網路(CNN)是目前最為有效的結構。
卷積神經網路是一個層級遞增的結構,其基本思想是從對像素、邊緣的認識開始,再到局部形狀,最後才是整體感知。在傳統方法中,我們需要在分類前對圖像進行預處理,如平滑、去噪、光照歸一化等,從中提取角點、梯度等特徵,而卷積神經網路把這一過程自動化。當然,神經網路是一個黑盒子,沒有前面所提到的這些概念,它所提取的都是抽象意義上的特徵,與人類理解的語意特徵無法對應。況且經過多層變換,圖片早已面目全非。另外卷積神經網路也可以用於圖像識別以外的領域。不過為了淺顯易懂,下文中仍然使用像素、顏色之類的日常用語。
卷積神經網路中特徵識別的基本手段是卷積(Convolution)。我們可以理解為把圖片進行特效處理,新圖片的每個位置的像素值是原圖片對應位置及相鄰位置像素值的某種方式的疊加或取反,類似於Photoshop中的濾鏡如模糊、銳化、馬賽克什麼的,TensorFlow中稱為過濾器(Filter)。卷積的計算方式是相鄰區域內像素的加權求和,用公式表示的話,仍是,不過計算限定在很小的矩形區域內。
由於卷積只針對圖片的相鄰位置,可保證訓練後能夠對於局部的輸入特徵有最強的響應。另外,不論在圖像的什麼位置,都使用同一組權重,相當於把過濾器當作手電筒在圖片上來回掃描,這使圖像內容在圖片中的位置不影響判斷結果。卷積網路的這些特點使它顯著減少參數數量的同時,又能夠更好的利用圖像的結構信息,提取出圖像從低級到複雜的特徵,甚至可以超過人類的表現。
神經網路需要使用激活函數去除線性化,否則即便增加網路的深度也依舊還是線性映射,起不到多層的效果。與Softmax模型所使用的Sigmoid函數不同,卷積神經網路鍾愛激活函數的是ReLU,它有利於反向傳播階段的計算,也能緩解過擬合。ReLU函數很簡單,就是忽略小於0的輸出,可以理解為像摺紙那樣對數據進行區分。注意在使用ReLU函數時,比較好的做法是用一個較小的正數來初始化偏置項,以避免神經元節點輸出恆為0的問題。下圖是Sigmoid和ReLU函數的對比。
除了卷積外,卷積神經網路通常還會用到降採樣(downsampling或subsampling)。我們可以理解為把圖片適當縮小,由此在一定程度上控制過擬合併減少圖像旋轉、扭曲對特徵提取的影響,因為降採樣過程中模糊了方向信息。卷積神經網路正是通過卷積和降採樣,成功將數據量龐大的圖像識別問題不斷降維,最終使其能夠被訓練。降採樣在卷積神經網路中通常被稱為池化(Pooling),包括最大池化、平均池化等。其中最常見的是最大池化,它將輸入數據分成不重疊的矩形框區域,對於每個矩形框的數值取最大值作為輸出。如下圖所示。
4.2. 構建LeNet-5網路
對卷積神經網路有了基本了解後,我們現在開始使用這種網路來處理MNIST數字識別問題。這裡參照最經典的LeNet-5模型,介紹如何使用TensorFlow進行深度學習。LeNet-5的結構如下圖所示。可以看出,LeNet-5中包含兩次的卷積和降採樣,再經過兩次全連接並使用Softmax分類作為輸出。
模型第一層是卷積層。輸入是原始圖片,尺寸為,顏色用灰度表示,因此數據類型為,考慮到批量輸入,數據應有4個維度。過濾器尺寸為,計算32個特徵,因此權重W為的張量,偏置b為長度32的向量。另外,為確保輸出的圖片仍為大小,在對圖片邊緣的像素進行卷積時,我們用0補齊周邊。
下面的代碼實現模型第一層:
x = tf.placeholder (tf.float32, [None, 784])
# 這裡使用tf.reshape函數校正張量的維度,-1表示自適應
x_image = tf.reshape (x, [-1, 28, 28, 1])
W_conv1 = tf.Variable (tf.truncated_normal ([5, 5, 1, 32], stddev = 0.1))
b_conv1 = tf.Variable (tf.constant (0.1, shape = [32]))
#執行卷積後使用ReLU函數去線性化
x_image, W_conv1, strides = [1, 1, 1, 1], padding = SAME ) + b_conv1)
strides = [1, 2, 2, 1], padding = SAME )
模型第三層為卷積層。輸入數據尺寸為,有32個特徵,過濾器尺寸仍為,需計算64個特徵,因此權重W的類型為,偏置b為長度64的向量。
W_conv2 = tf.Variable (tf.truncated_normal ([5, 5, 32, 64], stddev = 0.1))
b_conv2 = tf.Variable (tf.constant(0.1, shape = [64]))
#執行卷積後使用ReLU函數去線性化
h_pool1, W_conv2, strides = [1, 1, 1, 1], padding = SAME ) + b_conv2)
模型第四層為降採樣層,與第二層類似。圖像尺寸再次縮小一半。
strides = [1, 2, 2, 1], padding = SAME )
W_fc1 = tf.Variable (tf.truncated_normal ([7 * 7 * 64, 1024], stddev = 0.1))
b_fc1 = tf.Variable (tf.constant (0.1, shape = [1024]))
#把4維張量轉換為2維
h_pool2_flat = tf.reshape (h_pool2, [-1, 7*7*64])
keep_prob = tf.placeholder (tf.float32)
模型最後一層為全連接加上Softmax輸出,類似之前介紹的單層模型。
W_fc2 = tf.Variable (tf.truncated_normal ([1024, 10], stddev = 0.1))
b_fc2 = tf.Variable (tf.constant(0.1, shape = [10]))
y_conv = tf.matmul (h_fc1_drop, W_fc2) + b_fc2
cross_entropy = tf.reduce_mean(
4.3. 訓練和評估模型
為了進行訓練和評估,我們使用與之前簡單的單層Softmax模型幾乎相同的一套代碼,只是我們會用更加複雜的ADAM優化器來縮短收斂時間,另外在feed_dict中加入額外的參數keep_prob來控制Dropout比例。然後每100次迭代輸出一次日誌。
train_step = tf.train.AdamOptimizer (1e-4).minimize (cross_entropy)
correct_prediction = tf.equal (tf.argmax (y_conv, 1), tf.argmax (y_, 1))
accuracy = tf.reduce_mean (tf.cast (correct_prediction, tf.float32))
sess = tf.InteractiveSession()
sess.run (tf.global_variables_initializer())
for i in range(20000):
if i % 100 == 0:
train_accuracy = accuracy.eval (feed_dict = {
x: batch_xs, y_: batch_ys, keep_prob: 1.0})
print( step %d, training accuracy %g % (i, train_accuracy))
train_step.run (feed_dict = )
print ( test accuracy %g % accuracy.eval (feed_dict = {
以上代碼,在最終測試集上的準確率大概是99.2%。完整的代碼請參考https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/mnist/mnist_deep.py,局部有修改。
5. 總結
在本文中,我們介紹了TensorFlow的基本用法,並以MNIST數據為例,基於Softmax模型和卷積神經網路分別講解如何使用TensorFlow進行機器學習和深度學習。
TensorFlow對深度學習提供了強大的支持,包含豐富的訓練模型,還提供了TensorBoard、TensorFlow遊樂場、TensorFlow Debugger等可視化和調試等手段方便。限於篇幅,這裡不一一介紹,詳見TensorFlow的官方文檔。深度學習是一個較新的技術,理論和實踐中都有不少坑。不過只要多學多上手,相信能讓TensorFlow成為您手中的利器。
參考資料:
《TensorFlow:實戰Google深度學習框架》 才雲科技、鄭澤宇、顧思宇著
《面向機器智能的TensorFlow實踐》 Sam Abrahams等著,段菲、陳澎譯
《TensorFlow白皮書》(譯文) http://www.jianshu.com/p/65dc64e4c81f
《卷積神經網路》 http://blog.csdn.net/celerychen2009/article/details/8973218
校對:丁楠雅
編輯:黃繼彥


※語義視角下的跨學科與跨界數據認知講座
※開發者必看:超全機器學習術語辭彙表!
※清華大學文化經濟研究院成立 李嵐清篆章相贈:文化自信,經濟騰飛
※數據蔣堂 有序分組
※姚期智雲棲大會首日演講:為什麼我說現在是金融科技的「新」黃金時代
TAG:數據派THU |