Tensorflow執行模式:Eager Execution動態圖模式、Graph Execution圖模式、@tf.function實現Graph Execution圖模式、tf.Session

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)


 

4.7 Tensorflow執行模式

4.7.1 Eager Execution與Graph Execution

4.7.1.1 Graph Execution(圖模式)

  • 特點:
    • 預先定義計算圖,運行時反覆使用,不能改變
    • 速度更快,適合大規模部署,適合嵌入式平臺

TensorFlow 的圖執行模式是一個符號式的(基於計算圖的)計算框架。簡而言之,如果你需要進行一系列計算,則需要依次進行如下兩步:

  • 1、建立一個 “計算圖”,這個圖描述瞭如何將輸入數據通過一系列計算而得到輸出;

  • 2、建立一個會話,並在會話中與計算圖進行交互,即向計算圖傳入計算所需的數據,並從計算圖中獲取結果。

    • Session 用來給定 Graph 的輸入,指定 Graph 中的結果獲取方式, 並啓動數據在 Graph 中的流動
    • 擁有並管理 Tensorflow 程序運行時的所有資源,資源包括:硬件(CPU,GPU),數據

使用計算圖與會話進行基本運算,這裏做一個最基本的運算示例。

import tensorflow.compat.v1 as tf
tf.disable_eager_execution()

# 1、定義了一個簡單的“計算圖”
a = tf.constant(1)
b = tf.constant(1)
# 等價於 c = tf.add(a, b),c是張量a和張量b通過 tf.add 這一操作(Operation)所形成的新張量
c = a + b  

# 2、# 實例化一個會話(Session)
# 通過會話的 run() 方法對計算圖裏的節點(張量)進行實際的計算
sess = tf.Session()     
c_ = sess.run(c)
print(c_)

注:爲了使用圖執行模式,需要使用 TensorFlow 1.X 的 API 進行操作,所以使用 import tensorflow.compat.v1 as tf 導入 TensorFlow,並通過 tf.disable_eager_execution() 禁用默認的即時執行模式。

4.7.1.2 Eager Execution(動態圖模式)

eager 模式是在 TF 1.4 版本之後引入的,在 TF 2.0之後將會把 eager 模式變爲默認執行模式。TensorFlow 2.0 中的 Eager Execution 是一種命令式編程環境,可立即評估操作,無需構建圖:操作會返回具體的值,而不是構建以後再運行的計算圖。

Eager Execution 的優點如下:

  • 1、快速調試即刻的運行錯誤並通過 Python 工具進行整合
  • 2、藉助易於使用的 Python 控制流支持動態模型
  • 3、爲自定義和高階梯度提供強大支持
  • 4、適用於幾乎所有可用的 TensorFlow 運算

TensorFlow 2.0引入的eager提高了代碼的簡潔性,而且更容易debug。但是對於性能來說,eager執行相比Graph模式會有一定的損失。畢竟原生的Graph模式是先構建好靜態圖,然後才真正執行。這對於 在分佈式訓練、性能優化和生產部署方面具有優勢。但是好在,TensorFlow 2.0引入了tf.function和AutoGraph來縮小eager執行和Graph模式的性能差距,其核心是將一系列的Python語法轉化爲高性能的graph操作。

注:實際上,Eager Execution 在 1.x 的後期版本中也存在,但需要單獨執行 tf.enable_eager_execution() 進行手動啓用。

4.7.2 @tf.function實現Graph Execution 模式

@tf.function一般只用來修飾“包含了 數學公式運算/tf.GradientTape()中的前向傳播反向傳播等計算流程”的函數,無法用來修飾“包含 數據讀取等非數學運算流程”的函數。

在 TensorFlow 2.0 中,推薦使用 @tf.function (而非 1.X 中的 tf.Session )實現 Graph Execution,從而將模型轉換爲易於部署且高性能的 TensorFlow 圖模型。只需要將我們希望以 Graph Execution 模式運行的代碼封裝在一個函數內,並在函數前加上 @tf.function 即可。

上面例子代碼,可以通過基於 tf.function 的代碼等價去執行圖模式:

import tensorflow as tf

@tf.function
def graph():
    a = tf.constant(1)  # 定義一個常量張量(Tensor)
    b = tf.constant(1)
    c = a + b
    return c

c_ = graph()
print(c_.numpy())
  • 並不是任何函數都可以被 @tf.function 修飾!@tf.function 使用靜態編譯將函數內的代碼轉換成計算圖,因此對函數內可使用的語句有一定限制,且需要函數內的操作本身能夠被構建爲計算圖。
    • 建議在函數內只使用TensorFlow 的原生操作,不要使用過於複雜的 Python 語句,函數參數只包括 TensorFlow 張量或 NumPy 數組,並最好是能夠按照計算圖的思想去構建函數。
import tensorflow as tf

@tf.function
def train_one_step(X, y):    
    with tf.GradientTape() as tape:
        y_pred = model(X)
        loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
        loss = tf.reduce_mean(loss)
        # 注意這裏使用了TensorFlow內置的tf.print()
        # @tf.function不支持Python內置的print方法去當做計算節點
        tf.print("loss", loss)  
    grads = tape.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

if __name__ == '__main__':
    model = CNN()
    optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
    start_time = time.time()
    for batch_index in range(num_batches):
        X, y = data_loader.get_batch(batch_size)
        train_one_step(X, y)
    end_time = time.time()
    print(end_time - start_time)

4.7.2.2 @tf.function 機制原理

