TensorFlow 篇 | TensorFlow 2.x 基於 Keras 模型的本地訓練與評估

導語」模型的訓練與評估是整個機器學習任務流程的核心環節。只有掌握了正確的訓練與評估方法,並靈活使用,才能使咱們更加快速地進行實驗分析與驗證,從而對模型有更加深入的理解。

前言

在上一篇 Keras 模型構建的文章中,咱們介紹了在 TensorFlow 2.x 版本中使用 Keras 構建模型的三種方法,那麼本篇將在上一篇的基礎上着重介紹使用 Keras 模型進行本地訓練、評估以及預測的流程和方法。 Keras 模型有兩種訓練評估的方式,一種方式是使用模型內置 API ,如 model.fit()model.evaluate()model.predict() 等分別執行不一樣的操做;另外一種方式是利用即時執行策略 (eager execution) 以及 GradientTape 對象自定義訓練和評估流程。對全部 Keras 模型來講這兩種方式都是按照相同的原理來工做的,沒有本質上的區別。在通常狀況下,咱們更願意使用第一種訓練評估方式,由於它更爲簡單,更易於使用,而在一些特殊的狀況下,咱們可能會考慮使用自定義的方式來完成訓練與評估。python

內置 API 進行訓練評估

端到端完整示例

下面介紹使用模型內置 API 實現的一個端到端的訓練評估示例,能夠認爲要使用該模型去解決一個多分類問題。這裏使用了函數式 API 來構建 Keras 模型,固然也可使用 Sequential 方式以及子類化方式去定義模型。示例代碼以下所示:git

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

# Train and Test data from numpy array.
x_train, y_train = (
    np.random.random((60000, 784)),
    np.random.randint(10, size=(60000, 1)),
)
x_test, y_test = (
    np.random.random((10000, 784)),
    np.random.randint(10, size=(10000, 1)),
)

# Reserve 10,000 samples for validation.
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]

# Model Create
inputs = keras.Input(shape=(784, ), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)

# Model Compile.
model.compile(
    # Optimizer
    optimizer=keras.optimizers.RMSprop(),
    # Loss function to minimize
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    # List of metrics to monitor
    metrics=['sparse_categorical_accuracy'],
)

# Model Training.
print('# Fit model on training data')
history = model.fit(
    x_train,
    y_train,
    batch_size=64,
    epochs=3,
    # We pass some validation for monitoring validation loss and metrics
    # at the end of each epoch
    validation_data=(x_val, y_val),
)
print('\nhistory dict:', history.history)

# Model Evaluate.
print('\n# Evaluate on test data')
results = model.evaluate(x_test, y_test, batch_size=128)
print('test loss, test acc:', results)

# Generate predictions (probabilities -- the output of the last layer)
# Model Predict.
print('\n# Generate predictions for 3 samples')
predictions = model.predict(x_test[:3])
print('predictions shape:', predictions.shape)

從代碼中能夠看到,要完成模型的訓練與評估的總體流程,首先要構建好模型;而後要對模型進行編譯 (compile),目的是指定模型訓練過程當中須要用到的優化器 (optimizer),損失函數 (losses) 以及評估指標 (metrics) ;接着開始進行模型的訓練與交叉驗證 (fit),此步驟須要提早指定好訓練數據和驗證數據,並設置好一些參數如 epochs 等才能繼續,交叉驗證操做會在每輪 (epoch) 訓練結束後自動觸發;最後是模型評估 (evaluate) 與預測 (predict),咱們會根據評估與預測結果來判斷模型的好壞。這樣一個完整的模型訓練與評估流程就完成了,下面來對示例裏的一些實現細節進行展開講解。api

