本文是 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数组,以演示如何使用 optimizers
,losses
以及 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.fit
的 validation_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_weight
和class_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