【tf.keras】09: 使用 Keras 訓練和評估模型指南

本文是 tf.keras 系列教程的第九篇,介紹了使用 tensorflow2.0 實現兩種方式訓練和評估模型。包括tf.keras模塊中的方法實現和從頭開始編寫訓練循環。但本文並不涉及分佈式訓練。



代碼環境:

python version: 3.7.6
tensorflow version: 2.1.0

導入必要的包:

import tensorflow as tf
import numpy as np

注:本文所有代碼在 jupyter notebook編寫並測試通過。


1. 使用 tf.keras 模塊(內置)實現訓練和評估

tf.keras 內置的訓練評估API常用的有:

  • model.fit() :訓練
  • model.evaluate() :評估
  • model.predict():預測

將數據傳遞到模型的內置訓練循環時,應該使用Numpy數組(如果數據很小並且適合存儲在內存中)或 tf.data Dataset 對象。接下來的例子中,將MNIST數據集用作Numpy數組,以演示如何使用 optimizerslosses 以及 metrics 等訓練配置。

1.1 一個典型的端到端訓練示例

1.定義模型

使用 function API 自定義模型編寫順序模型示例:

from tensorflow import keras
from tensorflow.keras import layers

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)

2.加載數據

# 加載數據集
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# 將加載的numpy數組縮放到[-1,1],並修改數據類型爲float32格式;
x_train = x_train.reshape(60000, 784).astype('float32') / 255
x_test = x_test.reshape(10000, 784).astype('float32') / 255

y_train = y_train.astype('float32')
y_test = y_test.astype('float32')

# 劃分出10000個樣本用作驗證集
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]

3.編譯模型

model.compile(optimizer=keras.optimizers.RMSprop(),  # 指定優化器(Optimizer)配置
              # 指定要最小化的損失函數,這裏使用多分類交叉熵損失函數
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              # 列出要監控的指標,這裏監控多分類準確率
              metrics=['sparse_categorical_accuracy'])

4.訓練模型

print('# Fit model on training data')
history = model.fit(x_train, y_train, # 指定訓練集數據
                    batch_size=64, # 批處理大小
                    epochs=3, # 訓練循環
                    # W指定驗證集數據
                    validation_data=(x_val, y_val))

print('\nhistory dict:', history.history)

輸出:

# Fit model on training data
Train on 50000 samples, validate on 10000 samples
Epoch 1/3
50000/50000 [==============================] - 6s 114us/sample - loss: 0.3382 - sparse_categorical_accuracy: 0.9031 - val_loss: 0.1844 - val_sparse_categorical_accuracy: 0.9453
Epoch 2/3
50000/50000 [==============================] - 4s 71us/sample - loss: 0.1568 - sparse_categorical_accuracy: 0.9540 - val_loss: 0.1264 - val_sparse_categorical_accuracy: 0.9621
Epoch 3/3
50000/50000 [==============================] - 4s 70us/sample - loss: 0.1135 - sparse_categorical_accuracy: 0.9651 - val_loss: 0.1137 - val_sparse_categorical_accuracy: 0.9664

history dict: {'loss': [0.3381690269327164, 0.1568263268327713, 0.11351015178918838], 'sparse_categorical_accuracy': [0.90312, 0.95404, 0.9651], 'val_loss': [0.18442059101760389, 0.1264222780354321, 0.11371438533701003], 'val_sparse_categorical_accuracy': [0.9453, 0.9621, 0.9664]}

5.預測和評估

# 評估模型
print('\n# Evaluate on test data')
results = model.evaluate(x_test, y_test, batch_size=128)
print('test loss:{}, test acc:{}'.format(results[0],results[1]))

# 使用模型預測新數據(最後一層的輸出概率)
print('\n# Generate predictions for 3 samples')
predictions = model.predict(x_test[:3])
print('predictions shape:', predictions.shape)

輸出:

# Evaluate on test data
10000/10000 [==============================] - 0s 24us/sample - loss: 0.1101 - sparse_categorical_accuracy: 0.9664
test loss:0.11006137486696244, test acc:0.9664000272750854

