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

简介: 指标在机器学习或深度学习领域扮演着相当重要的角色。我们从根据问题选择指标开始,以了解特定模型的基线分数。 在本博客中,我们研究了多标签分类的最佳和最常用的指标,以及它们的不同之处。接下来,让我们深入了解什么是多标签分类,以防万一您需要它。 如果我们有关于狗的特征的数据,我们可以预测它属于哪个品种和宠物类别。在物体检测的情况下,多标签分类为我们提供了图像中所有对象的列表,如下图所示。我们可以看到,分类器检测到图像中的 3 个对象。 如果训练对象的总数为 4 个,则可以将其表示成如下列表[1 0 1 1](对应的对象为[狗、人、自行车、卡车])。这种分类被称为多标签分类。

前言


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

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

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

网络异常,图片无法展示
|


多类别(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,中文名为对数损失,又名逻辑损失或交叉熵损失。

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

其公式为:

loss(x,y)=−1C∗∑iy[i]∗log⁡(1(1+exp⁡(−x[i])))+(1−y[i])∗log⁡(exp⁡(−x[i])(1+exp⁡(−x[i])))loss(x, y) = - \frac{1}{C} * \sum_i y[i] * \log( \frac{1}{(1 + \exp(-x[i]))}) + (1-y[i]) * \log\left(\frac{\exp(-x[i])}{(1 + \exp(-x[i]))}\right)loss(x,y)=C1iy[i]log((1+exp(x[i]))1)+(1y[i])log((1+exp(x[i]))exp(x[i]))

其中 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\}。i{0,,x.nElement()1},y[i]{0,1}

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

首先,你需要将标签进行二值化处理,然后对每一列使用对数损失。最后,您可以取每列中对数损失的平均值,这也被称为平均列对数损失。当然,您还可以通过其他方式实现。

代码示例如下:

import numpy as np
# y_test = np.random.randint(0,2,(3,4))
# y_pred = np.random.random((3,4))
y_test = 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(f"{y_test}\n{y_pred}")
def sigmoid(z):
    return 1 / (1 + np.exp(-z))
def log_loss(y_test,y_pred):
    y_test = y_test.astype(np.float16)
    y_pred = y_pred.astype(np.float16)
    N,M = y_test.shape
    print(f"列-M:{M}, 行-N:{N}")
    a=[]
    for m in range(M):
        loss=0
        for i in range(N):
            loss -= (y_test[i,m]*np.log(sigmoid(y_pred[i,m])))+((1.0-y_test[i,m])*np.log(1.0-sigmoid(y_pred[i,m])))
        loss = loss/N
        a.append(round(loss,8))
    print(f"每一列的损失如下:{a}")
    return np.mean(a)
a = log_loss(y_test,y_pred)
print(a)
复制代码


运行结果如下:

[[1 1 0 0]
 [0 1 0 1]]
[[0.2 0.5 0.  0. ]
 [0.1 0.5 0.  0.8]]
列-M:4, 行-N:2
每一列的损失如下:[0.67131506, 0.47402386, 0.69314718, 0.53217012]
0.592664055
复制代码


上面的代码是对每一列求对数损失,我们也可以针对每一个样本(行)求对数损失,结果一致。具体代码如下:

import numpy as np
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))  # [batch_size,num_class]
    loss = t_loss.mean(axis=-1)  # 得到每个样本的损失值
    print(f"每个样本的损失如下:{loss}")
    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.5926
复制代码


运行结果如下:

每个样本的损失如下:[-0.61462755 -0.57068037]
0.5926539631803737
复制代码


对应的PyTorch实现代码如下:

import torch
import torch.nn as nn
y_true = torch.tensor([[1, 1, 0, 0], [0, 1, 0, 1]],dtype=torch.int16)
y_pred = torch.tensor([[0.2, 0.5, 0, 0], [0.1, 0.5, 0, 0.8]],dtype=torch.float32)
loss = nn.MultiLabelSoftMarginLoss(reduction='mean')
print(loss(y_pred, y_true)) # 0.5926
复制代码


运行结果如下:

tensor(0.5927)
复制代码


总结


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


参考文档


相关文章
|
6月前
|
机器学习/深度学习 安全
一文读懂分类模型评估指标
模型评估是深度学习和机器学习中非常重要的一部分,用于衡量模型的性能和效果。本文将逐步分解混淆矩阵,准确性,精度,召回率和F1分数。
544 1
|
6月前
|
vr&ar
垃圾分类模型想上maixpy(2)
1-1 关于模型部署,MaixPy文档的这一部分中可能有些有用的参考:部署模型到 Maix-I(M1) K210 系列开发板 - Sipeed Wiki 。 实际用数字图片进行测试时,手写数字识别的模型无法产生正确的输出。
165 1
|
6月前
|
编解码 并行计算 TensorFlow
垃圾分类模型想上maixpy(3)
1-5 对比Params与模型文件实际体积。 结果:模型实际大小与Params大小是可以对上的,参数应该是以float32存储。我把“字节”与“位”搞混了,应该是一个字节为8位。
77 0
|
机器学习/深度学习
评分是机器学习领域中的一种评估模型性能的指标
评分是机器学习领域中的一种评估模型性能的指标
97 1
构建一个分类模型,如何选择合适的损失函数和评估指标
构建一个分类模型,如何选择合适的损失函数和评估指标
|
机器学习/深度学习 搜索推荐 测试技术
【王喆-推荐系统】评估篇-(task2)推荐模型评估指标
准确率 (Accuracy) 是指分类正确的样本占总样本个数的比例。
1430 0
【王喆-推荐系统】评估篇-(task2)推荐模型评估指标
|
1月前
|
机器学习/深度学习 算法
五、分类模型
五、分类模型
21 0
|
3月前
|
自然语言处理
评估数据集CGoDial问题之数据集中包含哪些基线模型
评估数据集CGoDial问题之数据集中包含哪些基线模型
|
6月前
|
IDE TensorFlow 开发工具
垃圾分类模型想上maixpy(1)
maixpy笔记 Something 上下拉。应该就是强制高、低电平,可以避免不确定的状态。 模型区没有文件系统,模型之间烧录在指定地址。
124 0
|
机器学习/深度学习 测试技术
机器学习中评估分类模型性能的10个重要指标(一)
机器学习中评估分类模型性能的10个重要指标(一)
306 0
机器学习中评估分类模型性能的10个重要指标(一)
下一篇
无影云桌面