tensorboard 中 auc_precision_recall 曲线 y 轴值的理解

为了更清楚的表达我的问题,我将我在 stackoverflow 上提交的问题在这里再次用中文发一下吧。

版本:
TensorFlow:1.6.0
TensorBoard:1.6.0

我所做的事情:

  1. tf.estimator.DNNClassifier 训练一个二分类模型,且所用的训练数据是非常的不对称的(准确的说是预测某个用户点击广告的可能性。显然绝大多数的数据都是: 不点击 (noclick) ,而只有很少数的数据是: 点击 (click) )。
  2. 因此这里我们评价模型的好坏标准不能用传统的 AUC 值,而应该用 PR-AUC 值。
  3. 在模型训练到一定的时候,其准确率就不会再提高了,因此这时就需要试着从训练数据中尽可能找出存在的噪音特征 (noise feature),将其剔除。
  4. 而找噪音特征的过程就是用的迭代方式。假设我们用于训练的特征有 30 个(feat_1, feat_2, feat_3, … feat_30),当用所有 30 个特征数据训练时,我们的 auc=0.7。现在我们的模型准确率停留在了 0.7 不会再提高了。那么我们现在就用迭代的方式找找看有没有噪音:

a. 去掉特征 feat_1,然后用剩下的 29 个特征 (feat_2, feat_3, …, feat_30) 重新进行训练。

  • 假设最后得到的 auc=0.65,那么我们可以得出的结论是:feat_1 是包含有信息量的 (informative)。我们应该保留 feat_1。
  • 而如果最后得到的 auc=0.75,那么我们可以得出的结论是:feat_1 是包含有噪音 (uninformative)。feat_1 正是我们想找的噪音。
    b. 重复过程 a:去掉特征 feat_2,然后用剩下的 29 个特征 (feat_1, feat_3, feat_4, …, feat_30) 重新进行训练。

在寻找噪音特征的过程中,我从 tensorboard 得到 30 个 auc_precision_recall 曲线图,其中有 3 个图片如下:(我们依次称三个图片为 figure A, figure B, figure C)

image

image

image

关于图片的描述:

  • x 轴:训练步数
  • y 轴:0〜1 的区间值(这个值代表什么意思正是我的疑问所在。目前感觉可能是代表 PR-AUC ,但由于 figure A、figure B 的起始值很大,所以我表示怀疑)

我的问题是:

  • 上图中的 y 轴到底代表什么?
  • 如果这个 y 轴值代表 PR-AUC 值,那为什么在训练的第一步就会有如此大的值(如图 figure A:的第一个点 (x, y)=(1, 0.5009))?
  • 除了上述两个问题外。关于 figure A 的疑问:图里为什么会有一个向下的峰值(y 值接近 0)?

提问者 rz828,发表于 2018-4-26 13:46:26

讨论 1:

M 丶 Sulayman:
额,首先 0.5 是初始值,AUC 不管是哪种,它的取值都是在 [0.5,1]。出现小于 0.5 的分类器,你直接把结果取反。懂我意思不?反正是二分类问题,输出正样本的可能性大,它最后就是输出的正样本标签,可能性小,输出的就是正样本的取反(负样本标签)。因为 recall 就是只关注正样本预测准确度的指标,它小于 0.5 只是说明该分类器对负样本敏感,不是说它性能不好。你可以试试随机森林,它不会对样本太过敏感。额,啰嗦了一大堆,希望对你有用。
2018-4-26 15:03:33

rz828:
请问你能给我一些关于你说的这两个观点的文档看看吗?也许能帮助我理解
AUC 不管是哪种,它的取值都是在 [0.5,1]
0.5 是初始值

(AUC 我能理解其初值应该是 0.5,但是 PR-AUC 我不太理解)
2018-4-26 15:26

M 丶 Sulayman: 回复 rz828:
http://www.360doc.com/content/15/0326/20/17553313_458302508.shtml
2018-4-26 15:28

