多标签分类场景下的模型评估指标

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

指标在机器学习或深度学习领域扮演着相当重要的角色。我们从根据问题选择指标开始,以了解特定模型的基线分数。 在本博客中,我们研究了多标签分类的最佳和最常用的指标,以及它们的不同之处。

接下来,让我们深入了解什么是多标签分类,以防万一您需要它。 如果我们有关于狗的特征的数据,我们可以预测它属于哪个品种和宠物类别。

在物体检测的情况下,多标签分类为我们提供了图像中所有对象的列表,如下图所示。我们可以看到,分类器检测到图像中的 3 个对象。 如果训练对象的总数为 4 个,则可以将其表示成如下列表[1 0 1 1](对应的对象为[狗、人、自行车、卡车])。这种分类被称为多标签分类。

image.png

多类别(Multiclass)分类和多标签(Multilabel)分类的区别:

  • 多类别分类: 超过两个类别的分类任务。多类别分类假设每个样本属于且仅属于一个标签,例如一个水果可以是苹果或者是桔子,但是不能同时属于两者。
  • 多标签分类: 给每个样本分配一个或多个标签。例如一个新闻可以既属于体育类,也属于文娱类。

多标签分类评估指标

适用于多标签分类的最常见的指标如下:

  1. Precision at k
  2. Avg precision at k
  3. Mean avg precision at k
  4. Sampled F1 Score
  5. Log Loss

让我们来看看这些指标的详细信息。

Precision at k (P@K)

给定实际类别和预测类别的列表,Precision@K定义为仅考虑前k个元素正确预测的数量除以每个预测类别的前k个元素。值的范围在 0 到 1 之间。

代码示例如下:

def patk(actual, pred, k):
    #we return 0 if k is 0 because 
    #   we can't divide the no of common values by 0 
    if k == 0:
        return 0

    #taking only the top k predictions in a class 
    k_pred = pred[:k]

    # taking the set of the actual values 
    actual_set = set(actual)
    print(list(actual_set))
    # taking the set of the predicted values 
    pred_set = set(k_pred)
    print(list(pred_set))
    
    # 求预测值与真实值得交集
    common_values = actual_set.intersection(pred_set)
    print(common_values)

    return len(common_values)/len(pred[:k])

# defining the values of the actual and the predicted class
y_true = [1 ,2, 0]
y_pred = [1, 1, 0]

if __name__ == "__main__":
    print(patk(y_true, y_pred,3))
复制代码

运行结果如下:

K:3,真实值:{0, 1, 2}, 预测值:{0, 1}, 交集:{0, 1}, P@k:0.6666666666666666
0.6666666666666666
复制代码

Average Precision at K (AP@K)

它被定义为 k = 1 到 k 时, 所有Precision@K的平均值。 为了更清楚,让我们看一下代码。 值的范围在 0 到 1 之间。

import numpy as np

def apatk(acutal, pred, k):
    #creating a list for storing the values of precision for each k 
    precision_ = []
    for i in range(1, k+1):
        #calculating the precision at different values of k 
        #      and appending them to the list 
        precision_.append(patk(acutal, pred, i))

    #return 0 if there are no values in the list
    if len(precision_) == 0:
        return 0 

    #returning the average of all the precision values
    return np.mean(precision_)

#defining the values of the actual and the predicted class
y_true = [[1,2,0,1], [0,4], [3], [1,2]]
y_pred = [[1,1,0,1], [1,4], [2], [1,3]]

if __name__ == "__main__":
    for i in range(len(y_true)):
        for j in range(1, 4):
            print(
                f"""
                y_true = {y_true[i]}
                y_pred = {y_pred[i]}
                AP@{j} = {apatk(y_true[i], y_pred[i], k=j)}
                """
            )
        print("-----------")
            
复制代码

运行结果如下:

K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0

                y_true = [1, 2, 0, 1]
                y_pred = [1, 1, 0, 1]
                AP@1 = 1.0
                
K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:0.5

                y_true = [1, 2, 0, 1]
                y_pred = [1, 1, 0, 1]
                AP@2 = 0.75
                
K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:0.5
K:3,真实值:{0, 1, 2}, 预测值:{0, 1}, 交集:{0, 1}, P@k:0.6666666666666666

                y_true = [1, 2, 0, 1]
                y_pred = [1, 1, 0, 1]
                AP@3 = 0.7222222222222222
                
-----------
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0

                y_true = [0, 4]
                y_pred = [1, 4]
                AP@1 = 0.0
                
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0
K:2,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5

                y_true = [0, 4]
                y_pred = [1, 4]
                AP@2 = 0.25
                
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0
K:2,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5
K:3,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5

                y_true = [0, 4]
                y_pred = [1, 4]
                AP@3 = 0.3333333333333333
                
-----------
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0

                y_true = [3]
                y_pred = [2]
                AP@1 = 0.0
                
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:2,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0

                y_true = [3]
                y_pred = [2]
                AP@2 = 0.0
                
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:2,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:3,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0

                y_true = [3]
                y_pred = [2]
                AP@3 = 0.0
                
-----------
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0

                y_true = [1, 2]
                y_pred = [1, 3]
                AP@1 = 1.0
                
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5

                y_true = [1, 2]
                y_pred = [1, 3]
                AP@2 = 0.75
                
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5
K:3,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5

                y_true = [1, 2]
                y_pred = [1, 3]
                AP@3 = 0.6666666666666666
                
-----------
复制代码

