1. 程式人生 > >TensorFlow Estimator 教程之----自定義Estimator

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

前兩個引數是從輸入函式中返回的特徵和標籤;也就是說,featureslabels 是模型將使用的資料的控制代碼。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 的 trainevaluatepredict 方法時,就會呼叫模型函式。您應該記得,模型函式的簽名如下所示:

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。如下表所示,當有人呼叫 trainevaluatepredict 時,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 會在 TRAINEVAL 模式下向 TensorBoard 提供準確率(後文將對此進行詳細的介紹)。

4.2.4 訓練的實現

如果呼叫 Estimator 的 train 方法,則會呼叫 model_fn 並收到 mode = ModeKeys.TRAIN。在這種情況下,模型函式必須返回一個包含損失和訓練操作的 EstimatorSpec

構建訓練操作需要優化器。我們將使用 tf.train.AdagradOptimizer,因為我們模仿的是 DNNClassifier,它也預設使用 Adagradtf.train 檔案包提供很多其他優化器,您可以隨意嘗試它們。

下面是構建優化器的程式碼:

optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)

接下來,我們使用優化器的 minimize 方法根據我們之前計算的損失構建訓練操作。

minimize 方法還具有 global_step 引數。TensorFlow 使用此引數來計算已經處理過的訓練步數(以瞭解何時結束訓練)。此外,global_step 對於 TensorBoard 圖能否正常執行至關重要。只需呼叫 tf.train.get_global_step 並將結果傳遞給 minimizeglobal_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_accuracyloss 圖中的以下內容:

  • 橙線表示訓練。
  • 藍點表示評估。

在訓練期間,系統會隨著批次的處理定期記錄摘要資訊(橙線),因此它會變成一個跨越 x 軸範圍的圖形。

相比之下,評估在每次呼叫 evaluate 時僅在圖上生成一個點。此點包含整個評估呼叫的平均值。它在圖上沒有寬度,因為它完全根據特定訓練步(一個檢查點)的模型狀態進行評估。

如下圖所示,您可以使用左側的控制元件檢視並選擇性地停用/啟用報告。

在這裡插入圖片描述

總結

雖然使用預建立的 Estimator 可以快速高效地建立新模型,但您通常需要使用自定義 Estimator 才能實現所需的靈活性。幸運的是,預建立的 Estimator 和自定義 Estimator 採用相同的程式設計模型。唯一的實際區別是您必須為自定義 Estimator 編寫模型函式;除此之外,其他都是相同的。

要了解詳情,請務必檢視: