Tensorflow使用數(shù)據(jù)流圖dataflow graph表示各個(gè)獨(dú)立操作之間的依賴關(guān)系。
Tensorflow低級(jí)編程模式:
- 定義dataflow graph
- 創(chuàng)建會(huì)話session
- 使用session在本地或遠(yuǎn)程設(shè)備運(yùn)行g(shù)raph
什么事Dataflow graphs?
Dataflow是平行計(jì)算的通用模式。其中節(jié)點(diǎn)node表示計(jì)算單元,邊線edge表示計(jì)算產(chǎn)生和消耗的數(shù)據(jù)。例如上圖地步的MatMul計(jì)算節(jié)點(diǎn),有兩條數(shù)據(jù)輸入邊線和一條向上的輸出邊線,對(duì)應(yīng)tf.matmul(a,b)
方法輸出矩陣a和b相乘的結(jié)果。
Dataflow graph的優(yōu)勢:
- 平行結(jié)構(gòu)Parallelism。
- 分布式計(jì)算Distributed execution。Tensorflow可以協(xié)調(diào)多設(shè)備多機(jī)器運(yùn)算。
- 可編譯Compilation。XLA compiler可以利用數(shù)據(jù)流的關(guān)系產(chǎn)生高效率代碼,融合相鄰運(yùn)算。
- 可移植Portability。不依賴編程語言存在,可以多語言結(jié)合。
什么是tf.graph?
它包含了兩類相關(guān)信息:
圖結(jié)構(gòu)Graph structure。節(jié)點(diǎn)和邊線表示了各個(gè)操作之間的關(guān)系,但并不限定如何被使用。圖結(jié)構(gòu)類似組裝代碼:從它可以看到很多關(guān)系信息,但并不是實(shí)際代碼。
圖集合Graph collections。Tensorflow提供了一種生成機(jī)制,用來存儲(chǔ)圖中的元數(shù)據(jù)matadata。
tf.add_to_collection
可以將一個(gè)列表對(duì)象組裝到一個(gè)鍵key(由tf.GraphKeys
定義)。例如,當(dāng)我們使用tf.variable
創(chuàng)建一個(gè)變量的時(shí)候,它被添加到表示全局變量global variables和可訓(xùn)練變量trainabled
varibale兩個(gè)集合,當(dāng)稍后再創(chuàng)建其他tf.train.Saver、tf.train.Optimizer
的時(shí)候,集合中的這些變量將作為默認(rèn)參數(shù)使用。
創(chuàng)建一個(gè)tf.graph
大多數(shù)Tensorflow程序開始于圖的創(chuàng)建。我們使用tf.oparation
創(chuàng)建操作節(jié)點(diǎn),使用tf.Tensor
創(chuàng)建張量邊線,然后把它們添加到tf.graph
。
Tensorflow提供了一個(gè)默認(rèn)圖,作為一個(gè)顯式的參數(shù),作用于同一個(gè)上下文環(huán)境的接口函數(shù)。例如:
-
tf.constant(42.0)
,這將創(chuàng)建一個(gè)產(chǎn)生42.0的操作tf.operation
,并添加到默認(rèn)圖,并返回一個(gè)表示常數(shù)的張量tf.tensor
。
*tf.matmul(x,y)
,就是把xy兩個(gè)張量相乘的操作,自動(dòng)添加到默認(rèn)圖,返回相乘的結(jié)果(也是個(gè)張量)。
*v=tf.Variable(0)
,向默認(rèn)圖添加了一個(gè)操作,這個(gè)操作能夠存儲(chǔ)一個(gè)在會(huì)話多次運(yùn)行之間session.run
可讀寫的張量值。tf.Variable
對(duì)象包裹著這個(gè)操作,使它可以像普通張量一樣被讀寫使用。tf.Variable
對(duì)象自身也附帶了一些方法比如assign,assign_add
,這些方法也能生成新的操作,通過這些操作在運(yùn)算時(shí)候改變存儲(chǔ)的張量。
-
tf.train.Optimizer.minimize
,將添加操作和張量到默認(rèn)圖,它們用來計(jì)算梯度gradient并返回一個(gè)操作,當(dāng)被運(yùn)行的時(shí)候,這個(gè)操作將把梯度變化應(yīng)用到變量集合上。
絕大多數(shù)程序只要解算默認(rèn)圖就可以了。類似tf.estimator.Estimator
接口管理著默認(rèn)圖,當(dāng)然也會(huì)為訓(xùn)練和評(píng)價(jià)創(chuàng)建不同的圖。
Tensorflow大部分API接口都只是創(chuàng)建operation和tensor,并不真正執(zhí)行運(yùn)算。你編織整個(gè)圖譜網(wǎng)絡(luò),直到找到那個(gè)最終結(jié)果或可以得到期望最后結(jié)果的操作,然后把它傳遞到session會(huì)話進(jìn)行求解。
為操作命名
Graph為所有包含的操作定義了一個(gè)命名空間。在圖中,Tensorflow為每一個(gè)操作自動(dòng)設(shè)定了唯一的名稱,這個(gè)名稱與編程中的命名無關(guān)。有兩個(gè)方法覆蓋這個(gè)自動(dòng)的命名:
每個(gè)創(chuàng)建新操作或者返回新張量的的函數(shù)都接受name參數(shù)。例如
tf.constant(42.0, name="answer")
就會(huì)創(chuàng)建一個(gè)名叫answer的操作,并返回一個(gè)名叫answer:0的張量,如果默認(rèn)圖已經(jīng)存在同名操作,那么新操作就會(huì)被自動(dòng)添加_1,_2這樣的結(jié)尾。tf.name_scope
方法可以針對(duì)一個(gè)上下文環(huán)境添為特定范圍內(nèi)的操作都添加名稱前綴。/斜杠用來劃分name_scope的層級(jí),如果已有同名,則自動(dòng)添加_1,_2。
c_0 = tf.constant(0, name="c") # 得到名稱為c的操作
c_1 = tf.constant(2, name="c") # 重名!得到名稱為c_1的操作
#外層的命名空間.
with tf.name_scope("outer"):
c_2 = tf.constant(2, name="c") #outer/c
with tf.name_scope("inner"): #嵌套的命名空間
c_3 = tf.constant(3, name="c") #outer/inner/c
c_4 = tf.constant(4, name="c") #重名!變?yōu)閛uter/c_1
with tf.name_scope("inner"):
c_5 = tf.constant(5, name="c") #重名!變?yōu)閛uter/inner_1/c
tf.Tensor
對(duì)象命名規(guī)則是在產(chǎn)生它的操作名稱后面加冒號(hào)加數(shù)字,例如上面提到的answer操作產(chǎn)生的張量命名是answer:0。
將操作放置到不同設(shè)備
tf.device()
方法可以把同一個(gè)上下文中產(chǎn)生的各種操作分不到不同的設(shè)備中使用。
設(shè)備參數(shù)格式:
/job:<JOB_NAME>/task:<TASK_INDEX>/device:<DEVICE_TYPE>:<DEVICE_INDEX>
- JOB_NAME,非數(shù)字開頭字母數(shù)字組成的字符串
- DEVICE_TYPE,設(shè)備類型,GPU或CPU
- TASK_INDEX,工作中的任務(wù)序號(hào)
- DEVICE_TYPE,設(shè)備序號(hào)
# 這里創(chuàng)建的操作自動(dòng)選擇,優(yōu)先GPU
weights = tf.random_normal(...)
with tf.device("/device:CPU:0"):
#這里創(chuàng)建的操作將使用CPU
img = tf.decode_jpeg(tf.read_file("img.jpg"))
with tf.device("/device:GPU:0"):
#這里創(chuàng)建的操作將使用GPU.
result = tf.matmul(weights, img)
paramaters server(ps)參數(shù)服務(wù)器格式/job:ps,worker工作機(jī)格式/job:worker
with tf.device("/job:ps/task:0"):
weights_1 = tf.Variable(tf.truncated_normal([784, 100]))
biases_1 = tf.Variable(tf.zeroes([100]))
with tf.device("/job:ps/task:1"):
weights_2 = tf.Variable(tf.truncated_normal([100, 10]))
biases_2 = tf.Variable(tf.zeroes([10]))
with tf.device("/job:worker"):
layer_1 = tf.matmul(train_batch, weights_1) + biases_1
layer_2 = tf.matmul(train_batch, weights_2) + biases_2
使用tf.train.replica_device_setter
自動(dòng)分配:
with tf.device(tf.train.replica_device_setter(ps_tasks=3)):
# tf.Variable objects對(duì)象會(huì)被自動(dòng)循環(huán)分配任務(wù)
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"
類似張量對(duì)象Tensor-like objects
類似張量對(duì)象可以被隱式的轉(zhuǎn)化為張量,它分為下面幾種:
tf.Tensor
tf.variable
numpy.ndarray
- list(和由類似張量物體構(gòu)成的list)
- Python標(biāo)量值:bool,string,int,float
注意,在使用類似張量對(duì)象的時(shí)候,tensorflow每次都創(chuàng)建新的
tf.tensor
對(duì)象,如果這個(gè)對(duì)象很大,那么多次創(chuàng)建會(huì)消耗大量內(nèi)存。
使用tf.Session對(duì)圖進(jìn)行計(jì)算
Tensorflow使用會(huì)話tf.Session
來建立客戶端程序(Python或其他語言編寫的程序)與C++運(yùn)行時(shí)之間的關(guān)系。tf.Session
提供了訪問本機(jī)和分布式機(jī)器的能力,并能緩存圖graph的信息以便于多次運(yùn)行。
創(chuàng)建會(huì)話
# 創(chuàng)建默認(rèn)的進(jìn)程內(nèi)in-process會(huì)話.
with tf.Session() as sess:
# ...
# 創(chuàng)建遠(yuǎn)程會(huì)話
with tf.Session("grpc://example.org:2222"):
# ...
由于session擁有物理資源(如GPU和網(wǎng)絡(luò)鏈接),所以它只管理自己的代碼塊的內(nèi)容,退出代碼塊就會(huì)關(guān)閉。如果沒有使用代碼塊,那么必須顯式的關(guān)閉它tf.Session.close()
。
Hight-level Api比如
tf.estimator.Estimator
將會(huì)自動(dòng)管理session,可以通過target或config參數(shù)對(duì)session進(jìn)行設(shè)置。
tf.Session.init
包含三個(gè)參數(shù):
- 目標(biāo)target。如果為空,會(huì)話只使用本機(jī)。可以使用
grpc://
URL指定Tensorflow服務(wù)器,會(huì)話將可以使用該服務(wù)器控制的全部機(jī)器的全部設(shè)備。 - 圖graph。默認(rèn)一個(gè)新的會(huì)話將綁定到默認(rèn)圖,如果你的程序使用了多個(gè)圖,那么可以用它顯式的指定。
- 設(shè)置config。設(shè)定一個(gè)
tf.ConfigProto
控制會(huì)話動(dòng)作。它包含了一些設(shè)置比如: - allow_soft_placement,使用軟件設(shè)備soft device,這將忽略GPU,只用CPU。
- cluster_def,當(dāng)使用分布式的TensorFlow的時(shí)候,這個(gè)參數(shù)可以設(shè)定具體使用哪些機(jī)器進(jìn)行計(jì)算。
- graph_options.optimizer_options,在圖運(yùn)算之前,控制優(yōu)化器。
- gpu_options.allow_growth,設(shè)置gpu內(nèi)存分配器可以梯度增加內(nèi)存而不是初始占用很多。
使用tf.Session.run執(zhí)行操作
run方法是對(duì)圖進(jìn)行運(yùn)算的最主要的方法,接受一個(gè)或多個(gè)提取器fetches列表。提取器可以是:
- 一個(gè)操作operation
- 一個(gè)張量tensor
- 一個(gè)類似張量如tf.Variable
這些提取器限定了一個(gè)可被運(yùn)行的子圖subgraph,連接到全圖的所有相關(guān)操作。
import tensorflow as tf
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:
sess.run(init_op)
print(sess.run(output))
y_val, output_val = sess.run([y, output])
print(y_val)
print(output_val)
Session.run
接收feed_dic
參數(shù),一般是placeholder表示的值(scalar、list、numpy array),這些值在被計(jì)算的時(shí)候替換placeholder表示的張量,如下
import tensorflow as tf
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x) #這里使用了替代品張量
with tf.Session() as sess:
print(sess.run(y, {x: [1.0, 2.0, 3.0]})) # => "[1.0, 4.0, 9.0]"
print(sess.run(y, feed_dict={x: [0.0, 0.0, 5.0]})) # => "[0.0, 0.0, 25.0]"
sess.run(y) #出錯(cuò)!placeholder必須帶有feed_dict
sess.run(y, {x: 37.0}) #出錯(cuò),數(shù)值形狀與placeholder不匹配
Session.run
接收一個(gè)RunOptions參數(shù),可以用來收集運(yùn)算過程中的信息。這些信息可以用來構(gòu)建類似Tensorboard的信息圖譜。
import tensorflow as tf
y = tf.matmul([[37.0, -23.0], [1.0, 4.0]], tf.random_uniform([2, 2]))
with tf.Session() as sess:
#定義選項(xiàng)
options = tf.RunOptions()
options.output_partition_graphs = True
options.trace_level = tf.RunOptions.FULL_TRACE
#定義元數(shù)據(jù)容器
metadata = tf.RunMetadata()
sess.run(y, options=options, run_metadata=metadata)
# 打印每個(gè)設(shè)備上執(zhí)行的子圖結(jié)構(gòu).
print(metadata.partition_graphs)
# 打印每個(gè)操作執(zhí)行時(shí)候的時(shí)間.
print(metadata.step_stats)
視覺化計(jì)算圖
Tensorborad的Graph visualizer元件可以將計(jì)算圖的結(jié)構(gòu)渲染到瀏覽器內(nèi),只要在創(chuàng)建圖的時(shí)候把tf.graph
對(duì)象傳遞給tf.summary.FileWriter
:
import tensorflow as tf
import os
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)
dir_path = os.path.dirname(os.path.realpath(__file__))
sum_path=os.path.join(dir_path,'temp') #不要使用斜杠
with tf.Session() as sess:
writer = tf.summary.FileWriter(sum_path, sess.graph)
print(sess.run(y, {x: [1.0, 2.0, 3.0]})) # => "[1.0, 4.0, 9.0]"
writer.close()
然后命令行運(yùn)行tensorboard --logdir=~/desktop/Myprojects/xxx/xxx/
,再打開任意瀏覽器訪問http://localhost:6006
,點(diǎn)擊GRAPHS按鈕即可看到類似下圖:
使用多個(gè)計(jì)算圖編程
當(dāng)訓(xùn)練模型的時(shí)候,通常使用不同的圖進(jìn)行訓(xùn)練、評(píng)價(jià)和預(yù)測。你可以使用不同的Python進(jìn)程來創(chuàng)建和運(yùn)行不同的圖,也可以在統(tǒng)一進(jìn)程內(nèi)處理。
TensorFlow提供了默認(rèn)的graph圖:
-
tf.graph
定義了其下所有操作的命名空間,單個(gè)圖內(nèi)的每個(gè)操作必須具有唯一命名,重名會(huì)自動(dòng)添加'_1','_2'后綴。 - 默認(rèn)圖自動(dòng)存儲(chǔ)每個(gè)添加進(jìn)來的fetches提取器信息(operation、tensor等),如果你的圖包含了大數(shù)量節(jié)點(diǎn)的未連接子圖,最好的辦法是將它們分成不同的圖,以便于更有效的資源回收。
也可以使用自定義的tf.graph
來替代默認(rèn)圖,使用tf.graph.as_default
語法,以下實(shí)例代碼創(chuàng)建了兩個(gè)session分別運(yùn)行兩個(gè)不同的圖:
import tensorflow as tf
g_1 = tf.Graph()
with g_1.as_default():#以下操作將被添加到`g_1`.
c = tf.constant("Node in g_1")
# 這里創(chuàng)建的回話將運(yùn)行`g_1`.
sess_1 = tf.Session()
print(sess_1.run(c))
g_2 = tf.Graph()
with g_2.as_default():#以下操作將被添加到`g_2`.
d = tf.constant("Node in g_2")
#也可以直接為session指定graph
sess_2 = tf.Session(graph=g_2)
print(sess_2.run(d))
以上代碼輸出
b'Node in g_1'
b'Node in g_2'
使用tf.get_default_graph
獲取計(jì)算圖
import tensorflow as tf
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)
g = tf.get_default_graph()
print(g.get_operations())
本篇小結(jié)
- Dataflow graph數(shù)據(jù)流圖表,由節(jié)點(diǎn)node和邊線edge組成。
- tf.graph包含了節(jié)點(diǎn)直接的操作關(guān)系
- 創(chuàng)建的各種節(jié)點(diǎn)和張量都會(huì)被自動(dòng)添加到默認(rèn)圖
- name參數(shù)讓每個(gè)操作都有唯一的識(shí)別名,產(chǎn)生的每個(gè)張量也都有唯一識(shí)別名
- 在不同設(shè)備和不同機(jī)器上分布操作
- Tensor-like objects類似張量對(duì)象
- tf.Session會(huì)話對(duì)圖進(jìn)行計(jì)算
- session的創(chuàng)建和初始化
- tf.Session.run方法運(yùn)行整圖或子圖
- 利用Tensorboard視覺化計(jì)算圖
- 使用多個(gè)計(jì)算圖
探索人工智能的新邊界
如果您發(fā)現(xiàn)文章錯(cuò)誤,請不吝留言指正;
如果您覺得有用,請點(diǎn)喜歡;
如果您覺得很有用,感謝轉(zhuǎn)發(fā)~
END