rz828: 回复 M 丶 Sulayman : 你好,我想要的是对你上述两个观点的说明文档,你给的链接我好好看了,完全没有你所说这两个观点的文字。
2018-4-26 15:39

rz828: 请问下,你有用 tensorboard 的经验吗?
2018-4-26 15:39

rz828: 如果你没有 tensorboard 的使用经验,你肯定理解不了我贴的三张图的。
2018-4-26 15:40

M 丶 Sulayman: 回复 rz828 :额,好吧,我刚刚入门 TF,我以为你问的是数学方面的东西,抱歉~
2018-4-26 15:51

rz828: 回复 M 丶 Sulayman :依然谢谢你的慷慨解答
2018-4-26 16:02

yunhai_luo: 回复 M 丶 Sulayman :请教 M 丶 Sulayman 一个数学问题:能否详解一下当 AUC_PR 小于 0.5 时,取反可以得到大于 0.5 的 AUC_PR。我明白 AUC_ROC 取反可证曲线以 (0.5, 0.5) 为中心旋转 180 度,从而必然得到大于 0.5 的 AUC_ROC。
2018-4-27 15:21

M 丶 Sulayman: 回复 yunhai_luo :取反得到的当然不是 PR 了,这不是曲线旋转的问题。PR 代表的是正样本被模型正确的判为正样本的概率,你先去看下混淆矩阵…你就知道了
2018-4-27 15:37

yunhai_luo: 回复 M 丶 Sulayman : 其实我想求教的是为什么 AUC_PR 的取值必然可以大于 0.5,如果取反得到的不是 PR,那为什么说可以取反得到能使 AUC_PR 大于 0.5 的分类器呢?希望大神给个证明链接,查了混淆矩阵没能找到相关内容,让大神见笑了。正样本被模型正确的判为正样本的概率是 recall 吗?如果是,那为什么 precision-recall 曲线的 AUC 会代表 recall 呢?希望大神也能给个相关链接,麻烦了,先谢过了!

真心请教楼主两个问题:
1)你的 auc_precision_recall 曲线是怎么得到的?我猜测是通过 tf.metrics.auc 或者某个 canned estimator 的 metrics,如果是这样,那似乎没有理由怀疑 y 轴不是 PR-AUC(除非 tensorflow 里面有 bug)。
2)我大概可以理解看到 PR-AUC 太大或者跳到 0 的心情,但是有没有可能这是真的而不是 bug?或者楼主还有什么其他怀疑理由?如果你导出相应步骤的参数,搭好模型,应该可以用你确信的算法计算 PR-AUC,检验是真是假吧?我没有别的意思,我也对这个现象背后的原因感兴趣。不过既然参数初始化是随机的,而你三幅图选取的特征又不一样,另外模型优化也不是以 PR-AUC 为目标,所以我感觉理论上出现较大初始 PR-AUC 和 PR-AUC 跳动还是有可能的。


yunhai_luo,发表于 2018-4-28 15:52:49

其实我写的很清楚了,可能你们没有人用过这些现成的东西:tf.estimator.DNNClassifier,再也没有别的东西了,关于这个 estimator 的 API 链接我主楼也贴出来了。

  1. 可以肯定这不是 bug。因为 Precision-Recall 曲线的跳动就是很剧烈的,原因是:Precision-Recall 曲线就是对二分类中,占据比例很少的数据特别敏感,因为画这个曲线只需要计算 PPV (也称 Precision) 和 TPR (也称 Recall)。
  2. 按道理说,这个 y 轴的值应该就是 PR-AUC。所以我最大的问题就是:为什么我的 PR-AUC 的初始值会是 0.5?(因为我判断模型的好坏的时候,同样是根据这个 y 轴值来决定的,那么从我的图上来看:初值 0.5 就是最大值,难道说我刚训练第一步的结果就是最好结果?这显然说不过去)