模型編譯 (compile)

  1. 在模型訓練以前首先要進行模型編譯,由於只有知道了要優化什麼目標,如何進行優化以及要關注什麼指標,模型才能被正確的訓練與調整。 compile 方法包含三個主要參數,一個是待優化的損失 (loss) ,它指明瞭要優化的目標,一個是優化器 (optimizer),它指明瞭目標優化的方向,還有一個可選的指標項 (metrics),它指明瞭訓練過程當中要關注的模型指標。 Keras API 中已經包含了許多內置的損失函數,優化器以及指標,能夠拿來即用,可以知足大多數的訓練須要。
  2. 損失函數類主要在 tf.keras.losses 模塊下,其中包含了多種預約義的損失,好比咱們經常使用的二分類損失 BinaryCrossentropy ,多分類損失 CategoricalCrossentropy 以及均方根損失 MeanSquaredError 等。傳遞給 compile 的參數既能夠是一個字符串如 binary_crossentropy 也能夠是對應的 losses 實例如 tf.keras.losses.BinaryCrossentropy() ,當咱們須要設置損失函數的一些參數時(好比上例中 from_logits=True),則須要使用實例參數。
  3. 優化器類主要在 tf.keras.optimizers 模塊下,一些經常使用的優化器如 SGDAdam 以及 RMSprop 等均包含在內。一樣它也能夠經過字符串或者實例的方式傳給 compile 方法,通常咱們須要設置的優化器參數主要爲學習率 (learning rate) ,其餘的參數能夠參考每一個優化器的具體實現來動態設置,或者直接使用其默認值便可。
  4. 指標類主要在 tf.keras.metrics 模塊下,二分類裏經常使用的 AUC 指標以及 lookalike 裏經常使用的召回率 (Recall) 指標等均有包含。同理,它也能夠以字符串或者實例的形式傳遞給 compile 方法,注意 compile 方法接收的是一個 metric 列表,因此能夠傳遞多個指標信息。
  5. 固然若是 losses 模塊下的損失或 metrics 模塊下的指標不知足你的需求,也能夠自定義它們的實現。數組

    1. 對於自定義損失,有兩種方式,一種是定義一個損失函數,它接收兩個輸入參數 y_truey_pred ,而後在函數內部計算損失並返回。代碼以下:緩存

      def basic_loss_function(y_true, y_pred):
          return tf.math.reduce_mean(tf.abs(y_true - y_pred))
      
      model.compile(optimizer=keras.optimizers.Adam(), loss=basic_loss_function)
    2. 若是你須要的損失函數不只僅包含上述兩個參數,則能夠採用另一種子類化的方式來實現。定義一個類繼承自 tf.keras.losses.Loss 類,並實現其 __init__(self)call(self, y_true, y_pred) 方法,這種實現方式與子類化層和模型比較類似。好比要實現一個加權的二分類交叉熵損失,其代碼以下:網絡

      class WeightedBinaryCrossEntropy(keras.losses.Loss):
          """
          Args:
          pos_weight: Scalar to affect the positive labels of the loss function.
          weight: Scalar to affect the entirety of the loss function.
          from_logits: Whether to compute loss from logits or the probability.
          reduction: Type of tf.keras.losses.Reduction to apply to loss.
          name: Name of the loss function.
          """
          def __init__(self,
                      pos_weight,
                      weight,
                      from_logits=False,
                      reduction=keras.losses.Reduction.AUTO,
                      name='weighted_binary_crossentropy'):
              super().__init__(reduction=reduction, name=name)
              self.pos_weight = pos_weight
              self.weight = weight
              self.from_logits = from_logits
      
          def call(self, y_true, y_pred):
              ce = tf.losses.binary_crossentropy(
                  y_true,
                  y_pred,
                  from_logits=self.from_logits,
              )[:, None]
              ce = self.weight * (ce * (1 - y_true) + self.pos_weight * ce * y_true)
              return ce
      
      model.compile(
          optimizer=keras.optimizers.Adam(),
          loss=WeightedBinaryCrossEntropy(
              pos_weight=0.5,
              weight=2,
              from_logits=True,
          ),
      )
    3. 對於自定義指標,也能夠經過子類化的方式來實現,首先定義一個指標類繼承自 tf.keras.metrics.Metric 類並實現其四個方法,分別是 __init__(self) 方法,用來建立狀態變量, update_state(self, y_true, y_pred, sample_weight=None) 方法,用來更新狀態變量, result(self) 方法,用來返回狀態變量的最終結果, 以及 reset_states(self) 方法,用來從新初始化狀態變量。好比要實現一個多分類中真正例 (True Positives) 數量的統計指標,其代碼以下:app

      class CategoricalTruePositives(keras.metrics.Metric):
          def __init__(self, name='categorical_true_positives', **kwargs):
              super().__init__(name=name, **kwargs)
              self.true_positives = self.add_weight(name='tp', initializer='zeros')
      
          def update_state(self, y_true, y_pred, sample_weight=None):
              y_pred = tf.reshape(tf.argmax(y_pred, axis=1), shape=(-1, 1))
              values = tf.cast(y_true, 'int32') == tf.cast(y_pred, 'int32')
              values = tf.cast(values, 'float32')
              if sample_weight is not None:
                  sample_weight = tf.cast(sample_weight, 'float32')
                  values = tf.multiply(values, sample_weight)
              self.true_positives.assign_add(tf.reduce_sum(values))
      
          def result(self):
              return self.true_positives
      
          def reset_states(self):
              # The state of the metric will be reset at the start of each epoch.
              self.true_positives.assign(0.)
      
      model.compile(
          optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
          loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
          metrics=[CategoricalTruePositives()],
      )
    4. 對於一些在層 (layers) 內部定義的損失,能夠經過在自定義層的 call 方法裏調用 self.add_loss() 來實現,並且在模型訓練時,它會自動添加到總體的損失中,不用人爲干預。經過對比加入自定義損失先後模型訓練輸出的 loss 值的變化來確認這部分損失是否被加入到了總體的損失中。還能夠在 build 模型後,打印 model.losses 來查看該模型的全部損失。注意正則化損失是內置在 Keras 的全部層中的,只須要在調用層時加入相應正則化參數便可,無需在 call 方法中 add_loss()
    5. 對於指標信息來講,能夠在自定義層的 call 方法裏調用 self.add_metric() 來新增指標,一樣的,它也會自動出如今總體的指標中,無需人爲干預。
    6. 函數式 API 實現的模型,能夠經過調用 model.add_loss()model.add_metric() 來實現與自定義模型一樣的效果。示例代碼以下:less

      import tensorflow as tf
      from tensorflow import keras
      from tensorflow.keras import layers
      
      inputs = keras.Input(shape=(784, ), name='digits')
      x1 = layers.Dense(64, activation='relu', name='dense_1')(inputs)
      x2 = layers.Dense(64, activation='relu', name='dense_2')(x1)
      outputs = layers.Dense(10, name='predictions')(x2)
      model = keras.Model(inputs=inputs, outputs=outputs)
      
      model.add_loss(tf.reduce_sum(x1) * 0.1)
      
      model.add_metric(
          keras.backend.std(x1),
          name='std_of_activation',
          aggregation='mean',
      )
      
      model.compile(
          optimizer=keras.optimizers.RMSprop(1e-3),
          loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
      )
      model.fit(x_train, y_train, batch_size=64, epochs=1)
  6. 若是要編譯的是多輸入多輸出模型,則能夠爲每個輸出指定不一樣的損失函數以及不一樣的指標,後面會詳細介紹。

