1. 程式人生 > >【tensorflow2.0】AutoGraph的機制原理

【tensorflow2.0】AutoGraph的機制原理

有三種計算圖的構建方式:靜態計算圖,動態計算圖,以及Autograph。

TensorFlow 2.0主要使用的是動態計算圖和Autograph。

動態計算圖易於除錯,編碼效率較高,但執行效率偏低。

靜態計算圖執行效率很高,但較難除錯。

而Autograph機制可以將動態圖轉換成靜態計算圖,兼收執行效率和編碼效率之利。

當然Autograph機制能夠轉換的程式碼並不是沒有任何約束的,有一些編碼規範需要遵循,否則可能會轉換失敗或者不符合預期。

我們會介紹Autograph的編碼規範和Autograph轉換成靜態圖的原理。

並介紹使用tf.Module來更好地構建Autograph。

上篇我們介紹了Autograph的編碼規範,本篇我們介紹Autograph的機制原理。

一,Autograph的機制原理

當我們使用@tf.function裝飾一個函式的時候,後面到底發生了什麼呢?

例如我們寫下如下程式碼。

import tensorflow as tf
import numpy as np 
 
@tf.function(autograph=True)
def myadd(a,b):
    for i in tf.range(3):
        tf.print(i)
    c = a+b
    print("tracing")
    return c

後面什麼都沒有發生。僅僅是在Python堆疊中記錄了這樣一個函式的簽名。

當我們第一次呼叫這個被@tf.function裝飾的函式時,後面到底發生了什麼?

例如我們寫下如下程式碼。

myadd(tf.constant("hello"),tf.constant("world"))
tracing
0
1
2
<tf.Tensor: shape=(), dtype=string, numpy=b'helloworld'>

發生了2件事情,

第一件事情是建立計算圖。

即建立一個靜態計算圖,跟蹤執行一遍函式體中的Python程式碼,確定各個變數的Tensor型別,並根據執行順序將運算元新增到計算圖中。 在這個過程中,如果開啟了autograph=True(預設開啟),會將Python控制流轉換成TensorFlow圖內控制流。 主要是將if語句轉換成 tf.cond運算元表達,將while和for迴圈語句轉換成tf.while_loop運算元表達,並在必要的時候新增 tf.control_dependencies指定執行順序依賴關係。

相當於在 tensorflow1.0執行了類似下面的語句:

g = tf.Graph()
with g.as_default():
    a = tf.placeholder(shape=[],dtype=tf.string)
    b = tf.placeholder(shape=[],dtype=tf.string)
    cond = lambda i: i<tf.constant(3)
    def body(i):
        tf.print(i)
        return(i+1)
    loop = tf.while_loop(cond,body,loop_vars=[0])
    loop
    with tf.control_dependencies(loop):
        c = tf.strings.join([a,b])
    print("tracing")

第二件事情是執行計算圖。

相當於在 tensorflow1.0中執行了下面的語句:

with tf.Session(graph=g) as sess:
    sess.run(c,feed_dict={a:tf.constant("hello"),b:tf.constant("world")})

因此我們先看到的是第一個步驟的結果:即Python呼叫標準輸出流列印"tracing"語句。

然後看到第二個步驟的結果:TensorFlow呼叫標準輸出流列印1,2,3。

當我們再次用相同的輸入引數型別呼叫這個被@tf.function裝飾的函式時,後面到底發生了什麼?

例如我們寫下如下程式碼。

myadd(tf.constant("hello"),tf.constant("world"))
0
1
2
<tf.Tensor: shape=(), dtype=string, numpy=b'helloworld'>

只會發生一件事情,那就是上面步驟的第二步,執行計算圖。

所以這一次我們沒有看到列印"tracing"的結果。

當我們再次用不同的的輸入引數型別呼叫這個被@tf.function裝飾的函式時,後面到底發生了什麼?

例如我們寫下如下程式碼。

myadd(tf.constant(1),tf.constant(2))
tracing
0
1
2
<tf.Tensor: shape=(), dtype=int32, numpy=3>

由於輸入引數的型別已經發生變化,已經建立的計算圖不能夠再次使用。

需要重新做2件事情:建立新的計算圖、執行計算圖。

所以我們又會先看到的是第一個步驟的結果:即Python呼叫標準輸出流列印"tracing"語句。

然後再看到第二個步驟的結果:TensorFlow呼叫標準輸出流列印1,2,3。

需要注意的是,如果呼叫被@tf.function裝飾的函式時輸入的引數不是Tensor型別,則每次都會重新建立計算圖。

例如我們寫下如下程式碼。兩次都會重新建立計算圖。因此,一般建議呼叫@tf.function時應傳入Tensor型別。

myadd("hello","world")
myadd("good","morning")

tracing

0

1

2

tracing

0

1

2

二,重新理解Autograph的編碼規範

瞭解了以上Autograph的機制原理,我們也就能夠理解Autograph編碼規範的3條建議了。

1,被@tf.function修飾的函式應儘量使用TensorFlow中的函式而不是Python中的其他函式。例如使用tf.print而不是print.

解釋:Python中的函式僅僅會在跟蹤執行函式以建立靜態圖的階段使用,普通Python函式是無法嵌入到靜態計算圖中的,所以 在計算圖構建好之後再次呼叫的時候,這些Python函式並沒有被計算,而TensorFlow中的函式則可以嵌入到計算圖中。使用普通的Python函式會導致 被@tf.function修飾前【eager執行】和被@tf.function修飾後【靜態圖執行】的輸出不一致。

2,避免在@tf.function修飾的函式內部定義tf.Variable.

解釋:如果函式內部定義了tf.Variable,那麼在【eager執行】時,這種建立tf.Variable的行為在每次函式呼叫時候都會發生。但是在【靜態圖執行】時,這種建立tf.Variable的行為只會發生在第一步跟蹤Python程式碼邏輯建立計算圖時,這會導致被@tf.function修飾前【eager執行】和被@tf.function修飾後【靜態圖執行】的輸出不一致。實際上,TensorFlow在這種情況下一般會報錯。

3,被@tf.function修飾的函式不可修改該函式外部的Python列表或字典等資料結構變數。

解釋:靜態計算圖是被編譯成C++程式碼在TensorFlow核心中執行的。Python中的列表和字典等資料結構變數是無法嵌入到計算圖中,它們僅僅能夠在建立計算圖時被讀取,在執行計算圖時是無法修改Python中的列表或字典這樣的資料結構變數的。

 

參考:

開源電子書地址:https://lyhue1991.github.io/eat_tensorflow2_in_30_days/

GitHub 專案地址:https://github.com/lyhue1991/eat_tensorflow2_in_30_