【Tensorflow】多分類問題的Precision、Recall和F1計算及Tensorflow實現

一、二分類問題的Precision、Recall、F1

網絡上關於Precision、Recall和F1的介紹有很多,因此這裏只作簡單回顧。
在二分類問題中,根據真實類別和預測類別的組合可以分爲四中情況,分別是TP(True Positive)、FP(False Positive)、TN(True Negative)、FN(False Negative)。如下圖:
在這裏插入圖片描述
那麼Precision表示所有被預測爲Positive樣本中,真正的Positive比例,即Precision=TPTP+FPPrecision=\frac{TP}{TP+FP}

而Recall表示所有Positive樣本中,被正確預測出來的比例,即Recall=TPTP+FNRecall=\frac{TP}{TP+FN}

F1則是Precision和Recall的調和平均數,即F1=2PRP+RF1 = \frac{2PR}{P+R}

二、多分類問題的Precision、Recall和F1

顯然,在多分類中沒有了TP、FN、FP、TN的定義了。那麼如何計算Precision、Recall和F1呢?下面介紹兩種方式。


2.1 方法一:micro average

該方法的核心思想是將多個類別分組,真正關心的類別分爲Positive組,其他分爲Negative組。例如在命名實體識別任務中,將所有實體的類別分爲Positive組,而其他標籤’O’單獨分爲Negative組。下面以多分類的混淆矩陣(confuse matrix)爲基礎計算Precision、Recall和F1值。
下圖是多分類問題的混淆矩陣:
在這裏插入圖片描述
其中,假設類別1和類別2是Positive組,類別3和類別4是Negative組。
那麼真正例(TP)可以定義爲Positive組預測正確的樣本數,那麼下圖紅色框矩陣的對角線和(黃色單元格的和)即爲真正例(TP)
在這裏插入圖片描述
TP=3+2=5

TP+FP表示所有被預測爲Positive的樣本數,顯然下圖紅框中所有數的和就是TP+FP
在這裏插入圖片描述
TP+FP=3+3+1+0+4+2+3+2=18

TP+FN表示所有真實爲Positive的樣本數,顯然下圖藍色框中所有數的和就是TP+FN
在這裏插入圖片描述
TP+FN=3+4+5+1+3+2+4+0=22

因此

Precision=TPTP+FP=518Precision = \frac{TP}{TP+FP} = \frac{5}{18}

Recall=TPTP+FN=522Recall = \frac{TP}{TP+FN} = \frac{5}{22}

F1=2PRP+R=14F1 = \frac{2PR}{P+R} = \frac{1}{4}


2.2 方法二:macro average

該方法的核心思想是,單獨計算各個正例的Precison、Recall和F1,然後對所有正例的Precison、Recall和F1求平均值。
這裏我們仍然將類別1和類別2作爲Positive組,然後按上面的mincro分別計算類別1和類別2的Precison、Recall和F1。

2.2.1 計算類別1的Precision、Recall和F1

TP = 3

TP + FP = 3 + 3 + 1 + 0 = 7

TP + FN = 3 + 4 + 5 + 1 = 13

Precision1=TPTP+FP=37Precision_1 = \frac{TP}{TP+FP} = \frac{3}{7}

Recall1=TPTP+FN=313Recall_1 = \frac{TP}{TP+FN} = \frac{3}{13}

F11=2PRP+R=310F1_1 = \frac{2PR}{P+R} = \frac{3}{10}

2.2.2 計算類別2的Precision、Recall和F1

TP = 2

TP + FP = 4 + 2 + 3 + 2 = 11

TP + FN = 3 + 2 + 4 + 0 = 9

Precision2=TPTP+FP=211Precision_2 = \frac{TP}{TP+FP} = \frac{2}{11}

Recall2=TPTP+FN=29Recall_2 = \frac{TP}{TP+FN} = \frac{2}{9}

F12=2PRP+R=15F1_2 = \frac{2PR}{P+R} = \frac{1}{5}

2.2.3 計算平均值

Precision=Precision1+Precision22=3/7+2/112=0.305Precision = \frac{Precision_1+Precision_2}{2} = \frac{3/7 + 2/11}{2} = 0.305

