TensorFlow核心概念之Autograph

一、什么是Autograph

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

三、Autograph實(shí)現(xiàn)原理

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

三、Autograph的編碼規(guī)范

??介紹完TensorFlow的實(shí)現(xiàn)原理,下面我們簡單介紹一下Autograph的編碼規(guī)范和使用建議。并通過簡單的示例來演示為什么要有這些規(guī)范和建議。
1. 被@tf.function修飾的函數(shù)應(yīng)盡量使用TensorFlow中的函數(shù),而非外部函數(shù)。
2. 不能在@tf.function修飾的函數(shù)內(nèi)部定義tf.Variable變量。
3. 被@tf.function修飾的函數(shù)不可修改該函數(shù)外部的Python列表或字典等數(shù)據(jù)結(jié)構(gòu)變量。
4. 調(diào)用被@tf.function修飾的函數(shù),入?yún)⒈M量使用Tensor類型。

四、Autograph的編碼規(guī)范解析

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

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('---------')

下面我們調(diào)用兩次第一個(gè)被@tf.function修飾的函數(shù):

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

結(jié)果如下:

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

??這個(gè)時(shí)候我們會發(fā)現(xiàn)三個(gè)問題:

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

??下面針對以上問題,我們來詳細(xì)解釋一下:首先在第一次調(diào)用的是,會進(jìn)行靜態(tài)計(jì)算圖的創(chuàng)建,這個(gè)時(shí)候Python后端會跟蹤執(zhí)行一遍函數(shù)體Python的代碼,,并將方法體中的變量和算子進(jìn)行映射和加入計(jì)算圖中,這里需要注意的是,由于np.random.randn(3,3)print('---------')方法并不是TensorFlow中的方法,因此無法加入到計(jì)算圖中,因此只有tf.print(a)方法加入到了靜態(tài)計(jì)算圖中,因此只有在第一次創(chuàng)建計(jì)算圖的時(shí)候進(jìn)行跟蹤執(zhí)行,而第二次執(zhí)行時(shí),如果計(jì)算圖已經(jīng)存在,這個(gè)時(shí)候時(shí)不需要再執(zhí)行的,這也就是為什么print('---------')會先在tf.print(a)前面執(zhí)行,且執(zhí)行一次。因?yàn)樵趯?shí)際執(zhí)行計(jì)算圖的過程中,都只會執(zhí)行tf.print(a)這一個(gè)方法,這也導(dǎo)致了為什么多次調(diào)用之后,打印出來的a的結(jié)果是一樣的?;谝陨显?,我們再兩次調(diào)用一下第二個(gè)方法tf_random(),示例代碼和結(jié)果如下:

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

結(jié)果如下:

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

這個(gè)時(shí)候我們可以看出,全部使用TensorFlow函數(shù)的方法調(diào)用的結(jié)果是符合我們的預(yù)期的。

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

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

這個(gè)時(shí)候執(zhí)行的時(shí)候,代碼會直接報(bào)錯(cuò),報(bào)錯(cuò)信息如下:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-12-c95a7c3c1ddd> in <module>
      7 
      8 #執(zhí)行將報(bào)錯(cuò)
----> 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.

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

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()

結(jié)果如下:

2
3

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

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)

結(jié)果如下:

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

這個(gè)時(shí)候我們發(fā)現(xiàn)一切如我們的預(yù)期,沒有任何問題,接下來我們對這個(gè)append_tensor(x)函數(shù)加上@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)

結(jié)果如下:

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

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

4. 調(diào)用被@tf.function修飾的函數(shù),入?yún)⒈M量使用Tensor類型。
這一點(diǎn)是從性能的角度出發(fā)的,因?yàn)樵谡{(diào)用被@tf.function修飾的函數(shù)時(shí),TensorFlow會根據(jù)入?yún)㈩愋蛠頉Q定是否要重新創(chuàng)建靜態(tài)計(jì)算圖,這一點(diǎn)時(shí)從性能的角度出發(fā)的,對結(jié)果其實(shí)并沒有實(shí)際的影響。示例代碼如下:

import tensorflow as tf
import numpy as np 

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

首先我們使用Tensor類型的入?yún)⒍啻握{(diào)用該函數(shù):

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

結(jié)果如下:

第1次調(diào)用:
tracing
HelloWorld
第2次調(diào)用:
GoodBye

而當(dāng)我們使用非Tensor類型的入?yún)⒍啻握{(diào)用該函數(shù):

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

結(jié)果如下:

第1次調(diào)用:
tracing
HelloWorld
第2次調(diào)用:
tracing
GoodBye

??這個(gè)時(shí)候我們發(fā)現(xiàn),如果在調(diào)用@tf.function修飾的函數(shù)時(shí),如果入?yún)⒌念愋筒皇荰ensorFlow的類型,那么在多次調(diào)用該方法時(shí),如果入?yún)㈩愋筒蛔?,?nèi)容變換的化,是需要多次創(chuàng)建靜態(tài)計(jì)算圖的,而如果使用Tensor類型的入?yún)?,則不會出現(xiàn)重復(fù)創(chuàng)建靜態(tài)計(jì)算圖的過程,除非入?yún)㈩愋透淖?,這樣可以大大的提高調(diào)用性能。OK,關(guān)于TensorFlow中的Autograph就簡單介紹這么多。

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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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