模型訓練與驗證 (fit)

  1. 模型的訓練經過調用 model.fit() 方法來實現, fit 方法包括訓練數據與驗證數據參數,它們能夠是 numpy 類型數據,也能夠是 tf.data 模塊下 dataset 類型的數據。另外 fit 方法還包括 epochsbatch_size 以及 steps_per_epoch 等控制訓練流程的參數,而且還能夠經過 callbacks 參數控制模型在訓練過程當中執行一些其它的操做,如 Tensorboard 日誌記錄等。
  2. 模型的訓練和驗證數據能夠是 numpy 類型數據,最開始的端到端示例便是採用 numpy 數組做爲輸入。通常在數據量較小且內存能容下的狀況下采用 numpy 數據做爲訓練和評估的數據輸入。dom

    1. 對於 numpy 類型數據來講,若是指定了 epochs 參數,則訓練數據的總量爲原始樣本數量 * epochs
    2. 默認狀況下一輪訓練 (epoch) 全部的原始樣本都會被訓練一遍,下一輪訓練還會使用這些樣本數據進行訓練,每一輪執行的步數 (steps) 爲原始樣本數量/batch_size ,若是 batch_size 不指定,默認爲 32 。交叉驗證在每一輪訓練結束後觸發,而且也會在全部驗證樣本上執行一遍,能夠指定 validation_batch_size 來控制驗證數據的 batch 大小,若是不指定默認同 batch_size
    3. 對於 numpy 類型數據來講,若是設置了 steps_per_epoch 參數,表示一輪要訓練指定的步數,下一輪會在上輪基礎上使用下一個 batch 的數據繼續進行訓練,直到全部的 epochs 結束或者訓練數據的總量被耗盡。要想訓練流程不因數據耗盡而結束,則須要保證數據的總量要大於 steps_per_epoch * epochs * batch_size。同理也能夠設置 validation_steps ,表示交叉驗證所需步數,此時要注意驗證集的數據總量要大於 validation_steps * validation_batch_size
    4. fit 方法還提供了另一個參數 validation_split 來自動從訓練數據集中保留必定比例的數據做爲驗證,該參數取值爲 0-1 之間,好比 0.2 表明 20% 的訓練集用來作驗證, fit 方法會默認保留 numpy 數組最後面 20% 的樣本做爲驗證集。
  3. TensorFlow 2.0 以後,更爲推薦的是使用 tf.data 模塊下 dataset 類型的數據做爲訓練和驗證的數據輸入,它能以更加快速以及可擴展的方式加載和預處理數據。機器學習

    1. 使用 dataset 進行訓練的代碼以下:

      train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
      # Shuffle and slice the dataset.
      train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
      
      # Prepare the validation dataset
      val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
      val_dataset = val_dataset.batch(64)
      
      # Now we get a test dataset.
      test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
      test_dataset = test_dataset.batch(64)
      
      # Since the dataset already takes care of batching,
      # we don't pass a `batch_size` argument.
      model.fit(train_dataset, epochs=3, validation_data=val_dataset)
      result = model.evaluate(test_dataset)
    2. dataset 通常是一個二元組,第一個元素爲模型的輸入特徵,若是爲多輸入就是多個特徵的字典 (dict) 或元組 (tuple),第二個元素是真實的數據標籤 (label) ,即 ground truth
    3. 使用 from_tensor_slices 方法能夠從 nunpy 數組直接生成 dataset 類型數據,是一種比較方便快捷的生成方式,通常在測試時使用。其它較爲經常使用的生成方式,好比從 TFRecord 文件或文本文件 (TextLine) 中生成 dataset ,能夠參考 tf.data 模塊下的相關類的具體實現。
    4. dataset 能夠調用內置方法提早對數據進行預處理,好比數據打亂 (shuffle), batch 以及 repeat 等操做。shuffle 操做是爲了減少模型過擬合的概率,它僅爲小範圍打亂,須要藉助於一個緩存區,先將數據填滿,而後在每次訓練時從緩存區裏隨機抽取 batch_size 條數據,產生的空缺用後面的數據填充,從而實現了局部打亂的效果。batch 是對數據進行分批次,經常使用於控制和調節模型的訓練速度以及訓練效果,由於在 dataset 中已經 batch 過,因此 fit 方法中的 batch_size 就無需再提供了。repeat 用來對數據進行復制,以解決數據量不足的問題,若是指定了其參數 count,則表示整個數據集要複製 count 次,不指定就會無限次複製 ,此時必需要設置 steps_per_epoch 參數,否則訓練沒法終止。
    5. 上述例子中, train dataset 的所有數據在每一輪都會被訓練到,由於一輪訓練結束後, dataset 會重置,而後被用來從新訓練。可是當指定了 steps_per_epoch 以後, dataset 在每輪訓練後不會被重置,一直到全部 epochs 結束或全部的訓練數據被消耗完以後終止,要想訓練正常結束,須保證提供的訓練數據總量要大於 steps_per_epoch * epochs * batch_size。同理也能夠指定 validation_steps ,此時數據驗證會執行指定的步數,在下次驗證開始時, validation dataset 會被重置,以保證每次交叉驗證使用的都是相同的數據。validation_split 參數不適用於 dataset 類型數據,由於它須要知道每一個數據樣本的索引,這在 dataset API 下很難實現。
    6. 當不指定 steps_per_epoch 參數時, numpy 類型數據與 dataset 類型數據的處理流程徹底一致。但當指定以後,要注意它們之間在處理上的差別。對於 numpy 類型數據而言,在處理時,它會被轉爲 dataset 類型數據,只不過這個 datasetrepeatepochs 次,並且每輪訓練結束後,這個 dataset 不會被重置,會在上次的 batch 以後繼續訓練。假設原始數據量爲 n ,指定 steps_per_epoch 參數以後,二者的差別主要體如今真實的訓練數據量上, numpyn * epochsdatasetn。具體細節能夠參考源碼實現。
    7. dataset 還有 mapprefetch 方法比較實用。 map 方法接收一個函數做爲參數,用來對 dataset 中的每一條數據進行處理並返回一個新的 dataset ,好比咱們在使用 TextLineDataset 讀取文本文件後生成了一個 dataset ,而咱們要抽取輸入數據中的某些列做爲特徵 (features),某些列做爲標籤 (labels),此時就會用到 map 方法。prefetch 方法預先從 dataset 中準備好下次訓練所需的數據並放於內存中,這樣能夠減小每輪訓練之間的延遲等待時間。
  4. 除了訓練數據和驗證數據外,還能夠向 fit 方法傳遞樣本權重 (sample_weight) 以及類別權重 (class_weight) 參數。這兩個參數一般被用於處理分類不平衡問題,經過給類別少的樣本賦予更高的權重,使得各個類別對總體損失的貢獻趨於一致。

    1. 對於 numpy 類型的輸入數據,可使用上述兩個參數,以上面的多分類問題爲例,若是要給分類 5 一個更高的權重,可使用以下代碼來實現:

      import numpy as np
      
      # Here's the same example using `class_weight`
      class_weight = {0: 1., 1: 1., 2: 1., 3: 1., 4: 1.,
                    # Set weight "2" for class "5",
                    # making this class 2x more important
                    5: 2.,
                    6: 1., 7: 1., 8: 1., 9: 1.}
      print('Fit with class weight')
      model.fit(x_train, y_train, class_weight=class_weight, batch_size=64, epochs=4)
      
      # Here's the same example using `sample_weight` instead:
      sample_weight = np.ones(shape=(len(y_train), ))
      sample_weight[y_train == 5] = 2.
      print('\nFit with sample weight')
      
      model.fit(
          x_train,
          y_train,
          sample_weight=sample_weight,
          batch_size=64,
          epochs=4,
      )
    2. 而對於 dataset 類型的輸入數據來講,不能直接使用上述兩個參數,須要在構建 dataset 時將 sample_weight 加入其中,返回一個三元組的 dataset ,格式爲 (input_batch, target_batch, sample_weight_batch) 。示例代碼以下所示:

      sample_weight = np.ones(shape=(len(y_train), ))
      sample_weight[y_train == 5] = 2.
      
      # Create a Dataset that includes sample weights
      # (3rd element in the return tuple).
      train_dataset = tf.data.Dataset.from_tensor_slices((
          x_train,
          y_train,
          sample_weight,
      ))
      
      # Shuffle and slice the dataset.
      train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
      
      model.fit(train_dataset, epochs=3)
  5. 在模型的訓練過程當中有一些特殊時間點,好比在一個 batch 結束或者一個 epoch 結束時,通常都會作一些額外的處理操做來輔助咱們進行訓練,上面介紹過的模型交叉驗證就是其中之一。還有一些其它的操做,好比當模型訓練停滯不前時 (loss 值在某一值附近不斷波動),自動減少其學習速率 (learning rate) 以使損失繼續降低,從而獲得更好的收斂效果;在訓練過程當中保存模型的權重信息,以備重啓模型時能夠在已有權重的基礎上繼續訓練,從而減小訓練時間;還有在每輪的訓練結束後記錄模型的損失 (loss) 和指標 (metrics) 信息,以供 Tensorboard 分析使用等等,這些操做都是模型訓練過程當中不可或缺的部分。它們均可以經過回調函數 (callbacks) 的方式來實現,這些回調函數都在 tf.keras.callbacks 模塊下,能夠將它們做爲列表參數傳遞給 fit 方法以達到不一樣的操做目的。

    1. 下面以 EarlyStopping 爲例說明 callbacks 的使用方式。本例中,當交叉驗證損失 val_loss 至少在 2 輪 (epochs) 訓練中的減小值都低於 1e-2 時,咱們會提早中止訓練。其示例代碼以下所示:

      callbacks = [
          keras.callbacks.EarlyStopping(
              # Stop training when `val_loss` is no longer improving
              monitor='val_loss',
              # "no longer improving" being defined as "no better than 1e-2 less"
              min_delta=1e-2,
              # "no longer improving" being further defined as "for at least 2 epochs"
              patience=2,
              verbose=1,
          )
      ]
      
      model.fit(
          x_train,
          y_train,
          epochs=20,
          batch_size=64,
          callbacks=callbacks,
          validation_split=0.2,
      )
    2. 一些比較經常使用的 callbacks 須要瞭解並掌握, 如 ModelCheckpoint 用來保存模型權重信息, TensorBoard 用來記錄一些指標信息, ReduceLROnPlateau 用來在模型停滯時減少學習率。更多的 callbacks 函數能夠參考 tf.keras.callbacks 模塊下的實現。
    3. 固然也能夠自定義 callbacks 類,該子類須要繼承自 tf.keras.callbacks.Callback 類,並按需實現其內置的方法,好比若是須要在每一個 batch 訓練結束後記錄 loss 的值,則可使用以下代碼實現:

      class LossHistory(keras.callbacks.Callback):
          def on_train_begin(self, logs):
              self.losses = []
      
          def on_batch_end(self, batch, logs):
              self.losses.append(logs.get('loss'))
    4. TensorFlow 2.0 以前, ModelCheckpoint 內容和 TensorBoard 內容是同時記錄的,保存在相同的文件夾下,而在 2.0 以後的 keras API 中它們能夠經過不一樣的回調函數分開指定。記錄的日誌文件中,含有 checkpoint 關鍵字的文件通常爲檢查點文件,含有 events.out.tfevents 關鍵字的文件通常爲 Tensorboard 相關文件。

