TensorFlow核心概念之Autograph

一、什么是Autograph

??在前一篇文章TensorFlow核心概念之計算圖中我們提到過,TensorFlow中的構建方式主要有三種,分別是:靜態計算圖構建、動態計算圖構建和Autograph。其中靜態計算圖主要是在TensorFlow1.0中支持的計算圖構建方式,這種方式構建的計算圖雖然執行效率高,但不便于編碼過程中的調試,交互體驗差。因此2.0之后TensorFlow開始支持動態計算圖,雖然便于了編碼過程中調試和交互體驗,但是執行效率問題隨之而來。于是就有了Autograph,Autograph是一種將動態圖轉換成靜態圖的實現機制,通過在普通python方法上使用@tf.function進行裝飾,從而將動態圖轉換成靜態圖。

三、Autograph實現原理

??為了搞清楚Autograph的機制原理,我們需要知道,當我們使用@tf.function裝飾一個函數后,在調用這些函數時,TensorFlow到底做了什么?下面我們詳細介紹Autograph的實現原理。當調用被@tf.function時,TensorFlow一共做了兩件事:第一件事是創建靜態計算圖,第二件事是執行靜態計算圖。執行計算圖沒什么好講的,就是針對創建好的計算圖,根據輸入的參數進行執行,關鍵的問題是TensorFlow是如何創建計算這個靜態計算圖的。
??當執行被@tf.function裝飾的函數時,TensorFlow會在后端隱式的創建一個靜態計算圖,靜態計算圖的創建過程大體時這樣的:跟蹤執行一遍函數體中的Python代碼,確定各個變量的Tensor類型,并根據執行順序將各TensorFlow的算子添加到計算圖中。 在該過程中,如果@tf.function(autograph=True)(默認開啟autograph),TensorFlow會將Python控制流轉換成TensorFlow的靜態圖控制流。 主要是將if語句轉換成 tf.cond算子表達,將while和for循環語句轉換成tf.while_loop算子表達,并在必要的時候添加 tf.control_dependencies指定執行順序依賴關系。這里需要注意的是,非TensorFlow的函數不會被添加到計算圖中,也就是說,像Python原生支持的一些函數在構建靜態計算圖的過程中,只會被跟蹤執行,不會將該函數作為算子嵌入到TensorFlow的靜態計算圖中。
??另外還需要注意的一點是,當在調用@tf.function裝飾的函數時,如果輸入的參數是Tensor類型,此時TensorFlow會從性能的角度出發,去判斷當前入參類型下的靜態計算圖是否已經存在,如果已經存在,則直接執行計算圖,從而省去構建靜態計算圖的過程,進而提升效率。但是如果發現當前入參的靜態計算圖不存在,則需要重新創建新的計算圖。另外需要注意的是,如果調用被@tf.function裝飾的函數時,入參不是Tensor類型,則每次調用的時候都需要先創建靜態計算圖,然后執行計算圖。

三、Autograph的編碼規范

??介紹完TensorFlow的實現原理,下面我們簡單介紹一下Autograph的編碼規范和使用建議。并通過簡單的示例來演示為什么要有這些規范和建議。
1. 被@tf.function修飾的函數應盡量使用TensorFlow中的函數,而非外部函數。
2. 不能在@tf.function修飾的函數內部定義tf.Variable變量。
3. 被@tf.function修飾的函數不可修改該函數外部的Python列表或字典等數據結構變量。
4. 調用被@tf.function修飾的函數,入參盡量使用Tensor類型。

四、Autograph的編碼規范解析

1. 被@tf.function修飾的函數應盡量使用TensorFlow中的函數,而非外部函數。
我們可以看下面一段代碼,我們定義了兩個@tf.function修飾的函數,其中第一個函數體內使用了兩個外部函數,分別是np.random.randn(3,3)print('---------'),第二個函數體內全部使用TensorFlow中的函數。

import numpy as np
import tensorflow as tf

@tf.function
def np_random():
    a = np.random.randn(3,3)
    tf.print(a)
    print('---------')
    
@tf.function
def tf_random():
    a = tf.random.normal((3,3))
    tf.print(a)
    tf.print('---------')

下面我們調用兩次第一個被@tf.function修飾的函數:

print('第1次調用:')
np_random()
print('第2次調用:')
np_random()

結果如下:

第1次調用:
---------
array([[ 0.78826988, -0.05816027,  0.88905733],
       [-1.98118034, -0.10032147, -0.51427141],
       [ 0.50533615, -1.11163988, -0.87748809]])
第2次調用:
array([[ 0.78826988, -0.05816027,  0.88905733],
       [-1.98118034, -0.10032147, -0.51427141],
       [ 0.50533615, -1.11163988, -0.87748809]])

??這個時候我們會發現三個問題:

  1. 第一次調用的時候,print('---------')方法執行了,最起碼看起是執行了,也確實是執行了,而第二次調用的時候,print('---------')方法并沒有執行;
  2. 第一次調用的時候,print('---------')方法在tf.print(a)之前調用了;
  3. 兩次調用之后,變量a的結果是一樣的。

??下面針對以上問題,我們來詳細解釋一下:首先在第一次調用的是,會進行靜態計算圖的創建,這個時候Python后端會跟蹤執行一遍函數體Python的代碼,,并將方法體中的變量和算子進行映射和加入計算圖中,這里需要注意的是,由于np.random.randn(3,3)print('---------')方法并不是TensorFlow中的方法,因此無法加入到計算圖中,因此只有tf.print(a)方法加入到了靜態計算圖中,因此只有在第一次創建計算圖的時候進行跟蹤執行,而第二次執行時,如果計算圖已經存在,這個時候時不需要再執行的,這也就是為什么print('---------')會先在tf.print(a)前面執行,且執行一次。因為在實際執行計算圖的過程中,都只會執行tf.print(a)這一個方法,這也導致了為什么多次調用之后,打印出來的a的結果是一樣的。基于以上原因,我們再兩次調用一下第二個方法tf_random(),示例代碼和結果如下:

print('第1次調用:')
tf_random()
print('第2次調用:')
tf_random()

結果如下:

第1次調用:
[[1.47568643 -0.204902112 0.694708228]
 [-0.868299544 1.65556359 0.520012081]
 [-0.215179399 -0.400003046 -0.393970907]]
---------
第2次調用:
[[0.0756372586 1.06571424 -0.579676867]
 [-0.937381923 -2.79628611 -1.38038337]
 [-0.762175 -1.79867613 0.329570293]]
---------

這個時候我們可以看出,全部使用TensorFlow函數的方法調用的結果是符合我們的預期的。

2. 不能在@tf.function修飾的函數內部定義tf.Variable變量。
這個我們就直接示例,代碼如下:

@tf.function
def inner_var():
    x = tf.Variable(1.0,dtype = tf.float32)
    x.assign_add(1.0)
    tf.print(x)
    return(x)

這個時候執行的時候,代碼會直接報錯,報錯信息如下:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-12-c95a7c3c1ddd> in <module>
      7 
      8 #執行將報錯
----> 9 inner_var()
     10 inner_var()

~/anaconda3/lib/python3.7/site-packages/tensorflow_core/python/eager/def_function.py in __call__(self, *args, **kwds)
    566         xla_context.Exit()
    567     else:
--> 568       result = self._call(*args, **kwds)
    569 
    570     if tracing_count == self._get_tracing_count():
......
ValueError: tf.function-decorated function tried to create variables on non-first call.

如果我們將這個變量拿到@tf.function修飾的函數外,則可以直接執行,代碼如下:

x = tf.Variable(1.0,dtype=tf.float32)
@tf.function
def outer_var():
    x.assign_add(1.0)
    tf.print(x)
    return(x)

outer_var() 
outer_var()

結果如下:

2
3

3. 被@tf.function修飾的函數不可修改該函數外部的Python列表或字典等數據結構變量。
正對這個我們直接看代碼示例,首先我們在不用@tf.function修飾的函數來演示一下執行結果,代碼如下:

tensor_list = []

def append_tensor(x):
    tensor_list.append(x)
    return tensor_list

append_tensor(tf.constant(1.0))
append_tensor(tf.constant(2.0))
print(tensor_list)

結果如下:

[<tf.Tensor: shape=(), dtype=float32, numpy=1.0>, <tf.Tensor: shape=(), dtype=float32, numpy=2.0>]

這個時候我們發現一切如我們的預期,沒有任何問題,接下來我們對這個append_tensor(x)函數加上@tf.function修飾,代碼如下:

tensor_list = []

@tf.function
def append_tensor(x):
    tensor_list.append(x)
    return tensor_list

append_tensor(tf.constant(1.0))
append_tensor(tf.constant(2.0))
print(tensor_list)

結果如下:

[<tf.Tensor 'x:0' shape=() dtype=float32>]

??其實出現這個問題的原因呢也很好解釋,那就是tensor_list.append(x)不是一個TensorFlow的方法,在構建計算圖的時候呢,這個方法并不會作為算子加入到靜態計算圖中,那么在最后執行計算圖的時候,其實也就不會去執行這個方法了,這就是為啥最終這個列表內容為空的原因。

4. 調用被@tf.function修飾的函數,入參盡量使用Tensor類型。
這一點是從性能的角度出發的,因為在調用被@tf.function修飾的函數時,TensorFlow會根據入參類型來決定是否要重新創建靜態計算圖,這一點時從性能的角度出發的,對結果其實并沒有實際的影響。示例代碼如下:

import tensorflow as tf
import numpy as np 

@tf.function(autograph=True)
def myadd(a,b):
    c = a + b
    print("tracing")#為了方便知道在創建計算圖
    tf.print(c)
    return c

首先我們使用Tensor類型的入參多次調用該函數:

print("第1次調用:")
myadd(tf.constant("Hello"), tf.constant("World"))
print("第2次調用:")
myadd(tf.constant("Good"), tf.constant("Bye"))

結果如下:

第1次調用:
tracing
HelloWorld
第2次調用:
GoodBye

而當我們使用非Tensor類型的入參多次調用該函數:

print("第1次調用:")
myadd("Hello","World")
print("第2次調用:")
myadd("Good","Bye")

結果如下:

第1次調用:
tracing
HelloWorld
第2次調用:
tracing
GoodBye

??這個時候我們發現,如果在調用@tf.function修飾的函數時,如果入參的類型不是TensorFlow的類型,那么在多次調用該方法時,如果入參類型不變,內容變換的化,是需要多次創建靜態計算圖的,而如果使用Tensor類型的入參,則不會出現重復創建靜態計算圖的過程,除非入參類型改變,這樣可以大大的提高調用性能。OK,關于TensorFlow中的Autograph就簡單介紹這么多。

TensorFlow系列文章:
  1. TensorFlow核心概念之Tensor(1):張量創建
  2. TensorFlow核心概念之Tensor(2):索引切片
  3. TensorFlow核心概念之Tensor(3):變換拆合
  4. TensorFlow核心概念之Tensor(4):張量運算
  5. TensorFlow核心概念之計算圖
  6. TensorFlow核心概念之Autograph
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容