rz828(提问者),2018-4-28 16:17

可以把代码粘出来嘛?PS 如果可以的话,可以看看 precisoin 和 recall 的变化可以更容易帮助 debug。

PPS, 训练和验证的时候请 shuffle data


AirLRJ ,发表于 2018-4-28 16:19:16

当然可以,代码看下楼。
再提一句:验证的时候,不需要 shuffle data。例子请看 tensorflow 官方教程 https://www.tensorflow.org/get_started/get_started_for_beginners

用于训练模型的代码,没有任何特殊点(input_fn 我想就没必要贴了,至于 shuffle,那是最基本的操作,肯定不能少):

train_spec = tf.estimator.TrainSpec (
        input_fn=lambda: train_input_fn (
            args.train_files,
            args.train_batch_size
        ),
        max_steps=train_max_step
    )

    exporter = tf.estimator.LatestExporter (
        name='ctr',
        serving_input_receiver_fn=serving_fn,
        as_text=True,
        exports_to_keep=20
    )

    eval_spec = tf.estimator.EvalSpec (
        input_fn=lambda: eval_input_fn (
            args.eval_files,
            args.eval_batch_size
        ),
        name='eval',
        exporters=[exporter],
        start_delay_secs=120,
        throttle_secs=300
    )

    run_config = tf.estimator.RunConfig (
        model_dir=args.job_dir,
        save_summary_steps=50,
        save_checkpoints_steps=5000,
        keep_checkpoint_every_n_hours=1,
        keep_checkpoint_max=20
    )

    estimator = tf.estimator.DNNClassifier (
        hidden_units=args.hidden_units,
        feature_columns=FEATURE_SPEC,
        config=run_config
    )

    print_last_ckpt (estimator.latest_checkpoint ())

    tf.estimator.train_and_evaluate (
        estimator, train_spec, eval_spec)

rz828(提问者),发表于 2018-4-28 16:26:14

更新:

之前(下面)可能没有说清楚,我强调一下重点。
首先,最重要的整体考虑:我感觉现在还是找问题的阶段,所以最后先不要下结论解释。所谓 “插值错误” 只是一个例子,个人感觉很有可能还有其他我不知道的情况,最起码 “插值错误” 就有很多 “变体现象”,比如 predictions = tf.cast (np.random.choice ([.1, .9], size=n), tf.float32),AUC PR 得到 0.45407957,得到曲线是两个点组成的 0.2 左右的线。具体发生了什么是跟你的数据有关的,即使是在第一个步骤,尽管参数是随机的、跟数据无关的,但预测是跟你的特征作用后的结果,所以不同设定都是不同的。从这个角度讲,个人觉得"画出 PR 曲线看一看"不仅是没有办法的办法,而是其中一种正确思路。
其次,关于你这个问题中我感兴趣的部分主要是两个,第一是训练过程一直保持 0.5,另一个是跳低过一次。下面算是回答你点评中的问题:
1. 这里想说的是第几个步骤(是不是第一个点)跟你出现 0.5 这个问题很可能没有太直接的关系,具体能不能忽略掉应该需要看看 PR 曲线甚至是其他统计结果决定,不好直接下结论。
2. 同样的,我也不确定跳低过的那一次是不是就 “没错”。对于跳低过的那一次,我感兴趣的点在于到底有多低。这篇文章 讲到了 PR 空间的禁区问题,我不是完全理解其中的证明,但大概一个思路是当 recall100%的时候,precision 不可能低于偏移度比例,因为分类找到了所有正值,最差也是所有的负值都是一类错误。但是,随机分类的 AUC PR 是要大于这个曲线的。你的三幅图最低值到了 0.002,好像真的很低,说明你的偏移度可能真的很大,我好奇你这个值是不是低于了随机分类,如果是,感性上说还是挺难得的。
最后,提一个 1.8.0 的更新。今天重跑下面代码是发现有警告:“WARNING:tensorflow:Trapezoidal rule is known to produce incorrect PR-AUCs; please switch to “careful_interpolation” instead.”。仔细看过应该是 1.8.0 的 更新,“careful_interpolation” 也是新的,参考文章就是之前点评中提到的那篇。这个选项在 DNNClassifier 里好像还没用到。如果用这个方法计算下面的例子,得到的是 0.23976201 而不是 0.6105。

