TensorFlow核心概念之計(jì)算圖

什么是計(jì)算圖

??什么計(jì)算圖呢?計(jì)算圖跟圖計(jì)算不一樣,圖計(jì)算是對(duì)基于圖數(shù)據(jù)的計(jì)算的統(tǒng)稱。而計(jì)算圖是對(duì)一系列計(jì)算和數(shù)據(jù)流轉(zhuǎn)編排之后形成的有向無(wú)環(huán)圖的描述。搞過(guò)大數(shù)據(jù)的應(yīng)該都對(duì)大數(shù)據(jù)的調(diào)度及依賴任務(wù)編排比較熟悉,我們會(huì)將前后依賴的任務(wù)進(jìn)配置,設(shè)置個(gè)任務(wù)間的依賴關(guān)系,然后形成了一個(gè)關(guān)于各種數(shù)據(jù)加工和依賴任務(wù)的有向無(wú)環(huán)圖(DAG),在這個(gè)有向無(wú)環(huán)圖中,圖中的頂點(diǎn)(Vertex、Node)是一個(gè)個(gè)用于執(zhí)行某種計(jì)算的任務(wù),頂點(diǎn)直接的關(guān)系(Relationship也就是邊,Edge)有的是數(shù)據(jù)流轉(zhuǎn),有的是任務(wù)依賴。其實(shí)TensorFlow中的計(jì)算圖也基本上類似與這種有DAG圖。
??通常一個(gè)機(jī)器學(xué)習(xí)任務(wù)的核心是定義模型和求解參數(shù),在對(duì)模型定義和參數(shù)求解過(guò)程進(jìn)行抽象之后,在確定了數(shù)據(jù)的流轉(zhuǎn)方式、數(shù)據(jù)的計(jì)算方式以及各種計(jì)算之間的相互依賴關(guān)系之后,就可以確定一個(gè)唯一的計(jì)算執(zhí)行邏輯,然后將這個(gè)計(jì)算執(zhí)行邏輯用圖表示,最后我們稱這個(gè)有向無(wú)環(huán)圖為計(jì)算圖。
??TensorFlow中的計(jì)算圖由點(diǎn)(nodes)和邊(edges)組成,節(jié)點(diǎn)表示算子,邊表示算子間的依賴或數(shù)據(jù)(一般是張量)的傳遞方向,其中實(shí)線表示有數(shù)據(jù)傳遞依賴,傳遞的數(shù)據(jù)即為張量;而虛線通常表示控制依賴,即執(zhí)行先后順序,不存在數(shù)據(jù)傳遞依賴。所有的節(jié)點(diǎn)都通過(guò)邊連接,其中入度為0的節(jié)點(diǎn)沒(méi)有前置依賴,可以立即執(zhí)行;入度大于0的節(jié)點(diǎn),要等待其前置依賴的所有節(jié)點(diǎn)執(zhí)行結(jié)束之后才能執(zhí)行。下圖就是TensorFlow中一個(gè)簡(jiǎn)單的計(jì)算圖示例:

Compute Graph

??計(jì)算圖創(chuàng)建好了之后,TensorFlow就會(huì)需要啟動(dòng)Session去執(zhí)行計(jì)算圖,在TensorFlow中,一個(gè)Session可以執(zhí)行多個(gè)計(jì)算圖,每個(gè)計(jì)算圖之間的執(zhí)行相互獨(dú)立。計(jì)算圖的執(zhí)行參考了拓?fù)渑判虻乃枷?,關(guān)于拓?fù)渑判?,如果有不清楚的,可以參考我的另外一篇文章—?a href="http://www.itdecent.cn/p/cd90e771d515" target="_blank">直觀理解:拓?fù)渑判?/a>。計(jì)算圖G的執(zhí)行大體可以分為如下4個(gè)步驟:

  • a. 以節(jié)點(diǎn)id(node_id)作為key、入度(in_degree)作為value創(chuàng)建哈希表map,并將計(jì)算圖G中的所有節(jié)點(diǎn)(nodes)加入map中。
  • b. 為計(jì)算圖G創(chuàng)建一個(gè)可執(zhí)行節(jié)點(diǎn)隊(duì)列queue,將map中入度為0的節(jié)點(diǎn)加入queue,并從map中刪除這些節(jié)點(diǎn)。
  • c. 依次執(zhí)行queue中的每一個(gè)節(jié)點(diǎn),執(zhí)行成功之后將此節(jié)點(diǎn)輸出指向的節(jié)點(diǎn)的入度減1,更新map中對(duì)應(yīng)節(jié)點(diǎn)的入度。
  • d. 重復(fù)步驟b和c,直至queue為空。

