TensorFlow上手要點(diǎn)都總結(jié)在這兒了,你還有理由偷懶嗎?
本文作者 Steven Dufresne,總結(jié)了新手學(xué) TensorFlow 需要的核心知識(shí)點(diǎn)和實(shí)操內(nèi)容,旨在鼓勵(lì)更多人借 TensorFlow 邁入深度學(xué)習(xí)殿堂 。作為基礎(chǔ)入門教程,該教程從 TensorFlow 原理簡(jiǎn)介講到上手操作,對(duì)核心概念逐條解釋,非常適合基礎(chǔ)薄弱、苦無入門途徑的新手。
Steven Dufresne:在90年代我開始寫神經(jīng)網(wǎng)絡(luò)軟件。TensorFlow開源后,一直十分渴望用它搭建一些有趣的東西。
谷歌的人工智能系統(tǒng)是現(xiàn)在的新熱點(diǎn)。當(dāng)TensorFlow可以被安裝在樹莓派上,操作變得非常容易。在上面我很快就搭建了一個(gè)二進(jìn)制神經(jīng)網(wǎng)絡(luò)。這篇文章中,我將把經(jīng)驗(yàn)分享給大家,幫助其他想要嘗試、深入了解神經(jīng)網(wǎng)絡(luò)的人更快上手。
TensorFlow是什么?
引用TensorFlow官網(wǎng)的話,TensorFlow是一個(gè)“采用數(shù)據(jù)流圖進(jìn)行數(shù)值計(jì)算的開源軟件庫(kù)”。其中“數(shù)據(jù)流圖”是什么意思?這是個(gè)很酷的東西。在正式回答之前,我們先談?wù)勔粋€(gè)簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)。
神經(jīng)網(wǎng)絡(luò)基礎(chǔ)
一個(gè)簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)由輸入層(input units)、隱層(hidden units)、閾值(bias unit)、輸出層(output units)幾部分構(gòu)成。輸入層負(fù)責(zé)接收數(shù)據(jù)。隱層之所以這么叫是因?yàn)閺挠脩舻慕嵌葋砜?,它們是隱藏的。輸出層輸出我們獲得的結(jié)果。旁邊的閾值是用來控制隱含層和輸出層的值是否輸出(即超過閾值的神經(jīng)元才能輸出)。兩兩不同神經(jīng)元之間的連接是權(quán)重,只是一些數(shù)字,需要靠訓(xùn)練獲得。
訓(xùn)練神經(jīng)網(wǎng)絡(luò),就是為了給權(quán)重找到***的值,這讓神經(jīng)網(wǎng)絡(luò)一步步變得“智能”。在下面這個(gè)例子中,輸入神經(jīng)元的值被設(shè)置為二進(jìn)制數(shù)字0,0,0。接下來TensorFlow會(huì)完成這之間的所有事情,而輸出神經(jīng)元會(huì)神奇得包含數(shù)字0,0,1。即便你漏掉了,它也知道二進(jìn)制中000下面一個(gè)數(shù)是001,001接下來是010,就這樣一直到111.一旦權(quán)重被設(shè)定了合適的值,它將知道如何去計(jì)數(shù)。
在運(yùn)行神經(jīng)網(wǎng)絡(luò)中有一個(gè)步驟是將每一個(gè)權(quán)重乘以其對(duì)應(yīng)的輸入神經(jīng)元,然后將乘積結(jié)果保存在相應(yīng)的隱藏神經(jīng)元。
我們可以將這些神經(jīng)元和權(quán)重看作成數(shù)列(array),在Python中也被稱為列表(list)。從數(shù)學(xué)的角度來看,它們都是矩陣。圖中我們只繪制出了其中一部分,這里將輸入層矩陣和權(quán)重矩陣相乘,得到五元素隱藏層矩陣(亦稱為列表或數(shù)列)。
從矩陣到張量
在TensorFlow中,這些列表(lists)被稱為張量(tensors)。矩陣相乘被稱為操作(operation,也翻譯作計(jì)算節(jié)點(diǎn)或運(yùn)算),即程序員常說的op,閱讀TensorFlow官方文件時(shí)會(huì)經(jīng)常遇到。進(jìn)一步講,神經(jīng)網(wǎng)絡(luò)就是一堆張量、以及操作張量的 op 的集合,它們共同構(gòu)成了神經(jīng)網(wǎng)絡(luò)圖(graph)。
以下圖片取自《TensorBoard, a tool for visualizing the graph》這篇文章,用于檢測(cè)訓(xùn)練前后的張量值變化。張量是圖中的連線,上面的數(shù)字代表張量的維度(dimensions)。連接張量的節(jié)點(diǎn)是各種操作(op),雙擊后可以看到更多的細(xì)節(jié),比如后面一張圖是雙擊后展現(xiàn)的***層(layer 1)的細(xì)節(jié)。
最下面的X,是占位符操作,向輸入張量賦值。沿著左邊的線向上是輸入張量(input tensor)。上面節(jié)點(diǎn)標(biāo)著MatMul操作,使輸入張量(input tensor)和權(quán)重張量(weight tensor,導(dǎo)向MatMul操作的另一條線)矩陣相乘。
所有這些只是為了更直觀的展示出圖、張量和操作是什么,讓大家更好的理解為什么TensorFlow被稱為是“采用數(shù)據(jù)流圖進(jìn)行數(shù)值計(jì)算的開源軟件庫(kù)”。但是,我們?yōu)槭裁匆獎(jiǎng)?chuàng)建這些圖呢?
為什么創(chuàng)建圖?
當(dāng)前,TensorFlow 只有 Python 的穩(wěn)定 API,Python 是一門解釋型語(yǔ)言。神經(jīng)網(wǎng)絡(luò)需要大量的運(yùn)算,大型神經(jīng)網(wǎng)絡(luò)包含數(shù)千甚至數(shù)百萬(wàn)的權(quán)重,通過解釋(interpret)每一步來計(jì)算的效率極低。
因此,我們通過創(chuàng)建一個(gè)由張量和 op 構(gòu)成的圖,包括所有的數(shù)學(xué)運(yùn)算甚至變量的初始值,來描述神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)。只有在創(chuàng)建圖之后,才能加載到TensorFlow里的Session。這被稱為TensorFlow的“延遲執(zhí)行”(deferred execution)。 Session通過高效代碼來運(yùn)行計(jì)算圖。不僅如此,許多運(yùn)算,例如矩陣相乘,都可以在GPU上完成。此外,TensorFlow也支持多臺(tái)機(jī)器或者GPU同時(shí)運(yùn)行。
創(chuàng)建二進(jìn)制計(jì)數(shù)器圖
以下是創(chuàng)建二進(jìn)制計(jì)數(shù)器神經(jīng)網(wǎng)絡(luò)(binary counter neural network)的腳本,完整的代碼可以在我的 GitHub 網(wǎng)頁(yè)上找到。注意,在TensorBoard里還有其他的一些代碼保存在其中。
下面我們將從這些代碼開始創(chuàng)建張量和 op 組成的圖。
首先導(dǎo)入 "tensorflow" 模塊,創(chuàng)建一個(gè) session 隨后使用。同時(shí),為了讓腳本更容易理解,我們也創(chuàng)建了一些變量,包含了網(wǎng)絡(luò)中的神經(jīng)元個(gè)數(shù)。
然后,我們?yōu)檩斎牒洼敵龅纳窠?jīng)元?jiǎng)?chuàng)建占位符(placeholders)。占位符是TensorFlow里一個(gè)操作,便于后續(xù)輸入實(shí)際的數(shù)值。這里X和y_是圖中的兩個(gè)張量,每一個(gè)都有相關(guān)聯(lián)的 placeholder 操作。
你可能會(huì)覺得奇怪,為什么我們要將占位符shape定義為二維列表[None,NUM_INPUTS]和[None,NUM_OUTPUTS],***個(gè)維度都是”None”?從整體來看,神經(jīng)網(wǎng)絡(luò)有點(diǎn)像我們每次輸入一個(gè)值,訓(xùn)練它生成一個(gè)特定輸出值。但更有效率的方式是,一次提供多個(gè)輸入\輸出對(duì)(pair),這就是 batch 。上面shape中的***維,是每個(gè) batch 中有幾組輸入/輸出對(duì)。創(chuàng)建一個(gè) batch 之前我們并不知道里面有幾組。實(shí)際上,后續(xù)我們將使用同一個(gè)圖來進(jìn)行訓(xùn)練、測(cè)試以及實(shí)際使用,所以 batch 的大小不會(huì)每次都相同。因此,我們將***維的大小設(shè)置為 Python 占位符對(duì)象 ”None“。
接下來,我們創(chuàng)建神經(jīng)網(wǎng)絡(luò)圖的***層:將權(quán)重定義為W_fc1,閾值(或偏差)定義為b_fc1,隱層定義為h_fc1。這里”fc”意為“完全連接(fully connected)”的意思,因?yàn)闄?quán)重把每一個(gè)輸入神經(jīng)元和每一個(gè)隱藏神經(jīng)元連接起來。
tf.truncated_normal 導(dǎo)致了一系列操作和張量,將會(huì)把所有權(quán)重賦值為標(biāo)準(zhǔn)化的隨機(jī)數(shù)字。
Variable 的操作會(huì)給出初始化的值,這里是隨機(jī)數(shù)字,在后面可以多次引用。一旦訓(xùn)練完,也可以很方便的將神經(jīng)網(wǎng)絡(luò)保存至文件中。
你可以看到我們用 matmul 操作來執(zhí)行矩陣乘法的位置。我們插入一個(gè) add 操作來加入偏差權(quán)重(bias weights)。其中 relu 運(yùn)算執(zhí)行的就是“激活函數(shù)”(activation function)。矩陣乘法和加法都是線性運(yùn)算。神經(jīng)網(wǎng)絡(luò)用線性運(yùn)算能做的事非常少。激活方程提供了一些非線性。這里的relu激活函數(shù),就是將所有小于0的值設(shè)置為0,其余值不變。不管你信不信,這為神經(jīng)網(wǎng)絡(luò)能夠?qū)W習(xí)的東西打開了一扇全新的大門。
神經(jīng)網(wǎng)絡(luò)第二層中的權(quán)重和閾值與***層設(shè)置的一樣,只是輸出層不同。我們?cè)俅芜M(jìn)行矩陣相乘,這一回乘的是權(quán)重和隱層,隨后加入偏差權(quán)重(bias weights),激活函數(shù)被留到下一組代碼。
與上面的relu類似,Sigmoid是另一個(gè)激活函數(shù),也是非線性的。這里我使用sigmoid函數(shù),一定程度上是因?yàn)樗苁棺罱K輸出值為一個(gè)0和1之間,對(duì)于二進(jìn)制計(jì)數(shù)器而言是一個(gè)理想的選擇。在我們的例子中,為了表示二進(jìn)制111,所有的輸出神經(jīng)元都可以有一個(gè)很大的值。這和圖像分類不同,后者會(huì)希望僅僅用一個(gè)輸出單元來輸出一個(gè)很大的值。舉個(gè)例子,比如一張圖像里有長(zhǎng)頸鹿,我們會(huì)希望代表長(zhǎng)頸鹿的輸出單元輸出相當(dāng)大的值。這種情況下,用softmax函數(shù)作為激活函數(shù)反倒更適合。
仔細(xì)看下前面的代碼,會(huì)發(fā)現(xiàn)似乎有些重復(fù),我們插入了兩次sigmoid。實(shí)際是我們創(chuàng)建了兩次不同的、并行的輸出。其中cross_entropy張量將被用來訓(xùn)練神經(jīng)網(wǎng)絡(luò)。而 results 張量則是后續(xù)用來執(zhí)行訓(xùn)練過的神經(jīng)網(wǎng)絡(luò),不管它被訓(xùn)練出來作何目的。這是目前我能想到的***的方法。
***一件事就是訓(xùn)練(training)。也就是基于訓(xùn)練數(shù)據(jù)調(diào)整所有的權(quán)重。記住,在這里我們?nèi)匀恢皇莿?chuàng)建一個(gè)圖。真正的“訓(xùn)練”發(fā)生在我們開始運(yùn)行這個(gè)圖的時(shí)候。
運(yùn)行過程中供選擇的優(yōu)化器很多,這里我選取了 tf.train.RMSPropOptimizer。因?yàn)榫拖駍igmoid一樣,它比較適合所有輸出值都可能較大的情況。而針對(duì)分類的情形,例如圖像分類,用tf.train.GradientDescentOptimizer效果可能更好。
訓(xùn)練和使用二進(jìn)制計(jì)數(shù)器
在完成創(chuàng)建圖之后,就可以開始訓(xùn)練了。
首先,要準(zhǔn)備一些訓(xùn)練數(shù)據(jù):包括輸入變量 inputvals 和目標(biāo)變量 targetvals 。其中 inputvals 包含輸入值,后者的每一個(gè)都有對(duì)應(yīng)的 targetvals 目標(biāo)值。例如,inputvals[0]即[0, 0, 0] ,對(duì)應(yīng)的輸出或目標(biāo)值為targetvals[0] ,也就是 [0, 0, 1] 。
do_training和save_trained都可以硬編碼,每次都可以進(jìn)行更改,或者使用命令行參數(shù)進(jìn)行設(shè)置。
首先使所有 Variable 操作對(duì)張量初始化;然后,將之前創(chuàng)建的圖從底部到 train_step執(zhí)行最多不超過 10001 遍;這是***一個(gè)添加到圖中的東西。我們將 inputvals和targetvals通過RMSPropOptimizer導(dǎo)入train_step操作。這就是通過調(diào)整權(quán)重,在給定輸入值的情況下,讓輸出值不斷接近目標(biāo)值的步驟。只要輸出值和目標(biāo)值之間的誤差足夠小,小到某個(gè)能承受的范圍,這個(gè)循環(huán)就會(huì)停止。
如果你有成百上千的輸入/輸出組,你可以一次訓(xùn)練一個(gè)子集,也就是前面提到的一批(batch)。但這里我們一共只有8組,所以每次都全放進(jìn)去。
我們也可以將訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)保存在一個(gè)文件(file)中,下次就不用再訓(xùn)練了。下次可以直接導(dǎo)入一個(gè)已經(jīng)訓(xùn)練過的神經(jīng)網(wǎng)絡(luò)文件,文件中只包含進(jìn)行過變量運(yùn)算后的張量的值,而不包含整個(gè)圖的結(jié)構(gòu)。所以即便是執(zhí)行已經(jīng)訓(xùn)練好的圖,我們?nèi)匀恍枰_本來創(chuàng)建圖形。MetaGraphs可以進(jìn)行文件保存和導(dǎo)入圖,但這里我們不這么做。
請(qǐng)注意,我們是從圖形底部運(yùn)行至結(jié)果張量(results tensor),在訓(xùn)練網(wǎng)絡(luò)中不斷重復(fù)的創(chuàng)建結(jié)果。
我們輸入000,希望它返回一個(gè)接近001的值。然后將返回的值重復(fù)輸入再次執(zhí)行。這樣總共運(yùn)行9次,保證從000數(shù)到111有足夠的次數(shù),之后再次回到000。
以下就是成功訓(xùn)練后的輸出結(jié)果。在循環(huán)中被訓(xùn)練了200次(steps)。在實(shí)際中,訓(xùn)練了10001次仍沒有有效降低訓(xùn)練誤差的情況非常罕見。一旦訓(xùn)練成功,訓(xùn)練多少次并不重要。
下一步
前面說過,這里講的二進(jìn)制計(jì)數(shù)神經(jīng)網(wǎng)絡(luò)代碼可以在我的Github主頁(yè)上找到。你可以根據(jù)這些代碼開始學(xué)習(xí),或者觀看TensorFlow官網(wǎng)上的其他入門教程。下一步,根據(jù)機(jī)器人識(shí)別物體上獲取的靈感,我想通過它做一些硬件方面的研究。