# Generate predictions for 3 samples
predictions shape: (3, 10)

1.2 optimizer,loss,metrics配置

方式1:

model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=[keras.metrics.sparse_categorical_accuracy])

方式2:

model.compile(optimizer='rmsprop',
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['sparse_categorical_accuracy'])

爲了以後重用,將模型定義和包含編譯的模型封裝。下文的不同示例中將多次調用它們。

def get_uncompiled_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)
    return model

def get_compiled_model():
    model = get_uncompiled_model()
    model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
                loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                metrics=['sparse_categorical_accuracy'])
    return model

1.2.1 keras內置優化器(optimizer)

常用的優化器:

  • SGD()
  • RMSprop()
  • Adagrad()
  • Adadelta()
  • Adam()
  • Adamax()
  • Nadam()

👉keras 中文文檔:點擊此處
👉tf.keras 官方文檔:點擊此處


1.2.2 keras內置損失函數(loss)

  • MeanAbsoluteError()
  • MeanAbsolutePercentageError()
  • MeanSquaredError()
  • MeanSquaredLogarithmicError()
  • Poisson()
  • Reduction()
  • SparseCategoricalCrossentropy()
  • SquaredHinge()

👉 官方文檔:點擊此處


1.2.3 keras內置監視指標(metrics)

  • AUC()
  • Precision()
  • Recall()

👉官方文檔:點擊此處


1.2.4 自定義損失

Keras提供兩種方式來提供自定義損失。

1.一般方法

以下示例顯示了損失函數定義方法,計算實際數據(y_true)和預測(y_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)

model.fit(x_train, y_train, batch_size=64, epochs=3)

2.子類方法

可以對 tf.keras.losses.Loss 類進行子類化,並實現以下兩種方法:

  • __init__(self):接收在損失函數調用期間傳遞的參數;
  • call(self, y_true, y_pred):使用目標(y_true)和模型預測(y_pred)計算模型的損失
    在計算損失時 call() 可以使用 __init__() 傳入的參數。

以下示例顯示瞭如何實現WeightedCrossEntropy計算BinaryCrossEntropy損失的損失函數,其中,某個類或整個函數的損失可以通過標量進行修改。

class WeightedBinaryCrossEntropy(keras.losses.Loss):
    '''
    參數說明:
        pos_weight:影響損失函數的正標籤的標量。
        weight:影響損失函數的標量。 
        from_logits:是根據對數還是概率來計算損失。
        reduction:tf.keras.losses.reduction,指定損失的計算方式。
        name:損失函數的名稱。
    
    '''
    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(
            # [:,None]切片中,None表示該維不進行切片,而是將該維整體作爲數組元素處理;
            # 所以[:,None]就是將二維數組按每行分割,最後形成一個三維數組。
            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

這是一個二分類損失,但是數據集有10個類別,因此應用該損失就像模型爲每個類別進行單個二分類預測一樣。爲此,首先從類索引創建one-hot編碼向量:

one_hot_y_train = tf.one_hot(y_train.astype(np.int32), depth=10)

編碼後:

<tf.Tensor: shape=(50000, 10), dtype=float32, numpy=
array([[0., 0., 0., ..., 0., 0., 0.],
       [1., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 1., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 1., 0.]], dtype=float32)>

使用自定義的損失函數訓練:

model = get_uncompiled_model()

model.compile(
    optimizer=keras.optimizers.Adam(),
    loss=WeightedBinaryCrossEntropy(
        pos_weight=0.5, weight = 2, from_logits=True)
)

model.fit(x_train, one_hot_y_train, batch_size=64, epochs=5)

1.2.5 自定義指標

如果需要監視的指標不是API的一部分,則可以通過將 Metric 類子類化來創建自定義指標。需要定義子類中的4個方法:

  • __init__(self):爲指標創建狀態變量。
  • update_state(self, y_true, y_pred, sample_weight=None);使用期望輸出y_true和模型預測y_pred來更新狀態變量。
  • result(self);使用狀態變量來計算最終結果。
  • reset_states(self):重置指標狀態。

狀態更新和結果計算分別在 update_state()result() 中實現,因爲在某些情況下,計算很耗費資源,並且只能定期執行。

下例展示瞭如何使用 CategoricalTruePositives 度量標準,該度量標準統計了正確歸類爲給定類的樣本數量:

class CategoricalTruePositives(keras.metrics.Metric):
    
    def __init__(self, name='categorical_true_positives', **kwargs):
        super(CategoricalTruePositives, self).__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):
        # 指標狀態在每個epoch開始時重置。
        self.true_positives.assign(0.) # assign()函數可用於對變量進行更新,包括變量的value和shape

