選自jacobbuckman.com,做者:Jacob Buckman,機器之心編譯。node
雖然對於大多數人來講 TensorFlow 的開發語言是 Python,但它並非一個標準的 Python 庫。這個神經網絡框架經過構建「計算圖」來運行,對於不少新手來講,在理解其邏輯時會遇到不少困難。本文中,來自谷歌大腦的工程師 Jacob Buckman 將試圖幫你解決初遇 TensorFlow 時你會遇到的麻煩。
這是什麼?我是誰?python
我叫 Jacob,是 Google AI Resident 項目的研究學者。我是在 2017 年夏天加入該項目的,儘管已經擁有了豐富的編程經驗,而且對機器學習的理解也很深入,但此前我從未使用過 TensorFlow。當時我以爲憑個人能力應該很快就能上手。但讓我沒想到的是,學習曲線至關的陡峭,甚至在加入該項目幾個月後,我還偶爾對如何使用 TensorFlow 代碼來實現想法感到困惑。我把這篇博文看成瓶中信寫給過去的本身:一篇我但願在學習之初能被給予的入門介紹。我但願這篇博文也能幫助到其餘人。git
以往的教程缺乏了哪些內容?github
自 TensorFlow 發佈的三年以來,其已然成爲深度學習生態系統中的一塊基石。然而對於初學者來講,它可能並不直觀,特別是與 PyTorch 或 DyNet 這樣運行即定義的神經網絡庫相比。編程
市面上有許多 TensorFlow 的入門教程,包含從線性迴歸到 MNIST 分類和機器翻譯的內容。這些具體實用的指南是使 TensorFlow 項目啓動並運行的良好資源,同時能夠做爲相似項目的切入點。但對於有些應用開發人員而言,他們開發的應用並無好的教程,或對於那些想打破常規的人(在研究中很常見)而言,剛接觸 TensorFlow 確定是讓人沮喪的。數組
我試圖經過這篇文章去填補這個空白。我沒有專一於某個特定的任務,而是提出更通常的方法,並解析 TensorFlow 背後基礎的抽象概念。掌握好這些概念以後,用 TensorFlow 進行深度學習就會變得直觀易懂。瀏覽器
目標受衆bash
本教程適用於那些在編程和機器學習方面有必定經驗,並想要學習 TensorFlow 的人。例如:一位想在機器學習課程的最後一個項目中使用 TensorFlow 的計算機科學專業的學生;一位剛被分配到涉及深度學習項目的軟件工程師;或是一位處於困惑中的新的 Google AI Resident 新手(向過去的 Jacob 大聲打招呼)。若是你想進一步瞭解基礎知識,請參閱如下資源:網絡
咱們就開始吧!session
TensorFlow 不是一個標準的 Python 庫
大多數 Python 庫被編寫爲 Python 的天然擴展形式。當你導入一個庫時,你獲得的是一組變量、函數和類,他們擴展並補充了你的代碼「工具箱」。當你使用它們時,你能預期到返回的結果是怎樣的。在我看來,當談及 TensorfFlow 時,應該把這種認知徹底拋棄。思考什麼是 TensorFlow 及其如何與其餘代碼進行交互從根本上來講就是錯誤的。
Python 和 TensorFlow 之間的關係能夠類比 Javascript 和 HTML 之間的關係。Javascript 是一種全功能的編程語言,能夠作各類美妙的事情。HTML 是用於表示某種類型的實用計算抽象(此處指可由 Web 瀏覽器呈現的內容)的框架。Javascript 在交互式網頁中的做用是組裝瀏覽器看到的 HTML 對象,而後在須要時經過將其更新爲新的 HTML 來與其交互。
與 HTML 相似,TensorFlow 是用於表示某種類型的計算抽象(稱爲「計算圖」)的框架。但咱們用 Python 操做 TensorFlow 時,咱們用 Pyhton 代碼作的第一件事就是構建計算圖。一旦完成,咱們作的第二件事就是與它進行交互(啓動 TensorFlow 的「會話」)。但重要的是,要記住計算圖不在變量內部;而是處在全局命名空間中。正如莎士比亞所說:「全部的 RAM 都是一個階段,全部的變量都僅僅是指針」
第一個關鍵抽象:計算圖
當你在瀏覽 TensorFlow 文檔時,可能會發現對「圖形」和「節點」的間接引用。若是你仔細閱讀,你甚至可能已經發現了這個頁面(www.tensorflow.org/programmers…),該頁面涵蓋了我將以更準確和技術化的方式去解釋的內容。本節是一篇高級攻略,把握重要的直覺概念,同時忽略一些技術細節。
那麼:什麼是計算圖?它本質上是一個全局數據結構:是一個有向圖,用於捕獲有關如何計算的指令。
讓咱們來看看構建計算圖的一個示例。在下圖中,上半部分是咱們運行的代碼及其輸出,下半部分是生成的計算圖。
import tensorflow as tf
複製代碼
計算圖:
可見,僅僅導入 TensorFlow 並不會給咱們生成一個有趣的計算圖。而只是一個單獨的,空白的全局變量。但當咱們調用一個 TensorFlow 操做時,會發生什麼?
代碼:
import tensorflow as tf
two_node = tf.constant(2)
print two_node
複製代碼
輸出:
Tensor("Const:0", shape=(), dtype=int32)
複製代碼
計算圖:
快看!咱們獲得了一個節點。它包含常量 2。很驚訝吧,這來自於一個名爲 tf.constant 的函數。當咱們打印這個變量時,咱們看到它返回一個 tf.Tensor 對象,它是一個指向咱們剛剛建立的節點的指針。爲了強調這一點,如下是另一個示例:
代碼:
import tensorflow as tf
two_node = tf.constant(2)
another_two_node = tf.constant(2)
two_node = tf.constant(2)
tf.constant(3)
複製代碼
計算圖:
每次咱們調用 tf.constant 時,咱們都會在圖中建立一個新的節點。即便該節點的功能與現有節點相同,即便咱們將節點從新分配給同一個變量,或者即便咱們根本沒有將其分配給一個變量,結果都是同樣的。
代碼:
import tensorflow as tf
two_node = tf.constant(2)
another_pointer_at_two_node = two_node
two_node = None
print two_node
print another_pointer_at_two_node
複製代碼
輸出:
None
Tensor("Const:0", shape=(), dtype=int32)
複製代碼
計算圖:
好啦,讓咱們更進一步:
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node ## equivalent to tf.add(two_node, three_node)
複製代碼
計算圖:
如今咱們正談論—這纔是咱們真正想要的計算圖!請注意,+ 操做在 TensorFlow 中過載,所以同時添加兩個張量會在圖中增長一個節點,儘管它表面上看起來不像是 TensorFlow 操做。
那好,因此 two_node 指向包含 2 的節點,three_node 指向包含 3 的節點,同時 sum_node 指向包含 ...+ 的節點?怎麼回事?它不是應該包含 5 嗎?
事實證實,並無。計算圖只包含計算步驟;不包含結果。至少……如今尚未!
第二個關鍵抽象: 會話
若是錯誤地理解 TensorFlow 抽象概念也有個「瘋狂三月」(NCAA 籃球錦標賽,大部分在三月進行),那麼會話將成爲每一年的一號種子選手。會話有着那樣使人困惑的殊榮是由於其反直覺的命名卻又廣泛存在—幾乎每一個 TensorFlow 呈現都至少一次明確地調用 tf.Session()。
會話的做用是處理內存分配和優化,使咱們可以實際執行由計算圖指定的計算。你能夠將計算圖想象爲咱們想要執行的計算的「模版」:它列出了全部步驟。爲了使用計算圖,咱們須要啓動一個會話,它使咱們可以實際地完成任務;例如,遍歷模版的全部節點來分配一堆用於存儲計算輸出的存儲器。爲了使用 TensorFlow 進行各類計算,你既須要計算圖也須要會話。
會話包含一個指向全局圖的指針,該指針經過指向全部節點的指針不斷更新。這意味着在建立節點以前仍是以後建立會話都無所謂。
建立會話對象後,可使用 sess.run(node) 返回節點的值,而且 TensorFlow 將執行肯定該值所需的全部計算。
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
print sess.run(sum_node)
複製代碼
輸出:
5
複製代碼
計算圖:
太好了!咱們也能夠傳遞一個列表,sess.run([node1, node2, ...]),並讓它返回多個輸出:
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
print sess.run([two_node, sum_node])
複製代碼
輸出:
[2, 5]
複製代碼
計算圖:
通常來講,sess.run() 的調用每每是 TensorFlow 最大的瓶頸之一,所以調用它的次數越少越好。若是能夠的話,在一個 sess.run() 的調用中返回多個項目,而不是進行多個調用。
佔位符和 feed_dict
迄今爲止,咱們所作的計算一直很乏味:沒有機會得到輸入,因此它們老是輸出相同的東西。一個更有價值的應用可能涉及構建一個計算圖,它接受輸入,以某種(一致)方式處理它,並返回一個輸出。
最直接的方法是使用佔位符。佔位符是一種用於接受外部輸入的節點。
代碼:
import tensorflow as tf
input_placeholder = tf.placeholder(tf.int32)
sess = tf.Session()
print sess.run(input_placeholder)
複製代碼
輸出:
Traceback (most recent call last):
...
InvalidArgumentError (see above *for* traceback): You must feed a value *for* placeholder tensor 'Placeholder' *with* dtype int32
[[Node: Placeholder = Placeholder[dtype=DT_INT32, shape=<unknown>, _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]
複製代碼
計算圖:
... 這是一個糟糕的例子,由於它引起了一個異常。佔位符預計會被賦予一個值。但咱們沒有提供一個值,因此 TensorFlow 崩潰了。
爲了提供一個值,咱們使用 sess.run() 的 feed_dixt 屬性。
代碼:
import tensorflow as tf
input_placeholder = tf.placeholder(tf.int32)
sess = tf.Session()
print sess.run(input_placeholder, feed_dict={input_placeholder: 2})
複製代碼
輸出:
2
複製代碼
計算圖:
這就好多了。注意傳遞給 feed_dict 的 dict 格式,其關鍵應該是與圖中的佔位符節點相對應的變量(如前所述,它實際上意味着指向圖中佔位符節點的指針)。相應的值是要分配給每一個佔位符的數據元素——一般是標量或 Numpy 數組。
第三個關鍵抽象:計算路徑
讓咱們看看另外一個使用佔位符的示例:
代碼:
import tensorflow as tf
input_placeholder = tf.placeholder(tf.int32)
three_node = tf.constant(3)
sum_node = input_placeholder + three_node
sess = tf.Session()
print sess.run(three_node)
print sess.run(sum_node)
複製代碼
輸出:
3
Traceback (most recent call last):
...
InvalidArgumentError (see above for traceback): You must feed a value *for* placeholder tensor 'Placeholder_2' with dtype int32
[[Node: Placeholder_2 = Placeholder[dtype=DT_INT32, shape=<unknown>, _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]
複製代碼
計算圖:
爲何第二次調用 sess.run() 會失敗?即便咱們沒有評估 input_placeholder,爲何仍會引起與 input_placeholder 相關的錯誤?答案在於最終的關鍵 TensorFlow 抽象:計算路徑。幸運的是,這個抽象很是直觀。
當咱們在依賴於圖中其餘節點的節點上調用 sess.run() 時,咱們也須要計算那些節點的值。若是這些節點具備依賴關係,那麼咱們須要計算這些值(依此類推……),直到達到計算圖的「頂端」,即節點沒有父節點時。
sum_node 的計算路徑:
全部三個節點都須要進行求值以計算 sum_node 的值。最重要的是,這包含了咱們未填充的佔位符,並解釋了異常!
如今來看 three_node 的計算路徑:
根據圖結構,咱們不須要計算全部節點才能評估咱們想要的節點!由於咱們在評估 three_node 時不須要評估 placehoolder_node,因此運行 sess.run(three_node) 不會引起異常。
TensorFlow 僅經過必需的節點自動進行計算這一事實是該框架的一個巨大優點。若是計算圖很是大而且有許多沒必要要的節點,那麼它能夠節省大量調用的運行時間。它容許咱們構建大型的「多用途」計算圖,這些計算圖使用單個共享的核心節點集合,並根據所採起的不一樣計算路徑去作不一樣的事情。對於幾乎全部應用而言,根據所採起的計算路徑考慮 sess.run() 的調用是很重要的。
變量 & 反作用
至此,咱們已經看到兩種類型的「無祖先」節點(no-ancestor node):每次運行都同樣的 tf.constant 和每次運行都不同的 tf.placeholder。咱們經常要考慮第三種狀況:一個一般在運行時保持值不變的節點也能夠被更新爲新值。
這時就須要引入變量。
變量對於使用 TensorFlow 進行深度學習是相當重要的,由於模型的參數就是變量。在訓練期間,你但願經過梯度降低在每一個步驟更新參數;但在評估時,你但願保持參數不變,並將大量不一樣的測試集輸入模型。一般,模型全部可訓練參數都是變量。
要建立變量,就須要使用 tf.get_variable()。tf.get_variable() 的前兩個參數是必需的,其他參數是可選的。它們是 tf.get_variable(name,shape)。name 是一個惟一標識這個變量對象的字符串。它必須相對於全局圖是惟一的,因此要明瞭你使用過的全部命名,確保沒有重複。shape 是與張量形狀對應的整數數組,它的語法很是直觀:按順序,每一個維度只有一個整數。例如,一個 3x8 矩陣形狀是 [3, 8]。要建立一個標量,就須要使用形狀爲 [] 的空列表。
代碼:
import tensorflow as tf
count_variable = tf.get_variable("count", [])
sess = tf.Session()
print sess.run(count_variable)
複製代碼
輸出:
Traceback (most recent call last):
...
tensorflow.python.framework.errors_impl.FailedPreconditionError: Attempting to use uninitialized value count
[[Node: _retval_count_0_0 = _Retval[T=DT_FLOAT, index=0, _device="/job:localhost/replica:0/task:0/device:CPU:0"](count)]]
複製代碼
計算圖:
噫,另外一個異常。當首次建立變量節點時,它的值基本上爲「null」,而且任何試圖對它求值的操做都會引起這個異常。咱們只能在將值放入變量以後才能對其求值。主要有兩種將值放入變量的方法:初始化器和 tf.assign()。咱們先看看 tf.assign():
代碼:
import tensorflow as tf
count_variable = tf.get_variable("count", [])
zero_node = tf.constant(0.)
assign_node = tf.assign(count_variable, zero_node)
sess = tf.Session()
sess.run(assign_node)
print sess.run(count_variable)
複製代碼
輸出:
0
複製代碼
計算圖:
與咱們迄今爲止見過的節點相比,tf.assign(target, value) 是具有一些獨特屬性:
「反作用」節點支撐着大部分 Tensorflow 深度學習工做流程,因此請確保本身真正理解了在該節點發生的事情。當咱們調用 sess.run(assign_node) 時,計算路徑會經過 assign_node 和 zero_node。
計算圖:
當計算流經圖中的任何節點時,它還會執行由該節點控制的任何反作用,如圖中綠色所示。因爲 tf.assign 的特殊反作用,與 count_variable(以前爲「null」)關聯的內存如今被永久設置爲 0。這意味着當咱們下一次調用 sess.run(count_variable) 時,不會引起任何異常。相反,咱們會獲得 0 值。成功!
接下來,讓咱們看看初始化器:
代碼:
import tensorflow as tf
const_init_node = tf.constant_initializer(0.)
count_variable = tf.get_variable("count", [], initializer=const_init_node)
sess = tf.Session()
print sess.run([count_variable])
複製代碼
輸出:
Traceback (most recent call last):
...
tensorflow.python.framework.errors_impl.FailedPreconditionError: Attempting to use uninitialized value count
[[Node: _retval_count_0_0 = _Retval[T=DT_FLOAT, index=0, _device="/job:localhost/replica:0/task:0/device:CPU:0"](count)]]
複製代碼
計算圖:
那好,這裏發生了什麼?爲何初始化器不工做?
問題出如今會話和圖之間的分離。咱們已將 get_variable 的 initializer 屬性設置爲指向 const_init_node,但它只是在圖中的節點之間添加了一個新的鏈接。咱們尚未作任何解決異常根源的事:與變量節點(存儲在會話中,而不是計算圖中)相關聯的內存仍然設置爲「null」。咱們須要經過會話使 const_init_node 去更新變量。
代碼:
import tensorflow as tf
const_init_node = tf.constant_initializer(0.)
count_variable = tf.get_variable("count", [], initializer=const_init_node)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
print sess.run(count_variable)
複製代碼
輸出:
0
複製代碼
計算圖:
爲此,咱們添加另外一個特殊的節點:init = tf.global_variables_initializer()。與 tf.assign() 相似,這是一個帶有反作用的節點。與 tf.assign() 相反,實際上咱們不須要指定它的輸入是什麼!tf.global_variables_initializer() 將在其建立時查看全局圖並自動將依賴關係添加到圖中的每一個 tf.initializer。當咱們在以後使用 sess.run(init) 對它求值時,它會告訴每一個初始化程序執行變量初始化,並容許咱們運行 sess.run(count_variable) 而不出錯。
變量共享
你可能會遇到帶有變量共享的 Tensorflow 代碼,其涉及建立做用域並設置「reuse = True」。我強烈建議不要在本身的代碼中使用變量共享。若是你想在多個地方使用單個變量,只需以編程方式記錄指向該變量節點的指針,並在須要時從新使用它。換言之,對於想要保存在內存中的每一個變量,你只須要調用一次 tf.get_variable()。
優化器
最後:進行真正的深度學習!若是你跟上個人節奏,那麼其他概念對你來講應該很是簡單。
在深度學習中,典型的「內循環」訓練以下:
1. 獲取輸入和 true_output
2. 根據輸入和參數計算「推測」值
3. 根據推測與 true_output 之間的差別計算「損失」
4. 根據損失的梯度更新參數
讓咱們把全部東西放在一個快速腳本里,解決簡單的線性迴歸問題:
代碼:
import tensorflow as tf
### build the graph## first set up the parameters
m = tf.get_variable("m", [], initializer=tf.constant_initializer(0.))
b = tf.get_variable("b", [], initializer=tf.constant_initializer(0.))
init = tf.global_variables_initializer()
## then set up the computations
input_placeholder = tf.placeholder(tf.float32)
output_placeholder = tf.placeholder(tf.float32)
x = input_placeholder
y = output_placeholder
y_guess = m * x + b
loss = tf.square(y - y_guess)
## finally, set up the optimizer and minimization node
optimizer = tf.train.GradientDescentOptimizer(1e-3)
train_op = optimizer.minimize(loss)
### start the session
sess = tf.Session()
sess.run(init)
### perform the training loop*import* random
## set up problem
true_m = random.random()
true_b = random.random()
*for* update_i *in* range(100000):
## (1) get the input and output
input_data = random.random()
output_data = true_m * input_data + true_b
## (2), (3), and (4) all take place within a single call to sess.run()!
_loss, _ = sess.run([loss, train_op], feed_dict={input_placeholder: input_data, output_placeholder: output_data})
*print* update_i, _loss
### finally, print out the values we learned for our two variables*print* "True parameters: m=%.4f, b=%.4f" % (true_m, true_b)*print* "Learned parameters: m=%.4f, b=%.4f" % tuple(sess.run([m, b]))
複製代碼
輸出:
0 2.32053831 0.57927422 1.552543 1.57332594 0.64356485 2.40612656 1.07462567 2.19987158 1.67751169 1.646242310 2.441034
...99990 2.9878322e-1299991 5.158629e-1199992 4.53646e-1199993 9.422685e-1299994 3.991829e-1199995 1.134115e-1199996 4.9467985e-1199997 1.3219648e-1199998 5.684342e-1499999 3.007017e-11*True* parameters: m=0.3519, b=0.3242
Learned parameters: m=0.3519, b=0.3242
複製代碼
就像你看到的同樣,損失基本上變爲零,而且咱們對真實參數進行了很好的估計。我但願你只對代碼中的如下部分感到陌生:
## finally, set up the optimizer and minimization node
optimizer = tf.train.GradientDescentOptimizer(1e-3)
train_op = optimizer.minimize(loss)
複製代碼
可是,既然你對 Tensorflow 的基本概念有了很好的理解,這段代碼就很容易解釋!第一行,optimizer = tf.train.GradientDescentOptimizer(1e-3) 不會向計算圖中添加節點。它只是建立一個包含有用的幫助函數的 Python 對象。第二行,train_op = optimizer.minimize(loss) 將一個節點添加到圖中,並將一個指針存儲在變量 train_op 中。train_op 節點沒有輸出,可是有一個十分複雜的反作用:
train_op 回溯輸入和損失的計算路徑,尋找變量節點。對於它找到的每一個變量節點,計算該變量對於損失的梯度。而後計算該變量的新值:當前值減去梯度乘以學習率的積。最後,它執行賦值操做更新變量的值。
所以基本上,當咱們調用 sess.run(train_op) 時,它對咱們的全部變量作了一個梯度降低的步驟。固然,咱們也須要使用 feed_dict 填充輸入和輸出佔位符,而且咱們還但願打印損失的值,由於這樣方便調試。
用 tf.Print 調試
當你用 Tensorflow 開始作更復雜的事情時,你須要進行調試。通常來講,檢查計算圖中發生了什麼是至關困難的。由於你永遠沒法訪問你想打印的值—它們被鎖定在 sess.run() 的調用中,因此你不能使用常規的 Python 打印語句。具體來講,假設你是想檢查一個計算的中間值。在調用 sess.run() 以前,中間值還不存在。可是,當你調用的 sess.run() 返回時,中間值又不見了!
讓咱們看一個簡單的示例。
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
print sess.run(sum_node)
複製代碼
輸出:
5
複製代碼
這讓咱們看到了答案是 5。可是,若是咱們想要檢查中間值,two_node 和 three_node,怎麼辦?檢查中間值的一個方法是向 sess.run() 中添加一個返回參數,該參數指向要檢查的每一箇中間節點,而後在返回後,打印它的值。
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
answer, inspection = sess.run([sum_node, [two_node, three_node]])
print inspection
print answer
複製代碼
輸出:
[2, 3]5
複製代碼
這一般是有用的,但當代碼變得愈來愈複雜時,這可能有點棘手。一個更方便的方法是使用 tf.Print 語句。使人困惑的是,tf.Print 其實是一種具備輸出和反作用的 Tensorflow 節點!它有兩個必需參數:要複製的節點和要打印的內容列表。「要複製的節點」能夠是圖中的任何節點;tf.Print 是一個與「要複製的節點」相關的恆等操做,意味着輸出的是輸入的副本。可是,它的反作用是打印出「打印列表」裏的全部當前值。
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
print_sum_node = tf.Print(sum_node, [two_node, three_node])
sess = tf.Session()
print sess.run(print_sum_node)
複製代碼
輸出:
[2][3]5
複製代碼
計算圖:
有關 tf.Print 一個重要且有點微妙的點:打印是一個反作用。像全部其餘反作用同樣,只要在計算流經 tf.Print 節點時纔會進行打印。若是 tf.Print 節點不在計算路徑上,則不會打印任何內容。特別的是,即便 tf.Print 節點正在複製的原始節點位於計算路徑上,但 tf.Print 節點自己可能不在。請注意這個問題!當這種狀況發生時(總會發生的),若是你沒有明確地找到問題所在,它會讓你感到十分沮喪。通常來講,最好在建立要複製的節點後,當即建立你的 tf.Print 節點。
代碼:
import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node### this new copy of two_node is not on the computation path, so nothing prints!
print_two_node = tf.Print(two_node, [two_node, three_node, sum_node])
sess = tf.Session()
print sess.run(sum_node)
複製代碼
輸出:
5
複製代碼
計算圖:
這裏有一個很好的資源(wookayin.github.io/tensorflow-…),它提供了其餘一些實用的調試建議。
結論
但願這篇博文能夠幫助你更好地理解什麼是 Tensorflow,它是如何工做的以及怎麼使用它。總而言之,本文介紹的概念對全部 Tensorflow 項目都很重要,但只是停留在表面。在你探索 Tensorflow 的旅程中,你可能會遇到其餘各類你須要的有趣概念:條件、迭代、分佈式 Tensorflow、變量做用域、保存和加載模型、多圖、多會話和多核、數據加載器隊列等等。我將在將來的博文中討論這些主題。但若是你使用官方文檔、一些代碼示例和一點深度學習的魔力來鞏固你在本文學到的思想,我相信你必定能夠弄明白 Tensorflow!