TensorFlow Low-Level-APIs Graphs and Sessions學習筆記
Graphs and Sessions
本文翻譯自tensorflow官方網站的教程,只作為個人學習筆記,請勿用作商業用途。
tf使用一個數據流圖去表示使用Operation構建起來的計算流程,在low level api上需要和計算圖打交道,定義圖然後使用session在本地或者遠端伺服器上執行圖。如果你想要進行low level api的程式設計那麼這個教程很有用。高階的api例如tf.estimator.Estimator和Keras隱藏了計算圖和session的細節,如果你想要了解高階api背後的細節那麼這篇教程也很有用。
Why dataflow graphs?
資料流圖通常被使用來對平行計算進行建模。在一個數據流圖中節點表示計算的單元(Operation),邊表示資料(Tensor)的流動
- 並行Parallelism。使用資料流圖可以讓系統識別出哪些operation是可以並行獨立執行的。
- 分散式執行Distributed execution。通過使用邊來表示流動於operation之間的資料,tf可以將不同的操作分割開讓不同的GPU CPU來執行這些操作。tf也有不同裝置之間的通訊和協作介面。
- 編譯Compilation。tf的XLA編譯器可以使用計算圖中的資訊來生成更高效的底層程式碼。
- 移植性Portability。計算圖是獨立於具體程式語言的。你可以使用Python來定義計算圖儲存後可以使用C++中讀取。
What is a tf.Graph?
一個tf.Graph包括如下兩點資訊:
- Graph structure。圖中的節點和邊讓計算operation連線在一起。但是並沒有描述他們是怎麼使用的。計算圖的結構就像是組合起來的程式碼,檢視他們可以獲得一些有用的資訊,但是並沒有包含所有有用的上下文資訊(原始碼)。
- Graph collections。tf提供了一個較為general的機制來儲存tf.Graph的元資料。tf.add_to_collection函式使得你可以讓一個list中的物件都關聯起來,只使用一個關鍵詞key。tf.get_collection使得你可以檢視和同一個key關聯的所有物件。tf庫中的很多部分都使用這種機制例如:當你建立tf.Variable的時候它被加入global variable和trainable variable中。當隨後使用tf.train.Saver 或者tf.train.Optimizer的時候,這些儲存到collection中的variable將被關聯。
Building a tf.Graph
大多數的tf程式一開始都要構建起資料流圖,在這個階段為一個tf.Graph例項中新增新的tf.Operation(節點)和tf.Tensor(邊)。tf提供一個預設的計算圖大多數情況下這是夠用的,給出一些新增節點的例子:
- 呼叫tf.constant(42.0)建立一個tf.Operation接受值42.0並且返回一個Tensor,表示constant的Tensor。
- 呼叫tf.matmul(x, y)建立一個tf.Operation將傳入的兩個Tensor進行相乘。
- 執行v=tf.Variable(0)向圖中新增一個tf.Operation,這個op產生一個可變的Tensor。Variable也有assign assign_add方法可以往圖中新增ops,用於run時更新引數。
- 呼叫tf.train.Optimizer.minize將要往圖中新增新增ops和tensor,這些ops和tensor用於計算梯度,並且返回一個tf.Operation,當run的時候將會用這些梯度更新variable。
Note在定義計算圖的時候,並沒有執行真正的計算。而是等到計算圖構建完畢,有一個最終的可以代表整個計算圖的tensor或者operation的時候來通過tf.Session執行。
Naming operations
一個tf.Graph物件為它包含的tf.Operation定義了一個名稱空間。tf會自動地給計算圖中每個operation賦予一個唯一的名字,但是自己賦予可讀的名字讓程式更易懂。TensorFlow api提供了兩種賦予operation名字的方法:
- tf.constant(42.0, name=”answer”),這個呼叫添加了一個Operation名字為”answer”,並且返回了一個tensor名字為”answer:0”如果之前定義了”answer”的operation,那麼新的將被新增”_1” “_2”等。
- tf.name_scope函式使得在一個命名字首下給operation賦名字成為可能。當前的名字的前面將會加上”prefix/”,如果當前的名字已經在當前的scope下使用過了,那麼會在後面加上”_1” “_2”
c_0 = tf.constant(0, name="c") # => operation named "c"
# Already-used names will be "uniquified"
c_1 = tf.constant(2, name="c") # => operation named "c_1"
# Name scopes add a prefix to all operations created in the same context
with tf.name_scope("outer"):
c_2 = tf.constant(2, name="c") # => operation named "outer/c"
# Name scopes nest like paths in a hierarchical file system
with tf.name_scope("inner"):
c_3 = tf.constant(3, "c") # => operation named "outer/inner/c"
# Exiting a name scope context will return to the previous prefix
c_4 = tf.constant(4, name="c") # => operation named "outer/c_1"
# Already-used name scopes will be "uniquified"
with tf.name_scope("inner"):
c_5 = tf.constant(5, name="c") # => operation named "outer/inner_1/c"
graph visualizer使用name scope來將具有相同字首的operation合併到一起,這樣可以減少視覺化計算圖的複雜程度。注意到每個operation輸出的tensor都是以這個operation的名字來命名的”< OP_NAME >:< i >”:
- “< OP_NAME >”是產生這個tensor的operation的名字
- “< i >”是表示這個operation輸出的第幾個tensor
Placing operations on different devices
使用tf.device來將operation放置到不同的裝置上,tf.device可以讓在同一個上下文中建立的operation放置到同一個裝置上,在tf中表達一個裝置可以使用這樣的字串:”/job:/task:/device::”,其中:
- 是一個文字數alpha-numeric,不以數字開頭
- 是一個註冊的裝置型別(GPU CPU)
- 是一個非負的整數表示這個job中的第幾個task。看tf.train.CulsterSpec進一步瞭解。
其實並不需要對每個操作都指定裝置,如果你有一個GPU和CPU那麼你可以使用如下的例子來安排operation:
# Operation createdd outside either context will run on the "best possible" device.For example if you have a GPU and a CPU avaiable, and the operation has a GPU implementation, tf will choose the GPU
weights = tf.random_normal(...)
with tf.device("/device:CPU:0"):
# Operation created in this context will be pinedd to CPU
img = tf.decode_jpeg(tf.read_file("img.jpg"))
with tf.device("/device:GPU:0"):
# Operation created in this context will be pined to GPU
result = tf.matmul(weights, img)
如果在一個分散式的情況下部署tf,那麼需要指定job name和task ID,來放置某個task的variable到指定的job server(“/job:ps”)上,其他的task的operation會放置在worker job(“/job:worker”)上:
with tf.device("/job:ps/task:0"):
weights_1 = tf.Variable(tf.truncated_normal([784, 100]))
biases_1 = tf.Variable(tf.zeros([100]))
with tf.device("/job:ps/task:1"):
weights_2 = tf.Variable(tf.truncated_normal([100, 10]))
biases_2 = tf.Variable(tf.zeros([10]))
with tf.device("/job:worker"):
layer_1 = tf.matmul(train_batch, weights_1) + biases_1
layer_2 = tf.matmul(train_batch, weights_2) + baises_2
對於放置單獨的operation或者計算圖的某個區域,tf.device都提供了很高的靈活性。例如使用tf.train.replica_device_setter可以和tf.device一起使用來進行分散式計算:
with tf.device(tf.train.replica_device_setter(ps_tasks=3)):
# tf.Variable objects are, by default, placed on tasks in "job:ps" in a round-robin fashion
w_0 = tf.Variable(...) # placed on "/job:ps/task:0"
b_0 = tf.Variable(...) # placed on "/job:ps/task:1"
w_1 = tf.Variable(...) # placed on "/job:ps/task:2"
b_1 = tf.Variable(...) # placed on "/job:ps/task:0"
input_data = tf.placeholder(tf.float32) # placed on "/job:worker"
layer_0 = tf.matmul(input_data, w_0) + b_0 # placed on "/job:worker"
layer_1 = tf.matmul(layer_0, w_1) + b_1 # placed on "/job:worker"
Tensor-like object
一般tf中的operation都接受一個或者多個tf.Tensor物件作為引數。例如tf.matmul接受兩個tf.Tensor對像,tf.add_n接受由n個tf.Tensor組成的list。為了方便這些函式也接受Tensor-like的物件,並且也使用tf.convert_to_tensor方法將其轉換為tensor,Tensor-like物件如下:
- tf.Tensor
- tf.Variable
- numpy.ndarray
- list(and lists of tensor-like objects)
- Scalar Python types: bool, float, int, str
你也可以使用tf.register_tensor_conversion_function自己註冊新的tesor-like型別。
Note預設情況下,每次使用Tensor-like物件都會呼叫tf.convert_to_tesnro,假如使用的是包含大量訓練資料的numpy.ndarray,那麼每次呼叫都會重新產生tf.Tensor所以,最好人為的呼叫tf.convert_to_tensor然後使用返回的tensor,這樣可以避免memory out。
Excution a graph in a tf.Session
TensorFlow使用tf.Session去表示前端(Python或者其他語言)和執行時C++庫的連線。一個tf.Session物件提供了連線到本地裝置和遠端伺服器的方法。它也快取計算圖中的資訊,所以可以高效地執行計算。
Creating a tf.Session
如果使用low-level api那麼可以使用如下的方法對預設的計算圖產生一個Session:
# Create a default in-process session
with tf.Session() as sess:
# ...
# Create a remote session
with tf.Session("grpc://example.org:2222"):
# ...
因為tf.Session會呼叫物理資源(如GPU或者網路連線),所以使用with語句來管理程式碼,能夠自動關閉資源。當然不用with就需要自己手動關閉資源。
tf.Session.init接受如下三個可選引數:
- target如果不傳入這個引數,那麼就預設使用本地的機器。如果指定了引數類似於”grpc://”來指定一個遠端的TensorFlow伺服器,這個session將會提供對這個伺服器的連線控制。
- graph預設情況下Session將被繫結到當前預設的計算圖上,如果使用多個計算圖那麼可以通過graph引數來指定。
- config這個引數允許你指定一個tf.ConfigProto來控制session的行為。可以配置的選項如下:
- allow_soft_placement.設定這個選項為True將會啟用一個較為soft的裝置選擇,如果之前聲明瞭將CPU-only的操作放在GPU上,那麼這個設定將會忽略之前的宣告,把CPU-only的operation還是會放在CPU上。
- cluster_def.當使用分散式的TensorFlow的時候這個選項允許你指定使用哪個機器來進行計算,並提供job name、task indices和網路地址之間的對映。
- graph_options.optimizer_options.提供對TensorFlow對計算圖優化的控制。
- gpu_options.allow_growth.設定這個選項為True來改變GPU記憶體分配,逐步分配記憶體而不是一開始就分派很多記憶體。
Using tf.Session.run to execute operation
執行計算圖中的操作獲得某個tensor的值依靠的就是tf.Session.run方法。你可以傳遞一個或者多個tf.Opeartion或者tf.Tensor物件到tf.Session.run中,並且tf將會執行中間的operation來得到最終的結果。
tf.Session.run要求你指定a list of fetches來決定返回值,返回值可能是tf.Operation tf.Tensor或者一個tensor-like型別如tf.Variable物件。這些fetches決定了計算圖中的哪些子圖會被計算從而得到fetches的結果,所有執行的子圖都是為了得到fetches的結果,如例子:
x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
output = tf.nn.softmax(y)
init_op = w.initializer
with tf.Session() as sess:
# run the initializer on w
sess.run(init_op)
# evaluate output. sess.run(output) will return a NumPy array contain the result of computation.
print(sess.run(output))
# evaluate y and output. Note that y will only be computed once, and result used both to return y_val and as an input to the tf.nn.softmax() op. Both y_val and output_val will be NumPy arrays
y_val, output_val = sess.run([y, output])
tf.Session.run也接受一個字典作為feed,feed是一個從tf.Tensor物件(一般是tf.placeholder tensor)到具體的值(一般是Python 標量 list或者NumPy array)執行的時候將會用具體的值代替placeholder:
# Define a placeholder that expects a vector of three floating-point value and a computation that depends on it
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)
with tf.Session() as sess:
# Feeding a value change the result that is returned when you evaluate
print(sess.run(y, {x:[1.0, 2.0, 3.0]})) # => "[1.0, 4.0, 9.0]"
print(sess.run(y, {x:[0.0, 0.0, 5.0]})) # => "[0.0, 0.0, 25.0]"
# Raises <a href="../api_docs/python/tf/errors/InvalidArgumentError"><code>tf.errors.InvalidArgumentError</code></a>, because you must feed a value for
# a `tf.placeholder()` when evaluating a tensor that depends on it.
sess.run(y)
# Raises 'ValueError', because the shape of 37.0 does not match the shape of placeholder x
sess.run(y, {x:37.0})
tf.Session.run也接受一個options引數讓你可以指定run呼叫時的引數,還有一個可選的run_metadata引數使得你可以手機執行過程中的元資料,如下例子,可以使用這些可選引數來收集追蹤執行過程中的引數:
y = tf.matmul([[37.0, -23.0], [1.0, 4.0]], tf.random_uniform([2, 2]))
with tf.Session() as sess:
# Define options for the sess.run() call
options = tf.RunOptions()
options.output_partition_graphs = True
options.trace_level = tf.RunOptions.FULL_TRACE
# Define a container for the returned metadata
metadata = tf.RunMetadata()
sess.run(y, options=options, run_metadata=metadata)
# Print the subgraphs that executed on each device
print(metadata.partition_graphs)
# Print the timings of each operation the executed
print(metadata.step_starts)
Visualizing your graph
TensorFlow提供了幫助你理解計算圖的工具,graph visualizer是TensorBoard的一個元件,可以實現在瀏覽器中對計算圖進行視覺化。建立的方法就是建立tf.summary.FileWriter的時候將計算圖傳遞給它。
# Build your graph
x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul()
# ...
loss = ...
train_op = tf.tain.AdagradOptimizer(0.01).minimize(loss)
with tf.Session() as sess:
# sess.graph provides access to the graph used in a <a href="../api_docs/python/tf/Session"><code>tf.Session</code></a>.
writer = tf.summary.FileWriter(".temp/log/...", sess.graph)
# Perform your computation...
for i in range(1000):
sess.run(train_op)
#...
writer.close()
然後在tensorboard中開啟log,切換到Graph上就可以看到計算圖的視覺化結果了。