多輸入輸出模型

多輸入輸出模型圖

  1. 考慮如圖所示的多輸入多輸出模型,該模型包括兩個輸入和兩個輸出, score_output 輸出表示分值, class_output 輸出表示分類,其示例代碼以下:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    image_input = keras.Input(shape=(32, 32, 3), name='img_input')
    timeseries_input = keras.Input(shape=(None, 10), name='ts_input')
    
    x1 = layers.Conv2D(3, 3)(image_input)
    x1 = layers.GlobalMaxPooling2D()(x1)
    
    x2 = layers.Conv1D(3, 3)(timeseries_input)
    x2 = layers.GlobalMaxPooling1D()(x2)
    
    x = layers.concatenate([x1, x2])
    
    score_output = layers.Dense(1, name='score_output')(x)
    class_output = layers.Dense(5, name='class_output')(x)
    
    model = keras.Model(
        inputs=[image_input, timeseries_input],
        outputs=[score_output, class_output],
    )
  2. 在進行模型編譯時,若是隻指定一個 loss 明顯不能知足不一樣輸出的損失計算方式,因此此時能夠指定 loss 爲一個列表 (list),其中每一個元素分別對應於不一樣的輸出。示例代碼以下:

    model.compile(
        optimizer=keras.optimizers.RMSprop(1e-3),
        loss=[
            keras.losses.MeanSquaredError(),
            keras.losses.CategoricalCrossentropy(from_logits=True)
        ],
        loss_weights=[1, 1],
    )

    此時模型的優化目標爲全部單個損失值的總和,若是想要爲不一樣的損失指定不一樣的權重,能夠設置 loss_weights 參數,該參數接收一個標量係數列表 (list),用以對模型不一樣輸出的損失值進行加權。若是僅爲模型指定一個 loss ,則該 loss 會應用到每個輸出,在模型的多個輸出損失計算方式相同時,能夠採用這種方式。

  3. 一樣的對於模型的指標 (metrics),也能夠指定爲多個,注意由於 metrics 參數自己即爲一個列表,因此爲多個輸出指定 metrics 應該使用二維列表。示例代碼以下:

    model.compile(
        optimizer=keras.optimizers.RMSprop(1e-3),
        loss=[
            keras.losses.MeanSquaredError(),
            keras.losses.CategoricalCrossentropy(from_logits=True),
        ],
        metrics=[
            [
                keras.metrics.MeanAbsolutePercentageError(),
                keras.metrics.MeanAbsoluteError()
            ],
            [keras.metrics.CategoricalAccuracy()],
        ],
    )
  4. 對於有明確名稱的輸出,能夠經過字典的方式來設置其 lossmetrics。示例代碼以下:

    model.compile(
        optimizer=keras.optimizers.RMSprop(1e-3),
        loss={
            'score_output': keras.losses.MeanSquaredError(),
            'class_output': keras.losses.CategoricalCrossentropy(from_logits=True),
        },
        metrics={
            'score_output': [
                keras.metrics.MeanAbsolutePercentageError(),
                keras.metrics.MeanAbsoluteError()
            ],
            'class_output': [
                keras.metrics.CategoricalAccuracy(),
            ]
        },
    )
  5. 對於僅被用來預測的輸出,也能夠不指定其 loss。示例代碼以下:

    model.compile(
        optimizer=keras.optimizers.RMSprop(1e-3),
        loss=[
            None,
            keras.losses.CategoricalCrossentropy(from_logits=True),
        ],
    )
    
    # Or dict loss version
    model.compile(
        optimizer=keras.optimizers.RMSprop(1e-3),
        loss={
            'class_output': keras.losses.CategoricalCrossentropy(from_logits=True),
        },
    )
  6. 對於多輸入輸出模型的訓練來講,也能夠採用和其 compile 方法相同的方式來提供數據輸入,也就是說既可使用列表的方式,也可使用字典的方式來指定多個輸入。

    1. numpy 類型數據示例代碼以下:

      # Generate dummy Numpy data
      img_data = np.random.random_sample(size=(100, 32, 32, 3))
      ts_data = np.random.random_sample(size=(100, 20, 10))
      score_targets = np.random.random_sample(size=(100, 1))
      class_targets = np.random.random_sample(size=(100, 5))
      
      # Fit on lists
      model.fit(
          x=[img_data, ts_data],
          y=[score_targets, class_targets],
          batch_size=32,
          epochs=3,
      )
      
      # Alternatively, fit on dicts
      model.fit(
          x={
              'img_input': img_data,
              'ts_input': ts_data,
          },
          y={
              'score_output': score_targets,
              'class_output': class_targets,
          },
          batch_size=32,
          epochs=3,
      )
    2. dataset 類型數據示例代碼以下:

      # Generate dummy dataset data from numpy
      train_dataset = tf.data.Dataset.from_tensor_slices((
          (img_data, ts_data),
          (score_targets, class_targets),
      ))
      
      # Alternatively generate with dict
      train_dataset = tf.data.Dataset.from_tensor_slices((
          {
              'img_input': img_data,
              'ts_input': ts_data,
          },
          {
              'score_output': score_targets,
              'class_output': class_targets,
          },
      ))
      train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
      
      model.fit(train_dataset, epochs=3)