??TensorFlow在發(fā)展過(guò)程中一共提供了三種計(jì)算圖的構(gòu)建方式,分別是靜態(tài)計(jì)算圖、動(dòng)態(tài)計(jì)算圖和AutoGraph。其中靜態(tài)計(jì)算圖的構(gòu)建是TensorFlow在1.0提供的基礎(chǔ)功能,但是原生的靜態(tài)圖構(gòu)建這個(gè)功能在TensorFlow2.0之后被棄用,但為了保持對(duì)1.0版本的兼容,TensorFlow2.0在compat包中提供了兼容1.0版本的靜態(tài)圖構(gòu)建方式。關(guān)于三種計(jì)算圖的構(gòu)建方式的優(yōu)劣及不同,我們?cè)诤竺姘凑鹿?jié)進(jìn)行詳細(xì)講解。

靜態(tài)計(jì)算圖

??TensorFlow1.0是采用靜態(tài)計(jì)算圖的方式來(lái)構(gòu)建計(jì)算圖,需先用TensorFlow中的各種算子創(chuàng)建計(jì)算圖,然后開(kāi)啟一個(gè)Session來(lái)顯式地執(zhí)行計(jì)算圖。TensorFlow2.0為了保證對(duì)TensorFlow1.0項(xiàng)目的兼容性,在tf.compat.v1子模塊中保留了對(duì)TensorFlow1.0提供的靜態(tài)計(jì)算圖構(gòu)建方式的支持。但是在TensorFlow2.0中,這種靜態(tài)圖的構(gòu)建方式已經(jīng)不被推薦,后面漸漸可能會(huì)被舍棄。下面我們以兼容包里的靜態(tài)計(jì)算圖構(gòu)建方式來(lái)展示靜態(tài)計(jì)算圖的構(gòu)建方式。代碼如下:

import tensorflow as tf

#定義靜態(tài)計(jì)算圖g
g = tf.compat.v1.Graph()
with g.as_default():
    #placeholder為占位符,會(huì)話執(zhí)行的時(shí)候會(huì)填充具體的對(duì)象內(nèi)容
    x = tf.compat.v1.placeholder(name='x', shape=[], dtype=tf.string)
    y = tf.compat.v1.placeholder(name='y', shape=[], dtype=tf.string)
    z = tf.strings.join([x, y], name="join", separator=" ")

#開(kāi)啟一個(gè)session,執(zhí)行計(jì)算圖g
result = None
with tf.compat.v1.Session(graph=g) as sess:
    # fetches的結(jié)果非常像一個(gè)函數(shù)的返回值,而feed_dict中的占位符相當(dāng)于函數(shù)的參數(shù)序列。
    result = sess.run(fetches=z, feed_dict={x:"Hello", y:"World!"})

#打印計(jì)算結(jié)果
tf.print(result)

結(jié)果如下:

b'Hello World!'

動(dòng)態(tài)計(jì)算圖

??動(dòng)態(tài)計(jì)算圖,也稱之為Eager Execution,其和靜態(tài)計(jì)算圖最大的區(qū)別在于,動(dòng)態(tài)計(jì)算圖無(wú)需顯式的定義計(jì)算圖,然后開(kāi)啟個(gè)session來(lái)執(zhí)行,在動(dòng)態(tài)計(jì)算圖中,默認(rèn)開(kāi)啟session,所有的算子定義之后立即執(zhí)行。示例代碼如下:

# 動(dòng)態(tài)計(jì)算圖在每個(gè)算子構(gòu)建后立即執(zhí)行
x = tf.constant("Hello")
tf.print("x:", x)
y = tf.constant("World!")
tf.print("y:", y)

result = tf.strings.join([x, y], separator=" ")
tf.print("result:", result)

結(jié)果如下:

x: "Hello"
y: "World!"
result: "Hello World!"

另外,從模塊化和函數(shù)化編程的角度出發(fā),也可以將上述的動(dòng)態(tài)計(jì)算圖進(jìn)行函數(shù)化封裝,從而將計(jì)算圖的輸入和輸出封裝在一個(gè)函數(shù)里面,示例代碼如下:

# 將x,y及result的輸入輸出關(guān)系封裝成函數(shù)
def str_join(x,y):
    tf.print("x:", x)
    tf.print("y:", y)
    return tf.strings.join([x, y], separator=" ")

result = str_join(tf.constant("Hello"), tf.constant("World!"))
tf.print("result:", result)

結(jié)果如下:

x: "Hello"
y: "World!"
result: "Hello World!"

AutoGraph