使用自定義的指標進行訓練:

model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=[CategoricalTruePositives()])
model.fit(x_train, y_train,
          batch_size=64,
          epochs=3)

1.2.6 非標準損失和指標處理

絕大多數損失和指標可以通過y_true和計算y_pred,其中y_pred是模型的輸出。但並非所有都可以通過這兩個參數計算。例如,正則化損失可能僅需要激活層(在這種情況下沒有target),並且此激活可能不是模型輸出。

在這種情況下,可以從自定義圖層的調用方法內部調用 self.add_loss(loss_value)。下例是一個添加活動正則化的簡單示例:

class ActivityRegularizationLayer(layers.Layer):

    def call(self, inputs):
        self.add_loss(tf.reduce_sum(inputs) * 0.1)
        return inputs  # Pass-through layer.

inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)

# 將自定義的活動正則化作爲層傳入
x = ActivityRegularizationLayer()(x)

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(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True))

# 由於正則化組件,顯示的損失將比以前高得多。
model.fit(x_train, y_train,
          batch_size=64,
          epochs=1)

輸出:

Train on 50000 samples
50000/50000 [==============================] - 3s 69us/sample - loss: 2.4960
<tensorflow.python.keras.callbacks.History at 0x16d27581c08>

可以對記錄指標值執行相同的操作:

class MetricLoggingLayer(layers.Layer):

    def call(self, inputs):
        # aggregation參數定義了在每個epoch 彙總每個batch的方式:此處使用平均。
        self.add_metric(keras.backend.std(inputs),
                        name='std_of_activation',
                        aggregation='mean')
        return inputs  # Pass-through layer.


inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)

# 將自定義的記錄指標作爲層傳入
x = MetricLoggingLayer()(x)

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(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True))
model.fit(x_train, y_train,
          batch_size=64,
          epochs=1)

輸出:

Train on 50000 samples
50000/50000 [==============================] - 4s 82us/sample - loss: 0.3346 - std_of_activation: 0.97040s - loss: 0.3345 - std_of_activation: 0.970
<tensorflow.python.keras.callbacks.History at 0x16ce206c708>

在 Functional API 中,可以調用 model.add_loss(loss_tensor)model.add_metric(metric_tensor, name, aggregation) 實現同樣的功能。下例是一個簡單的示例:

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)

輸出:

Train on 50000 samples
50000/50000 [==============================] - 4s 78us/sample - loss: 2.4774 - std_of_activation: 0.0019
<tensorflow.python.keras.callbacks.History at 0x16d278de2c8>

1.2.7 設置自動劃分驗證集參數

以上的示例中,是使用手動劃分的方式劃分驗證集,其實可以使用 model.fitvalidation_split= 參數,實現自動的劃分驗證集:

model = get_compiled_model()
model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1, steps_per_epoch=1)

輸出:

Train on 40000 samples, validate on 10000 samples
   64/40000 [..............................] - ETA: 10:39 - loss: 2.3797 - sparse_categorical_accuracy: 0.1250 - val_loss: 2.2091 - val_sparse_categorical_accuracy: 0.2615
<tensorflow.python.keras.callbacks.History at 0x16d29d51588>

1.3 使用 tf.data.Dataset 格式數據訓練

如果只想對該數據集中的特定批次進行訓練,則可以傳遞 steps_per_epoch 參數,該參數指定一個epoch訓練多少個batch的數據。執行此操作,不會在每個時期結束時重置數據集,而是繼續使用下一個batch的數據,知道用完數據。

