簡介
本文旨在知道您使用低級別TensorFlow API(TensorFlow Core)開始編程。您可以學習執行以下操作:
- 管理自己的TensorFlow程序(tf.Graph)和TensorFlow運行時(tf.Session),而不是依靠Estimator來管理它們
- 使用tf.Session運行TensorFlow操作
- 在此低級別環境中使用高級別組件(數據集、層和feature_columns)
- 構建自己的訓練循環,而不是使用Estimator提供的訓練循環
我們建議盡可能使用高階的API構建模型。以下是TensorFlow Core為何很重要的原因:
- 如果您能夠使用低階TensorFlow操作,實驗和調試都會更直接
- 在使用高階的API時,能夠理解其內部的工作原理
設置
在使用本教程之前,請先安裝TensorFlow。
要充分理解本指南中的內容,您應當具備以下方面的知識:
- 如何使用Python編程
- 對陣列有所了解
- 理想情況下,最好對機器學習有一定的了解
您隨時啟動Python,並按照以下演示進行操作。運行以下行來設置你的python環境:
1 from __future__ import absolute_import 2 from __future__ import division 3 from __future__ import print_function 4 5 import numpy as np 6 import tensorflow as tf
張量值
TensorFlow的核心數據單位是張量。一個張量由一組形成陣列(任意維數)的原始值組成。張量的階是它的維數,而它的形狀是一個整數元組,指定了陣列每個維度的長度。以下是張量值的一些示例:
1 3. # a rank 0 tensor; a scalar with shape [], 2 [1., 2., 3.] # a rank 1 tensor; a vector with shape [3] 3 [[1., 2., 3.], [4., 5., 6.]] # a rank 2 tensor; a matrix with shape [2, 3] 4 [[[1., 2., 3.]], [[7., 8., 9.]]] # a rank 3 tensor with shape [2, 1, 3]
TensorFlow使用numpy陣列來表示張量值。
TensorFlow Core演示
您可以將TensorFlow Core程序看作由兩個相互獨立的部分組成:
- 構造計算圖(tf.Graph)
- 運行計算圖(tf.Session)
圖
計算圖是排列成一個圖的一系列TensorFlow指令。圖由兩種類型的對象組成:
- 操作(簡稱“op”):圖的節點。操作描述了消耗和生成張量的計算。
- 張量:圖的邊。它們代表將流經圖的值。大多數TensorFlow函數會返回tf.Tensors。
重要提示:tf.Tensors
不具有值,它們只是計算圖中元素的手柄。
我們來構建一個簡單的計算圖。最基本的指令是一個常量。構建指令的Python函數將一個張量值作為輸入值。生成的指令不需要輸入值。它在運行時輸出的是被傳遞給構造函數的值。我們可以創建兩個如下所示的兩個浮點數常量 a 和 b :
1 a = tf.constant(3.0, dtype=tf.float32) 2 b = tf.constant(4.0) # also tf.float32 implicitly 3 total = a + b 4 print(a) 5 print(b) 6 print(total)
打印語句會生成:
1 Tensor("Const:0", shape=(), dtype=float32) 2 Tensor("Const_1:0", shape=(), dtype=float32) 3 Tensor("add:0", shape=(), dtype=float32)
請注意,打印張量並不會如您可能預期的那樣輸出值3.0、4.0和7.0。上述語句只會構建計算圖。這些tf.Tensor對象僅代表將要運行的操作的結果。
圖中的每個指令都擁有唯一的名稱。這個名稱不同於使用Python分配給相應對象的名稱。張量是根據生成它們的指令命名的,后面跟着輸出索引,如上文的 “add:0” 所示,表示該add指令的第0個輸出。
TensorBoard
TensorFlow提供了一個名為TensorBoard的使用程序。TensorBoard的諸多功能之一是將計算圖可視化。您只需要使用幾個簡單的命令就能輕松完成此操作。
首先將計算圖保存為TensorBoard摘要文件具體操作如下所示:
1 writer = tf.summary.FileWriter('.') 2 writer.add_graph(tf.get_default_graph())
這將在當前目錄中生成一個 event 文件,其命名格式如下:
1 events.out.tfevents.{timestamp}.{hostname}
現在,在新的終端使用以下shell命令啟動TensorBoard:
1 tensorboard --logdir .
接下來,在您的瀏覽器中打開TensorBoard的圖頁面,您應該會看到與以下圖形類似的圖:
要想詳細了解TensorBoard的計算圖可視化工具,請參閱TensorBoard:圖的直觀展示。
會話(Session)
要評估張量,需要實例化一個tf.Session對象(非正式名稱為會話)。會話會封裝TensorFlow運行時的狀態,並運行TensorFlow操作。如果說tf.Graph像一個 .py 文件,那么tf.Session就像一個python可執行對象。
下面的代碼會創建一個tf.Session對象,然后調用其 run 方法來評估我們在上文中所創建的 total 張量:
1 sess = tf.Session() 2 print(sess.run(total))
當您用 Session.run 請求輸出節點時,TensorFlow會回溯整個圖,並流經提供了所請求的輸出節點對應的輸入值的所有節點。因此此指令會打印預期的值7.0:
1 7.0
您可以將多個張量值傳遞給 tf.Session.run。run方法以透明方式處理元組或字典的任何組合,如下例所示:
1 print(sess.run({'ab':(a, b), 'total':total}))
它返回的結果擁有相同的布局結構:
1 {'total': 7.0, 'ab': (3.0, 4.0)}
在調用tf.Session.run期間,任何tf.Tensor都只有單個值。例如,以下代碼調用 tf.random_uniform 來生成一個tf.Tensor,后者都會生成隨機的三元素矢量(值位於[0,1)區間內):
1 vec = tf.random_uniform(shape=(3,)) 2 out1 = vec + 1 3 out2 = vec + 2 4 print(sess.run(vec)) 5 print(sess.run(vec)) 6 print(sess.run((out1, out2)))
每次調用run時,結果都會顯示不同的隨機值,但在單個run期間(out1 和 out2接收到相同的隨即輸入值),結果顯示的值是一致的:
1 [ 0.52917576 0.64076328 0.68353939] 2 [ 0.66192627 0.89126778 0.06254101] 3 ( 4 array([ 1.88408756, 1.87149239, 1.84057522], dtype=float32), 5 array([ 2.88408756, 2.87149239, 2.84057522], dtype=float32) 6 )
部分TensorFlow會返回 tf.Operations,而不是tf.Tensors。對指令調用run的結果是None。您運行指令是為了產生副作用,而不是為了檢索一個值。這方面的例子包括稍后將演示初始化和訓練操作。
供給
目前來講,這個圖不是特別有趣,因為它總是生成一個常量結果。圖可以參數化以便接收外部輸入,也稱為占位符。占位符表示承諾在稍后提供值,它就像函數參數。
1 x = tf.placeholder(tf.float32) 2 y = tf.placeholder(tf.float32) 3 z = x + y
前面三行有點像函數。我們定義了這個函數的兩個輸入參數(x 和 y),然后對它們運行指令。我們可以使用run方法的 feed_dict 參數為占位符提供具體的值,從而評估這個具有多個輸入的圖:
1 print(sess.run(z, feed_dict={x: 3, y: 4.5})) 2 print(sess.run(z, feed_dict={x: [1, 3], y: [2, 4]}))
上述操作的結果是輸出以下內容:
1 7.5 2 [ 3. 7.]
另請注意,feed_dict 參數可以覆蓋圖中的任何張量。占位符和其它任何 tf.Tensors 的唯一不同之處在於如果沒有為占位符提供值,那么占位符會拋出錯誤。
數據集
占位符適應於簡單的實驗,而數據集是將數據流式傳輸到模型的首要方法。
要從數據集中獲取可運行的tf.Tensor,您必須先將其轉換成 tf.data.Iterator,然后調用迭代器的 get_next 方法。
創建迭代器的最簡單的方式是采用 make_one_shot_iterator 方法。例如,在下面的代碼中,next_item 張量將在每次 run 調用時從 my_data陣列中返回一行:
1 my_data = [ 2 [0, 1,], 3 [2, 3,], 4 [4, 5,], 5 [6, 7,], 6 ] 7 slices = tf.data.Dataset.from_tensor_slices(my_data) 8 next_item = slices.make_one_shot_iterator().get_next()
到達數據流末端時,Dataset會拋出 OutOfRangeError 。例如,下面的額代碼會一直讀取next_item,直到沒有數據可讀。
1 while True: 2 try: 3 print(sess.run(next_item)) 4 except tf.errors.OutOfRangeError: 5 break
如果Dataset依賴於有狀態操作,則可能需要在迭代器之前先初始化它,如下所示:
1 r = tf.random_normal([10,3]) 2 dataset = tf.data.Dataset.from_tensor_slices(r) 3 iterator = dataset.make_initializable_iterator() 4 next_row = iterator.get_next() 5 6 sess.run(iterator.initializer) 7 while True: 8 try: 9 print(sess.run(next_row)) 10 except tf.errors.OutOfRangeError: 11 break
要詳細的了解數據集和迭代器,請參閱導入數據。
層
可訓練的模型必須修改圖中的值,以便在輸入相同值的情況下獲得新的輸出值。將可訓練參數添加到圖中的首選方法是層。
層將變量和作用到它們的操作打包在一起。例如,密集連接層會對每個輸出對應的所有輸入執行加權和,並應用激活函數(可選)。連接權重和偏差由層對象管理。
創建層
下面的代碼會創建一個 Dense 層,該層會接收一批輸入矢量,並為每個矢量生成一個輸出值。要將層應用於輸入值,請將該層作為函數來調用。例如:
1 x = tf.placeholder(tf.float32, shape=[None, 3]) 2 linear_model = tf.layers.Dense(units=1) 3 y = linear_model(x)
層會檢查其輸入數據,以確定其內部變量的大小。因此,我們必須在這里設置x占位符的形狀,以便層構建正確大小的權重矩陣。
我們現在定義了輸出值y的計算,在我們運行計算前,還需要處理一個細節。
初始化層
層包含的變量必須先初始化,然后才能使用。盡管可以單獨初始化各個變量,但也可以輕松的初始化一個TensorFlow圖中的所有變量(如下所示):
1 init = tf.global_variables_initializer() 2 sess.run(init)
重要提示:調用 tf.global_variables_initializer
僅會創建並返回 TensorFlow 操作的句柄。當我們使用 tf.Session.run
運行該操作時,該操作將初始化所有全局變量。
另請注意,此 global_variables_initializer 僅會初始化創建初始化程序時圖中就存在的變量。因此您應該在構建圖表的最后一步添加初始化程序。
執行層
我們現在已經完成了層的初始化,可以像處理任何其它張量一樣評估 linear_model 的輸出張量了。
例如,下面的代碼:
1 print(sess.run(y, {x: [[1, 2, 3],[4, 5, 6]]}))
會生成一個兩元素輸出向量,如下所示:
1 [[ 4.5423756] 2 [11.656053 ]]
層函數的快捷方式
對於每個層類(如 tf.layers.Dense),TensorFlow提供了一個快捷函數(如 tf.layers.dense)。兩者唯一的區別是快捷函數版本是在單次調用中創建和使用層。例如,以下代碼等同於較早版本:
1 x = tf.placeholder(tf.float32, shape=[None, 3]) 2 y = tf.layers.dense(x, units=1) 3 4 init = tf.global_variables_initializer() 5 sess.run(init) 6 7 print(sess.run(y, {x: [[1, 2, 3], [4, 5, 6]]}))
盡管這種方法很方便,但無法訪問 tf.layers.Layer 對象。這會讓自省和調試變得更加困難,並且無法重復使用相應的層。
特征列
使用特征列進行實驗的最簡單方法是使用 tf.feature_column.input_layer 函數。此函數只接受密集列作為輸入,因此要查看類別列的結果,您必須將其封裝在 tf.feature_column.indicator_column 中,例如:
1 features = { 2 'sales' : [[5], [10], [8], [9]], 3 'department': ['sports', 'sports', 'gardening', 'gardening']} 4 5 department_column = tf.feature_column.categorical_column_with_vocabulary_list( 6 'department', ['sports', 'gardening']) 7 department_column = tf.feature_column.indicator_column(department_column) 8 9 columns = [ 10 tf.feature_column.numeric_column('sales'), 11 department_column 12 ] 13 14 inputs = tf.feature_column.input_layer(features, columns)
運行input張量會將feature解析為一批向量。
特征列和層一樣具有內部狀態,因此通常需要將它們初始化。類別列表會在內部使用對照表,而這些表需要單獨的初始化操作 tf.tables_initializer。
1 var_init = tf.global_variables_initializer() 2 table_init = tf.tables_initializer() 3 sess = tf.Session() 4 sess.run((var_init, table_init))
初始化內部狀態后,您可以運行input(像運行其它tf.Tensor一樣):
1 print(sess.run(inputs))
這顯示了特征列如何打包輸入矢量,並將“department”作為第一和第二索引,將"sales"作為第三個索引。
1 [[ 1. 0. 5.] 2 [ 1. 0. 10.] 3 [ 0. 1. 8.] 4 [ 0. 1. 9.]]
訓練
您現在已經了解TensorFlow核心部分的基礎知識了,我們來手動訓練一個小型回歸模型吧。
定義數據
我們首先來定義一些輸入值x,以及每個輸入值得預期輸出值y_ture:
1 x = tf.constant([[1], [2], [3], [4]], dtype=tf.float32) 2 y_true = tf.constant([[0], [-1], [-2], [-3]], dtype=tf.float32)
定義模型
接下來,建立一個簡單得線性模型,其輸出值只有一個:
1 linear_model = tf.layers.Dense(units=1) 2 3 y_pred = linear_model(x)
您可以如下評估預測值:
1 sess = tf.Session() 2 init = tf.global_variables_initializer() 3 sess.run(init) 4 5 print(sess.run(y_pred))
該模型尚未訓練,因此四個“預測值”並不理想。以下是我們得到得結果,您自己的輸出應該有所不同:
1 [[-1.460115] 2 [-2.92023 ] 3 [-4.380345] 4 [-5.84046 ]]
損失
要優化模型,你首先需要定義損失。我們將使用均方誤差,這是回歸問題的標准損失。
雖然您可以使用較低級別的數學運算手動定義,但 tf.losses 模塊提供了一系列常用的損失函數。您可以用它來計算均方誤差,具體操作如下所示:
1 loss = tf.losses.mean_squared_error(labels=y_true, predictions=y_pred) 2 3 print(sess.run(loss))
會得到如下所示的損失值(你的應該有所不同):
1 3.6203036
訓練
TensorFlow提供了執行標准優化算法的優化器。這些優化器被實現為 tf.train.Optimizer 的子類。它們會逐漸改變每個變量,以便將損失最小化。最簡單的優化算法是梯度下降法,由 tf.train.GrandientDescentOptimizer 實現。它會根據損失相對於變量的導數大小來修改各個變量。例如:
1 optimizer = tf.train.GradientDescentOptimizer(0.01) 2 train = optimizer.minimize(loss)
該代碼構建了優化所需的所有圖組件,並返回一個訓練指令。該訓練指令在運行時會更新圖中的變量。您可以按照以下方式運行該指令:
1 for i in range(100): 2 _, loss_value = sess.run((train, loss)) 3 print(loss_value)
由於train是一個指令而不是張量,因此它在運行時不會返回一個值。為了查看訓練期間損失的進展,我們會同時運行損失張量,生成如下所示的輸出值:
1 5.4017677 2 3.8268266 3 2.7335377 4 1.9744602 5 1.4472877 6 1.081032 7 0.8264358 8 0.6493206 9 0.5259704 10 0.4399293 11 0.37977904 12 ...
完整程序
1 x = tf.constant([[1], [2], [3], [4]], dtype=tf.float32) 2 y_true = tf.constant([[0], [-1], [-2], [-3]], dtype=tf.float32) 3 4 linear_model = tf.layers.Dense(units=1) 5 6 y_pred = linear_model(x) 7 loss = tf.losses.mean_squared_error(labels=y_true, predictions=y_pred) 8 9 optimizer = tf.train.GradientDescentOptimizer(0.01) 10 train = optimizer.minimize(loss) 11 12 init = tf.global_variables_initializer() 13 14 sess = tf.Session() 15 sess.run(init) 16 for i in range(100): 17 _, loss_value = sess.run((train, loss)) 18 print(loss_value) 19 20 print(sess.run(y_pred))
后續步驟
要詳細了解如何使用TensorFlow構建模型,請參閱以下內容:
- 自定義Estimator,了解如何使用TensorFlow構建自定義模型。掌握TensorFlow Core知識有助於理解和調試您的模型。
如果您像詳細了解TensorFlow的內部工作原理,請參閱以下文檔。這些文檔深入探討了這篇文章中提到的許多主題:
- 圖與會話
- 張量
- 變量
參考鏈接:https://tensorflow.google.cn/guide/low_level_intro#training_2