写在前面
有关如何使用tf.estimator,请官网中文版:
https://www.tensorflow.org/guide/custom_estimators?hl=zh-cn
有关tf.estimator检查点checkpoints的详细分析,请参考
https://juejin.im/entry/5b0226986fb9a07ac85ab475
按照代码使用tf.estimator.train_and_evaluate进行训练和验证时,出现了一个问题:
我将batch_size设置为891(所有数据),训练epoches设为10,验证时tf.estimator.EvalSpec的steps参数设置为1。
显然,训练会将完整数据集过10遍,验证会将数据集过1遍。
由于训练时间过短,checkpoints只保存train开始和结束时的两个检查点,训练结束后最后的模型会保存起来。
而验证会从最近的检查点中恢复出数据,也就是会加载出训练结束时保存的模型。
然而问题出现了:
训练时显示的accuracy等指标和验证时的结果相差太大:
第八步:INFO:tensorflow:accuracy = 0.68237936, auc = 0.7075512, loss = 1.1856021
第九步:INFO:tensorflow:accuracy = 0.6830808, auc = 0.71318686, loss = 1.138809
第十步:INFO:tensorflow:accuracy = 0.6837511, auc = 0.7182056, loss = 1.095356验证时结果:
INFO:tensorflow:Saving dict for global step 10: accuracy = 0.72053874, auc = 0.76414853, global_step = 10, loss = 1.054848
按理说,训练和验证用相同的数据集,第十步的结果应该和验证时的结果一样,但是相差这么大,真是不能理解。因此做了一系列测试实验来探究原因。
训练过程中的指标显示
tf.summary
tf.summary.scalar 会在 TRAIN 和 EVAL 模式下向 TensorBoard 提供准确率,代码分别如下:
tf.summary.scalar('accuracy', accuracy[1])
tf.summary.scalar('train_auc', auc[1])
summary_hook = tf.train.SummarySaverHook(1,output_dir="./checkpoint/",
summary_op=tf.summary.merge_all())
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op, training_hooks=[summary_hook])
metrics = {'accuracy': accuracy}
return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)
训练过程中的accuracy需要使用tf.summary
手动记录,并且只使用tf.summary.scalar
不行,此处加了个summary_hook
才能正常使用。
使用tensorboard观察训练过程中产生的accuracy指标:
没有第0步的accuracy,第1步是0.6779,第九步0.6838,第十步0.6869。
log_hook参数
可以在自定义的model_fn中这样使用:
show_dict = {
"loss":"loss",
"accuracy":'accuracy/value',
"auc":'auc/value'
}
log_hook = tf.train.LoggingTensorHook(show_dict, every_n_iter=10)
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op, training_hooks=[log_hook])
every_n_iter是指迭代几次显示一次。也可以在tf.estimator.train_and_evaluate中这样使用:
show_dict = {
"loss":"loss",
"accuracy":"accuracy/value",
"auc":"auc/value"
}
log_hook = tf.train.LoggingTensorHook(show_dict, every_n_iter=100)
train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn, hooks=[log_hook])
eval_spec = tf.estimator.EvalSpec(input_fn=train_input_fn, steps=1)
tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)
效果是一样的。我们看看这样记录训练指标时,在控制台的结果:
第0步accuracy是0,第1步0.67789,第9步0.6837511,没有第10步的结果。
这和使用tf.summary是一致的。它们这和验证时的结果都不一致:
通过这两个小实验,可以看出不是显示工具的问题。应该是训练时显示的指标本身有问题,训练时显示的accuracy是我们定义的accuracy吗?
阶段训练和直接训练
我将epoches设为10,训练两次。然后直接将epoches设为20,看看两次指标有何变化。
将epoches设为10,第一次训练结果:
第0步是0,只显示到第9步,第10步结果通过tensorboard可以看到。第二次训练结果:
第10步accuracy为0,这并不影响,每次训练的第一步向来是0。tensorboard中可以看到第一次训练已经获得第10步的值。
但是,第11步的结果是0.7205,这不是第一步验证时的值吗?而且0.7205和0.6838差距太大,它们并不是正常训练过程中的平滑上升,accuracy的显示肯定有问题!
看看epoches设为20训练结果:
第11步显示的accuracy是0.6899296,并不是分阶段训练中显示的0.7205,显然这个显示的指标accuracy并不是我们在程序中定义的accuracy,但肯定和它有关,并且是动态变化的!
tf.estimator的另一种训练方式
实战代码中我们使用tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)
这个高阶API,配置好train_spec和eval_spec即可。但是这样使用不够灵活。比如我将数据分为训练集,验证集和测试集三部分,将验证集上表现较好的模型拿出来在测试集上测试,这样就不能用。这时可以使用相对底层些的estimator.train
和estimator.evaluate
。
我们看看如何使用这两个方法来训练和测试数据:
estimator = tf.estimator.Estimator(
model_fn = model_fn,
model_dir=FLAGS.model_path,
params=hparams,
config=tf.estimator.RunConfig(
tf_random_seed=hparams.seed,
log_step_count_steps=1))
train_metrics = estimator.evaluate(input_fn=train_input_fn)
eval_metrics = estimator.evaluate(input_fn=train_input_fn)
print("train metrics: %r"% train_metrics)
print("eval metrics: %r"% eval_metrics)
# 训练数据在模型上的预测准确率
for i in range(10):
estimator.train(input_fn=train_input_fn, steps=1, hooks=[log_hook])
train_metrics = estimator.evaluate(input_fn=train_input_fn)
# 评估数据在模型上的预测准确率
eval_metrics = estimator.evaluate(input_fn=train_input_fn)
print("train metrics: %r"% train_metrics)
print("eval metrics: %r"% eval_metrics)
此时显示的accuracy肯定是真实的accuracy,得到结果后,我整理了个表格,将其记录为real_acc:
最开始我觉得,可能是每次计算后都会重新取均值,比如第7步训练结束后,结果应该是:
avg_acc(7) = (avg_acc(6)*6+acc(7))/7
根据这个公式可以计算出真正的acc(7),记为cal_acc,如上图所示。可以发现这个猜想不对,因为第10步计算出的0.7152和真实的0.7205差太多。但已经很接近了。
我又发现cal_acc(7)和real_acc(6)一致,cal_acc(6)和real_acc(5)一致。会不会是这样的呢:
avg_acc(7) = (avg_acc(6)*6+real_acc(6))/7
通过计算,所有数据都满足这个规律!也就是说,第7步显示的accuracy是前6步的accuracy的均值,不是第7步的训练结束后在数据集上获得的实时结果。
但是还有两个问题:
第1步的值哪来的?
我猜想是按一定规律生成的一个比第一个real_acc小的值,这样在tensorboard中显示的就是accuracy是平滑上升。通过改变随机种子的值,重新得到一组数据,我验证了这个猜想。
第10步训练结束后的real_acc会加入计算吗?
我们可以发现,控制台只显示到0.68375,此时第9步和第10步的real_acc都没有加入计算;
而在tensorboard中显示step=10时accuracy=0.6869,此时第9步的real_acc加入了计算;
第10步训练结束后,模型的效果是0.7205,这个结果没有在任何地方显示。不过可以通过estimator.evaluate(input_fn=train_input_fn)
自己算啊。
结论
我们发现,使用tf.estimator.train_and_evaluate时,训练过程中实时显示的准确率并非我们代码中定义的accuracy,而是一个取均值的结果,并且这个均值不包括当前step模型的accuracy值。
实际上,结合之前分析的验证集和测试集问题,还是建议使用estimator.train
和estimator.evaluate
这种相对更底层的方式。tf.estimator.train_and_evaluate
这种高阶API,快速搭配模型挺好用,但是不方便自己魔改。
参考
[1] https://www.tensorflow.org/guide/custom_estimators?hl=zh-cn
[2] https://juejin.im/entry/5b0226986fb9a07ac85ab475
公众号
更多精彩内容请移步公众号:推荐算法工程师