model = get_compiled_model()

# 準備數據集
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64).repeat()

# 每個epoch使用100個批次的數據 (共使用 64 * 100 個樣本)
model.fit(train_dataset, steps_per_epoch=100, epochs=3)

輸出:

Train for 100 steps
Epoch 1/3
100/100 [==============================] - 1s 11ms/step - loss: 0.7605 - sparse_categorical_accuracy: 0.8091
Epoch 2/3
100/100 [==============================] - 0s 4ms/step - loss: 0.3646 - sparse_categorical_accuracy: 0.8989
Epoch 3/3
100/100 [==============================] - 0s 4ms/step - loss: 0.3146 - sparse_categorical_accuracy: 0.9067
<tensorflow.python.keras.callbacks.History at 0x16d2a1d3a88>

設置驗證集

設置 validation_data 參數傳遞給 fit()

model = get_compiled_model()

# 準備訓練數據
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)) # x_train 包含 50000個樣本
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# 準備驗證數據
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val)) # x_val 包含 10000個樣本
val_dataset = val_dataset.batch(64)

model.fit(train_dataset, epochs=3, validation_data=val_dataset)

輸出:

Train for 782 steps, validate for 157 steps
Epoch 1/3
782/782 [==============================] - 5s 6ms/step - loss: 0.3333 - sparse_categorical_accuracy: 0.9059 - val_loss: 0.1749 - val_sparse_categorical_accuracy: 0.9514
Epoch 2/3
782/782 [==============================] - 4s 5ms/step - loss: 0.1526 - sparse_categorical_accuracy: 0.9545 - val_loss: 0.1471 - val_sparse_categorical_accuracy: 0.9579
Epoch 3/3
782/782 [==============================] - 4s 5ms/step - loss: 0.1117 - sparse_categorical_accuracy: 0.9665 - val_loss: 0.1179 - val_sparse_categorical_accuracy: 0.9669
<tensorflow.python.keras.callbacks.History at 0x16d2b5ab908>

注意,驗證數據集在每次使用後重置。從Dataset對象進行訓練時,不支持參數 validation_split(從訓練數據生成保留集),因爲此功能需要索引數據集樣本的能力,而這通常是 Dataset API 無法實現的。


1.4 賦予樣本或分類不同的權重 👀

除了輸入數據和目標數據外,還可以在使用時將樣本權重或類權重傳遞給模型fit:

  • 數據是numpy格式:通過設置 sample_weightclass_weight 參數來分配權重。
  • 數據是tf.data.Dataset格式:將 Dataset 返回一個元組(input_batch, target_batch, sample_weight_batch)。

“樣本權重”數組是一個數字數組,用於指定批次中每個樣本在計算總損失時應具有的權重。它通常用於不平衡的分類問題中(這種想法是爲很少見的分類賦予更多的權重)。當所使用的權重爲1和0時,該數組可用作損失函數的掩碼(mask),即完全丟棄某些樣本對總損失的貢獻。

“類別權重”字典是同一概念的一個更具體的實例:它將類別索引映射到應該用於屬於該類別的樣本的樣本權重。例如,如果在數據中類“ 0”的表示量比類“ 1”的表示量少兩倍,則可以使用 class_weight={0: 1., 1: 0.5}

下例是一個Numpy示例,其中使用類權重或樣本權重來更加重視類別5(在MNIST數據集中的數字“ 5”)的正確分類。


1.4.1 設置 model.fit 參數實現

設置分類權重:

import numpy as np

class_weight = {0: 1., 1: 1., 2: 1., 3: 1., 4: 1.,
                # 設置類別 "5" 的權重爲 "2",表示更關注這個類別
                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)

設置樣本權重:

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.
print('\nFit with sample weight')

model = get_compiled_model()
model.fit(x_train, y_train,
          sample_weight=sample_weight,
          batch_size=64,
          epochs=4)

1.4.2 tf.data.Dataset 實現

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.

train_dataset = tf.data.Dataset.from_tensor_slices(
    (x_train, y_train, sample_weight))

train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model = get_compiled_model()
model.fit(train_dataset, epochs=3)

