https://www.tensorflow.org/guide/custom_estimators?hl=zh-cn
創建自定義 Estimator
本文檔介紹了自定義 Estimator。具體而言,本文檔介紹了如何創建自定義 Estimator 來模擬預創建的 Estimator DNNClassifier
在解決鳶尾花問題時的行為。要詳細了解鳶尾花問題,請參閱預創建的 Estimator 這一章。
要下載和訪問示例代碼,請執行以下兩個命令:
git clone https://github.com/tensorflow/models/
cd models/samples/core/get_started
在本文檔中,我們將介紹 custom_estimator.py
。您可以使用以下命令運行它:
python custom_estimator.py
如果您時間並不充足,歡迎對比 custom_estimator.py
與 premade_estimator.py
(位於同一個目錄中)。
預創建的 Estimator 與自定義 Estimator
如下圖所示,預創建的 Estimator 是 tf.estimator.Estimator
基類的子類,而自定義 Estimator 是 tf.estimator.Estimator 的實例:

預創建的 Estimator 已完全成形。不過有時,您需要更好地控制 Estimator 的行為。這時,自定義 Estimator 就派上用場了。您可以創建自定義 Estimator 來完成幾乎任何操作。如果您需要以某種不尋常的方式連接隱藏層,則可以編寫自定義 Estimator。如果您需要為模型計算獨特的指標,也可以編寫自定義 Estimator。基本而言,如果您需要一個針對具體問題進行了優化的 Estimator,就可以編寫自定義 Estimator。
模型函數(即 model_fn
)會實現機器學習算法。采用預創建的 Estimator 和自定義 Estimator 的唯一區別是:
- 如果采用預創建的 Estimator,則有人已為您編寫了模型函數。
- 如果采用自定義 Estimator,則您必須自行編寫模型函數。
您的模型函數可以實現各種算法,定義各種各樣的隱藏層和指標。與輸入函數一樣,所有模型函數都必須接受一組標准輸入參數並返回一組標准輸出值。正如輸入函數可以利用 Dataset API 一樣,模型函數可以利用 Layers API 和 Metrics API。
我們來看看如何使用自定義 Estimator 解決鳶尾花問題。快速提醒:以下是我們嘗試模擬的鳶尾花模型的結構:

編寫輸入函數
我們的自定義 Estimator 實現與我們的預創建的 Estimator 實現使用的是同一輸入函數(來自 iris_data.py
)。即:
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
是字典特征。
創建特征列
按照預創建的 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))
編寫模型函數
我們要使用的模型函數具有以下調用簽名:
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,
})
要實現一般的模型函數,您必須執行下列操作:
定義模型
基本的深度神經網絡模型必須定義下列三個部分:
定義輸入層
在 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'])
上面的行會應用特征列定義的轉換,從而創建模型的輸入層。

隱藏層
如果您要創建深度神經網絡,則必須定義一個或多個隱藏層。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)
這里的變量 net
表示網絡的當前頂層。在第一次迭代中,net
表示輸入層。在每次循環迭代時,tf.layers.dense
都使用變量 net
創建一個新層,該層將前一層的輸出作為其輸入。
創建兩個隱藏層后,我們的網絡如下所示。為了簡單起見,下圖並未顯示各個層中的所有單元。

請注意,tf.layers.dense
提供很多其他功能,包括設置多種正則化參數的功能。不過,為了簡單起見,我們只接受其他參數的默認值。
輸出層
我們再次調用 tf.layers.dense
定義輸出層,這次不使用激活函數:
# Compute logits (1 per class).
logits = tf.layers.dense(net, params['n_classes'], activation=None)
在這里,net
表示最后的隱藏層。因此,所有的層如下所示連接在一起:

定義輸出層時,units
參數會指定輸出的數量。因此,通過將 units
設置為 params['n_classes']
,模型會為每個類別生成一個輸出值。輸出向量的每個元素都將包含針對相關鳶尾花類別(山鳶尾、變色鳶尾或維吉尼亞鳶尾)分別計算的分數或“對數”。
之后,tf.nn.softmax
函數會將這些對數轉換為概率。
實現訓練、評估和預測
創建模型函數的最后一步是編寫實現預測、評估和訓練的分支代碼。
每當有人調用 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 方法 | Estimator 模式 |
---|---|
train() |
ModeKeys.TRAIN |
evaluate() |
ModeKeys.EVAL |
predict() |
ModeKeys.PREDICT |
例如,假設您實例化自定義 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。
預測
如果調用 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
方法會生成這些字典。
計算損失
對於訓練和評估,我們都需要計算模型的損失。這是要進行優化的目標。
我們可以通過調用 tf.losses.sparse_softmax_cross_entropy
計算損失。當正確類別的概率(索引為 label
)接近 1.0 時,此函數返回的值將最低,接近 0。隨着正確類別的概率不斷降低,返回的損失值越來越大。
此函數會針對整個批次返回平均值。
# Compute loss.
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
評估
如果調用 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')
針對評估返回的 EstimatorSpec
通常包含以下信息:
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 提供准確率(后文將對此進行詳細的介紹)。
訓練
如果調用 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())
針對訓練返回的 EstimatorSpec
必須設置了下列字段:
loss
:包含損失函數的值。train_op
:執行訓練步。
下面是用於調用 EstimatorSpec
的代碼:
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
模型函數現已完成。
自定義 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)
TensorBoard
您可以在 TensorBoard 中查看自定義 Estimator 的訓練結果。要查看相應報告,請從命令行啟動 TensorBoard,如下所示:
# Replace PATH with the actual path passed as model_dir
tensorboard --logdir=PATH
然后,通過以下網址打開 TensorBoard:http://localhost:6006
所有預創建的 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 編寫模型函數;除此之外,其他都是相同的。
要了解詳情,請務必查看:
- 官方 TensorFlow MNIST 實現:使用了自定義 Estimator。
- TensorFlow 官方模型代碼庫:其中包含更多使用自定義 Estimator 的精選示例。
- TensorBoard 視頻:介紹了 TensorBoard。
- 低階 API 簡介:展示了如何直接使用 TensorFlow 的低階 API 更輕松地進行調試。