在上一篇文章中稍微提到了 tf.function
在 Tensorflow 2.0 的角色,這次我們仍舊會著重在 tf.function
,但也會介紹 tf.function
的底層實作細節,那也就是 AutoGraph。Tensorflow 另外有一個 tf.autograph
模組,主要是提供使用者介面,可以 access AutoGraph 的一些特徵,如上一篇文章的例子。
雖然說,AutoGraph 的內容都實作在 tf.function
那麼倒底這個 AutoGraph 需要實作些什麼東西呢?
y = 0
while x > 0:
x = x - 1
print(y)
# 對應的 tensorflow 語法:
# x = tf.while_loop(
# cond=lambda x: x > 0,
# body=lambda x: x-=1,
# loop_vars=(x,)
# # y 不是 loop_vars
tf.TensorArray(tf.int32, size=0, dynamic_size=True)
來替換 list
,在[上一篇文章](https://ith1l elp.ithome.com.tw/articles/10224165)中提到
a = tf.Variable(1.0)
b = tf.Variable(1.0)
@tf.function
def f():
# a.assign 和 b.assign 的執行順序,使用者無法控制
a.assign(2.0)
b.assign(3.0)
return a + b
print(f())
有 side-effect 的運算元:tf.assign
和 tf.print
等運算元。這些運算元被包覆在 tf.function
的時候,在執行前會先自動設立執行的相依關係,最終自動決定執行的順序。
在底下的例子中,第一個 assign op 為第二個 assign op 的依賴者,也就是第二個 assign op 必須要依賴第一個 assign op 的結果來執行,所以相依關係必須要強制包括進計算圖。
v = tf.Variable(5)
@tf.function
def find_next_odd():
v.assign(v + 1) # v += 1, in-place addition
if v % 2 == 0:
v.assign(v + 1) # 依賴 v 目前的值,故不能先執行
find_next_odd()
v
讀者若有興趣知道 automatic control dependencies 是如何執行,可以閱讀這段原始碼
在Tensorflow 2.0 再造訪 eager-execution 有提到若在__init__
方法的 dynamic 引數設為 True,則會建立 dynamic model,以指令式編程(imperative programming)的方式來執行模型。不過 tf.function
是屬於事先編譯的程式模型所以在這裡必須將 dynamic 引數設為 False。
現在,就讓我們看一下 non-dynamic 的 tf.keras.Model
如何和 tf.function
結合:
class CustomModel(tf.keras.models.Model):
@tf.function
def call(self, input_data):
if tf.reduce_mean(input_data) > 0:
return input_data
else:
return input_data // 2
model = CustomModel()
model(tf.constant([-2, -4]))
#=> <tf.Tensor: id=710, shape=(2,), dtype=int32, numpy=array([-1, -2], dtype=int32)>
下面的程式碼是使用 tf.function
和 tf.GradientTape
來建立 training loop。
在進入 training loop 之前,須用 Sequential API 建立好一個簡單的 keras 模型,而 optimizer 則是 tf.keras.optimizers.Adam()
建立。
資料的部分則是用 tf.keras.datasets.mnist.load_data()
來 load MNIST 手寫數字影像集到記憶體內,並用 tf.data.Dataset.from_tensor_slices
來轉成 tf.data.Dataset
,batch size 為 100。
從這個範例我們可以看到整個 trainingn process 包括了 tf.data
將資料批次載入,計算梯度,更新參數,計算驗證集正確率,以及重複整個過程直到收斂為止,都可以在 tf.function
的視野下,根據計算圖執行。
compute_loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
compute_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
def train_one_step(model, optimizer, x, y):
with tf.GradientTape() as tape:
logits = model(x)
loss = compute_loss(y, logits)
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
compute_accuracy(y, logits)
return loss
@tf.function
def train(model, optimizer):
train_ds = mnist_dataset()
step = 0
loss = 0.0
accuracy = 0.0
for x, y in train_ds:
step += 1
loss = train_one_step(model, optimizer, x, y)
if step % 10 == 0:
tf.print('Step', step, ': loss', loss, '; accuracy', compute_accuracy.result())
return step, loss, accuracy
step, loss, accuracy = train(model, optimizer)
print('Final step', step, ': loss', loss, '; accuracy', compute_accuracy.result())
上述的程式碼,大家可以試試看結果,這裡就不再把輸出列出。