1.5 多輸入多輸出模型 傳遞數據方法 👀

在前面的示例中,考慮的都是單個輸入(shape的張量(764,))和單個輸出(shape的預測張量(10,))的模型。但是多個輸入或輸出的模型的數據應該怎樣傳遞呢?

考慮以下模型,該模型具有形狀爲 (32, 32, 3) (即(height, width, channels))的圖像輸入和形狀爲 (None, 10)(即(timesteps, features))的時間序列輸入。我們的模型將具有根據這些輸入的組合計算出的兩個輸出:“得分”(形狀(1,))和五類(形狀(5,))的概率分佈。

1.5.1 定義模型

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])

繪製這個模型(注意:圖中顯示的形狀是批處理形狀,而不是樣本的形狀)。

keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True, dpi=150)

輸出:
在這裏插入圖片描述


1.5.2 定義不同的損失函數

在編譯模型時,可以將損失函數作爲列表傳遞給loss參數,爲不同的輸出指定不同的損失:

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

如果僅將單個損失函數傳遞給模型,則每個輸出將使用相同的損失函數,此處不合適。


1.5.3 定義不同的指標

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()]])

1.5.4 字典方式傳參

由於爲輸出層命名,因此還可以通過dict指定每個輸出的損失和指標:

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()]})

如果有兩個以上的輸出,tensorflow官方文檔建議使用顯式名稱和字典傳遞的方式。


可以使用 loss_weights 參數對不同輸出的損失賦予不同的權重:

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()]},
    loss_weights={'score_output': 2., 'class_output': 1.})

如果這些輸出僅用於預測而不是訓練,可以選擇不爲某些輸出計算損失:

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

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={'class_output':keras.losses.CategoricalCrossentropy(from_logits=True)})

三種方式爲多輸入、多輸出模型指定數據、損失函數:

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

# 構造虛擬數據
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))

# 列表方式
model.fit([img_data, ts_data], [score_targets, class_targets],
          batch_size=32,
          epochs=3)

# 字典方式
model.fit({'img_input': img_data, 'ts_input': ts_data},
          {'score_output': score_targets, 'class_output': class_targets},
          batch_size=32,
          epochs=3)

# 字典元組方式
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)

輸出:

Train on 100 samples
Epoch 1/3
100/100 [==============================] - 6s 59ms/sample - loss: 7.9416 - score_output_loss: 2.8117 - class_output_loss: 4.9897
Epoch 2/3
100/100 [==============================] - 0s 375us/sample - loss: 6.4782 - score_output_loss: 1.3994 - class_output_loss: 5.0691
Epoch 3/3
100/100 [==============================] - 0s 363us/sample - loss: 5.8427 - score_output_loss: 1.2465 - class_output_loss: 4.6070
Train on 100 samples
Epoch 1/3
100/100 [==============================] - 0s 374us/sample - loss: 5.4004 - score_output_loss: 0.6153 - class_output_loss: 4.6934
Epoch 2/3
100/100 [==============================] - 0s 382us/sample - loss: 5.1590 - score_output_loss: 0.4449 - class_output_loss: 4.8196
Epoch 3/3
100/100 [==============================] - 0s 330us/sample - loss: 5.0042 - score_output_loss: 0.3273 - class_output_loss: 4.5378

Train for 2 steps
Epoch 1/3
2/2 [==============================] - 1s 452ms/step - loss: 4.9104 - score_output_loss: 0.1947 - class_output_loss: 4.7098
Epoch 2/3
2/2 [==============================] - 0s 16ms/step - loss: 4.8905 - score_output_loss: 0.1724 - class_output_loss: 4.7156
Epoch 3/3
2/2 [==============================] - 0s 18ms/step - loss: 4.8706 - score_output_loss: 0.1627 - class_output_loss: 4.7040

1.6 使用回調(callback)👀

常用的內置回調有:

  • ModelCheckpoint:定期保存模型。
  • EarlyStopping:當訓練過程中驗證指標不再變化時,停止訓練。
  • TensorBoard:將訓練日誌寫入到TensorBoard,實現可視化(下文會介紹)。
  • CSVLogger:將損失和指標數據流式傳輸到CSV文件。

1.6.1 編寫自定義回調類

可以通過擴展基類 keras.callbacks.Callback 來創建自定義回調。回調可以通過class屬性訪問其關聯的模型self.model

這是一個簡單的示例,在訓練過程中保存了每批次損失值的列表:

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'))

1.6.2 保存檢查點文件

model = get_compiled_model()

callbacks = [
    keras.callbacks.ModelCheckpoint(
        filepath='mymodel_{epoch}', # 檢查點文件保存路徑
        save_best_only=True, # monitor設置的監視指標變化時,保存檢查點文件並覆蓋已保存的檢查點文件
        monitor='val_loss',
        verbose=1)
]

model.fit(x_train, y_train,
          epochs=3,
          batch_size=64,
          callbacks=callbacks,
          validation_split=0.2)

1.7 設置優化器學習率衰減規則參數(lr_shcedule)

訓練深度學習模型的常見模式是隨着訓練的進行逐漸減少學習。這通常稱爲 “學習率衰減(learning rate decay)”

學習衰減進度表可以是靜態的(根據當前epoch或當前批次索引預先確定),也可以是動態的(響應於模型的當前指標,尤其是驗證損失)。


1.7.2 設置靜態學習率衰減

設置optimizer中的 learning_rate 參數,使用靜態學習率衰減:

initial_learning_rate = 0.1
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate,
    decay_steps=100000,
    decay_rate=0.96,
    staircase=True)

optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)

內置多種規則可供選擇:

  • ExponentialDecay
  • PiecewiseConstantDecay
  • PolynomialDecay
  • InverseTimeDecay

1.7.3 設置動態學習率衰減

由於優化器無法訪問驗證指標,因此無法使用這些schedule對象實現動態學習速率調度(例如,在驗證損失不再改善時降低學習速率)。但是,回調(callback)可以訪問所有指標,包括驗證指標!因此,可以通過使用回調來修改優化器上的當前學習率來實現這種模式。實際上,它甚至內置在 ReduceLROnPlateau 回調函數中。


1.8 TensorBoard 可視化訓練損失和指標

tensorboard --logdir=/full_path_to_your_logs

1.8.1 設置回調

將TensorBoard與Keras模型一起使用的最簡單fit方法是TensorBoard回調。

在最簡單的情況下,只需指定回調日誌寫入的位置即可:

tensorboard_cbk = keras.callbacks.TensorBoard(log_dir='/full_path_to_your_logs')
model.fit(dataset, epochs=10, callbacks=[tensorboard_cbk])

2. 從頭開始編寫訓練和評估循環👀

tf.keras 中的model.fit和model.evaluate是封裝好的訓練評估循環,如果需要編寫更低級別的訓練評估循環,可以自定義訓練和評估循環。

2.1 使用GradientTape編寫端到端示例

在GradientTape範圍內調用一個模型使您能夠檢索與損失值相關的層的可訓練權值的梯度。使用優化器實例,可以使用這些梯度來更新這些變量(可以使用 model.trainable_weights 檢索這些變量)。

定義模型:

# 定義模型
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)

# 實例化優化器
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# 實例化損失函數
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# 準備訓練數據
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)

自定義訓練循環:

epochs = 3

for epoch in range(epochs):
    print('Start of epoch %d' % (epoch,))
    # 在數據集上以批處理大小迭代
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

        # 打開GradientTape來記錄前向傳遞過程中運行的操作,這支持自動微分。
        with tf.GradientTape() as tape:

            # 通過該層前向傳遞。圖層應用於其輸入的操作將記錄在GradientTape上。
            logits = model(x_batch_train, training=True)  # 小批量的Logit

            # 計算小批量數據的損失
            loss_value = loss_fn(y_batch_train, logits)

        # 使用梯度自動獲取可訓練變量的loss梯度。
        grads = tape.gradient(loss_value, model.trainable_weights)

        # 通過更新變量的值來最大程度地減少損失,從而執行梯度下降
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # 每200個batch打印一次日誌
        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))