當被 @tf.function 修飾的函數第一次被調用的時候,進行以下操作:

  • 1、在 Eager Execution 模式關閉的環境下,函數內的代碼依次運行。也就是說,每個 tf. 方法都只是定義了計算節點,而並沒有進行任何實質的計算。這與 TensorFlow 1.X 的 Graph Execution 是一致的;

  • 2、使用 AutoGraph 將函數中的 Python 控制流語句轉換成 TensorFlow 計算圖中的對應節點(比如說 while 和 for 語句轉換爲 tf.while , if 語句轉換爲 tf.cond 等等;

  • 3、基於上面的兩步,建立函數內代碼的計算圖表示;然後運行一次這個計算圖;

    • 基於函數的名字和輸入的函數參數的類型生成一個哈希值,並將建立的計算圖緩存到一個哈希表中。
  • 如果在被 @tf.function 修飾的函數之後再次被調用的時候,根據函數名和輸入的函數參數的類型計算哈希值,檢查哈希表中是否已經有了對應計算圖的緩存。如果是,則直接使用已緩存的計算圖,否則重新按上述步驟建立計算圖。

1、使用下面例子體現tf.function整個計算步驟:

import tensorflow as tf
import numpy as np

@tf.function
def f(x):
      # 注意這裏是print,不是tf.print
    print("The function is running in Python")
    tf.print(x)

# 運行過程
a = tf.constant(1, dtype=tf.int32)
f(a)
b = tf.constant(2, dtype=tf.int32)
f(b)
b_array = np.array(2, dtype=np.int32)
f(b_array)
c = tf.constant(0.1, dtype=tf.float32)
f(c)
d = tf.constant(0.2, dtype=tf.float32)
f(d)

# 對於Python的類型
f(1)
f(2)
f(1)

上述程序的計算結果是?答案是:

# Tensor類型
The function is running in Python
1
2
2
The function is running in Python
0.1
0.2

# Python類型
The function is running in Python
1
The function is running in Python
2
1

當計算 f(a) 時,由於是第一次調用該函數,TensorFlow 進行了以下操作:

  • 1、將函數內的代碼依次運行了一遍;

  • 2、構建了計算圖,然後運行了一次該計算圖(因此輸出了 1)。這裏 tf.print(x) 可以作爲計算圖的節點,但 Python 內置的 print 則不能被轉換成計算圖的節點。

  • 3、將該計算圖緩存到了一個哈希表中(如果之後再有類型爲 tf.int32 ,shape 爲空的張量輸入,則重複使用已構建的計算圖)。

接下來第二次計算之後

  • 一、計算 f(b) 時,由於 b 的類型與 a 相同,所以 TensorFlow 重複使用了之前已構建的計算圖並運行(因此輸出了 2)。這裏由於並沒有真正地逐行運行函數中的代碼,所以函數第一行的文本輸出代碼沒有運行。計算 f(b_array) 時,TensorFlow 自動將 numpy 的數據結構轉換成了 TensorFlow 中的張量,因此依然能夠複用之前已構建的計算圖。

  • 二、計算 f(c) 時,雖然張量 c 的 shape 和 a 、 b 均相同,但類型爲 tf.float32 ,因此 TensorFlow 重新運行了函數內代碼(從而再次輸出了文本)並建立了一個輸入爲 tf.float32 類型的計算圖。

  • 三、計算 f(d) 時,由於 d 和 c 的類型相同,所以 TensorFlow 複用了計算圖,同理沒有輸出文本。

問題:如果打印字符串的print()替換成tf.print(),結果會是?

正常的按照順序輸出:

The function is running in Python
1
The function is running in Python
2
The function is running in Python
2
The function is running in Python
0.1
The function is running in Python
0.2
The function is running in Python
1
The function is running in Python
2
The function is running in Python
1

總結:之後的計算結果則顯示出 @tf.function 對 Python 內置的整數和浮點數類型的處理方式。只有當值完全一致的時候, @tf.function 纔會複用之前建立的計算圖,而並不會自動將 Python 內置的整數或浮點數等轉換成張量。一般而言,應當只在指定超參數等少數場合使用 Python 內置類型作爲被 @tf.function 修飾的函數的參數。

4.7.3 使用傳統的 tf.Session(瞭解)

TensorFlow 2.0 提供了 tf.compat.v1 模塊以支持 TensorFlow 1.X 版本的 API。同時,只要在編寫模型的時候稍加註意,Keras 的模型是可以同時兼容 Eager Execution 模式和 Graph Execution 模式的。

例如,之前的MLP 或 CNN 模型去訓練MNIST數據的代碼如下:


optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate)
num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
# 1、建立計算圖
X_placeholder = tf.compat.v1.placeholder(name='X', shape=[None, 28, 28, 1], dtype=tf.float32)
y_placeholder = tf.compat.v1.placeholder(name='y', shape=[None], dtype=tf.int32)
y_pred = model(X_placeholder)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y_placeholder, y_pred=y_pred)
loss = tf.reduce_mean(loss)
train_op = optimizer.minimize(loss)
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()

# 2、建立Session,並運行圖計算
with tf.compat.v1.Session() as sess:
  sess.run(tf.compat.v1.global_variables_initializer())
  for batch_index in range(num_batches):
    X, y = data_loader.get_batch(batch_size)
    # 使用Session.run()將數據送入計算圖節點,進行訓練以及計算損失函數
    _, loss_value = sess.run([train_op, loss], feed_dict={X_placeholder: X, y_placeholder: y})
    print("batch %d: loss %f" % (batch_index, loss_value))

    num_batches = int(data_loader.num_test_data // batch_size)
    for batch_index in range(num_batches):
      start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
      y_pred = model.predict(data_loader.test_data[start_index: end_index])
# 運行預測結果
sess.run(sparse_categorical_accuracy.update(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred))
      print("test accuracy: %f" % sess.run(sparse_categorical_accuracy.result()))

4.7.4 總結

  • tf的圖執行模式原理與會話模式的區別
  • tf.function的作用和原理

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章