之前的发言:

我感觉 AirLRJ 的建议是很好的,楼主应该考虑把可以的模型或者相应步骤的预测值提取出来,画出 PR 曲线看一看。到底出了什么问题,或者是不是真的 AUC PR 是可以检验的。个人认为仅凭楼主的代码分析是不够的,问题很有可能跟楼主的数据特征有关。我对 PR 曲线的了解确实不够深入,但是我能想到的一个特例就是当预测概率为定值,或者说所有的预测概率在一个相对于阈值采样区间很小的范围中时,PR 曲线变成了一个 precision 为偏移度、recall 为 1 的点,这样在接下来的 AUC 数值计算中可能会导致 0.5 左右的 AUC,但这是完全无意义的伪值。这个特例在 tensorflow 中可以的到证实:

import numpy as np
import tensorflow as tf
from tensorboard.plugins.pr_curve import summary

n = 1000
np.random.seed (408)
labels = tf.cast (np.random.choice (2, size=n, p=(0.8, 0.2)), tf.bool)
predictions = tf.random_uniform (shape=[n], minval=0.1, maxval=0.1005, seed=408)
auc = tf.metrics.auc (labels, predictions, curve='PR')
summary_op = summary.op ('Big_AUC-PR', labels, predictions)

with tf.Session () as sess:
    init = tf.local_variables_initializer ()
    sess.run (init)
    auc_value, summ = sess.run ((auc, summary_op))
    with tf.summary.FileWriter ('Big_AUC-PR') as writer:
        writer.add_summary (summ)
    print (auc_value)

得到的 AUC 为 0.6105,tensorboard 中的曲线图为:
image

当然这只是一个特例,很可能还有其他特例,或者其他可能性,我水平有限,说不上来。不过还是那句话,画出 PR 曲线看看没准有帮助。

另外,想在这里老话重提一遍。之前向楼主和 @M 丶 Sulayman 请教过 AUC-PR 大于 0.5 的问题,但一直没有下文,不知道是不是言语不周有所得罪,在这里先道个歉,请见谅。这次想再提的是以下代码,分类器是最差的均匀随机分类器,我用 1-预测值的方法取反,但取反前后的 AUC-PR 都小于 0.5。

import numpy as np
import tensorflow as tf
from tensorboard.plugins.pr_curve import summary

n = 1000
labels = tf.cast (np.random.choice (2, size=n, p=(0.8, 0.2)), tf.bool)
predictions = tf.random_uniform ([n])
ops = []
for name, pred in zip (['Prediction', '1-prediction'],
                      [predictions, 1-predictions]):
    ops.append (tf.metrics.auc (labels, pred, curve='PR'))
    summary_op = summary.op (name, labels, pred)
ops.append (tf.summary.merge_all ())

with tf.Session () as sess:
    init = tf.local_variables_initializer ()
    sess.run (init)
    auc1, auc2, merged_summary = sess.run (ops)
    with tf.summary.FileWriter ('PR_curve') as writer:
        writer.add_summary (merged_summary)
    print ('Prediction: ', auc1 [1])
    print ('1-prediction: ', auc2 [1])
import numpy as np
import tensorflow as tf
from tensorboard.plugins.pr_curve import summary

n = 1000
labels = tf.cast (np.random.choice (2, size=n, p=(0.8, 0.2)), tf.bool)
predictions = tf.random_uniform ([n])
ops = []
for name, pred in zip (['Prediction', '1-prediction'],
                      [predictions, 1-predictions]):
    ops.append (tf.metrics.auc (labels, pred, curve='PR'))
    summary_op = summary.op (name, labels, pred)