自定義訓練流程

  1. 若是你不想使用 model 內置提供的 fitevaluate 方法,而想使用低階 API 自定義模型的訓練和評估的流程,則能夠藉助於 GradientTape 來實現。深度神經網絡在後向傳播過程當中須要計算損失 (loss) 關於權重矩陣的導數(也稱爲梯度),以更新權重矩陣並得到最優解,而 GradientTape 能自動提供求導幫助,無需咱們手動求導,它本質上是一個求導記錄器 ,可以記錄前項傳播的過程,並據此計算導數。
  2. 模型的構建過程與以前相比沒有什麼不一樣,主要體如今訓練的部分,示例代碼以下:

    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import layers
    import numpy as np
    
    # Get the model.
    inputs = keras.Input(shape=(784, ), name='digits')
    x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
    x = layers.Dense(64, activation='relu', name='dense_2')(x)
    outputs = layers.Dense(10, name='predictions')(x)
    model = keras.Model(inputs=inputs, outputs=outputs)
    
    # Instantiate an optimizer.
    optimizer = keras.optimizers.SGD(learning_rate=1e-3)
    # Instantiate a loss function.
    loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)
    
    # Prepare the metrics.
    train_acc_metric = keras.metrics.SparseCategoricalAccuracy()
    val_acc_metric = keras.metrics.SparseCategoricalAccuracy()
    
    # Prepare the training dataset.
    batch_size = 64
    train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
    train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)
    
    # Prepare the validation dataset.
    val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
    val_dataset = val_dataset.batch(64)
    
    epochs = 3
    for epoch in range(epochs):
        print('Start of epoch %d' % (epoch, ))
    
        # Iterate over the batches of the dataset.
        for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
    
            # Open a GradientTape to record the operations run
            # during the forward pass, which enables autodifferentiation.
            with tf.GradientTape() as tape:
    
                # Run the forward pass of the layer.
                # The operations that the layer applies
                # to its inputs are going to be recorded
                # on the GradientTape.
                logits = model(x_batch_train,
                            training=True)  # Logits for this minibatch
    
                # Compute the loss value for this minibatch.
                loss_value = loss_fn(y_batch_train, logits)
    
            # Use the gradient tape to automatically retrieve
            # the gradients of the trainable variables with respect to the loss.
            grads = tape.gradient(loss_value, model.trainable_weights)
    
            # Run one step of gradient descent by updating
            # the value of the variables to minimize the loss.
            optimizer.apply_gradients(zip(grads, model.trainable_weights))
    
            # Update training metric.
            train_acc_metric(y_batch_train, logits)
    
            # Log every 200 batches.
            if step % 200 == 0:
                print('Training loss (for one batch) at step %s: %s' %
                    (step, float(loss_value)))
                print('Seen so far: %s samples' % ((step + 1) * 64))
    
        # Display metrics at the end of each epoch.
        train_acc = train_acc_metric.result()
        print('Training acc over epoch: %s' % (float(train_acc), ))
        # Reset training metrics at the end of each epoch
        train_acc_metric.reset_states()
    
        # Run a validation loop at the end of each epoch.
        for x_batch_val, y_batch_val in val_dataset:
            val_logits = model(x_batch_val)
            # Update val metrics
            val_acc_metric(y_batch_val, val_logits)
        val_acc = val_acc_metric.result()
        val_acc_metric.reset_states()
        print('Validation acc: %s' % (float(val_acc), ))
  3. 注意 with tf.GradientTape() as tape 部分的實現,它記錄了前向傳播的過程,而後使用 tape.gradient 方法計算出 loss 關於模型全部權重矩陣 (model.trainable_weights) 的導數(也稱做梯度),接着利用優化器 (optimizer) 去更新全部的權重矩陣。
  4. 在上述訓練流程中,模型的訓練指標在每一個 batch 的訓練中進行更新操做 (update_state()) ,在一個 epoch 訓練結束後打印指標的結果 (result()) ,而後重置該指標 (reset_states()) 並進行下一輪的指標記錄,交叉驗證的指標也是一樣的操做。
  5. 注意與使用模型內置 API 進行訓練不一樣,在自定義訓練中,模型中定義的損失,好比正則化損失以及經過 add_loss 添加的損失,是不會自動累加在 loss_fn 以內的。若是要包含這部分損失,則須要修改自定義訓練的流程,經過調用 model.losses 來將模型的所有損失加入到要優化的損失中去。示例代碼以下所示:

    with tf.GradientTape() as tape:
        logits = model(x_batch_train)
        loss_value = loss_fn(y_batch_train, logits)
    
        # Add extra losses created during this forward pass:
        loss_value += sum(model.losses)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))

參考資料

  1. Keras 模型訓練與評估
  2. Keras 模型 fit 方法
  3. tf.data.Dataset
相關文章
相關標籤/搜索