日萌社
人工智能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的作用和原理