ops.append (tf.summary.merge_all ())

with tf.Session () as sess:
    init = tf.local_variables_initializer ()
    sess.run (init)
    auc1, auc2, merged_summary = sess.run (ops)
    with tf.summary.FileWriter ('PR_curve') as writer:
        writer.add_summary (merged_summary)
    print ('Prediction: ', auc1 [1])
    print ('1-prediction: ', auc2 [1])

没有设随机种子,我得到的结果是 0.20946483 和 0.19511054。PR 曲线图如下:
image

恳请楼主和 @M 丶 Sulayman 帮忙看看算法 / 代码有什么问题,或者能详细指点一下为什么 AUC-PR 必然大于 0.5,真的非常感谢!


yunhai_luo,发表于 2018-4-29 16:27:56

rz828:

  1. “画出 PR 曲线看一看”,这貌似也是个没有办法的办法,为什么这么说呢:因为我的疑问是第一步就得到接近 0.5 的 PR-AUC 值,是很奇怪的。而且,才仅仅训练了 1 个 step,就去做 evaluation 并且画 PR 曲线,真的只能说是没有办法的办法。
  2. “AUC 为 0.6105,tensorboard 中的曲线图”:你这句话下面的图上只有一个点,无法表明 AUC 值为 0.6105,是不是你图贴错了(因为 AUC 是指面积,一个点无法构成面积值)?
  3. 关于"AUC-PR 必然大于 0.5":这个观点是这个层主 @M 丶 Sulayman 的观点。不是我的观点。你可能没有仔细看楼上的回复。
    2018-5-2 09:48

yunhai_luo: 回复 rz828 :

  1. 确实没有贴错图,楼主可能比较忙,没时间测试代码,如果试过得到不同结果请告知。一个点确实无法构成面积值,楼主说的很对,所以我才说是 “完全无意义的伪值”,但在数值求解过程中会进行 “错误” 插值,得到 0.6105 的,完全没有报错。引用这篇文章可能没有可比性 https://www.biostat.wisc.edu/~page/rocpr.pdf ,但其中也谈到了插值有误的问题。
    2018-5-2 12:47

rz828: 回复 yunhai_luo :

  1. 貌似你的这个解释可以说得通,那么这样子的话:对于 tensorboard 上显示的 auc_precision_recall 图,第一个点就直接当成"错误插值",把它忽略掉是不是就可以了?
  2. 但是还有一点不好解释:关于图 figure A,除了一个点以外,其所有点的值都是 0.5。这个就不好解释了,因为如果用"错误插值"来解释的话,那 figure A 里面的点只有一个不是"错误插值"了?
    依然困惑。。。
    2018-5-2 16:32

yunhai_luo: 回复 rz828 :

之前可能没有说清楚,说的有点儿多,更新了帖子,请查收。


yunhai_luo & rz828(提问者),2018-5-2 09:48

没有没有,上次是我看的有问题。混淆矩阵里面的计算很简单的,就是求个分数。但是我看代码好像计算很复杂啊…我用 sklearn 试过了是对的,但是和 TF 的 auc_precision_recall 对比区别很大。是我没搞清楚原理,所以也就不好意思给你发消息了。


M 丶 Sulayman,发表于 2018-5-2 16:18:51

您客气了,我只是想搞清楚问题,也确实是新手,所以有点着急、刨根问底啥的请别忘心里去,完全没有别的意思。您肯跟花时间我讨论我感激还来不及呢,来这儿也是想通过这种方式学习,没有人讨论提高才是失败、失望。无论对错成败都谢谢您!


yunhai_luo,发表于 2018-5-3 01:53

非常感谢您的回复!看到你更新回复的 timestamp,更是被你感动了!(Do not tell me if you have jetlag with me.)

