本笔记参照TensorFlow官方教程,主要是对‘Keras-train and evaluate’(以下简称KFA)教程内容翻译和内容结构编排,原文链接:Keras-train and evaluate
目录
设置(Setup)
第一部分:使用内置的training&evaluation循环
1.1 API概率:第一个端到端例子
1.2指定代价、指标和优化器
1.2.1 许多内置优化器(optimizers)、代价(losses)、指标(metrics)
1.2.2定制代价(losses)
1.2.3定制指标(metrics)
1.2.4处理不符合标准签名的损失和度量
1.2.5自动设置验证保留集
1.3使用tf.data Datasets进行Training&evaluation
使用一个验证数据集
1.4其它支持的输入格式
1.5使用样本加权和类加权
1.6传递数据给多输入多输出模型
1.7使用回调函数(callbacks)
Keras里有许多内置的回调函数
1.8训练期间代价和指标的可视化
第二部分:从头开始写自己的training&evaluation循环
2.1使用GradientTape:第一个端到端例子
2.2训练指标的低级处理
2.3对额外损失的低层次处理
设置(Setup)
from __future__ import absolute_import, division, print_function, unicode_literals
try:
%tensorflow_version 2.x
except Exception:
pass
import tensorflow as tf
tf.keras.backend.clear_session() # For easy reset of notebook state.
第一部分:使用内置的training&evaluation循环
当传递数据给模型的内置训练循环时,我们应该使用Numpy数组(如果数据很小并且适合内存)或tf.data数据集对象。在接下来的几个段落中,我们将使用MINIST数据集作为Numpy数组,以演示如何使用优化器、代价和指标。
1.1 API概率:第一个端到端示例
让我们看下下面的模型(这是使用Funcitonal 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, activation='softmax', name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)
以下是典型的端到端工作流,包括训练、对原始训练数据生成的holdout集进行验证,最后是对测试数据的评估:
# Load a toy dataset for the sake of this example
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# Preprocess the data (these are Numpy arrays)
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')
# 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]
# Specify the training configuration (optimizer, loss, metrics)
model.compile(optimizer=keras.optimizers.RMSprop(), # Optimizer
# Loss function to minimize
loss=keras.losses.SparseCategoricalCrossentropy(),
# List of metrics to monitor
metrics=[keras.metrics.SparseCategoricalAccuracy()])
# Train the model by slicing the data into "batches"
# of size "batch_size", and repeatedly iterating over
# the entire dataset for a given number of "epochs"
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))
# The returned "history" object holds a record
# of the loss values and metric values during training
print('\nhistory dict:', history.history)
# Evaluate the model on the test data using `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)
# on new data using `predict`
print('\n# Generate predictions for 3 samples')
predictions = model.predict(x_test[:3])
print('predictions shape:', predictions.shape)
1.2指定代价、指标和优化器
要使用fit训练模型,您需要指定一个损失函数、一个优化器,以及一些可选的要监控的指标。将这些作为参数传递给compile()方法:
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss=keras.losses.SparseCategoricalCrossentropy(),
metrics=[keras.metrics.SparseCategoricalAccuracy()])
‘度量’(metrics)参数应该是一个列表—我们的模型可以有任意数量的度量。如果模型有多个输出,我们可以为每个输出指定不同的代价和度量,并且可以调节每个输出对模型总代价的贡献。注意:在许多情况下,代价(loss)和指标(metric)是通过字符串标识符指定的,作为一种快捷方式:
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss='sparse_categorical_crossentropy',
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, activation='softmax', 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='sparse_categorical_crossentropy',
metrics=['sparse_categorical_accuracy'])
return model
1.2.1Keras内部有很多优化器、代价、指标
总体来说,我们不需要从头开始写自己的代价、指标、优化器,因为我们所需的Keras API已经提供了:
优化器(Optimizers):SGD\RMSprop()\Adam()等等
代价(Losses):MeanSquareError()\KLDivergence()\CosineSimilarity()等等
指标(Metrics):AUC()\Precision()\Recall()等等
1.2.2定制代价
在Keras里,有两种方法来定制代价函数。第一个示例创建一个接受输入y_true和y_pred的函数。下面的例子显示了一个损失函数,它计算真实数据和预测之间的平均距离:
def basic_loss_function(y_true, y_pred):
return tf.math.reduce_mean(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)
如果您需要一个接受y_true和y_pred之外的参数的loss函数,您可以子类化tf.keras.losses。并实现了以下两种方法:
init(self)–在调用丢失函数期间,接受要传递的参数
call(self, y_true, y_pred)——使用目标(y_true)和模型预测(y_pred)来计算模型的损失
当计算代价时,传递给init__()的参数可以在调用()期间使用。下面的示例演示如何实现一个WeightedCrossEntropy代价函数,该函数计算一个BinaryCrossEntropy损失,其中某个类或整个函数的代价可以通过一个标量进行修改。
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 form 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(WeightedBinaryCrossEntropy, self).__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):
if not self.from_logits:
# Manually calculate the weighted cross entropy.
# Formula is qz * -log(sigmoid(x)) + (1 - z) * -log(1 - sigmoid(x))
# where z are labels, x is logits, and q is the weight.
# Since the values passed are from sigmoid (assuming in this case)
# sigmoid(x) will be replaced by y_pred
# qz * -log(sigmoid(x)) 1e-6 is added as an epsilon to stop passing a zero into the log
x_1 = y_true * self.pos_weight * -tf.math.log(y_pred + 1e-6)
# (1 - z) * -log(1 - sigmoid(x)). Epsilon is added to prevent passing a zero into the log
x_2 = (1 - y_true) * -tf.math.log(1 - y_pred + 1e-6)
return tf.add(x_1, x_2) * self.weight
# Use built in function
return tf.nn.weighted_cross_entropy_with_logits(y_true, y_pred, self.pos_weight) * self.weight
model.compile(optimizer=keras.optimizers.Adam(),
loss=WeightedBinaryCrossEntropy(0.5, 2))
model.fit(x_train, y_train, batch_size=64, epochs=3)
1.2.3定制指标
如果你需要的指标不在Keras API里,你可以很简单地子类化‘Metric’类来定制你需要的指标。你将需要执行4种方法:
(1)init(self),创建指标的状态变量
(2)update_state(self, y_true, y_pred, sample_weight=None),使用目标‘y_true’和模型预测值‘y_pred’来更新状态变量
(3)result(self),使用状态变量来计算最终的结果
(4)reset_states(self),重新初始化指标状态
状态更新和结果计算是分开的(分别在update_state()和result()中),因为在某些情况下,结果计算可能非常昂贵,并且只能定期执行。
下面是一个简单的例子,展示了如何实现CatgoricalTruePositives指标,计数有多少样本被正确分类为属于一个给定的类:
class CatgoricalTruePositives(keras.metrics.Metric):
def __init__(self, name='categorical_true_positives', **kwargs):
super(CatgoricalTruePositives, 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):
# 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(),
metrics=[CatgoricalTruePositives()])
model.fit(x_train, y_train,
batch_size=64,
epochs=3)
1.2.4处理不符合标准签名的损失和度量
绝大多数的损失和度量可以从y_true和y_pred计算出来,y_pred是模型的输出。但不是全部。例如,正则化损失可能只需要激活一个层(在本例中没有目标),并且这个激活可能不是一个模型输出。
在这种情况下,可以从自定义层的调用方法中调用self.add_loss(loss_value)。这里有一个简单的例子,添加了活动正则化(activity regularization)(注意,活动正则化是内置在所有的Keras层-这一层只是为了提供一个具体的例子):
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)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss='sparse_categorical_crossentropy')
# The displayed loss will be much higher than before
# due to the regularization component.
model.fit(x_train, y_train,
batch_size=64,
epochs=1)
我们也可以做些日志指标值(logging metric values):
class MetricLoggingLayer(layers.Layer):
def call(self, inputs):
# The `aggregation` argument defines
# how to aggregate the per-batch values
# over each epoch:
# in this case we simply average them.
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)
# Insert std logging as a layer.
x = MetricLoggingLayer()(x)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss='sparse_categorical_crossentropy')
model.fit(x_train, y_train,
batch_size=64,
epochs=1)
在KFA中,我们还可以调用‘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, activation='softmax', 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='sparse_categorical_crossentropy')
model.fit(x_train, y_train,
batch_size=64,
epochs=1)
1.2.5自动设置验证保留集
在第一个端到端示例中,我们使用validation_data参数将一组Numpy数组(x_val,y_val)传递给模型,用于在每个epoch结束时评估验证损失和验证指标
这里有另一个选项:参数validation_split允许您为验证自动保留部分训练数据。参数值表示保留用于验证的数据的比例,因此应该将其设置为大于0且小于1的数字。例如,validation_split=0.2表示“使用20%的数据进行验证”,validation_split=0.6表示“使用60%的数据进行验证”。
计算‘验证’的方法是在进行任何变换之前,从fit调用接收的数组中提取最后的x%样本。
在使用Numpy数据进行训练时,只能使用validation_split。
model = get_compiled_model()
model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=3)
1.3使用tf.data Datasets进行Training&evaluation
在前几段中,我们了解了如何处理代价、指标和优化,以及如何在以Numpy数组的形式传递数据时,在fit中使用validation_data和validation_split参数。
下面我们来看看使用tf.data.Dataset的例子
tf.data API是TensorFlow2.0中的一组实用程序,用于以快速和可伸缩的方式加载和预处理数据。完整的指南可以参考‘the tf.data documentation’
我们可以将数据集实例直接传递给方法fit()、evaluate()和predict():
model = get_compiled_model()
# First, let's create a training Dataset instance.
# For the sake of our example, we'll use the same MNIST data as before.
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)
# 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)
# You can also evaluate or predict on a dataset.
print('\n# Evaluate')
model.evaluate(test_dataset)
注意:Dataset在每个纪元的结尾被重启过,所以可以在下一个纪元重新使用。
如果希望仅在此数据集的特定批次上运行训练,则可以传递steps_per_epoch参数,该参数指定在转移到下一个epoch之前,模型应该使用此数据集运行多少训练步骤。如果这样做,数据集不会在每个epoch结束时重置,而是继续绘制下一批数据。数据集最终将耗尽数据(除非它是一个无限循环的数据集)。
model = get_compiled_model()
# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
# Only use the 100 batches per epoch (that's 64 * 100 samples)
model.fit(train_dataset.take(100), epochs=3)
1.3.1使用验证数据集
我们可以在fit中传递一个数据集实例作为validation_data参数:
model = get_compiled_model()
# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
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)
model.fit(train_dataset, epochs=3, validation_data=val_dataset)
在每个纪元(epoch)结束时,模型将遍历验证数据集并计算验证损失和验证指标。如果我们只想在这个数据集上特定批次上运行验证,我们可以传递validation_steps参数,该参数指定在中断验证并进入下一个纪元(epoch)之前,模型应该在验证数据集上运行多少验证步骤:
model = get_compiled_model()
# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
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)
model.fit(train_dataset, epochs=3,
# Only run validation using the first 10 batches of the dataset
# using the `validation_steps` argument
validation_data=val_dataset, validation_steps=10)
注意:验证数据集每次使用后都会重启(那样我们可以在每个纪元间使用相同的样本来评估)
当从Dataset对象进行训练时,不支持参数validation_split(从培训数据生成一个holdout集),因为该特性要求能够索引数据集的样本,而通常情况下,使用Dataset API是不可能做到这一点的。
其他支持的输入格式:除了Numpy数组和TensorFlow数据集之外,还可以使用panda数据流或Python生成器来训练Keras模型,生成批量数据。一般情况下,如果您的数据很小并且适合存储,我们建议您使用Numpy输入数据,否则建议使用Dataset数据集。
1.4使用样本权重和类权重
除了输入数据和目标数据外,使用‘fit’时还可以将样本权值或类权值传递给模型:
(1)当通过Numpy data训练时:通过‘sample_weight’和‘class_weight’参数。
(2)当通过Dataset训练时:通过使用Dataset来返回元组(input_batch,target_batch,sample_weight_batch)。
“样本权重”数组是一个数字数组,它指定在计算总损失时,批处理中的每个样本的权重应该是多少。它通常用于不平衡的分类问题(其思想是给予很少见到的类更多的权重)。当使用的权重为1和0时,该数组可以用作损失函数的掩码(完全丢弃某些样本对总损失的贡献)。
“类权重”dict是同一概念的一个更具体的实例:它将类索引映射到应该用于属于该类的样本的样本权重。例如,如果类“0”在数据中的表示量是类“1”的两倍,则可以使用class_weight={0: 1。1:0.5}。
下面是一个Numpy示例,我们使用类权值或样本权值来更加重视类#5的正确分类(即MNIST数据集中的数字“5”)。
import numpy as np
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 = get_compiled_model()
model.fit(x_train, y_train,
sample_weight=sample_weight,
batch_size=64,
epochs=4)
下面是一个匹配Dataset例子:
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 = get_compiled_model()
model.fit(train_dataset, epochs=3)
1.4传递数据给多输入,多输出模型
在前面的例子中,我们考虑了一个只有一个输入(一个形状(764,)的张量),和一个输出(一个形状(10,)的预测张量)的模型。但是如果模型有多个输入或输出呢?
考虑下面的模型,它有一个图形输入(32、32、3)(即(高度、宽度、通道))和一个图形输入timeseries(无、10)(即(时间步长、特性))。我们的模型将从这些输入的组合中计算出两个输出:一个“分数”(shape(1,))和一个涵盖5个种类的概率分布(shape(5,))。
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, activation='softmax', 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)
在编译时,我们可以为不同的输出指定不懂的代价,通过传递代价函数列表:
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[keras.losses.MeanSquaredError(),
keras.losses.CategoricalCrossentropy()])
如果我们只将一个损失函数传递给模型,那么同样的损失函数将应用于每个输出,这在这里并不合适。
对于‘指标’也是同样的做法:
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[keras.losses.MeanSquaredError(),
keras.losses.CategoricalCrossentropy()],
metrics=[[keras.metrics.MeanAbsolutePercentageError(),
keras.metrics.MeanAbsoluteError()],
[keras.metrics.CategoricalAccuracy()]])
由于我们给我们的输出层起了名字,我们还可以通过dict来指定没输出的代价和指标:
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={'score_output': keras.losses.MeanSquaredError(),
'class_output': keras.losses.CategoricalCrossentropy()},
metrics={'score_output': [keras.metrics.MeanAbsolutePercentageError(),
keras.metrics.MeanAbsoluteError()],
'class_output': [keras.metrics.CategoricalAccuracy()]})
如果您有两个以上的输出,我们建议使用显式名称和字典。可以使用loss_weight参数为不同的特定于输出的代价提供不同的权重(例如,在我们的示例中,通过将类代价的重要性增加2倍,可以为“score”代价提供特权):
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={'score_output': keras.losses.MeanSquaredError(),
'class_output': keras.losses.CategoricalCrossentropy()},
metrics={'score_output': [keras.metrics.MeanAbsolutePercentageError(),
keras.metrics.MeanAbsoluteError()],
'class_output': [keras.metrics.CategoricalAccuracy()]},
loss_weight={'score_output': 2., 'class_output': 1.})
你也可以选择对确定的输出不计算代价,如果这些输出意味着预测而不是训练:
# List loss version
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[None, keras.losses.CategoricalCrossentropy()])
# Or dict loss version
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={'class_output': keras.losses.CategoricalCrossentropy()})
传递数据到多输入/多输出模型适合在‘fit’步骤,和在‘comple’指定代价函数相似:我们可以传递Numpy数组列表(1:1映射到接收代价函数的输出)或字典映射输出名字到Numpy训练数据的数组。
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[keras.losses.MeanSquaredError(),
keras.losses.CategoricalCrossentropy()])
# 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([img_data, ts_data], [score_targets, class_targets],
batch_size=32,
epochs=3)
# Alernatively, fit on dicts
model.fit({'img_input': img_data, 'ts_input': ts_data},
{'score_output': score_targets, 'class_output': class_targets},
batch_size=32,
epochs=3)
下面使用‘Dataset’的用例,类似Numpy数组,Dataset需要返回一个元祖字典。
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.7使用回调
Keras中的回调是指在训练过程中的不同时间点(纪元开始时、批处理结束时、纪元结束时等)调用的对象,可用于实现以下行为:
(1)在训练期间的不同时间点进行验证(除了内置的per-epoch验证之外)
(2)定期给模型设置检查点或当它超过一定的准确性阈值
(3)当训练似乎趋于停滞时,改变模型的学习率
(4)当训练似乎趋于停滞时,对顶层进行微调
(5)发送邮件或及时消息通知当训练结束或者超过某个性能阈值时
等等
回调可以以列表的形式传递到‘’fit‘’
model = get_compiled_model()
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)
Keras里面有许多内置的回调:
(1)ModelCheckpoint:周期性保存模型。
(2)EarlyStopping:当训练不再提升验证指标时停止训练
(3)TensorBoard:周期性地写可以在TensorBoard上显示的模型日志
(4)CSVLogger:将代价和指标数据‘流’stream到CSV中
编写我们自己的回调:
我们可以通过扩展‘keras.callbacks.Callback’基类来定制一个回调。一个回调可以通过类属性‘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'))
设置模型检查点
当我们在相对较大的数据集上训练模型时,经常保存模型的检查点是非常重要的。最简单的实现方法是使用ModelCheckpoint回调:
model = get_compiled_model()
callbacks = [
keras.callbacks.ModelCheckpoint(
filepath='mymodel_{epoch}.h5',
# Path where to save the model
# The two parameters below mean that we will overwrite
# the current checkpoint if and only if
# the `val_loss` score has improved.
save_best_only=True,
monitor='val_loss',
verbose=1)
]
model.fit(x_train, y_train,
epochs=3,
batch_size=64,
callbacks=callbacks,
validation_split=0.2)
我们也可以使用定制的回调来保存和恢复模型
使用学习速率表
在训练深度学习模型时,一个常见的模式是随着训练的进展逐步减少学习。这通常被称为“学习速率衰减”。
学习速率衰减表可以是静态的(预先固定,作为当前epoch或当前批处理索引的函数),也可以是动态的(响应模型当前的行为,特别是验证损失)。
将学习速率衰减表传递给优化器:我们可以简单地在优化器里通过传递一个‘schedule object’作为‘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)
Keras里有几个内置的计划表(schedule): ExponentialDecay, PiecewiseConstantDecay, PolynomialDecay, and InverseTimeDecay.
使用回调来执行动态学习速率计划表:由于优化器无法访问验证指标,因此无法使用这些schedule对象实现动态学习速率调度(例如,在验证损失不再改善时降低学习速率)。但是,回调确实可以访问所有指标,包括验证指标!因此,可以通过使用一个回调来修改优化器上的当前学习率来实现这种模式。实际上,它甚至内置在ReduceLROnPlateau回调中。
1.8训练期间代价和指标的可视化
在训练期间关注你的模型的最好方法是使用TensorBoard,这是一个基于浏览器的应用程序,我们可以本地运行,它可以提供:
(1)用于训练和评估的代价和指标的现场图
(2)可视化层激活的直方图(可选)
(3)通过嵌入层学习的嵌入空间的3D可视化(可选)
在CMD中使用TensorBoard的指令:
tensorboard --logdir=/full_path_to_your_logs
使用TensorBoard回调
使用带有Keras模型和fit方法的TensorBoard的最简单方法是TensorBoard回调。在最简单的情况下,只要指定你想要回调写日志的地方,就可以了:
tensorboard_cbk = keras.callbacks.TensorBoard(log_dir='/full_path_to_your_logs')
model.fit(dataset, epochs=10, callbacks=[tensorboard_cbk])
TensorBoard回调有许多有用的观点,包括是否嵌入日志,历史图像以及多久写日志:
keras.callbacks.TensorBoard(
log_dir='/full_path_to_your_logs',
histogram_freq=0, # How often to log histogram visualizations
embeddings_freq=0, # How often to log embedding visualizations
update_freq='epoch') # How often to write logs (default: once per epoch)
第二部分:从头开始写自己的training&evaluation循环
如果我们希望自己的训练和评估循环比‘fit()’和‘evaluate()’提供的层次更低,那么我们应该编写自己的。其实很简单,但我们要准备好自己要进行更多的调试工作。
2.1使用GradientTape:第一个端到端示例
在GradientTape范围内调用一个模型使您能够检索与损失值相关的层的可训练权重的梯度。使用优化器实例,可以使用这些梯度来更新这些变量(可以使用model.trainable_weights检索这些变量)。
我们现在再重新使用第一部分的MNIST模型,使用带有定制训练循环的mini-batch gradient来训练它。
# 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, activation='softmax', 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()
# 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)
# Iterate over epochs.
for epoch in range(3):
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) # 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))
# 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))
2.2 处理低层次指标
让我们将指标添加到组合中。在从头开始编写的训练循环中,我们可以很容易地重用内置的指标(或自定义的指标)。流程是这样的:
(1)在循环开始时实例化度量
(2)在每个批次里调用‘metric.update_state()’
(3)当需要显示当前指标的值时调用‘metric.result()’
(4)当需要清除指标状态时调用‘metric.reset_states()’(比如在一个纪元结束时)
现在我们用刚刚讲的知识,在每个纪元结束时,计算验证数据上的‘SparseCategoricalAccuracy’:
# Get 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, activation='softmax', name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)
# Instantiate an optimizer to train the model.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy()
# 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)
# Iterate over epochs.
for epoch in range(3):
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):
with tf.GradientTape() as tape:
logits = model(x_batch_train)
loss_value = loss_fn(y_batch_train, logits)
grads = tape.gradient(loss_value, model.trainable_weights)
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),))
2.3处理低层次的额外代价
在前面的部分,通过在调用方法中调用self.add_loss(value),可以通过层添加正则化损失。
在一般情况下,我们要在自定义训练循环中考虑这些代价(除非已经知道自己编写的模型不会造成此类代价)
重新调用上面版块中的例子,主要调用创建正则化代价的层:
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, activation='softmax', name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)
当我们像这样调用一个模型时:
logits = model(x_train)
前向传递的过程中创造的代价也相应地被添加到‘model.losses’属性:
logits = model(x_train[:64])
print(model.losses)
被追踪的代价首先在‘model__call__’开始时被清除,因此我们只看得到在这个前向传递过程中产生的代价。例如,反复调用模型,然后查询代价只显示最新的代价,即在最后一次调用期间创建的代价:
logits = model(x_train[:64])
logits = model(x_train[64: 128])
logits = model(x_train[128: 192])
print(model.losses)
要在训练过程考虑这些代价,所有我们需要做的是通过添加‘sum(model.losses)’到总代价中来修正我们自己编写的训练循环:
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
for epoch in range(3):
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)
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))
# 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))
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
for epoch in range(3):
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)
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))
# 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))
现在我们可以从头开始编写自己的训练和评估循环了!