[0.3] 续--Tensorflow踩坑记之tf.metrics

【续】--Tensorflow踩坑记之tf.metrics

欠下的帐总归还是要还的,之前一直拖着,总是懒得写tf.metrics这个API的一些用法,今天总算是克服了懒癌,总结一下tf.metrics遇到的一些坑。

插一句闲话,这一次的博客基本上用的都是 Jupyter,感觉一级好用啊。可以一边写代码,一边记markdown,忍不住上一张效果图,再次欢迎大噶去我的Github上看一看,而且Github支持 jupyter notebook 显示,真得效果很好。

jupyter

在这篇伪Tensorflow-tf-metrics中,澜子介绍了tf.metrics中涉及的一些指标和概念,包括:精确率(precision),召回率(recall),准确率(accuracy),AUC,混淆矩阵(confusion matrix)。下面先给出官方的API文档,看看这个模块中都有哪些隐藏秘笈。

看了官方文档之后,大噶可能会发现其中有好多可以调用的函数,不仅有precision / accuracy/ auc/ recall,还有precision_at_k / recall_at_k,更有precision_at_thresholds/ precision_at_top_k/ sparse_precision_at_k...天啦噜,这都是什么呀,澜子已经彻底晕了,到底要怎么用啊(眼冒金星中)。别急,让我一个坑一个坑地告诉你。

划重点

首先,这篇文章是受到Ronny Restrepo的启发,
这是一篇很好的文章,将tf.metrics.accuracy()讲解滴很清楚,本文就模仿他的思路,验证一下precision的计算。

精确率的计算公式

Precision = \frac{truePositive}{truePositive + falsePositive}

让我们先造点数据,传统算算看

import tensorflow as tf
import numpy as np

labels = np.array([[1,1,1,0],
                   [1,1,1,0],
                   [1,1,1,0],
                   [1,1,1,0]], dtype=np.uint8)

predictions = np.array([[1,0,0,0],
                        [1,1,0,0],
                        [1,1,1,0],
                        [0,1,1,1]], dtype=np.uint8)

n_batches = len(labels)
# First,calculate precision over entire set of batches 
# using formula mentioned above
pred_p = (predictions > 0).sum()
# print(pred_p)
true_p = (labels*predictions > 0).sum()
# print(true_p)
precision = true_p / pred_p
print("Precision :%1.4f" %(precision))

上述方法的问题

由于硬件方面的一些限制,导致此方法不能扩展到大型数据集,比如当数据集很大时,就无法一次性适应内存。
因而,为了使其可扩展,我们希望使评估指标能够逐步更新,每批新的预测和标签。 为此,我们需要跟踪两个值。

  • 正确预测的正样本数量
  • 预测样本中所有正样本的数量

所以我们要这么做

# Initialize running variables
N_TRUE_P = 0
N_PRED_P = 0

# Specific steps
# Create running variables
N_TRUE_P = 0
N_PRED_P = 0

def reset_running_variables():
    """ Resets the previous values of running variables to zero """
    global N_TRUE_P, N_PRED_P
    N_TRUE_P = 0
    c = 0

def update_running_variables(labs, preds):
    global N_TRUE_P, N_PRED_P
    N_TRUE_P += ((labs * preds) > 0).sum()
    N_PRED_P += (preds > 0).sum()

def calculate_precision():
    global N_TRUE_P, N_PRED_P
    return float (N_TRUE_P) / N_PRED_P

怎么用上面的函数呢?

接下来的两个例子,给出了运用的具体代码,并且可以更好滴帮助我们理解tf.metrics.precision()的计算逻辑以及对应输出所代表的含义

样本整体准确率(直接计算)

# Overall precision
reset_running_variables()

for i in range(n_batches):
    update_running_variables(labs=labels[i], preds=predictions[i])

precision = calculate_precision()
print("[NP] SCORE: %1.4f" %precision)

批次准确率(直接计算)

# Batch precision
for i in range(n_batches):
    reset_running_variables()
    update_running_variables(labs=labels[i], preds=predictions[i])
    prec = calculate_precision()
    print("- [NP] batch %d score: %1.4f" %(i, prec))
[NP] batch 0 score: 1.0000
[NP] batch 1 score: 1.0000
[NP] batch 2 score: 1.0000
[NP] batch 3 score: 0.6667

不要小瞧这两个变量和三个函数

上面说了这么多,感觉没有tensorflow的什么事哇,别急,先看一个tensorflow的官方文档

放一个官方的解释

The precision function creates two local variables,
true_positives and false_positives, that are used to compute the precision. This value is ultimately returned as precision, an idempotent operation that simply divides true_positives by the sum of true_positives and false_positives.
For estimation of the metric over a stream of data, the function creates an update_op operation that updates these variables and returns the precision.