吐槽下论坛的通知系统,在楼层里的点评没有通知,所以不能及时看到在楼层里面的回复。

我认真看了你的更新回复。

  1. 很可能如你所说:就是这个 1.6.0 版本下,计算 PR-AUC 时,利用 trapezoidal rule 所导致的问题。所以造成得到的图形比较奇怪。因为我查看了 tensorflow 代码库,DNNClassifier 的确是用默认的 trapezoidal rule 来计算 PR-AUC 值的(代码来源见以下链接)。
  1. 我会先更新到 1.8.0,然后再尝试(可能会过一段时间,因为 google ml-engine 的确太贵了),如果有新的结果,我会再来更新的。

  2. 你更新前的关于 AUC-PR 大于 0.5 的问题:其实正如你在这个贴子 6 楼 https://www.tensorflowers.cn/t/355#pid1353 的回复” AUC_PR 值似乎没有特别直观的类似 AUC_ROC 那样的概率解释”,所以 PR-AUC 没有必须要大于 0.5 这么一说,也没有取反 (即用 1 减所得的结果) 这么一说。例子如下,下图为均匀分布 (Balanced) 和不均匀分布 (Imbalanced) 的数据集的 PR-AUC 数据比较:

上图来源:https://classeval.wordpress.com/simulation-analysis/roc-and-precision-recall-with-imbalanced-datasets/

正如这个表格和这篇博文所说:

  • 只有均匀分布的数据集其 ROC-AUC 值和 PR-AUC 值是一致的。(即 PR-AUC 的结果 0.5 和 ROC-AUC 一样也表示随机猜想 (random guess),所以对于这种均匀的数据集,我们就直接用 ROC-AUC 来判断模型的好坏就好了。)
  • 而对于分类为不均匀的数据集:其 PR-AUC 和 ROC-AUC 会有很大的不同,如例子中,0.09 的 PR-AUC 值可能代表随机猜想 (和 0.5 的 ROC-AUC 值一样的意义),而 0.51 的 PR-AUC 可能已经表示一个很不错的结果了 (相当于 ROC-AUC=0.84),所以对于不均匀的数据,我们绝不能简单的把某个 PR-AUC 值想像成和同值的 ROC-AUC 的意义相同 (如上表中 0.23 的 PR-AUC,其实表示一个也还不错的结果)。

再次表示感谢!


rz828,发表于 2018-5-3 16:37:38

-1. 确有时差,不必客气。楼主看的很仔细,点赞(表情里没有点赞?)
0. 我大概没有出现过点评没通知的情况,如果确有 bug 应该可以去反馈。顺便问一下,楼主知道这里怎么 @ 人吗?
2. 有结果请不吝更新,共同学习。另外 1.8.0 的 DNNClassifier 也没用 “careful_interpolation”,如果楼主有代码实现(hook 等)也请分享一下,先行谢过。
3. 非常感谢你的解释,对正确理解使用 PR-AUC 真的很有帮助。


yunhai_luo,发表于 2018-5-4 00:54

yunhai_luo:自从上次我们找到问题的来源后,我就针对这个问题,对 tensorflow 库提交了一个 合并请求,在经过一个月的等待后,结果如下:
tensorflow 社区人员也讨论过将 careful_interpolation 这种方式作为计算 AUC 的默认方式,但是最后发现这依然会造成向后兼容的问题。所以这个 AUC 的计算问题暂时不会修改。
同时社区开发人员也给我提出了另一个方法,就是在 tensorflow/contrib/estimator/python/estimator/head.py 里面作改动。但是我经过思考以后,觉得可能还是不这样做比较好。
所以我们还是期待这个问题能在下一个大的版本里面得到修正吧。

最后还是感谢你能发现这个问题的线索!


rz828,发表于 2018-6-5 10:25:37

多谢楼主的反馈,确实很有帮助!也非常感谢楼主提出并维护这样一个好问题。


yunhai_luo,发表于 2018-6-5 12:16