??使用動(dòng)態(tài)計(jì)算圖(Eager Execution)的好處是方便代碼調(diào)試,因?yàn)樗械闹虚g過(guò)程可以在寫(xiě)代碼過(guò)程中立即執(zhí)行并顯示結(jié)果。但是動(dòng)態(tài)計(jì)算圖的運(yùn)行效率相對(duì)較低,因?yàn)镋ager Execution會(huì)有許多次Python進(jìn)程和TensorFlow的C++進(jìn)程之間的通信。而靜態(tài)計(jì)算圖構(gòu)建完成之后幾乎全部在TensorFlow內(nèi)核上使用C++代碼執(zhí)行,無(wú)需與Python進(jìn)程頻繁進(jìn)行交互通信,因而效率更高。此外靜態(tài)圖會(huì)對(duì)計(jì)算步驟進(jìn)行一定的優(yōu)化,省略和結(jié)果無(wú)關(guān)的計(jì)算步驟。鑒于此,TensorFlow2.0提供了tf.function讓算子從 Eager Execution 切換到靜態(tài)計(jì)算圖執(zhí)行??梢允褂聾tf.function注解將普通Python函數(shù)轉(zhuǎn)換成對(duì)應(yīng)的TensorFlow計(jì)算圖,而調(diào)用該函數(shù)就相當(dāng)于在TensorFlow1.0中開(kāi)啟一個(gè)Session執(zhí)行計(jì)算圖。這種使用tf.function構(gòu)建靜態(tài)圖的方式就叫做 Autograph。
??在TensorFlow2.0中,如果采用Autograph的方式使用計(jì)算圖,第一步需要定義函數(shù),第二步調(diào)用函數(shù)。無(wú)需顯示定義計(jì)算圖,然后顯式地開(kāi)啟session去執(zhí)行計(jì)算圖,執(zhí)行計(jì)算圖變得跟Python中函數(shù)的定義和調(diào)用一樣簡(jiǎn)單。下面用代碼來(lái)展示采用Autograph的計(jì)算圖執(zhí)行方式,代碼如下:

import tensorflow as tf

# 使用autograph構(gòu)建靜態(tài)圖
@tf.function
def str_join(x, y):
    tf.print("x:", x)
    tf.print("y:", y)
    return tf.strings.join([x, y], separator=" ")

# 調(diào)用函數(shù)@tf.function裝飾的函數(shù),執(zhí)行計(jì)算圖
result = str_join(tf.constant("Hello"), tf.constant("World!"))
tf.print("result:", result)

結(jié)果如下:

hello world
tf.Tensor(b'hello world', shape=(), dtype=string)

三種計(jì)算圖對(duì)比

??靜態(tài)計(jì)算圖,動(dòng)態(tài)計(jì)算圖和AutoGraph在執(zhí)行效率和編程體驗(yàn)兩方面各有取舍。原始的使用靜態(tài)計(jì)算圖需要嚴(yán)格分兩步,第一步定義計(jì)算圖,第二步在會(huì)話中執(zhí)行計(jì)算圖。而在TensorFlow2.0中,在兼容原始靜態(tài)圖構(gòu)建方式的同時(shí),新推出了采用Autograph的方式使用計(jì)算圖,使得計(jì)算圖的定義和使用分別變成了定義函數(shù)和調(diào)用函數(shù),兼顧效率的同時(shí),極大的提升了編程體驗(yàn)和效率。關(guān)于三種計(jì)算圖的比較,分別從定義、執(zhí)行和效率三個(gè)方面做了簡(jiǎn)單的總結(jié):

  • 定義:靜態(tài)計(jì)算圖需要嚴(yán)格遵循先定義,后使用的原則,定義過(guò)程比較麻煩;而動(dòng)態(tài)計(jì)算圖和AutoGraph的計(jì)算圖定義更接近普通的Python任務(wù)編程,定義過(guò)程比較簡(jiǎn)單。
  • 執(zhí)行:靜態(tài)計(jì)算圖需要顯示開(kāi)啟session后執(zhí)行,比較麻煩;動(dòng)態(tài)計(jì)算圖無(wú)需顯示執(zhí)行,可以即刻執(zhí)行并查看中間結(jié)果;AutoGraph調(diào)用函數(shù)即可執(zhí)行,簡(jiǎn)單易用,調(diào)用后會(huì)在后臺(tái)會(huì)按照靜態(tài)圖一樣的方式執(zhí)行。
  • 效率:靜態(tài)計(jì)算圖后臺(tái)有優(yōu)化策略,效率最高;AutoGraph裝飾后的函數(shù)也按靜態(tài)圖的方式去執(zhí)行;動(dòng)態(tài)計(jì)算圖由于Eager Execution,C++內(nèi)核和Python內(nèi)核需要進(jìn)行頻繁的交互,效率最低。
TensorFlow系列文章:
  1. TensorFlow核心概念之Tensor(1):張量創(chuàng)建
  2. TensorFlow核心概念之Tensor(2):索引切片
  3. TensorFlow核心概念之Tensor(3):變換拆合
  4. TensorFlow核心概念之Tensor(4):張量運(yùn)算
  5. TensorFlow核心概念之計(jì)算圖
  6. TensorFlow核心概念之Autograph
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容