两个变量和 tf.metrics.precision()的关系

官方文档提及的two local variablestrue_postivesfalse_positives分别对应上文定义的两个变量。

  • true_postives -- N_TRUE_P
  • false_postives -- N_PRED_P - N_TRUE_P

三个函数和头大的update_op

官方文档提及的update_opprecision分别对应上文定义的两个函数

  • precision--calculate_precision()
  • update_op--update_running_variables()

大家不要被这个update_op搞晕,其实从字面来理解就是一个变量更新的操作,上文的代码中,就是通过reset_running_variables()的位置来决定何时对变量进行更新,其实就是对应于tf.variables_initializer()。我之所以一直用错这个API,是因为我将tf.variables_initializer()放在了错误的位置,导致变量没有按照我的预期正常更新,进而结果一直不正确。具体看看tensorflow是怎么实现的吧。

Overall precision using tensorflow

# Overall precision using tensorflow
import tensorflow as tf

graph = tf.Graph()
with graph.as_default():
    # Placeholders to take in batches onf data
    tf_label = tf.placeholder(dtype=tf.int32, shape=[None])
    tf_prediction = tf.placeholder(dtype=tf.int32, shape=[None])

    # Define the metric and update operations
    tf_metric, tf_metric_update = tf.metrics.precision(tf_label,
                                                      tf_prediction,
                                                      name="my_metric")

    # Isolate the variables stored behind the scenes by the metric operation
    running_vars = tf.get_collection(tf.GraphKeys.LOCAL_VARIABLES, scope="my_metric")

    # Define initializer to initialize/reset running variables
    running_vars_initializer = tf.variables_initializer(var_list=running_vars)


with tf.Session(graph=graph) as session:
    session.run(tf.global_variables_initializer())

    # initialize/reset the running variables
    session.run(running_vars_initializer)

    for i in range(n_batches):
        # Update the running variables on new batch of samples
        feed_dict={tf_label: labels[i], tf_prediction: predictions[i]}
        session.run(tf_metric_update, feed_dict=feed_dict)

    # Calculate the score
    score = session.run(tf_metric)
    print("[TF] SCORE: %1.4f" %score)

[TF] SCORE: 0.8889

Batch precision using tensorflow

# Batch precision using tensorflow
with tf.Session(graph=graph) as session:
    session.run(tf.global_variables_initializer())

    for i in range(n_batches):
        # Reset the running variables
        session.run(running_vars_initializer)

        # Update the running variables on new batch of samples
        feed_dict={tf_label: labels[i], tf_prediction: predictions[i]}
        session.run(tf_metric_update, feed_dict=feed_dict)

        # Calculate the score on this batch
        score = session.run(tf_metric)
        print("[TF] batch %d score: %1.4f" %(i, score))

[TF] batch 0 score: 1.0000
[TF] batch 1 score: 1.0000
[TF] batch 2 score: 1.0000
[TF] batch 3 score: 0.6667

再次划重点

大噶一定要注意

session.run(running_vars_initializer)
score = session.run(tf_metric)

这两行代码在计算整体样本精确度以及批次精确度所在位置的不同。
澜子第一次的时候由于粗心,并没有注意两段代码的不同,才会导致tf计算结果普通计算结果不一致

还需要注意的点

不要在一个sess.run()里面同时调用tf_metrictf_metric_update下面的代码是错误的示范

_ , score = session.run([tf_metric_update,tf_metric],\
                        feed_dict=feed_dict)

update_op究竟返回了什么捏

此处参考了
stackoverflow的一个回答

具体代码如下

rel = tf.placeholder(tf.int64, [1,3])
rec = tf.constant([[7, 5, 10, 6, 3, 1, 8, 12, 31, 88]], tf.int64) 
precision, update_op = tf.metrics.precision_at_k(rel, rec, 10)

sess = tf.Session()
sess.run(tf.local_variables_initializer())

stream_vars = [i for i in tf.local_variables()]
#Get the local variables true_positive and false_positive

print("[PRECSION_1]: ",sess.run(precision, {rel:[[1,5,10]]})) # nan
#tf.metrics.precision maintains two variables true_positives 
#and  false_positives, each starts at zero.
#so the output at this step is 'nan'

print("[UPDATE_OP_1]:",sess.run(update_op, {rel:[[1,5,10]]})) #0.2
#when the update_op is called, it updates true_positives 
#and false_positives using labels and predictions.

print("[STREAM_VARS_1]:",sess.run(stream_vars)) #[2.0, 8.0]
# Get true positive rate and false positive rate

print("[PRECISION_1]:",sess.run(precision,{rel:[[1,10,15]]})) # 0.2
#So calling precision will use true_positives and false_positives and outputs 0.2

