給讀者: 9/21 11:45 pm 是搶先版,陸續還會有更新。
這個禮拜我們將要離開 PyTorch 一陣子,再回到我們尚未探險完全的 Tensorflow 2.0。我們在這個禮拜中將會把重心放在 Tensorflow, 領著大家來看看 Tensorflow 官方提供的指南們。
今天則是著重在 High-level 的 tensorflow.keras,一開始會先快速介紹 keras,然後以 keras 的 callback 的例子來說明如何建立模型和完成訓練。在進入主題之前,讓我先介紹一下%tensorflow_version
這個可在 colab 中使用的 line magic。無須另外安裝任何套件,就可以在 colab 的 cell 裡使用,用法大概如下:
%tensorflow_version
#=>Currently selected TF version: 1.x
#Available versions:
#* 1.x
#* 2.x`
%tensorflow_version
後增加引數,目前接受 1.x 或 2.x,作為在 1.x 或 2.x 版本之間的互換。%tensorflow_version 2.x
#=>%tensorflow_version` only switches the major version: '1.x' or '2.x'.
#You set: '2.0.0a0'. This will be interpreted as: `2.x`.
#TensorFlow 2.x selected.
%tensorflow_version
後特別註明版本,如。那麼則會開始安裝該版本。在tensorflow 2.0 mini-intro中,我們提到了,為了要 keras 能夠無縫與 Tensorflow 相接,一定要從 tensorflow import keras 模組。倘若在自己的環境中安裝了 keras 和 tensorflow,可以透過 tf.keras.version
來查看包入 tensorflow 內的是哪一版本的 keras。
使用 tf.keras 來建立模型,可以使用下列兩個 API:
在這個最簡單的 API 來建立深度學習模型,則是利用一個“容器”的概念,在 tf.keras 則是 tf.keras.Sequential()
。大家可以把 tf.keras.Sequential()
當作是一個 layer-wise 地可直線放入許多類神經網路的層級實踐類別:如,fully-connected layer 的 tf.keras.layers.Dense
。
以下的原始碼可以建造出一個
# Create a sigmoid layer:
from tensorflow.keras import layers
model = tf.keras.Sequential()
#加入第一層,該層的 output neurons 數目=64,啟動函式為 sigmoid
model.add(layers.Dense(64, activation='sigmoid'))
#加入第二層,該層的 output neurons 數目=64,啟動函式為預設 relu
#並對該層參數做 l1-regularization,其係數為 0.01
model.add(layers.Dense(64, kernel_regularizer=tf.keras.regularizers.l1(0.01)))
#加入第三層,該層的 output neurons 數目=64,啟動函式為預設 relu
#並對該層參數做 l2-regularization,其係數為 0.01
model.add(layers.Dense(64, bias_regularizer=tf.keras.regularizers.l2(0.01)))
#加入第四層,該層的 output neurons 數目=64,啟動函式為預設 relu
#該層參數初始化是用正交策略
model.add(layers.Dense(64, kernel_initializer='orthogonal'))
#加入第五層,該層的 output neurons 數目=64,啟動函式為預設 relu
#該層偏移初始化是用常數策略(皆設為2.0)
model.add(layers.Dense(64, bias_initializer=tf.keras.initializers.Constant(2.0)))
上面的原始碼,有一個問題,就是缺少指定輸入的 neurons。我們可以在加入第一層的全聯階層,把 input 的 dimensions 傳入(layers.Dense(64, activation='relu', input_shape=(32,)),
),或者我們可以用 tf.keras.Input
來建構輸入。
inputs = tf.keras.Input(shape=(32,)) # Returns an input placeholder
y = model(inputs)
Sequential API 有一個問題,就是建立的模型在連結上變化較少,幾乎是將類神經網路的實踐層,如排隊般一直線的執行。由於缺乏彈性,所以像 Inception 或是 ResNet 有額外的分支以及連結。所以,為了能夠滿足建立模型的靈活性,keras 還有一個 Functional API,來滿足研究者們的需求。
如果說 Sequential API 是一個 layer-wise 的容器,那麼這個 Functional API,則像拿著積木,由底層開始拼湊起模型的全貌。而這裡所說的底層,就是從 input 開始。Functional API 提供一個建構模型的類別 tf.keras.Model
,這個類別在實例化的時候需要使用者提供最初 input 和最後 output,而 input 到 output 之間的相依關係或計算圖建置則完全交付與使用者,tf.keras.Model
對計算圖如何連結一無所知。
以下的原始碼是一個簡單的例子,如何用 Functional API 完成 Sequential API 的任務。
inputs = keras.Input(shape=(784,), name='img')
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dense(64, activation='relu')(x)
outputs = layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs=inputs, outputs=outputs, name='mnist_model')
keras 實作了一個 tf.keras.callbacks.Callback
的介面。這個介面上實作了多個方法,每一個方法都會有對應的事件,一旦事件發生就會 trigger tf.keras.callbacks.Callback
類別物件的相對應方法。
tf.keras.callbacks.Callback
提供了以下事件方法的介面:
針對 training 的事件則還有下列兩項:
哪些 functions 可以接受 callbakcs?tf.keras.Model
物件的下列三個方法都可以使用 keyword callback
來指定傳入的 callback
fit()
, fit_generator()
evaluate()
, evaluate_generator()
predict()
, predict_generator()
Learning rate scheduling 是一種 adaptive learning rate 的方法,主要的觀察是當 training epochs 增加時,相對應的 learning rate 應該減緩。Learning rate scheduling 則是手動提供 learning rate 減少的時程表。
首先我們來看一下我們的 Callback 物件,LearningRateScheduler 類別的實例。類別 LearningRateScheduler 要先繼承tf.keras.callbacks.Callback
才能使用不同事件的介面。在實例化時,__init__
需要傳入一個 schedule function,該 function 接受一個 epoch 的 index,回傳該 epoch 對應的 learning rate。
因為這是一個 epoch 等級的事件,在每次 epoch 開始前,LearningRateScheduler 必須就現在的 epoch 數目,利用自身的 scedule 物件,傳回新的 learning rate,因此,需實做的部分為on_epoch_begin
。
class LearningRateScheduler(tf.keras.callbacks.Callback):
"""Learning rate scheduler which sets the learning rate according to schedule.
Arguments:
schedule: a function that takes an epoch index
(integer, indexed from 0) and current learning rate
as inputs and returns a new learning rate as output (float).
"""
def __init__(self, schedule):
super(LearningRateScheduler, self).__init__()
self.schedule = schedule
def on_epoch_begin(self, epoch, logs=None):
if not hasattr(self.model.optimizer, 'lr'):
raise ValueError('Optimizer must have a "lr" attribute.')
# Get the current learning rate from model's optimizer.
lr = float(tf.keras.backend.get_value(self.model.optimizer.lr))
# Call schedule function to get the scheduled learning rate.
scheduled_lr = self.schedule(epoch, lr)
# Set the value back to the optimizer before this epoch starts
tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)
print('\nEpoch %05d: Learning rate is %6.4f.' % (epoch, scheduled_lr))
這裏則是利用 keras 的 backend API 中的 get_value()
來取得 optimizer 的 learning rate 參數。使用的方法為 tf.keras.backend.get_value(self.model.optimizer.lr)
而關於 schedule 的實踐方法,下面是一種可能,主要是用 lookup 的方式來完成。在下面的程式碼,首先會建立一個 LR_SCHEDULE
的 list 全域物件,該物件內的每一個元素都是一個 tuple。tuple 內的第一個元素是 epoch 的數字,而第二個元素則是對應的 learning rate。
小幫手函式 lr_schedule,會將傳入的 epoch 參數,也是目前即將開始的 epoch 數目,如果在 LR_SCHEDULE
內(透過迴圈檢查),則回傳相對應的 learning rate,否則就原封不動地回傳原本的 learning rate。
LR_SCHEDULE = [
# (epoch to start, learning rate) tuples
(3, 0.05), (6, 0.01), (9, 0.005), (12, 0.001)
]
def lr_schedule(epoch, lr):
"""Helper function to retrieve the scheduled learning rate based on epoch."""
if epoch < LR_SCHEDULE[0][0] or epoch > LR_SCHEDULE[-1][0]:
return lr
for i in range(len(LR_SCHEDULE)):
if epoch == LR_SCHEDULE[i][0]:
return LR_SCHEDULE[i][1]
return lr
下面我們來看如何應用 callback 在模型中。首先,先建立一個模型,在下面我們用的是 Sequential API 來建構沒有 hidden layer 的類神經網路。建構完模型後,要呼叫模型的 compile
方法,將 optimizer 等等與訓練相關的參數設定好。
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(1, activation = 'linear', input_dim = 784)) model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=0.1), loss='mean_squared_error', metrics=['mae'])
_ = model.fit(x_train, y_train,
batch_size=64,
steps_per_epoch=5,
epochs=15,
verbose=0,
callbacks=[LearningRateScheduler(lr_schedule)])
這樣就可以在 training loop 中,看到列印出的 learning rate 變化。