一、二分類問題的Precision、Recall、F1
網絡上關於Precision、Recall和F1的介紹有很多,因此這裏只作簡單回顧。
在二分類問題中,根據真實類別和預測類別的組合可以分爲四中情況,分別是TP(True Positive)、FP(False Positive)、TN(True Negative)、FN(False Negative)。如下圖:
那麼Precision表示所有被預測爲Positive樣本中,真正的Positive比例,即。
而Recall表示所有Positive樣本中,被正確預測出來的比例,即。
F1則是Precision和Recall的調和平均數,即。
二、多分類問題的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
因此
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
2.2.2 計算類別2的Precision、Recall和F1
TP = 2
TP + FP = 4 + 2 + 3 + 2 = 11
TP + FN = 3 + 2 + 4 + 0 = 9
2.2.3 計算平均值
三、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