tensorflow AUC & streaming_auc

对AUC和它计算方法的解释可以看这位大佬的文章:https://www.cnblogs.com/peizhe123/p/5081559.html,本文主要讲tensorflow中计算AUC的API实现。

tf.contrib.metrics.streaming_auc & tf.metrics.auc

在tf1.x中有两个计算AUC的API: tf.contrib.metrics.streaming_auc()tf.metrics.auc(),在使用tf.contrib.metrics.streaming_auc()的时候会有warning提示这个API将要被删除,建议用另外一个。在tf1.14源码中可以看到两个API的实现是完全一样的:

@deprecated(None, 'Please switch to tf.metrics.auc. Note that the order of '
            'the labels and predictions arguments has been switched.')
def streaming_auc(predictions,
                  labels,
                  weights=None,
                  num_thresholds=200,
                  metrics_collections=None,
                  updates_collections=None,
                  curve='ROC',
                  name=None):
# 中间省略一万行注释

  return metrics.auc(
      predictions=predictions,
      labels=labels,
      weights=weights,
      metrics_collections=metrics_collections,
      num_thresholds=num_thresholds,
      curve=curve,
      updates_collections=updates_collections,
      name=name)
计算方法

以下是对API源码思路的一个简略版解释,有很多地方不严谨(比如thresholds其实是在(0,1)采样n-2个值,加上头尾的-1e-7和1+1e-7一共得到n个值),所以还是推荐大家有余力的话去看源码。
我们日常手写代码计算AUC的时候可能会用它的等价公式来做:
A U C = ∑ i ∈ p o s i t i v e C l a s s r a n k i − M ( 1 + M ) 2 M × N AUC = \frac{\sum_{i \in positiveClass}rank_i-\frac{M(1+M)}{2}}{M \times N} AUC=M×NipositiveClassranki2M(1+M)
tensorflow中没有采用这种方法,而是“画”出了ROC曲线,然后用某种近似方法求出曲线下的面积得到AUC。API默认的面积求法是梯形法。
tensorflow “画”ROC曲线的方法也很简单,就是得到了一系列曲线上点的坐标。例如下面这张偷来的 ROC曲线图,得到图上20个点的坐标后,就很容易用梯形法计算出曲线下的面积,得到近似的AUC。
那么ROC曲线上点的坐标是怎么画出来的呢?首先tensorflow会在[0, 1]范围内均匀采样n个值,将这n个值作为预测正负样本的阈值。例如采样20个值,得到的阈值集合就是{0.05, 0.1, 0.15, …, 1},对每个阈值分别计算出当前阈值下的tpr和fpr,(tpr, fpr)就是ROC曲线上的点了。
在调用metrics.auc()时,采样的阈值个数,也就是ROC曲线上点的个数,是作为参数传入的,即num_thresholdsnum_thresholds默认值为200,这个值越大,“画”出的点就越多,ROC曲线就越精细,得到的AUC值就越精确。

使用中的问题

一开始促使我去翻源码的是我在用tf.contrib.metrics.streaming_auc()这个API的时候发现的一个奇怪的现象。我的模型训练过程中loss的波动很大,但是训练集的AUC一直在稳步上升,稳到从来没有波动的那种。再看到streaming_auc这个名字,就猜想tf计算AUC是一个累计的过程,翻了下源码确实是这样。
如前所述,tf在计算AUC前,会先计算在每个threshold下的tpr和fpr,而计算tpr和fpr需要通过prediction和label得到混淆矩阵,累计就发生在计算混淆矩阵时。
以混淆矩阵中的true positive为例:

  values = {}
  update_ops = {}

  if 'tp' in includes:
    true_p = metric_variable(
        [num_thresholds], dtypes.float32, name='true_positives')
    is_true_positive = math_ops.to_float(
        math_ops.logical_and(label_is_pos, pred_is_pos))
    if weights_tiled is not None:
      is_true_positive *= weights_tiled
    update_ops['tp'] = state_ops.assign_add(true_p,
                                            math_ops.reduce_sum(
                                                is_true_positive, 1))
    values['tp'] = true_p

计算true positive的过程中,tf维护一个不参与训练的局部变量true_p和一个opupdate_ops['tp']true_p返回给metrics.auc()用来进行tpr和fpr的计算,而update_ops同时也是metrics.auc()的返回值之一,用来对每个threshold下的true_p进行累加。所以metrics.auc()的计算结果并不是每个batch的AUC的均值,而是在每个step运行时,把当前batch和以前所有batch的tp, fp, fn, tn进行累加,重进计算了tpr和fpr,也就是每个step都会重新“画”一遍ROC曲线。
这也就引出了使用中的几个问题:

  1. 因为在计算混淆矩阵时定义了局部变量,所以在运行前必须进行初始化,也就是调用tf.local_variables_initializer()
  2. metrics.auc()的返回值有两个,aucupdate_ops,在运行时要把update_ops也加进去,否则tf不会更新混淆矩阵的值,计算出的AUC一直为0。
  3. 在一次代码运行过程中AUC的值会一直累计,所以如果你的代码在同一个session里跑训练集和测试集的话,那么测试集的AUC还是会在训练集的基础上进行累计。对此可以在跑完训练集或者跑完一个epoch或者whatever之后重新对局部变量进行初始化,但前提是要确保你的代码其他地方没有用到局部变量。有大佬专门写了个解决方案指路
    关于这个问题,个人觉得,还是算出测试集的prediction之后,把session关掉,用sklearn算AUC,比较香。
  4. tf中局部变量在saver.save()的时候是不写入checkpoint的,所以模型restore之后计算AUC不会再累计。这个属实也没什么办法……如果你的模型训练得比较好那新的epoch或者增量的时候重新开始计算AUC虽然会和累计的有差距,但也不会很差所以还是要保持一颗平常心
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值