「 导语」模型的训练与评估是整个机器学习任务流程的核心环节。只有掌握了正确的训练与评估方法,并灵活使用,才能使咱们更加快速地进行实验分析与验证,从而对模型有更加深入的理解。
在上一篇 Keras
模型构建的文章中,咱们介绍了在 TensorFlow 2.x
版本中使用 Keras
构建模型的三种方法,那么本篇将在上一篇的基础上着重介绍使用 Keras
模型进行本地训练、评估以及预测的流程和方法。 Keras
模型有两种训练评估的方式,一种方式是使用模型内置 API
,如 model.fit()
, model.evaluate()
和 model.predict()
等分别执行不一样的操做;另外一种方式是利用即时执行策略 (eager execution
) 以及 GradientTape
对象自定义训练和评估流程。对全部 Keras
模型来讲这两种方式都是按照相同的原理来工做的,没有本质上的区别。在通常状况下,咱们更愿意使用第一种训练评估方式,由于它更为简单,更易于使用,而在一些特殊的状况下,咱们可能会考虑使用自定义的方式来完成训练与评估。python
下面介绍使用模型内置 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
方法包含三个主要参数,一个是待优化的损失 (loss
) ,它指明了要优化的目标,一个是优化器 (optimizer
),它指明了目标优化的方向,还有一个可选的指标项 (metrics
),它指明了训练过程当中要关注的模型指标。 Keras API
中已经包含了许多内置的损失函数,优化器以及指标,能够拿来即用,可以知足大多数的训练须要。tf.keras.losses
模块下,其中包含了多种预约义的损失,好比咱们经常使用的二分类损失 BinaryCrossentropy
,多分类损失 CategoricalCrossentropy
以及均方根损失 MeanSquaredError
等。传递给 compile
的参数既能够是一个字符串如 binary_crossentropy
也能够是对应的 losses
实例如 tf.keras.losses.BinaryCrossentropy()
,当咱们须要设置损失函数的一些参数时(好比上例中 from_logits=True
),则须要使用实例参数。tf.keras.optimizers
模块下,一些经常使用的优化器如 SGD
, Adam
以及 RMSprop
等均包含在内。一样它也能够经过字符串或者实例的方式传给 compile
方法,通常咱们须要设置的优化器参数主要为学习率 (learning rate
) ,其余的参数能够参考每一个优化器的具体实现来动态设置,或者直接使用其默认值便可。tf.keras.metrics
模块下,二分类里经常使用的 AUC
指标以及 lookalike
里经常使用的召回率 (Recall
) 指标等均有包含。同理,它也能够以字符串或者实例的形式传递给 compile
方法,注意 compile
方法接收的是一个 metric
列表,因此能够传递多个指标信息。固然若是 losses
模块下的损失或 metrics
模块下的指标不知足你的需求,也能够自定义它们的实现。数组
对于自定义损失,有两种方式,一种是定义一个损失函数,它接收两个输入参数 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)
若是你须要的损失函数不只仅包含上述两个参数,则能够采用另一种子类化的方式来实现。定义一个类继承自 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, ), )
对于自定义指标,也能够经过子类化的方式来实现,首先定义一个指标类继承自 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()], )
layers
) 内部定义的损失,能够经过在自定义层的 call
方法里调用 self.add_loss()
来实现,并且在模型训练时,它会自动添加到总体的损失中,不用人为干预。经过对比加入自定义损失先后模型训练输出的 loss
值的变化来确认这部分损失是否被加入到了总体的损失中。还能够在 build
模型后,打印 model.losses
来查看该模型的全部损失。注意正则化损失是内置在 Keras
的全部层中的,只须要在调用层时加入相应正则化参数便可,无需在 call
方法中 add_loss()
。call
方法里调用 self.add_metric()
来新增指标,一样的,它也会自动出如今总体的指标中,无需人为干预。函数式 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)
model.fit()
方法来实现, fit
方法包括训练数据与验证数据参数,它们能够是 numpy
类型数据,也能够是 tf.data
模块下 dataset
类型的数据。另外 fit
方法还包括 epochs
, batch_size
以及 steps_per_epoch
等控制训练流程的参数,而且还能够经过 callbacks
参数控制模型在训练过程当中执行一些其它的操做,如 Tensorboard
日志记录等。模型的训练和验证数据能够是 numpy
类型数据,最开始的端到端示例便是采用 numpy
数组做为输入。通常在数据量较小且内存能容下的状况下采用 numpy
数据做为训练和评估的数据输入。dom
numpy
类型数据来讲,若是指定了 epochs
参数,则训练数据的总量为原始样本数量 * epochs
。epoch
) 全部的原始样本都会被训练一遍,下一轮训练还会使用这些样本数据进行训练,每一轮执行的步数 (steps
) 为原始样本数量/batch_size
,若是 batch_size
不指定,默认为 32
。交叉验证在每一轮训练结束后
触发,而且也会在全部验证样本上执行一遍,能够指定 validation_batch_size
来控制验证数据的 batch
大小,若是不指定默认同 batch_size
。numpy
类型数据来讲,若是设置了 steps_per_epoch
参数,表示一轮要训练指定的步数,下一轮会在上轮基础上使用下一个 batch
的数据继续进行训练,直到全部的 epochs
结束或者训练数据的总量
被耗尽。要想训练流程不因数据耗尽而结束,则须要保证数据的总量
要大于 steps_per_epoch * epochs * batch_size
。同理也能够设置 validation_steps
,表示交叉验证所需步数,此时要注意验证集的数据总量要大于 validation_steps * validation_batch_size
。fit
方法还提供了另一个参数 validation_split
来自动从训练数据集中保留必定比例的数据做为验证,该参数取值为 0-1
之间,好比 0.2
表明 20%
的训练集用来作验证, fit
方法会默认保留 numpy
数组最后面 20%
的样本做为验证集。TensorFlow 2.0
以后,更为推荐的是使用 tf.data
模块下 dataset
类型的数据做为训练和验证的数据输入,它能以更加快速以及可扩展的方式加载和预处理数据。机器学习
使用 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)
dataset
通常是一个二元组,第一个元素为模型的输入特征,若是为多输入就是多个特征的字典 (dict
) 或元组 (tuple
),第二个元素是真实的数据标签 (label
) ,即 ground truth
。from_tensor_slices
方法能够从 nunpy
数组直接生成 dataset
类型数据,是一种比较方便快捷的生成方式,通常在测试时使用。其它较为经常使用的生成方式,好比从 TFRecord
文件或文本文件 (TextLine
) 中生成 dataset
,能够参考 tf.data
模块下的相关类的具体实现。dataset
能够调用内置方法提早对数据进行预处理,好比数据打乱 (shuffle
), batch
以及 repeat
等操做。shuffle
操做是为了减少模型过拟合的概率,它仅为小范围打乱,须要借助于一个缓存区,先将数据填满,而后在每次训练时从缓存区里随机抽取 batch_size
条数据,产生的空缺用后面的数据填充,从而实现了局部打乱的效果。batch
是对数据进行分批次,经常使用于控制和调节模型的训练速度以及训练效果,由于在 dataset
中已经 batch
过,因此 fit
方法中的 batch_size
就无需再提供了。repeat
用来对数据进行复制,以解决数据量不足的问题,若是指定了其参数 count
,则表示整个数据集要复制 count
次,不指定就会无限次复制
,此时必需要设置 steps_per_epoch
参数,否则训练没法终止。train dataset
的所有数据在每一轮都会被训练到,由于一轮训练结束后, dataset
会重置,而后被用来从新训练。可是当指定了 steps_per_epoch
以后, dataset
在每轮训练后不会被重置,一直到全部 epochs
结束或全部的训练数据被消耗完以后终止,要想训练正常结束,须保证提供的训练数据总量要大于 steps_per_epoch * epochs * batch_size
。同理也能够指定 validation_steps
,此时数据验证会执行指定的步数,在下次验证开始时, validation dataset
会被重置,以保证每次交叉验证使用的都是相同的数据。validation_split
参数不适用于 dataset
类型数据,由于它须要知道每一个数据样本的索引,这在 dataset API
下很难实现。steps_per_epoch
参数时, numpy
类型数据与 dataset
类型数据的处理流程彻底一致。但当指定以后,要注意它们之间在处理上的差别。对于 numpy
类型数据而言,在处理时,它会被转为 dataset
类型数据,只不过这个 dataset
被 repeat
了 epochs
次,并且每轮训练结束后,这个 dataset
不会被重置,会在上次的 batch
以后继续训练。假设原始数据量为 n
,指定 steps_per_epoch
参数以后,二者的差别主要体如今真实的训练数据量上, numpy
为 n * epochs
, dataset
为 n
。具体细节能够参考源码实现。dataset
还有 map
与 prefetch
方法比较实用。 map
方法接收一个函数
做为参数,用来对 dataset
中的每一条数据进行处理并返回一个新的 dataset
,好比咱们在使用 TextLineDataset
读取文本文件后生成了一个 dataset
,而咱们要抽取输入数据中的某些列做为特征 (features
),某些列做为标签 (labels
),此时就会用到 map
方法。prefetch
方法预先从 dataset
中准备好下次训练所需的数据并放于内存中,这样能够减小每轮训练之间的延迟等待时间。除了训练数据和验证数据外,还能够向 fit
方法传递样本权重 (sample_weight
) 以及类别权重 (class_weight
) 参数。这两个参数一般被用于处理分类不平衡问题,经过给类别少的样本赋予更高的权重,使得各个类别对总体损失的贡献趋于一致。
对于 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, )
而对于 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)
在模型的训练过程当中有一些特殊时间点,好比在一个 batch
结束或者一个 epoch
结束时,通常都会作一些额外的处理操做来辅助咱们进行训练,上面介绍过的模型交叉验证就是其中之一。还有一些其它的操做,好比当模型训练停滞不前时 (loss
值在某一值附近不断波动),自动减少其学习速率 (learning rate
) 以使损失继续降低,从而获得更好的收敛效果;在训练过程当中保存模型的权重信息,以备重启模型时能够在已有权重的基础上继续训练,从而减小训练时间;还有在每轮的训练结束后记录模型的损失 (loss
) 和指标 (metrics
) 信息,以供 Tensorboard
分析使用等等,这些操做都是模型训练过程当中不可或缺的部分。它们均可以经过回调函数 (callbacks
) 的方式来实现,这些回调函数都在 tf.keras.callbacks
模块下,能够将它们做为列表参数传递给 fit
方法以达到不一样的操做目的。
下面以 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, )
callbacks
须要了解并掌握, 如 ModelCheckpoint
用来保存模型权重信息, TensorBoard
用来记录一些指标信息, ReduceLROnPlateau
用来在模型停滞时减少学习率。更多的 callbacks
函数能够参考 tf.keras.callbacks
模块下的实现。固然也能够自定义 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'))
TensorFlow 2.0
以前, ModelCheckpoint
内容和 TensorBoard
内容是同时记录的,保存在相同的文件夹下,而在 2.0
以后的 keras API
中它们能够经过不一样的回调函数分开指定。记录的日志文件中,含有 checkpoint
关键字的文件通常为检查点文件,含有 events.out.tfevents
关键字的文件通常为 Tensorboard
相关文件。考虑如图所示的多输入多输出模型,该模型包括两个输入和两个输出, 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], )
在进行模型编译时,若是只指定一个 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
会应用到每个输出,在模型的多个输出损失计算方式相同时,能够采用这种方式。
一样的对于模型的指标 (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()], ], )
对于有明确名称的输出,能够经过字典的方式来设置其 loss
和 metrics
。示例代码以下:
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
。示例代码以下:
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), }, )
对于多输入输出模型的训练来讲,也能够采用和其 compile
方法相同的方式来提供数据输入,也就是说既可使用列表的方式,也可使用字典的方式来指定多个输入。
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, )
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)
model
内置提供的 fit
和 evaluate
方法,而想使用低阶 API
自定义模型的训练和评估的流程,则能够借助于 GradientTape
来实现。深度神经网络在后向传播过程当中须要计算损失 (loss
) 关于权重矩阵的导数(也称为梯度),以更新权重矩阵并得到最优解,而 GradientTape
能自动提供求导帮助,无需咱们手动求导,它本质上是一个求导记录器
,可以记录前项传播的过程,并据此计算导数。模型的构建过程与以前相比没有什么不一样,主要体如今训练的部分,示例代码以下:
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), ))
with tf.GradientTape() as tape
部分的实现,它记录了前向传播的过程,而后使用 tape.gradient
方法计算出 loss
关于模型全部权重矩阵 (model.trainable_weights
) 的导数(也称做梯度),接着利用优化器 (optimizer
) 去更新全部的权重矩阵。batch
的训练中进行更新操做 (update_state()
) ,在一个 epoch
训练结束后打印指标的结果 (result()
) ,而后重置该指标 (reset_states()
) 并进行下一轮的指标记录,交叉验证的指标也是一样的操做。注意与使用模型内置 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))