Mean avg precision at k (MAP@K)

整个训练数据上 AP@k 的所有值的平均值称为 MAP@k。这有助于我们准确表示整个预测数据的准确性。值的范围在 0 到 1 之间。

AP@k衡量的是学出来的模型在每个类别上的好坏,MAP@k衡量的是学出的模型在所有类别上的好坏 代码示例如下:

import numpy as np

def mapk(acutal, pred, k):

    #creating a list for storing the Average Precision Values
    average_precision = []
    #interating through the whole data and calculating the apk for each 
    for i in range(len(acutal)):
        ap = apatk(acutal[i], pred[i], k)
        print(f"AP@k: {ap}")
        average_precision.append(ap)

    #returning the mean of all the data
    return np.mean(average_precision)

#defining the values of the actual and the predicted class
y_true = [[1,2,0,1], [0,4], [3], [1,2]]
y_pred = [[1,1,0,1], [1,4], [2], [1,3]]

if __name__ == "__main__":
    print(mapk(y_true, y_pred,3))
复制代码

运行结果如下:

K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:0.5
K:3,真实值:{0, 1, 2}, 预测值:{0, 1}, 交集:{0, 1}, P@k:0.6666666666666666
AP@k: 0.7222222222222222
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0
K:2,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5
K:3,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5
AP@k: 0.3333333333333333
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:2,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:3,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
AP@k: 0.0
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5
K:3,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5
AP@k: 0.6666666666666666
0.4305555555555556
复制代码

这里分数很差,因为预测集有很多错误。

Sampled F1 Score

该指标先计算数据中每个实例的 F1 分数,然后计算 F1 分数的平均值。

我们将在代码中使用 sklearn 的实现。计算 F1 分数的文档请参考这里。 值的范围在 0 到 1 之间。

我们首先将数据转换为0-1格式,然后对其计算F1分数。

代码示例如下:

from sklearn.metrics import f1_score
from sklearn.preprocessing import MultiLabelBinarizer

def f1_sampled(actual, pred):
    # converting the multi-label classification to a binary output
    mlb = MultiLabelBinarizer()
    actual = mlb.fit_transform(actual)
    pred = mlb.fit_transform(pred)
    print(f"多标签二值化后的标签值:{mlb.classes_}")
    print(f"真实值:\n{actual}  \n预测值:\n{pred}")
    # fitting the data for calculating the f1 score 
    f1 = f1_score(actual, pred, average = "samples")
    return f1

# defining the values of the actual and the predicted class
# 总共有五个类别
y_true = [[1,2,0,1], [0,4], [3], [1,2]]
y_pred = [[1,1,0,1], [1,4], [2], [1,3]]

if __name__ == "__main__":
    print(f1_sampled(y_true, y_pred))

复制代码

运行结果如下:

多标签二值化后的标签值:[0 1 2 3 4]
真实值:
[[1 1 1 0 0]
 [1 0 0 0 1]
 [0 0 0 1 0]
 [0 1 1 0 0]]  
预测值:
[[1 1 0 0 0]
 [0 1 0 0 1]
 [0 0 1 0 0]
 [0 1 0 1 0]]
0.45
复制代码

我们知道 F1 分数介于 0 和 1 之间,这里我们得到了 0.45 的分数。 这是因为预测集不好。 如果我们有更好的预测集,该值将更接近 1。

Log Loss

Log Loss,中文名为对数损失,又名逻辑损失或交叉熵损失。

首先,你可以将目标转换为0-1格式,然后对每一列使用对数损失。 最后,您可以取每列中对数损失的平均值。这也被称为平均列对数损失。

其公式为:

l o s s ( x , y ) = 1 C i y [ i ] log ( ( 1 + exp ( x [ i ] ) ) 1 ) + ( 1 y [ i ] ) log ( exp ( x [ i ] ) ( 1 + exp ( x [ i ] ) ) ) loss(x, y) = - \frac{1}{C} * \sum_i y[i] * \log((1 + \exp(-x[i]))^{-1}) + (1-y[i]) * \log\left(\frac{\exp(-x[i])}{(1 + \exp(-x[i]))}\right)

其中 i { 0 ,    ,    x.nElement ( ) 1 } , y [ i ] { 0 ,    1 } i \in \left\{0, \; \cdots , \; \text{x.nElement}() - 1\right\}, y[i] \in \left\{0, \; 1\right\}。

公式中x相当于预测值,y相当于真实值。

这种误差损失衡量方式其实就是在逻辑回归中用来衡量预测概率与真实标签之间误差的方法。

代码示例如下:

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def compute_loss_v1(y_true, y_pred):
    t_loss = y_true * np.log(sigmoid(y_pred)) + (1 - y_true) * np.log(1 - sigmoid(y_pred))
    loss = t_loss.mean(axis=-1)  # 得到每个样本的损失值
    return -loss.mean()  # 返回整体样本的损失均值

if __name__ == '__main__':
    y_true = np.array([[1, 1, 0, 0], [0, 1, 0, 1]])
    y_pred = np.array([[0.2, 0.5, 0, 0], [0.1, 0.5, 0, 0.8]])
    print(compute_loss_v1(y_true, y_pred)) 
复制代码

运行结果如下:

0.5926539631803737
复制代码

总结

针对多标签分类场景问题,我们通常使用 MAP@K 、 Sampled F1 Score或Log Loss来为您的问题设置评估指标。

参考文档

猜你喜欢

转载自juejin.im/post/7016681258340057102