tf.estimator.train_and_evaluate() 训练与测试不一致

问题背景

以一个简单的分类任务为例,在处理完数据之后,使用如下code进行训练:

estimator = tf.estimator.Estimator(model_fn, 'model', cfg, params)
train_spec = tf.estimator.TrainSpec(input_fn=train_inpf, hooks=[])
eval_spec = tf.estimator.EvalSpec(input_fn=eval_inpf, throttle_secs=120)
tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)

在训练保存最后一次模型之后,同样的dev集,模型训练的结果,和重新使用tf.evaluation 函数测试的结果居然不一致!
而本质上,model_fn 中定义的测评函数就是estimator.evaluate()…

model_fn() 函数中定义的测评如下:

metrics = {
 'label_acc': tf.metrics.accuracy(real_label_ids, pred_label_ids)
}
for metric_name, op in metrics.items():
        tf.summary.scalar(metric_name, op[1])
 if mode == tf.estimator.ModeKeys.EVAL:
       return tf.estimator.EstimatorSpec(
                mode, loss=loss, eval_metric_ops=metrics)

这就比较诡异了,将道理,加入最后是3000个step保存模型,那么第3000步结果,应该是和直接用estimator.evaluate() 函数得到的结果是一样…

问题分析

数据问题

首先考虑是否是数据问题。保证评测的dev和train的时候的dev的数据量以及顺序确实是一致的。排除数据方面的问题;

github上的某个issue,也提出了这个问题:
https://github.com/tensorflow/tensorflow/issues/22857
总的来说,就是数据输入有问题,只计算了一个batch。
这里我check了一下,数据加载部分不存在这个问题。

metric 问题

后来考虑到是否是 tf.metrics.accuracy() 这个op本身计算有问题呢?
于是重新自定了一个计算accuracy的op,并赋值给loss,这样比较方便直接地对比两个op输出的结果:

correct_prediction = tf.equal(true_ids, pred_ids)
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
metrics = {
 'label_acc': tf.metrics.accuracy(real_label_ids, pred_label_ids)
}
 if mode == tf.estimator.ModeKeys.EVAL:
       return tf.estimator.EstimatorSpec(
                mode, loss=accuracy, eval_metric_ops=metrics)

结果发现这两者是一致的,并没有区别,可见这个op是没问题的。
不过在做这部分实验的时候,踩了另一个坑:
我本想直接在 metric = {},里面直接这样:

metrics = {
 'label_acc': tf.metrics.accuracy(real_label_ids, pred_label_ids)
'my_acc':  tf.reduce_mean(tf.cast(correct_prediction, "float"))
}

发现并不可行,因为metric是作为
tf.estimator.EstimatorSpec() 参数,有着明确的类型限制,tf.metrics 会返回metrics类,而不能是一个op!!!

GPU or CPU ?

联想到很早之前做生成的时候,由于GPU和CPU训练的精度问题,导致生成的结果差异很大,就想着是否是精度影响了结果。这里老大直接告诉我,绝对不可能,以照经验来看,绝不会差异这么大的…可是我偏不信…(不听老人言啊,衰…),
于是尝试在cpu和gpu环境下分别测试,果然并无大差别。

后来想想,确实问题不太一样,生成任务,每个时刻都需要输入上一此输出的预测词的embedding和hidden state,由于词表都是上万的,导致分类空间很大,每个时刻微小的hidden state会影响最后softmax之后的结果,而softmax 的结果又会导致下次输入的embedding差异较大,这种累积的误差,远比直接累积的hidden计算误差要大的多,毕竟分类问题只在最后一步做softmax.

打印出来的是什么?

本想查看源码,分析一下到底内部评测code是如何实现的,然鹅这部分的code,真的是看的头晕眼花,好在突然找到一篇文章,和我遇到了一模一样的问题,并做了详细的分析,这里我就借花献佛了,这篇文章思路很清晰,分析的也很到位,可以看看整个实验分析过程:
https://www.twblogs.net/a/5d4b31d2bd9eee5327fc116a/zh-cn

总的来说,问题是由函数本身日志打印问题导致的,显示的第3000个step的结果,并不是第3000个check point的模型结果,而是前面step的acc的平均值!!!

总结

  1. 多看文档多分析

  2. 慎用高阶API啊

  3. 经验总结:
    3.1 metric 中定义的只能是tf.metrics, 而不能是别的自定义的op,如果需要自定义,可以继承 tf.metrics。

    3.2 使用tf.estimator.train_and_evaluate时,训练中实时显示的准确率并非代码中定义的accuracy,而是一个均值结果。
    可以把这个高级API拆分为相对底层的两个API:
    estimator.train和estimator.evaluate。

你可能感兴趣的:(tensorflow)