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
相关文章
相关标签/搜索