关于tensorflow:TensorFlow-篇-TensorFlow-2x-基于-Keras-模型的本地训练与评估

24次阅读

共计 18972 个字符,预计需要花费 48 分钟才能阅读完成。

导语」模型的训练与评估是整个机器学习工作流程的外围环节。只有把握了正确的训练与评估办法,并灵便应用,能力使咱们更加疾速地进行试验剖析与验证,从而对模型有更加粗浅的了解。

前言

在上一篇 Keras 模型构建的文章中,咱们介绍了在 TensorFlow 2.x 版本中应用 Keras 构建模型的三种办法,那么本篇将在上一篇的根底上着重介绍应用 Keras 模型进行本地训练、评估以及预测的流程和办法。Keras 模型有两种训练评估的形式,一种形式是应用模型内置 API,如 model.fit()model.evaluate()model.predict() 等别离执行不同的操作;另一种形式是利用即时执行策略 (eager execution) 以及 GradientTape 对象自定义训练和评估流程。对所有 Keras 模型来说这两种形式都是依照雷同的原理来工作的,没有实质上的区别。在个别状况下,咱们更违心应用第一种训练评估形式,因为它更为简略,更易于应用,而在一些非凡的状况下,咱们可能会思考应用自定义的形式来实现训练与评估。

内置 API 进行训练评估

端到端残缺示例

上面介绍应用模型内置 API 实现的一个端到端的训练评估示例,能够认为要应用该模型去解决一个多分类问题。这里应用了 函数式 API 来构建 Keras 模型,当然也能够应用 Sequential 形式以及子类化形式去定义模型。示例代码如下所示:

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),咱们会依据评估与预测后果来判断模型的好坏。这样一个残缺的模型训练与评估流程就实现了,上面来对示例里的一些实现细节进行开展解说。

模型编译 (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) 数量 的统计指标,其代码如下:

      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() 来实现与自定义模型同样的成果。示例代码如下:

      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 数据作为训练和评估的数据输出。

    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

正文完
 0