print("[UPDATE_OP_2]:",sess.run(update_op,{rel:[[1,10,15]]})) #0.15
#the update_op updates the values to the new calculated value 0.15.

print("[STREAM_VARS_2]:",sess.run(stream_vars)) #[3.0, 17.0]

[STREAM_VARS_1]: [0.0, 0.0, 0.0, 0.0, 2.0, 8.0]
[PRECISION_1]: 0.2
[UPDATE_OP_2]: 0.15
[STREAM_VARS_2]: [0.0, 0.0, 0.0, 0.0, 3.0, 17.0]

tf.metrics.precision_at_k

上面的代码中,我们看到运用的是tf.metrics.precision_at_k()这个API,这里的k是什么呢?
首先,我们要理解一个概念,究竟什么是Precision at k,这里有两份资料,应该能很好地帮助你理解这个概念。
澜子就是看了这两份资料之后,理解了Precision at k的概念的。

然后我们来看看这个函数是怎么用的,第一步当然要先看看输入啦。

tf.metrics.precision_at_k(
    labels,
    predictions,
    k,
    class_id=None,
    weights=None,
    metrics_collections=None,
    updates_collections=None,
    name=None
)

我们重点关注labels,predictions,k这三个参数,应该可以满足日常简单地使用了。
labels,predictions,k的输入形式是什么样的呢?

闲话不说,直接看看上面的栗子。栗子中rel其实对应为labelsrec对应为predictions,那k又是什么意思呢?
划重点:这里的k表明你需要对多少个预测样本进行排序。这样说可能有一点抽象,给一个解释。

Precision@k = (Recommended items @k that are relevant) / (# Recommended items @k)

可以先去看一下Github,发现其实在tf.metrics.precision_at_k这个函数中,对于predictions会根据输入的k值进行top_k操作。
对应上面的代码中,当k=10,即对rec = tf.constant([[7, 5, 10, 6, 3, 1, 8, 12, 31, 88]], tf.int64)
所有的样本进行排序,进而在函数中实际运用的是rec样本数值从大到小排列的索引值。这样解释应该就能看懂上面代码的意思了。

后来,澜子又在

看到有人问怎么用tf.metrics.sparse_average_precision_at_k,就又去求是了一波,
还完成了知乎的技术首答以及stackoverflow上第一个赞
欢迎互粉知乎stackoverflow哇。下面给出栗子和简单解释啦。

import tensorflow as tf
import numpy as np

y_true = np.array([[2], [1], [0], [3], [0]]).astype(np.int64)
y_true = tf.identity(y_true)

y_pred = np.array([[0.1, 0.2, 0.6, 0.1],
                   [0.8, 0.05, 0.1, 0.05],
                   [0.3, 0.4, 0.1, 0.2],
                   [0.6, 0.25, 0.1, 0.05],
                   [0.1, 0.2, 0.6, 0.1]
                   ]).astype(np.float32)
y_pred = tf.identity(y_pred)

_, m_ap = tf.metrics.sparse_average_precision_at_k(y_true, y_pred, 3)

sess = tf.Session()
sess.run(tf.local_variables_initializer())

stream_vars = [i for i in tf.local_variables()]

tf_map = sess.run(m_ap)
print("TF_MAP",tf_map)

print("STREAM_VARS",(sess.run(stream_vars)))

tmp_rank = tf.nn.top_k(y_pred,3)

print("TMP_RANK",sess.run(tmp_rank))

简单解释一下

  • 首先y_true代表标签值(未经过one-hot),shape:(batch_size, num_labels) ,y_pred代表预测值(logit值) ,shape:(batch_size, num_classes)

  • 其次,要注意的是tf.metrics.sparse_average_precision_at_k中会采用top_k根据不同的k值y_pred进行排序操作 ,所以tmp_rank是为了帮助大噶理解究竟y_pred在函数中进行了怎样的转换。

  • 然后,stream_vars = [i for i in tf.local_variables()]这一行是为了帮助大噶理解 tf.metrics.sparse_average_precision_at_k创建的tf.local_varibles 实际输出值,进而可以更好地理解这个函数的用法。

  • 具体看这个例子,当k=1时,只有第一个batch的预测输出是和标签匹配的 ,所以最终输出为:1/6 = 0.166666 ;当k=2时,除了第一个batch的预测输出,第三个batch的预测输出也是和标签匹配的,所以最终输出为:(1+(1/2))/6 = 0.25。

P.S:在以后的tf版本里,将tf.metrics.average_precision_at_k替代tf.metrics.sparse_average_precision_at_k

简直超累的,目测是最近的最后一篇博客啦,有什么错误一定告诉我啦。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容