輸出:

Start of epoch 0
Training loss (for one batch) at step 0: 2.34749174118042
Seen so far: 64 samples
Training loss (for one batch) at step 200: 2.2381997108459473
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 2.14272403717041
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 2.0917067527770996
Seen so far: 38464 samples
Start of epoch 1
Training loss (for one batch) at step 0: 2.0158851146698
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.8915430307388306
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.8089723587036133
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 1.6553739309310913
Seen so far: 38464 samples
Start of epoch 2
Training loss (for one batch) at step 0: 1.618318796157837
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.6097385883331299
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.3778656721115112
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 1.329228162765503
Seen so far: 38464 samples

2.1 自定義指標

可以在從頭開始編寫的訓練循環中隨時使用內置指標(或您編寫的自定義指標)。流程如下:

  • 在循環開始時實例化指標
  • metric.update_state() 需要更新狀態時調用(通常在每個批次之後)
  • metric.result() 需要顯示指標的當前值時調用
  • metric.reset_states() 需要清除指標狀態時調用(通常在紀元末尾)

實例:

# 定義模型
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)

# 實例化優化器
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# 實例化損失函數
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# 準備訓練數據
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)

# 設置指標
train_acc_metric = keras.metrics.SparseCategoricalAccuracy()
val_acc_metric = keras.metrics.SparseCategoricalAccuracy()

# 準備驗證數據
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,))
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            logits = model(x_batch_train, training=True)  # 小批量的Logit
            loss_value = loss_fn(y_batch_train, logits)

        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))
        # 更新訓練指標
        train_acc_metric(y_batch_train, logits)

        # 每200個batch打印一次日誌
        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))
            
    # 每個epoch之後打印指標
    train_acc = train_acc_metric.result()
    print('Training acc over epoch: %s' % (float(train_acc),))
    # 每個epoch之後重置指標
    train_acc_metric.reset_states()

    # 在每個epoch結束時運行一個驗證循環
    for x_batch_val, y_batch_val in val_dataset:
        val_logits = model(x_batch_val)
        # 通過指標更新
        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),))

2.2 自定義損失

回顧上節示例,包含正則化損失的層:

class ActivityRegularizationLayer(layers.Layer):

    def call(self, inputs):
        self.add_loss(1e-2 * tf.reduce_sum(inputs))
        return inputs

inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs)

調用模型:

logits = model(x_train)

它在前向傳遞過程中產生的損失將添加到model.losses屬性中:

logits = model(x_train[:64])
print(model.losses)

輸出:

[<tf.Tensor: shape=(), dtype=float32, numpy=6.426128>]

要在訓練過程中考慮這些損失,需要修改訓練循環以增加sum(model.losses)總損失:

epochs = 3
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
for epoch in range(epochs):
    print('Start of epoch %d' % (epoch,))
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

        with tf.GradientTape() as tape:
            logits = model(x_batch_train, training=True)  # 小批量的Logit
            loss_value = loss_fn(y_batch_train, logits)
            
            # 在前向傳播過程中添加額外的損失
            loss_value += sum(model.losses)
            
        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # 每200個batch打印一次日誌
        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))

輸出:

Start of epoch 0
Training loss (for one batch) at step 0: 8.735221862792969
Seen so far: 64 samples
Training loss (for one batch) at step 200: 2.5059332847595215
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 2.413942337036133
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 2.3521640300750732
Seen so far: 38464 samples
Start of epoch 1
Training loss (for one batch) at step 0: 2.3404712677001953
Seen so far: 64 samples
Training loss (for one batch) at step 200: 2.3348522186279297
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 2.3144493103027344
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 2.3205440044403076
Seen so far: 38464 samples
Start of epoch 2
Training loss (for one batch) at step 0: 2.318399429321289
Seen so far: 64 samples
Training loss (for one batch) at step 200: 2.316324472427368
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 2.3073883056640625
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 2.304961681365967
Seen so far: 38464 samples

參考:https://www.tensorflow.org/guide/keras/train_and_evaluate#specifying_a_loss_metrics_and_an_optimizer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章