如何將TensorFlow用作計算框架 已認證的機構
摘要: 如果你剛剛接觸TensorFlow并想使用其來作為計算框架,那么本文是你的一個很好的選擇,閱讀它相信會對你有所幫助。
Tensorflow可能是最受歡迎,增長最快的機器學習框架。在 Github 擁有超過70000個點贊,并得到Google的支持,不僅擁有比 Linux 更多的點贊,還擁有大量的資源。
如果那都不能激起你的興趣,我不知道還會有什么可以引起你的興趣。
如果你以前一直在關注 機器學習101 系列,你會注意到我們已經使用 sklearn框架 來實現我們的 模型 。然而,當我們開始勇于進入神經網絡, 深度學習 和一些算法的內部運作時,我們將開始使用Tensorflow框架,該框架具有訪問更多低級API的能力,為我們提供在模型上更細致的控制。
因此,我們將花費一些時間熟悉Tensorflow及其設計理念,以便我們在后續教程中可以開始使用它,而無需介紹。
在本教程中,我們將討論:
總體設計理念
可視化
涵蓋常見用例的示例
它與機器學習有什么關系?
在官方白皮書中,Tensorflow被描述為“用于表達機器學習算法的接口和用于執行這種算法的實現”。 它比其他框架的主要優點是在各種設備上執行代碼非常容易。這與它在開源之前的發展動機有關。 Google最初開發了Tensorflow來彌合研究與生產之間的差距,希望從研究到生產都不需要對代碼進行編輯。
|Tensorflow是用于表達機器學習算法的接口,以及用于執行這種算法的實現。
為了實現這一點,Tensorflow在幕后實現一個計算圖; 在你的代碼中,你只是定義那個圖:張量的流動。
那么,什么是張量?
就像一個向量可以看作是一個數組或列表,標量(普通數字1, 2,PI),那么矩陣可以看作數組向量,張量可以認為是矩陣數組。所以張量實際上是一個n維矩陣。事實上,正如我們在編碼示例中所看到的那樣,這種架構在使進行機器學習時非常有意義。
流動是什么呢?
流動是張量如何在網絡中傳遞。當張量傳遞時,它們的值和形狀由圖運算更新。
做個比喻,你可以把圖形想象成一個擁有一系列工作站的汽車工廠。一個站可以裝上汽車的輪子,另一個安裝變速箱。然后,流程描述一個汽車骨架必須采取的路線,以便成為一個全功能的汽車。這個比喻中傳遞的張量是汽車原型或骨架。
安裝Tensorflow
你可以使用以下命令使用pip安裝Tensorflow:
pip install tensorflow
或者如果你有一個GPU:
pip install tensorflow-gpu
請注意,如果要安裝GPU版本,則需要安裝CUDA和cuDNN。
撰寫本文時,Tensorflow(v1.3)支持CUDA 8和CUDNN 6。
安裝Tensorflow后,你可以使用以下方法驗證所有操作是否正常:
# Figure out what devices are available
from tensorflow.python.client import device_lib
def get_devices():
return [x.name for x in device_lib.list_local_devices()]
print (get_devices())
['/cpu:0', '/gpu:0']
有關詳細信息,請參閱 安裝頁面 。
Tensorflow的原子
我們已經討論過Tensorflow是否是張量的流動,但是我們沒有詳細介紹。為了更好地證明架構的正確性,我們將詳細闡述這個問題。
三種類型的張量
在Tensorflow中,有三種主要的張量類型:
- tf.Variable
- tf.constant
- tf.placeholder
看一看其中的每一個,討論它們之間的差異,以及何時使用這些,是值得的。
tf.Variable
tf.Variable張量是最直接的基本張量,并且在很多方面類似于純Python變量,因為它的值是很好的變量。
變量在整個會話控制期間保留其值,因此在定義可學習的參數(如神經網絡中的權重)或其他任何將隨代碼運行而改變的參數時很有用。
你可以按如下方式定義一個變量:
a = tf.Variable([1,2,3], name="a")
在這里,我們創建一個具有初始狀態[1,2,3]和名稱a的張量變量。 請注意,Tensorflow無法繼承Python變量名稱,因此,如果要在圖形上有一個名稱(稍后將會有更多的名稱),則需要指定一個名稱。
還有幾個選項,但這只是為了涵蓋基礎知識。與這里討論的任何事情一樣,你可以在 文檔頁面 上閱讀更多信息。
tf.constant
tf.Constant與tf.Variable非常相似,有一個主要區別,它們是不可變的,就是值是恒定的。
tf.Variable張量的用法如下:
b = tf.constant([1,2,3], name="b")
當你有一個不通過代碼執行改變的值,例如表示數據的某些屬性,或者在使用神經網絡來存儲學習速率時,就使用這個。
tf.placeholder
最后,我們解釋tf.placeholder張量。 顧名思義,該張量類型用于定義您沒有初始值的變量或圖形節點(操作)。然后,你可以延遲設置值,直到實際使用sess.run進行計算。這在定義網絡時可用作培訓數據的代理。
運行操作時,需要傳遞占位符的實際數據。是這樣做的:
c = tf.placeholder(tf.int32, shape=[1,2], name="myPlaceholder")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
res = sess.run(c,
feed_dict={
c:[[5,6]]
})
print (res)
[[5 6]]
請注意,我們先通過元素類型(這里是tf.int32)的非可選參數來定義占位符,然后使用矩陣維符號定義形狀。 [1,2]表示具有1行和2列的矩陣。 如果你沒有研究線性代數,這可能看起來很混亂:為什么表示寬度之前的高度?并且不是[1,2] a 1乘以2的矩陣本身的值1和2?
這些是有效的問題,但深入的答案超出了本文的范圍。 然而,告訴你它的要點,奇怪的符號形式顯然有一些很整齊的記憶特性的矩陣運算,[ 1,2 ]也可以被看作是一個由兩個矩陣本身。Tensorflow使用像符號這樣的列表,因為它支持n維矩陣,因此非常方便,我們將在后面看到。
你可以在 這里 找到支持Tensorflow數據類型的完整列表。
當我們用sess.run評估c的值時,我們使用feed_dict傳入實際的數據。 請注意,我們使用Python變量名稱,而不是給予Tensorflow圖形的名稱來定位占位符。 同樣的方法也擴展到多個占位符,其中每個變量名映射到相同名稱的字典鍵。
定義形狀時的通配符
有時候,當你定義時,你不知道一些,或者一個占位符的整個形狀。例如,你可以在訓練時使用變量批量大小,這是通配符進入的地方。
通配符基本上允許你說“我不知道”給Tensorflow,并且讓它從傳入張量推斷形狀。
-1和None有什么區別?
老實說,我試圖弄清楚這個的答案,但是我沒有找到任何記錄的差異,而在Tensorflow的源代碼中我挖掘的小部分也沒有產生任何結果。 但是,我遇到了一些例子,其中一個會引發錯誤,而另一個則不會。
在這兩個中,None似乎對我來說使用更好,所以我一直使用,如果我收到與占位符大小相關的錯誤,我嘗試將其更改為-1,但我覺得它們應該是等價的。
為什么不只是通配符?!
具有明確的形狀可以幫助調試,因為很多錯誤將被捕獲在“編譯時間”,而不是在訓練的時候,這使你更快地發現錯誤,并且它確保錯誤不會默默地爬行(至少它嘗試了)。
所以為了避免以后的麻煩,你應該只使用通配符來描述一些變量,如輸入大小,而不是一些靜態的,如網絡參數大小。
基本計算實例
了解了變量如何工作,我們現在可以看看如何創建更復雜的交互。
Tensorflow中的圖形包含互連操作(ops)。OP本質上是一個函數,即任何需要輸入并產生某些輸出的函數。正如我們之前討論過的,Tensorflow的默認數據類型是張量,所以操作可以說是進行張量操作。
看一個非常基本的例子,乘以兩個標量,可以這樣做:
a = tf.Variable(3)
b = tf.Variable(4)
c = tf.multiply(a,b)
print (c)
Tensor("Mul:0", shape=(), dtype=int32)
print (a)
print (b)
<tf.Variable 'Variable_4:0' shape=() dtype=int32_ref>
<tf.Variable 'Variable_5:0' shape=() dtype=int32_ref>
請注意,當我們打印結果時,我們得到另一個Tensor,而不是實際結果。另外,請注意,變量具有shape(),這是因為標量是零維張量。最后,因為我們沒有指定一個名字,所以我們得到名字'Variable_4:0'和'Variable_5:0',這意味著它們在圖形0上是變量4和5。
要獲得實際結果,我們必須在會話的上下文中計算值。 這可以這樣做:
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # this is important
print (sess.run(c))
12
你也可以使用tf.InteractiveSession,如果你使用像IDLE或者jupyter筆記本這類的,這很有用。 此外,還可以通過聲明sess = tf.Session()來開始一個會話控制,然后使用sess.close()關閉它,但是我不建議這樣做,因為很容易忘記關閉會話會話控制, 使用此方法作為交互式會話可能對性能會有影響,因為Tensorflow真的喜歡占用盡可能多的資源(在這方面有點像 Chrome )。
我們首先創建一個向Tensorflow發出信號的會話,我們要開始進行實際的計算。 在幕后,Tensorflow做了一些事情; 它選擇一個設備來執行計算(默認情況下是你的第一個CPU),并初始化計算圖。 雖然你可以使用多個圖,但通常建議僅使用一個,因為不通過Python(我們建立的是慢的),兩個圖形之間的數據無法發送。即使你有多個斷開連接的部件也是如此。
接下來我們初始化變量。 為什么在開始會話時不能這樣做,我不知道,但它填充了圖中變量的值,所以我們可以在我們的計算中使用它們。 這是這些小煩惱之一,你必須記住每次你想要計算的東西。
你可能記得,Tensorflow真的很懶,想盡可能少做。因此,你必須明確告訴Tensorflow來初始化變量。
Tensorflow是懶的
了解更多細節可能是有用的,因為了解如何以及為什么選擇Tensorflow非常重要。
Tensorflow喜歡延長計算時間。 它是這樣做的,因為Python很慢,所以它想要在Python之外運行計算。 通常,我們使用諸如numpy之類的庫來實現這一點,但是在Python和優化的庫之間傳輸數據(如numpy)是非常昂貴的。
Tensorflow通過首先使用Python定義一個圖而不做任何計算,然后將所有數據發送到Python之外的圖形,使用高效的GPU庫(CUDA)可以運行。 這樣,將數據傳輸所花的時間保持在最低限度。
因此,Tensorflow只需要計算實際需要的圖形部分。當你運行操作來發現計算所依賴的所有依賴項時,它通過網絡傳播回來,并且僅計算它們。它忽略了網絡的其余部分。
請思考以下代碼:
a = tf.Variable(3)
b=tf.Variable(4)
c = tf.multiply(a,b)
d = tf.add(a,c)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
c_value = sess.run(c)
d_value = sess.run(d)
print (c_value, d_value)
12 15
這里,我們有兩個原始值a和b,以及兩個復合值c和d。
c依賴于a和b。
d依賴于a和c。
那么當我們計算復合值時會發生什么? 如果我們從最簡單的c開始,我們看到它依賴于原始值a和b,所以當計算c時,Tensorflow通過反向傳播發現(這與通過神經網絡的反向傳播不同),獲取這些原始值并將它們相乘在一起。
d的值以類似的方式計算。 Tensorflow發現d是依賴于a和c的值的加法運算,所以Tensorflow獲取它們中的每一個的值。 對于值a,一切都很好,而Tensorflow可以使用原始值,但是使用值c,Tensorflow發現它本身是一個復合值,這里是依賴于a和b的乘法運算。 Tensorflow現在可以獲取a和b的值,它用于計算c的值,因此可以計算d的值。
|Tensorflow遞歸地計算操作的依賴關系以找到其計算值。
然而,這也意味著一旦計算出值就被丟棄,因此不能用于加速未來的計算。 使用上面的例子,這意味著在計算d的值時,即使我們剛剛計算出c并且自那以后沒有改變,c的值被重新計算。
下面,進一步探討這個概念。 我們看到,當c的結果在計算后立即被丟棄,可以將結果保存到一個變量(這里是res)中,當這樣做時,甚至可以在會話關閉后訪問結果。
12 Tensor("Mul:0", shape=(), dtype=int32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
res = sess.run(c)
print (res,c)
12 Tensor("Mul:0", shape=(), dtype=int32)
選擇設備
你可以選擇使用以下模板在特定設備上計算某些操作:
with tf.device("/gpu:0"):
# do stuff with GPU
with tf.device("/cpu:0"):
# do some other stuff with CPU
在驗證Tensorflow正確安裝時,您可以使用任何可用的設備名稱字符串替換字符串“/ gpu:0”和“/ cpu:0”。
如果你安裝了GPU版本,Tensorflow將自動嘗試運行GPU上的圖形,而無需明確定義它。
|如果一個GPU可用,它將優先于CPU。
當使用多個設備時,值得考慮的是設備之間的切換相當慢,因為所有的數據必須被復制到新設備的內存中。
分布式計算
因為一臺電腦是不夠的。
Tensorflow允許分布式計算。 我想像這對我們大多數人來說是不相關的,所以請隨便跳過這個部分,但是,如果你相信你可能會使用多臺電腦來解決問題,這一節可能對你有所幫助。
Tensorflow的分布式模型可以分為以下兩個部分:
服務器
集群
這些類似于服務器/客戶端模型。 當服務器包含主副本時,集群包含一組作業,每個作業都有一組任務是實際的計算。
管理具有一個作業的群集的服務器和兩個共享兩個任務之間的負載的工作人員可以如下創建:
cluster = tf.train.ClusterSpec({"my_job": ["worker1.ip:2222", "worker2.ip:2222"]})
server = tf.train.Server(cluster, job_name="my_job", task_index=1)
a = tf.Variable(5)
with tf.device("/job:my_job/task:0"):
b = tf.multiply(a, 10)
with tf.device("/job:my_job/task:1"):
c = tf.add(b, a)
with tf.Session("grpc://localhost:2222") as sess:
res = sess.run(c)
print(res)
可以像這樣創建相應的worker-client:
# Get task number from command line
import sys
task_number = int(sys.argv[1])
import tensorflow as tf
cluster = tf.train.ClusterSpec({"my_job": ["worker1.ip:2222", "worker2.ip:2222"]})
server = tf.train.Server(cluster, job_name="my_job", task_index=task_number)
print("Worker #{}".format(task_number))
server.start()
server.join()
如果將客戶端代碼保存到文件中,可以通過鍵入終端來啟動工作人員:
python filename.py 1
這將啟動兩個監聽my_job作業的任務0和任務1的工作人員。 一旦服務器啟動,它會將任務發送給將返回到服務器的答案的工作人員。
要更深入地了解Tensorflow的分布式計算,請參閱 文檔 。
保存變量(模型)
在計算完這些困難的參數后,不得不扔掉它們,這并不有趣。
幸運的是,使用保存對象,在Tensorflow中保存一個模型非常簡單,如下例所示:
a = tf.Variable(5)
b = tf.Variable(4, name="my_variable")
# set the value of a to 3
op = tf.assign(a, 3)
# create saver object
saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
sess.run(op)
print ("a:", sess.run(a))
print ("my_variable:", sess.run(b))
# use saver object to save variables
# within the context of the current session
saver.save(sess, "/tmp/my_model.ckpt")
a: 3
my_variable: 4
加載變量(模型)
與保存模型一樣,從文件加載模型也很簡單。
注意:如果你指定了Tensorflow名稱,則必須在加載程序中使用相同的名稱,因為它的優先級高于Python名稱。如果你尚未指定Tensorflow名稱,則使用Python名稱保存變量。
# Only necessary if you use IDLE or a jupyter notebook
tf.reset_default_graph()
# make a dummy variable
# the value is arbitrary, here just zero
# but the shape must the the same as in the saved model
a = tf.Variable(0)
c = tf.Variable(0, name="my_variable")
saver = tf.train.Saver()
with tf.Session() as sess:
# use saver object to load variables from the saved model
saver.restore(sess, "/tmp/my_model.ckpt")
print ("a:", sess.run(a))
print ("my_variable:", sess.run(c))
INFO:tensorflow:Restoring parameters from /tmp/my_model.ckpt
a: 3
my_variable: 4
可視化圖形
在將模型視為代碼時,很容易失去大局,隨著時間的推移,可能難以看出模型的性能演變。這就是可視化的來源。
Tensorflow提供了一些可以從創建圖形中進行大量工作的工具。
可視化工具可分為兩個部分:tensorboard和summary writer。tensorboard就是你將看到的可視化,而summary writer是將模型和變量轉換成tensorboard可以渲染的東西。
沒有任何工作,summary writer可以給你一個模型的圖形表示,并且只有很少的工作,你可以得到更詳細的摘要,如損失的演變,以及模型學習的準確性。
首先考慮Tensorflow支持的最簡單的可視化形式:可視化圖形。
為了達到這個目的,我們只需創建一個summary writer,給它一個保存摘要的路徑,并將其指向我們想要保存的圖形。 這可以在一行代碼中完成:
fw = tf.summary.FileWriter("/tmp/summary", sess.graph)
在一個例子中,這成為:
a = tf.Variable(5, name="a")
b = tf.Variable(10, name="b")
c = tf.multiply(a,b, name="result")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print (sess.run(c))
fw = tf.summary.FileWriter("/tmp/summary", sess.graph)
使用下面的命令運行tensorboard,并打開URL,我們得到一個簡單的圖形概述。
tensorboard --logdir=/tmp/summary

命名和范圍
有時在使用大型模型時,圖形可視化可能變得復雜。 為了幫助這一點,我們可以使用tf.name_scope定義范圍來添加另一個抽象級別,實際上我們可以在范圍內定義范圍,如下面的示例所示:
with tf.name_scope('primitives') as scope:
a = tf.Variable(5, name='a')
b = tf.Variable(10, name='b')
with tf.name_scope('fancy_pants_procedure') as scope:
# this procedure has no significant interpretation
# and was purely made to illustrate why you might want
# to work at a higher level of abstraction
c = tf.multiply(a,b)
with tf.name_scope('very_mean_reduction') as scope:
d = tf.reduce_mean([a,b,c])
e = tf.add(c,d)
with tf.name_scope('not_so_fancy_procedure') as scope:
# this procedure suffers from imposter syndrome
d = tf.add(a,b)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print (sess.run(c))
print (sess.run(e))
fw = tf.summary.FileWriter("/tmp/summary", sess.graph)
注意,作用域名稱必須是一個單詞。
在tensorboard中打開這個總結可得到:

我們可以擴展范圍,以查看構成范圍的各個操作。

如果我們進一步擴展very_mean_reduction,我們可以看到reduce和mean是reduce_mean函數的一部分。 我們甚至可以擴大它們,看看它們是如何實施的。

可視化變化的數據
雖然只是可視化圖表非常酷,當學習參數時,能夠可視化某些變量隨時間變化將是有用的。
可變化數據的最簡單方法是添加標量摘要。下面是一個實現此操作并記錄c變化的示例。
import random
a = tf.Variable(5, name="a")
b = tf.Variable(10, name="b")
# set the intial value of c to be the product of a and b
# in order to write a summary of c, c must be a variable
init_value = tf.multiply(a,b, name="result")
c = tf.Variable(init_value, name="ChangingNumber")
# update the value of c by incrementing it by a placeholder number
number = tf.placeholder(tf.int32, shape=[], name="number")
c_update = tf.assign(c, tf.add(c,number))
# create a summary to track to progress of c
tf.summary.scalar("ChangingNumber", c)
# in case we want to track multiple summaries
# merge all summaries into a single operation
summary_op = tf.summary.merge_all()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
# initialize our summary file writer
fw = tf.summary.FileWriter("/tmp/summary", sess.graph)
# do 'training' operation
for step in range(1000):
# set placeholder number somewhere between 0 and 100
num = int(random.random()*100)
sess.run(c_update, feed_dict={number:num})
# compute summary
summary = sess.run(summary_op)
# add merged summaries to filewriter,
# so they are saved to disk
fw.add_summary(summary, step)
那么發生了什么?
如果我們從實際邏輯開始,我們看到c的值,變化的變量,由a和b(50)的乘積開始。
然后,我們運行1000次更新操作,將c值增加0到100之間隨機選擇的量。
這樣,如果我們計算C的值隨著時間的推移,我們會看到它隨時間線性增加。
因此,讓我們看看我們如何創建c的摘要。
在會議之前,我們首先告訴Tensorflow,我們實際上想要一個c的總結。
tf.summary.scalar("ChangingNumber", c)
在這種情況下,我們使用標量摘要,因為,c是一個標量。 但是,Tensorflow支持一系列不同的摘要,包括:
直方圖(接受張量數組)
文本
音頻
圖片
如果你需要總結可能用于提供網絡的豐富數據,則最后三項將非常有用。
接下來,我們將所有摘要添加到summary op以簡化計算。
summary_op = tf.summary.merge_all()
嚴格來說,這不是必需的,因為我們只記錄一個值的總結,但是在一個更現實的例子中,通常會有多個摘要,這使得這非常有用。你也可以使用tf.summary.merge來合并特定的摘要,如下所示:
summary = tf.summary.merge([summ1, summ2, summ3])
如果加上范圍,這將是非常強大的。
接下來,我們開始執行實際摘要寫入的會話。 我們必須告訴Tensorflow什么和什么時候寫; 每當一個變量變化,即使它是有用的,它也不會自動寫一個摘要條目。
因此,每當我們想要一個新的條目的摘要,我們必須運行摘要操作。 這允許你靈活地多長時間,或以什么精度記錄您的進度。 例如,你可以選擇僅記錄每千次迭代的進度,以加快計算速度,并免費IO呼叫。
這里我們只需要在每次迭代中記錄下面的代碼行:
summary = sess.run(summary_op)
現在我們總結tensorboard用途,但是我們還沒有將它寫入磁盤。 為此,我們需要將摘要添加到FileWriter:
fw.add_summary(summary, step)
這里,第二個參數步驟表示摘要的位置索引,或者它的圖形中的x值。 這可以是你想要的任何數字,而訓練網絡時,你通常只能使用迭代號。 通過手動指定索引號,當創建圖形時,摘要寫入程序可以靈活地進行向后移動,跳過值,甚至為相同索引計算兩個或多個值。
這就是我們所需要的。如果我們現在打開tensorboard,我們將看到生成的圖形,以及摘要中的圖形。


正如所預測的,總結圖的趨勢確實與正斜率呈線性關系。
幾乎實際的例子
雖然迄今為止的小例子都很好地演示了個人的想法,但它們卻很難展示它們是如何組合在一起的。
為了說明這一點,我們現在將使用我們對Tensorflow了解的一切(幾乎所有的東西),使我們至少可以假裝有些實用; 我們將構建一個非常簡單的神經網絡來對經典MNIST數據( http:// yann.lecun.com/exdb/mni st/ )集中的數字進行分類。 如果你沒有完全掌握神經網絡的速度,你可以在回到此之前閱讀本簡介(即將推出)。
神經網絡的構建和訓練可以分為幾個階段:
- 導入數據。
- 構建模型架構。
- 定義一個損失函數進行優化,并有一種優化方法。
- 實際培訓模式。
- 評估模型。
但是,在我們開始創建模型之前,我們必須先準備Tensorflow:
import tensorflow as tf
tf.reset_default_graph() # again, this is not needed if run as a script
接下來,我們導入數據。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
由于mnist是一個眾所周知的數據集,我們可以使用內置的數據提取器來獲取數據周圍的精美包裝。
現在,現在是定義要使用的實際模型的時候了。 對于這個任務,我們將使用具有兩個隱藏層的前饋網絡,分別具有500和100個參數。
使用范圍的概念將圖分割成塊,我們可以像這樣實現模型:
# input
with tf.name_scope('input') as scope:
x = tf.placeholder(tf.float32, [None, 28*28], name="input")
# a placeholder to hold the correct answer during training
labels = tf.placeholder(tf.float32, [None, 10], name="label")
# the probability of a neuron being kept during dropout
keep_prob = tf.placeholder(tf.float32, name="keep_prob")
with tf.name_scope('model') as scope:
with tf.name_scope('fc1') as scope: # fc1 stands for 1st fully connected layer
# 1st layer goes from 784 neurons (input) to 500 in the first hidden layer
w1 = tf.Variable(tf.truncated_normal([28*28, 500], stddev=0.1), name="weights")
b1 = tf.Variable(tf.constant(0.1, shape=[500]), name="biases")
with tf.name_scope('softmax_activation') as scope:
# softmax activation
a1 = tf.nn.softmax(tf.matmul(x, w1) + b1)
with tf.name_scope('dropout') as scope:
# dropout
drop1 = tf.nn.dropout(a1, keep_prob)
with tf.name_scope('fc2') as scope:
# takes the first hidden layer of 500 neurons to 100 (second hidden layer)
w2 = tf.Variable(tf.truncated_normal([500, 100], stddev=0.1), name="weights")
b2 = tf.Variable(tf.constant(0.1, shape=[100]), name="biases")
with tf.name_scope('relu_activation') as scope:
# relu activation, and dropout for second hidden layer
a2 = tf.nn.relu(tf.matmul(drop1, w2) + b2)
with tf.name_scope('dropout') as scope:
drop2 = tf.nn.dropout(a2, keep_prob)
with tf.name_scope('fc3') as scope:
# takes the second hidden layer of 100 neurons to 10 (which is the output)
w3 = tf.Variable(tf.truncated_normal([100, 10], stddev=0.1), name="weights")
b3 = tf.Variable(tf.constant(0.1, shape=[10]), name="biases")
with tf.name_scope('logits') as scope:
# final layer doesn't have dropout
logits = tf.matmul(drop2, w3) + b3
對于訓練,我們,我們將使用交叉熵損失函數和ADAM 優化器,學習率為0.001。 按照上面的例子,我們繼續使用范圍來組織圖。
我們還為準確性和平均損失添加了兩個摘要,并創建了一個合并的摘要操作,以簡化后續步驟。
最后,一旦我們添加了保護對象,所以我們不會在訓練后失去模型,我們有這個:
with tf.name_scope('train') as scope:
with tf.name_scope('loss') as scope:
# loss function
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits)
# use adam optimizer for training with a learning rate of 0.001
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
with tf.name_scope('evaluation') as scope:
# evaluation
correct_prediction = tf.equal(tf.argmax(logits,1), tf.argmax(labels,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# create a summarizer that summarizes loss and accuracy
tf.summary.scalar("Accuracy", accuracy)
# add average loss summary over entire batch
tf.summary.scalar("Loss", tf.reduce_mean(cross_entropy))
# merge summaries
summary_op = tf.summary.merge_all()
# create saver object
saver = tf.train.Saver()
現在是開始訓練網絡的時候了。 使用前面討論的技術,我們每100個步驟寫出一個總結,一共20000個步驟。
在每一步,我們通過運行train_step操作,通過一系列100個示例來訓練網絡,該操作將根據學習速率更新網絡的權重。
最后,一旦學習完成,我們打印出測試精度,并保存模型。
with tf.Session() as sess:
# initialize variables
tf.global_variables_initializer().run()
# initialize summarizer filewriter
fw = tf.summary.FileWriter("/tmp/nn/summary", sess.graph)
# train the network
for step in range(20000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, labels: batch_ys, keep_prob:0.2})
if step%1000 == 0:
acc = sess.run(accuracy, feed_dict={
x: batch_xs, labels: batch_ys, keep_prob:1})
print("mid train accuracy:", acc, "at step:", step)
if step%100 == 0:
# compute summary using test data every 100 steps
summary = sess.run(summary_op, feed_dict={
x: mnist.test.images, labels: mnist.test.labels, keep_prob:1})
# add merged summaries to filewriter,
# so they are saved to disk
fw.add_summary(summary, step)
print ("Final Test Accuracy:", sess.run(accuracy, feed_dict={
x: mnist.test.images, labels: mnist.test.labels, keep_prob:1}))
# save trained model
saver.save(sess, "/tmp/nn/my_nn.ckpt")
mid train accuracy: 0.1 at step: 0
mid train accuracy: 0.91 at step: 1000
mid train accuracy: 0.89 at step: 2000
mid train accuracy: 0.91 at step: 3000
[...]
mid train accuracy: 0.97 at step: 17000
mid train accuracy: 0.98 at step: 18000
mid train accuracy: 0.97 at step: 19000
Final Test Accuracy: 0.9613
96%的準確度是好嗎?
不,這確實有點糟糕,但是這個網絡的重點不是最好的網絡。 相反,它的重點是演示如何使用Tensorflow來構建一個網絡,并為很少的工作獲得大量的可視化效果。
如果我們運行的模型,并在tensorboard打開它,我們得到:

此外,我們可以看到Tensorflow對精度和損失做出的總結,并且按預期,它們的行為大致相似。 我們還看到,開始時的準確性增加了很多,但是隨著時間的推移,它的平均化程度有所增加,這部分是因為我們使用ADAM優化器,部分原因在于梯度的性質。

使用嵌套的范圍讓我們逐漸改變抽象級別。 注意,如果我們擴展模型,我們可以在單個圖層組件之前看到各個圖層。

如果你想自己運行這個網絡,你可以訪問 Github 上的代碼。
總結
如果你已經看到了這里,現在你應該對Tensorflow的基礎有了一個基本的了解:它的功能如何,如何進行基本的計算,如何可視化圖形,最后,你已經看到了一個真正的例子來使用它創建一個基本的神經網絡。
另外,如果你能做到這一點,可以給我發私信 @kasperfredn 。
由于這只是Tensorflow的一個介紹,我們沒有介紹很多,但現在應該足夠了解 API文檔 ,你可以在其中找到可以納入代碼的模塊。
如果你想要一個挑戰來測試您的理解,請嘗試使用Tensorflow來實現另一種機器學習模型,方法是從我們在此創建的模型中工作,或從頭開始。
要獲得反饋,請將結果發送到“homework [at] kasperfred.com”。 請記住在主題行中包含論文的標題。