TensorFlow Estimator 教程之----自定義Estimator
建立自定義 Estimator
本文件介紹了自定義 Estimator。具體而言,本文件介紹瞭如何建立自定義 Estimator 來實現和 內建 Estimator DNNClassifier
類似的功能。
要獲取本文的示例程式碼,請執行以下兩個命令:
git clone https://github.com/tensorflow/models/
cd models/samples/core/get_started
在本文件中,我們將介紹 custom_estimator.py
。您可以使用以下命令執行它:
python custom_estimator.py
如果你時間充裕,可以對比下 內建 Estimator 與 自定義 Estimator。
1. 內建 Estimator 與 自定義 Estimator 的對比 ¶
如下圖所示,內建 Estimator 是 tf.estimator.Estimator
基類的子類,而自定義 Estimator 是 `tf.estimator.Estimator` 的例項:
內建 Estimator 已由開發人員寫好,直接呼叫即可。不過有時,為了更好地控制 Estimator 的行為。這時就需要自定義 Estimator。您可以建立自定義 Estimator 來實現想要的功能。
模型函式(即 model_fn
)為機器學習演算法的主體。採用內建 Estimator 和自定義 Estimator 的唯一區別是:
- 如果採用內建 Estimator,直接呼叫即可。
- 如果採用自定義 Estimator,必須自行編寫模型函式。
模型函式的內容可以自定義,但所有模型函式都必須接受一組標準輸入引數並返回一組標準輸出值。一般推薦在模型函式中使用 Layers API 和 Metrics API。
我們來看看如何使用自定義 Estimator 解決鳶尾花問題。這裡的網路結構選擇和使用內建 DNNClassifier
時一樣,以方便兩者的對比:
2. 編寫輸入函式 ¶
def train_input_fn(features, labels, batch_size):
"""An input function for training"""
# Convert the inputs to a Dataset.
dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
# Shuffle, repeat, and batch the examples.
dataset = dataset.shuffle(1000).repeat().batch(batch_size)
# Return the read end of the pipeline.
return dataset.make_one_shot_iterator().get_next()
此輸入函式會構建可以生成批次 (features, labels)
對的輸入管道,其中 features
是字典特徵。
3. 建立特徵列 ¶
按照內建 Estimator 和特徵列章節中詳細介紹的內容,您必須定義模型的特徵列來指定模型應該如何使用每個特徵。無論是使用預建立的 Estimator 還是自定義 Estimator,您都要使用相同的方式定義特徵列。
以下程式碼為每個輸入特徵建立一個簡單的 numeric_column
,表示應該將輸入特徵的值直接用作模型的輸入:
# Feature columns describe how to use the input.
my_feature_columns = []
for key in train_x.keys():
my_feature_columns.append(tf.feature_column.numeric_column(key=key))
4. 編寫模型函式 ¶
我們要定義的模型函式具有以下引數:
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode, # An instance of tf.estimator.ModeKeys
params): # Additional configuration
前兩個引數是從輸入函式中返回的特徵和標籤;也就是說,features
和 labels
是模型將使用的資料的控制代碼。mode
引數表示以何種模式呼叫(訓練、預測還是評估)。
呼叫程式可以將 params
傳遞給 Estimator 的建構函式。傳遞給建構函式的所有 params
轉而又傳遞給 model_fn
。在 custom_estimator.py
中,以下行將建立 Estimator 並設定引數來配置模型。此配置步驟與我們配置 tf.estimator.DNNClassifier
(在內建 Estimator 中)的方式相似。
classifier = tf.estimator.Estimator(
model_fn=my_model,
params={
'feature_columns': my_feature_columns,
# Two hidden layers of 10 nodes each.
'hidden_units': [10, 10],
# The model must choose between 3 classes.
'n_classes': 3,
})
模型函式的定義一般分以下幾步:
- 定義模型。
- 配置模型在不同 mode 下的行為:
- 預測
- 評估
- 訓練
4.1 定義模型 ¶
基本的深度神經網路模型必須定義下列三個部分:
- 一個輸入層
- 一個或多個隱藏層
- 一個輸出層
4.1.1 定義輸入層 ¶
在 model_fn
的第一行呼叫 tf.feature_column.input_layer
,以將特徵字典和 feature_columns
轉換為模型的輸入,如下所示:
# Use `input_layer` to apply the feature columns.
net = tf.feature_column.input_layer(features, params['feature_columns'])
上面的行會應用特徵列定義的轉換,從而建立模型的輸入層。
4.1.2 定義隱藏層 ¶
如果您要建立深度神經網路,則必須定義一個或多個隱藏層。Layers API 提供一組豐富的函式來定義所有型別的隱藏層,包括卷積層、池化層和丟棄層。對於鳶尾花,我們只需呼叫 tf.layers.dense
來建立隱藏層,並使用 params['hidden_layers']
定義的維度。在 dense
層中,每個節點都連線到前一層中的各個節點。下面是相關程式碼:
# Build the hidden layers, sized according to the 'hidden_units' param.
for units in params['hidden_units']:
net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
units
引數會定義指定層中輸出神經元的數量。activation
引數會定義啟用函式 - 在本文使用的是 Relu。
這裡的變數 net
表示網路的當前頂層。在第一次迭代中,net
表示輸入層。在每次迴圈迭代時,tf.layers.dense
都使用變數 net
建立一個新層,該層將前一層的輸出作為其輸入。
建立兩個隱藏層後,我們的網路如下所示。為了簡單起見,下圖並未顯示各個層中的所有單元。
請注意,tf.layers.dense
提供很多其他功能,包括設定多種正則化引數的功能。不過,為了簡單起見,我們只接受其他引數的預設值。
4.1.3 定義輸出層 ¶
我們再次呼叫 tf.layers.dense
來定義輸出層,這次不使用啟用函式:
# Compute logits (1 per class).
logits = tf.layers.dense(net, params['n_classes'], activation=None)
在這裡,net
表示最後的隱藏層。因此,所有的層如下所示連線在一起:
定義輸出層時,units
引數會指定輸出的數量。因此,通過將 units
設定為 params['n_classes']
,模型會為每個類別生成一個輸出值。輸出向量的每個元素都將包含針對相關鳶尾花類別(山鳶尾、變色鳶尾或維吉尼亞鳶尾)分別計算的分數或“對數”。
4.2 訓練、評估和預測的實現 ¶
建立模型函式的最後一步是編寫實現預測、評估和訓練的分支程式碼。
每當有人呼叫 Estimator 的 train
、evaluate
或 predict
方法時,就會呼叫模型函式。您應該記得,模型函式的簽名如下所示:
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode, # An instance of tf.estimator.ModeKeys, see below
params): # Additional configuration
重點關注第三個引數 mode。如下表所示,當有人呼叫 train
、evaluate
或 predict
時,Estimator 框架會呼叫模型函式並將 mode 引數設定為如下所示的值:
例如,假設您例項化自定義 Estimator 來生成名為 classifier
的物件。然後,您做出以下呼叫:
classifier = tf.estimator.Estimator(...)
classifier.train(input_fn=lambda: my_input_fn(FILE_TRAIN, True, 500))
然後,Estimator 框架會呼叫模型函式並將 mode 設為 ModeKeys.TRAIN
。
模型函式必須提供程式碼來處理全部三個 mode 值。對於每個 mode 值,您的程式碼都必須返回 tf.estimator.EstimatorSpec
的一個例項,其中包含呼叫程式需要的資訊。我們來詳細瞭解各個 mode。
4.2.1 預測的實現 ¶
如果呼叫 Estimator 的 predict
方法,則 model_fn
會收到 mode = ModeKeys.PREDICT
。在這種情況下,模型函式必須返回一個包含預測的 tf.estimator.EstimatorSpec
。
該模型必須經過訓練才能進行預測。經過訓練的模型儲存在磁碟上,位於您例項化 Estimator 時建立的 model_dir
目錄中。
此模型用於生成預測的程式碼如下所示:
# Compute predictions.
predicted_classes = tf.argmax(logits, 1)
if mode == tf.estimator.ModeKeys.PREDICT:
predictions = {
'class_ids': predicted_classes[:, tf.newaxis],
'probabilities': tf.nn.softmax(logits),
'logits': logits,
}
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
預測字典中包含模型在預測模式下執行時返回的所有內容。
predictions
儲存的是下列三個鍵值對:
class_ids
儲存的是類別 ID(0、1 或 2),表示模型對此樣本最有可能歸屬的品種做出的預測。probabilities
儲存的是三個概率(在本例中,分別是 0.02、0.95 和 0.03)logit
儲存的是原始對數值(在本例中,分別是 -1.3、2.6 和 -0.9)
我們通過 predictions
引數(屬於 tf.estimator.EstimatorSpec
)將該字典返回到呼叫程式。Estimator 的 predict
方法會生成這些字典。
4.2.2 計算損失 ¶
對於訓練和評估,我們都需要計算模型的損失。這是要進行優化的目標。
我們可以通過呼叫 tf.losses.sparse_softmax_cross_entropy
來計算損失。此函式返回的值將是最低的,接近 0,而正確類別的概率(索引為 label
)接近 1.0。隨著正確類別的概率不斷降低,返回的損失值越來越大。
此函式會針對整個批次返回平均值。
# Compute loss.
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
4.2.3 評估的實現 ¶
如果呼叫 Estimator 的 evaluate
方法,則 model_fn
會收到 mode = ModeKeys.EVAL
。在這種情況下,模型函式必須返回一個包含模型損失和一個或多個指標(可選)的 tf.estimator.EstimatorSpec
。
雖然返回指標是可選的,但大多數自定義 Estimator 至少會返回一個指標。TensorFlow 提供一個指標模組 tf.metrics
來計算常用指標。為簡單起見,我們將只返回準確率。tf.metrics.accuracy
函式會將我們的預測值與真實值進行比較,即與輸入函式提供的標籤進行比較。tf.metrics.accuracy
函式要求標籤和預測具有相同的形狀。下面是對 tf.metrics.accuracy
的呼叫:
# Compute evaluation metrics.
accuracy = tf.metrics.accuracy(labels=labels,
predictions=predicted_classes,
name='acc_op')
loss
:這是模型的損失eval_metric_ops
:這是可選的指標字典。
我們將建立一個包含我們的唯一指標的字典。如果我們計算了其他指標,則將這些指標作為附加鍵值對新增到同一字典中。然後,我們將在 eval_metric_ops
引數(屬於 tf.estimator.EstimatorSpec
)中傳遞該字典。具體程式碼如下:
metrics = {'accuracy': accuracy}
tf.summary.scalar('accuracy', accuracy[1])
if mode == tf.estimator.ModeKeys.EVAL:
return tf.estimator.EstimatorSpec(
mode, loss=loss, eval_metric_ops=metrics)
tf.summary.scalar
會在 TRAIN
和 EVAL
模式下向 TensorBoard 提供準確率(後文將對此進行詳細的介紹)。
4.2.4 訓練的實現 ¶
如果呼叫 Estimator 的 train
方法,則會呼叫 model_fn
並收到 mode = ModeKeys.TRAIN
。在這種情況下,模型函式必須返回一個包含損失和訓練操作的 EstimatorSpec
。
構建訓練操作需要優化器。我們將使用 tf.train.AdagradOptimizer
,因為我們模仿的是 DNNClassifier
,它也預設使用 Adagrad
。tf.train
檔案包提供很多其他優化器,您可以隨意嘗試它們。
下面是構建優化器的程式碼:
optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
接下來,我們使用優化器的 minimize
方法根據我們之前計算的損失構建訓練操作。
minimize
方法還具有 global_step
引數。TensorFlow 使用此引數來計算已經處理過的訓練步數(以瞭解何時結束訓練)。此外,global_step
對於 TensorBoard 圖能否正常執行至關重要。只需呼叫 tf.train.get_global_step
並將結果傳遞給 minimize
的 global_step
引數即可。
下面是訓練模型的程式碼:
train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
loss
:包含損失函式的值。train_op
:執行訓練步。
下面是用於呼叫 EstimatorSpec
的程式碼:
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
模型函式現已完成。
5. 自定義 Estimator ¶
通過 Estimator 基類例項化自定義 Estimator,如下所示:
# Build 2 hidden layer DNN with 10, 10 units respectively.
classifier = tf.estimator.Estimator(
model_fn=my_model,
params={
'feature_columns': my_feature_columns,
# Two hidden layers of 10 nodes each.
'hidden_units': [10, 10],
# The model must choose between 3 classes.
'n_classes': 3,
})
在這裡,params
字典與 DNNClassifier
的關鍵字引數用途相同;即藉助 params
字典,您無需修改 model_fn
中的程式碼即可配置 Estimator。
使用 Estimator 訓練、評估和生成預測要用的其餘程式碼與預建立的 Estimator 一章中的相同。例如,以下行將訓練模型:
# Train the Model.
classifier.train(
input_fn=lambda:iris_data.train_input_fn(train_x, train_y, args.batch_size),
steps=args.train_steps)
6. TensorBoard ¶
您可以在 TensorBoard 中檢視自定義 Estimator 的訓練結果。要檢視相應報告,請從命令列啟動 TensorBoard,如下所示:
# Replace PATH with the actual path passed as model_dir
tensorboard --logdir=PATH
所有預建立的 Estimator 都會自動將大量資訊記錄到 TensorBoard 上。不過,對於自定義 Estimator,TensorBoard 只提供一個預設日誌(損失圖)以及您明確告知 TensorBoard 要記錄的資訊。對於您剛剛建立的自定義 Estimator,TensorBoard 會生成以下內容:
簡而言之,下面是三張圖顯示的內容:
global_step/sec:這是一個性能指標,顯示我們在進行模型訓練時每秒處理的批次數(梯度更新)。
loss:所報告的損失。
accuracy:準確率由下列兩行記錄:
eval_metric_ops={'my_accuracy': accuracy}
(評估期間)。tf.summary.scalar('accuracy', accuracy[1])
(訓練期間)。
這些 Tensorboard 圖是務必要將 global_step
傳遞給優化器的 minimize
方法的主要原因之一。如果沒有它,模型就無法記錄這些圖的 x 座標。
注意 my_accuracy
和 loss
圖中的以下內容:
- 橙線表示訓練。
- 藍點表示評估。
在訓練期間,系統會隨著批次的處理定期記錄摘要資訊(橙線),因此它會變成一個跨越 x 軸範圍的圖形。
相比之下,評估在每次呼叫 evaluate
時僅在圖上生成一個點。此點包含整個評估呼叫的平均值。它在圖上沒有寬度,因為它完全根據特定訓練步(一個檢查點)的模型狀態進行評估。
如下圖所示,您可以使用左側的控制元件檢視並選擇性地停用/啟用報告。
總結 ¶
雖然使用預建立的 Estimator 可以快速高效地建立新模型,但您通常需要使用自定義 Estimator 才能實現所需的靈活性。幸運的是,預建立的 Estimator 和自定義 Estimator 採用相同的程式設計模型。唯一的實際區別是您必須為自定義 Estimator 編寫模型函式;除此之外,其他都是相同的。
要了解詳情,請務必檢視: