在Tensorflow 1.x Eager Mode的介紹中,我們提到了如何用 eager mode 的 tf.GradientTape() 的 context 來進行梯度計算的客製化。除此之外,我們也提到了將 eager mode 建立起的計算圖 export 到硬碟中,在 graph mode 中讀入並執行。
在 Tensorflow 2.0 中,eager execution 已經是預設,大家可以使用昨日提到的 %tensorflow_version line magic 在 colab 上驗證看看。
%tensorflow_version 2.x
tf.executing_eagerly()
#=> True
Eager execution 在 Tensorflow 2.0 仍如在 1.x,可以將動態的程式 flow control 併入計算中,與 numpy 無縫接合,以 python 程式員直覺式的除錯方式,如使用 python debugger 和 python print 來進行traceback 以及變數檢視。
在計算梯度方面,tf.GradientTape的愛好者,在 2.0 仍能使用 tf.GradientTape context 來記錄正向計算過程後,在 context 外呼叫 gradient 方法,來取得梯度運算結果。
甚至我們能夠在 eager mode 中建立一個 keras 模型,納入一個最佳化演算法,並進行訓練。在進入一個完整模型訓練的原始碼前,讓我們來看看 tf.Variable 和 tf.GradientTape 的關係。
tf.Variable in Eager Modetf.Variable 是一個具有狀態,可以被改變的 tf.Tensor 物件,通常作為模型的參數。藉著持有資料狀態的特性,tf.Variable 可以快速地被修改,尤其當最佳化演算法是如梯度下降演算法,參數需要時常被更新。在 Tensorflow 2.0 中,為了使 ** eager execution** 更平順,所以對 tf.Variable 做了以下改變:
tf.Variable 生命期由 python 直譯器決定tf.train.CheckPoint 處理 tf.Variable 的儲存或載入tf.keras.metrics.result 取得 keras metrics 物件結果tf.Variable 生命期由 python 直譯器決定在 Tensorflow 1.x 環境創建的 tf.Variable 物件,他們的 reference 都收集在靜態計算圖的 global collection,而物件的生命期則由 tf.Session 管理。在 Tensorflow 2.0 之後,由於 tf.Session 和 global collection 都被移除,現在 tf.Variable 物件的生命期就和一個普通的 python 物件一樣,不再另外管理。我們可以用下列的程式碼來說明:
妥善的應用 tf.Variable 在 tf.GradientTape 的 context 中,可以打造一個有效率的模型。我們現在就用一個簡單的模型來做說明。
if tf.config.experimental.list_physical_devices("GPU"):
  with tf.device("gpu:0"):
    print("GPU enabled")
    v = tf.Variable(tf.random.normal([1000, 1000]))
    v = None  # v no longer takes up GPU memory
在上面的程式碼中,我們在 GPU 的 device,建立了一個 tf.Variable --- v。在 1.x,tf.Variable 由靜態圖物件的 global collection 管理,所以其生命期是全域,不會受限於宣告語法所在的 scope。然而,在 2.0 所有的 tf.Variable 都是尋常 python object,也因次當 v 這個變數的 reference 被一個 None 物件參考時,原先的 tf.Variable 參考的生命期也停止。
tf.train.CheckPoint 處理 tf.Variable 的儲存或載入在 2.0 可以使用 tf.train.CheckPoint 物件來對 tf.Variable 做儲存和載入的動作。如果要建立一個tf.train.CheckPoint,可以傳入一個 tf.Variable。若要儲存,則是呼叫 tf.train.CheckPoint 物件的 save 方法,並傳入所要儲存的位置。若是要載入,則是呼叫tf.train.CheckPoint 物件的 restore方法。
至於如何應用在儲存 keras 的模型,可以看下面的程式範例:
import os
model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
checkpoint_dir = 'path/to/model_dir'
if not os.path.exists(checkpoint_dir):
  os.makedirs(checkpoint_dir)
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tf.train.Checkpoint(optimizer=optimizer,
                           model=model)
root.save(checkpoint_prefix)
root.restore(tf.train.latest_checkpoint(checkpoint_dir))
tf.keras.metrics.result 取得 keras metrics 物件結果在 keras 有一類物件被稱為 metrics 物件,其用途就是計算訓練和評估時所需要 metrics,如:tf.keras.metrics.Mean("loss")就是一個計算損失平均的 metrics 物件。我們可以看下面的程式碼範例說明如何取得一個 metrics 物件的值。
m = tf.keras.metrics.Mean("loss")
m(0)
m(5)
m.result()  # => 2.5
m.result()
tf.keras.Model in Eager Modetf.keras.Model 介面提供使用者一個較自由的實作方法得以靈活撰寫客製化的深度學習模型。有兩種方法可以建立 tf.keras.Model實例:
Input 物件和輸出就可以建立 tf.keras.Model物件。然而,如何從輸入到輸出的計算邏輯,則不在tf.keras.Model 的視野內,仰賴的是 keras 的 backend 提供計算圖。許多細節都已在上一篇中提到,在此不再贅述tf.keras.Model:透過繼承 tf.keras.Model,使用者必須實作一些方法,來完成模型建制。然而由這個方法建制的模型,使用者擁有最大的自由度去設計正向傳播的邏輯,同時 tf.keras.Model 物件可以呼叫實作正向傳播的物件方法,客製化梯度計算以及確保在 eager mode,所有的程式碼都會邊解析邊評估(run imperatively)。如果要透過繼承 tf.keras.Model,客製化模型的架構則必須完成下面的步驟:
__init__() 的方法中,先呼叫 super.__init__方法,使物件擁有父類別的方法和屬性。爾後,建立 tf.keras.layers 物件,並將物件指派為物件屬性。在 super.__init__()中有一個 dynamic 參數,若指派 True 給 dynamic 則建立的模型物件會邊解析語法邊評估每一行程式碼call()的方法中,實作正向傳播的邏輯。在這個方法中傳入的引數,包括了這個模型的輸入外,還有一個 keyword 參數為 training,若指派 True 給 training 表示這次執行是訓練,而若指派 False 給 training ,表示這次執行為預測。現在我們來看一個繼承 tf.keras.Model 的例子。首先,:
import tensorflow as tf
class MyModel(tf.keras.Model):
  def __init__(self):
    super(MyModel, self).__init__(name='MyModel', dynamic=False)
    self.dense1 = tf.keras.layers.Dense(32, activation=tf.nn.relu)
    self.dense2 = tf.keras.layers.Dense(10, activation=tf.nn.softmax)
    self.dropout = tf.keras.layers.Dropout(0.5)
  def call(self, inputs, training=False): 
    # 使用在 `__init__` 建構的 layers 物件,在這裡實作正向傳播
    x = self.dense1(inputs)
    if training:  # 在 inference 或 prediction 時不要使用 dropout
      x = self.dropout(x, training=training)
    return self.dense2(x)
model = MyModel()