Recall=Recall1+Recall22=3/13+2/92=0.226Recall = \frac{Recall_1+Recall_2}{2} = \frac{3/13 + 2/9}{2} = 0.226

F1=F11+F122=3/10+1/52=0.25F1 = \frac{F1_1+F1_2}{2} = \frac{3/10 + 1/5}{2} = 0.25

三、Tensorflow實現

import tensorflow as tf
import numpy as np
from tensorflow.python.ops.metrics_impl import _streaming_confusion_matrix

1、計算混淆矩陣(confuse matrix)

labels = tf.convert_to_tensor(np.array([0,0,1,1,2,2]))
predictions = tf.convert_to_tensor(np.array([0,1,2,0,0,1]))
num_classes = 3  # 3個類別0,1,2
# cm是上一個batch的混淆矩陣,op是更新完當前batch的
cm,op = _streaming_confusion_matrix(labels,predictions,num_classes)
with tf.Session() as sess:
    sess.run(tf.local_variables_initializer()) # 初始化所有的local variables
    print(sess.run(cm))
    print(sess.run(op))
cm = op # 令cm是更新後的混淆矩陣
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
[[1. 1. 0.]
 [1. 0. 1.]
 [1. 1. 0.]]

2、計算micro average的precision、recall和f1

def safe_div(numerator, denominator):
    """安全除,分母爲0時返回0"""
    numerator, denominator = tf.cast(numerator,dtype=tf.float64), tf.cast(denominator,dtype=tf.float64)
    zeros = tf.zeros_like(numerator, dtype=numerator.dtype) # 創建全0Tensor
    denominator_is_zero = tf.equal(denominator, zeros) # 判斷denominator是否爲零
    return tf.where(denominator_is_zero, zeros, numerator / denominator) # 如果分母爲0,則返回零
def pr_re_f1(cm, pos_indices):
    num_classes = cm.shape[0]
    neg_indices = [i for i in range(num_classes) if i not in pos_indices]
    cm_mask = np.ones([num_classes, num_classes])
    cm_mask[neg_indices, neg_indices] = 0 # 將負樣本預測正確的位置清零零
    diag_sum = tf.reduce_sum(tf.diag_part(cm * cm_mask)) # 正樣本預測正確的數量

    cm_mask = np.ones([num_classes, num_classes])
    cm_mask[:, neg_indices] = 0 # 將負樣本對應的列清零
    tot_pred = tf.reduce_sum(cm * cm_mask) # 所有被預測爲正的樣本數量

    cm_mask = np.ones([num_classes, num_classes])
    cm_mask[neg_indices, :] = 0 # 將負樣本對應的行清零
    tot_gold = tf.reduce_sum(cm * cm_mask) # 所有正樣本的數量

    pr = safe_div(diag_sum, tot_pred)
    re = safe_div(diag_sum, tot_gold)
    f1 = safe_div(2. * pr * re, pr + re)
    
    return pr, re, f1
pr,re,f1 = pr_re_f1(cm,[0,1])
with tf.Session() as sess:
    sess.run(tf.local_variables_initializer())
    print(sess.run(pr))
    print(sess.run(re))
    print(sess.run(f1))
0.2
0.25
0.22222222222222224

3、計算macro average的precision、recall和f1

precisions, recalls, f1s, n_golds = [], [], [], []
pos_indices = [0,1] # 正例
# 計算每個正例的precison,recall和f1
for idx in pos_indices:
    pr, re, f1 = pr_re_f1(cm, [idx])
    precisions.append(pr)
    recalls.append(re)
    f1s.append(f1)
    cm_mask = np.zeros([num_classes, num_classes])
    cm_mask[idx, :] = 1
    n_golds.append(tf.to_float(tf.reduce_sum(cm * cm_mask)))
pr = tf.reduce_mean(precisions)
re = tf.reduce_mean(recalls)
f1 = tf.reduce_mean(f1s)
with tf.Session() as sess:
    sess.run(tf.local_variables_initializer())
    print(sess.run(pr))
    print(sess.run(re))
    print(sess.run(f1))
0.16666666666666666
0.25
0.2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章