更新于 2026年6月29日

3.13 多标签分类#

「第3.5.5节 Softmax回归原理:从逻辑回归到多分类模型」内容中,我们介绍了在单标签分类问题中模型损失的度量方法,即交叉熵损失函数。但是在实际应用中我们还会遇到多标签分类(Multi-Label Class)的情况,即对于每个样本来说都可能存在不止一个正确标签的情况。例如在文本分类这一场景中,同一条文本可能会涉及到“体育”、“娱乐”等多个类别标签。在接下来的这篇文章中,我们将会详细介绍在多标签分类任务中两种常见的损失评估方法,以及在多标签分类场景中的模型评价指标。

3.13.1 Sigmoid损失#

在多标签分类场景中,第1种损失衡量方式就是将原始输出层的Softmax操作替换为Sigmoid操作,然后通过计算输出层与标签之间的Sigmoid交叉熵来作为误差的衡量标准,具体计算公式为:

$$ loss(y,\hat{y})=-\frac{1}{C} \sum_{i=1}^m\left[y^{(i)}\cdot\log\left(\frac{1}{1+\exp(-\hat{y}^{(i)})}\right)+\left(1-y^{(i)}\right)\cdot\log\left(\frac{\exp(-\hat{y}^{(i)})}{1+\exp(-\hat{y}^{(i)})}\right)\right]\tag{3-122} $$

其中$C$表示类别数量,$y^{(i)}$和$\hat{y}^{(i)}$均为一个向量,分别用来表示真实标签和未经任何激活函数处理的网络输出值。

从式(3-122)可以发现,这种误差损失衡量方式其实就是在逻辑回归中用来衡量预测概率与真实标签之间误差的方法。

在PyTorch中,可以通过torch.nn模块中的MultiLabelSoftMarginLoss类来完成损失的计算,示例代码如下所示:

1 def Sigmoid_loss(y_true, y_pred):
2     loss = nn.MultiLabelSoftMarginLoss(reduction='mean')
3     print(loss(y_pred, y_true))  # 0.5927
4 
5 if __name__ == '__main__':
6     y_true = torch.tensor([[1, 1, 0, 0], [0, 1, 0, 1]], dtype=torch.int16)
7     y_pred = torch.tensor([[0.2, 0.5, 0, 0], [0.1, 0.5, 0, 0.8]], dtype=torch.float32)
8     Sigmoid_loss(y_true, y_pred)

在上述代码中,第6~7行是构造了两个样本的预测结果和真实标签,且每个样本均有两个类别。同时,需要注意的是MultiLabelSoftMarginLoss默认返回的是所有样本损失的均值,我们可以通过指定参数reductionmeansum来指定返回的类型。

对于上述计算过程,我们还可以通过如下代码来进行完成:

1 l = -(y_true * torch.log(1 / (1 + torch.exp(-y_pred))) 
2       + (1 - y_true) * torch.log(torch.exp(-y_pred) / (1 + torch.exp(-y_pred)))).mean()

在完成模型的训练过程后,我们可以通过如下方式来得到模型的预测结果:

1 def prediction(logits, K):
2     y_pred = np.argsort(-logits, axis=-1)[:, :K]
3     print("预测标签:", y_pred)
4     p = np.vstack([logits[r, c] for r, c in enumerate(y_pred)])
5     print("预测概率:", p)
6 prediction(y_pred, 2)

在上述代码中,第1行中K表示多标签的数量。运行结束以后,我们便可以得到如下所示结果:

1 预测标签 tensor([[1, 0], [3, 1]])
2 预测概率 [[0.5 0.2] [0.8 0.5]]

在上述输出结果中,第1~2行便是每个样本对应每个类别的标签,且是以概率值递减进行排序

3.13.2 交叉熵损失#

在衡量多标签分类损失的方法中,除了Sigmoid损失以外还有一种常用的损失函数。这种损失函数本质上是我们在单标签分类中用到的交叉熵损失函数的扩展版,单标签可以看作是其中的一种特例情况。其具体计算公式为:

$$ loss(y,\hat{y})=-\frac{1}{m}\sum_{i=1}^m\sum_{j=1}^qy^{(i)}_j\log{\hat{y}^{(i)}_j}\tag{3-123} $$

其中$y^{(i)}_j$表示第$i$个样本第$j$个类别的真实值,$\hat{y}^{(i)}_j$表示第$i$个样本第$j$个类别的输出经过$\text{Softmax}$处理后的结果。

例如对于如下样本来说:

1 y_true = np.array([[1, 1, 0, 0], [0, 1, 0, 1.]])
2 y_pred = np.array([[0.2, 0.5, 0.1, 0], [0.1, 0.5, 0, 0.8]])

经过$\text{Softmax}$处理后的结果为:

1 [[0.24549354 0.33138161 0.22213174 0.20099311]
2  [0.18482871 0.27573204 0.16723993 0.37219932]]

此时,根据式(3-123)可知,对于上述2个样本来说其损失值为:

$$ loss= -\frac{1}{2}\left(1\cdot \log{(0.24549)}+1\cdot \log{(0.33138)}+1\cdot \log{(0.27573)} +1\cdot \log{(0.37219)}\right)\approx 2.392 \tag{3-124} $$

由于PyTorch中并没有直接提供对应的实现,所以我们需要自己动手实现,示例代码如下所示:

1 def cross_entropy(logits, y):
2     s = torch.exp(logits)
3     logits = s / torch.sum(s, dim=1, keepdim=True)
4     c = -(y * torch.log(logits)).sum(dim=-1)
5     return torch.mean(c)
6 
7 if __name__ == '__main__':
8     loss = cross_entropy(y_pred,y_true)
9     print(loss)# 2.392

在介绍完两种不同的损失度量方法后,我们再来看如何对多标签分类任务中模型的预测结果进行评估。根据多标签分类任务的性质来看,评估指标整体上可以分为两类:不考虑部分正确的评估指标和考虑部分正确的评估指标。下面我们开始分别进行介绍。

3.13.3 不考虑部分正确的评估指标#

1. 绝对匹配率

所谓绝对匹配率(Exact Match Ratio)是指,对于每一个样本来说除非每个标签的预测结果均正确,否则认为该样本的预测结果为错误。也就是说只有预测值与真实值完全相同的情况下才算预测正确,因此其计算公式为

$$ \text{MR}=\frac{1}{m}\sum_{i=1}^mI(y^{(i)}==\hat{y}^{(i)})\tag{3-125} $$

其中 $m$ 表示样本总数;$I(\cdot)$为指示函数(Indicator Function),当$y^{(i)}$完全等同于$\hat{y}^{(i)}$时取$1$,否则为$0$。

从式(3-125)可以看出,MR值越大,表示分类的准确率越高。

例如现有如下真实值和预测值:

1 y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 1]])
2 y_pred = np.array([[0, 1, 1, 0], [0, 1, 1, 0], [1, 1, 0, 0]])

那么其对应的MR就应该是$0.333$,因为只有第2个样本才算预测正确。此时,我们可以直接通过sklearn.metrics模块中的accuracy_score方法来完成计算 [1],示例代码如下所示:

1 from sklearn.metrics import accuracy_score
2 print(accuracy_score(y_true,y_pred)) # 0.33333333

2. 0-1损失

除了绝对匹配率之外,还有另外一种与之计算过程恰好相反的评估指标,即0-1损失(Zero-One Loss)。绝对准确率计算的是完全预测正确的样本占总样本数的比例,而0-1损失计算的则是预测错误的样本占总样本的比例。因此对于上面的预测值和真实值来说,其0-1损失就应该为0.667。对应的计算公式为:

$$ L_{0-1}=\frac{1}{m}\sum_{i=1}^mI(y^{(i)}\neq\hat{y}^{(i)})\tag{3-126} $$

此时,我们可以通过sklearn.metrics模块中的zero_one_loss方法来完成计算 [1],示例代码如下所示:

1 from sklearn.metrics import zero_one_loss
2 print(zero_one_loss(y_true,y_pred))# 0.66666

3.13.4 考虑部分正确的评估指标#

从上面的两种评估指标可以看出,不管是绝对匹配率还是0-1损失,两者在计算结果时都没有考虑部分正确的情况,而这对于模型的评估来说显然是不够准确的。例如,假设某个样本的正确标签为[1, 0, 0, 1],模型的预测标签为[1, 0, 1, 0]。可以看到,尽管模型没有把该样本的所有标签都预测正确,但是同样也预测正确了一部分。因此,一种可取的做法就是将部分预测正确的结果也考虑进去 [2]。

为了实现这一想法,文献 [3]中提出了在多标签分类场景下的准确率(Accuracy)、精确率(Precision)、召回率(Recall)和$F_1$值($F_1$-Measure)计算方法,整体思想类似于「第3.9节 分类模型评估指标:准确率、召回率与F-score」中的内容,下面我们逐一进行介绍。

1. 准确率

对于准确率来说,其计算公式为:

$$ \text{Accuracy} = \frac{1}{m} \sum_{i=1}^{m} \frac{\lvert y^{(i)} \cap \hat{y}^{(i)}\rvert}{\lvert y^{(i)} \cup \hat{y}^{(i)}\rvert}\tag{3-127} $$

从式(3-127)可以看出,准确率计算的其实是所有样本的平均准确率。而对于每个样本来说,准确率就是预测正确的标签数在整个预测为正确或真实为正确标签数中的占比。例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 1, 1, 0]。那么该样本对应的准确率为:

$$ \text{Acc} = \frac{1}{1+1+1}=\frac{1}{3}\tag{3-128} $$

因此,对于如下真实结果和预测结果来说:

1 y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 1]])
2 y_pred = np.array([[0, 1, 1, 0], [0, 1, 1, 0], [1, 1, 0, 0]])

其准确率为:

$$ \text{Accuracy}=\frac{1}{3}\times(\frac{1}{3}+\frac{2}{2}+\frac{0}{4})\approx0.4444\tag{3-129} $$

对于式(3-127)所示的计算过程来说,其对应的实现代码为 [4]:

1 def Accuracy(y_true, y_pred):
2     count = 0
3     for i in range(y_true.shape[0]):
4         p = sum(np.logical_and(y_true[i], y_pred[i]))
5         q = sum(np.logical_or(y_true[i], y_pred[i]))
6         count += p / q
7     return count / y_true.shape[0]
8 print(Accuracy(y_true, y_pred)) # 0.4444

2. 精确率

对于精确率来说,其计算公式为:

$$ \text{Precision} = \frac{1}{m} \sum_{i=1}^{m} \frac{\lvert y^{(i)} \cap \hat{y}^{(i)}\rvert}{\lvert \hat{y}^{(i)}\rvert}\tag{3-130} $$

从式(3-130)可以看出,精确率其实计算的是所有样本的平均精确率。而对于每个样本来说,精确率就是预测正确的标签数在整个预测为正确的标签数中的占比。例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 1, 1, 0]。那么该样本对应的精确率为:

$$ \text{Pre} = \frac{1}{1+1}=\frac{1}{2}\tag{3-131} $$

因此,对于上面的真实值和预测值来说,其精确率为:

$$ \text{Precision} = \frac{1}{3}\times(\frac{1}{2}+\frac{2}{2}+\frac{0}{2})\approx0.5\tag{3-132} $$

对于式(3-130)所示的计算过程来说,其对应的实现代码为:

1 def Precision(y_true, y_pred):
2     count = 0
3     for i in range(y_true.shape[0]):
4         if sum(y_pred[i]) == 0:
5             continue
6         count += sum(np.logical_and(y_true[i], y_pred[i])) / sum(y_pred[i])
7     return count / y_true.shape[0]
8 print(Precision(y_true, y_pred))# 0.5

3. 召回率

对于召回率来说,其计算公式为:

$$ \text{Recall} = \frac{1}{m} \sum_{i=1}^{m} \frac{\lvert y^{(i)} \cap \hat{y}^{(i)}\rvert}{\lvert y^{(i)}\rvert} \tag{3-133} $$

从式(3-133)可以看出,召回率其实计算的是所有样本的平均召回率。而对于每个样本来说,召回率就是预测正确的标签数在整个正确的标签数中的占比。

因此,对于上面的真实值和预测值来说,其召回率为:

$$ \text{Recall}=\frac{1}{3}\times(\frac{1}{2}+\frac{2}{2}+\frac{0}{2})\approx0.5\tag{3-134} $$

对于式(3-133)所示的计算过程来说,其对应的实现代码为:

1 def Recall(y_true, y_pred):
2     count = 0
3     for i in range(y_true.shape[0]):
4         if sum(y_true[i]) == 0:
5             continue
6         count += sum(np.logical_and(y_true[i], y_pred[i])) / sum(y_true[i])
7     return count / y_true.shape[0]
8 print(Recall(y_true, y_pred))# 0.5

4. $\text{F}_1$值

对于$F_1$值来说,其计算公式为:

$$ \text{F}_{1} = \frac{1}{m} \sum_{i=1}^{m} \frac{2 \lvert y^{(i)} \cap \hat{y}^{(i)}\rvert}{\lvert y^{(i)}\rvert + \lvert \hat{y}^{(i)}\rvert}\tag{3-135} $$

从式(3-135)可以看出,$\text{F}_1$计算的也是所有样本的平均$\text{F}_1$值。因此,对于上面的真实值和预测值来说,其$\text{F}_1$值为:

$$ \text{F}_1=\frac{2}{3}\times(\frac{1}{4}+\frac{2}{4}+\frac{0}{4})\approx0.5\tag{3-136} $$

对于式(3-135)所示的计算过程来说,其对应的实现代码为:

 1 def F1Measure(y_true, y_pred):
 2     count = 0
 3     for i in range(y_true.shape[0]):
 4         if (sum(y_true[i]) == 0) and (sum(y_pred[i]) == 0):
 5             continue
 6         p = sum(np.logical_and(y_true[i], y_pred[i]))
 7         q = sum(y_true[i]) + sum(y_pred[i])
 8         count += (2 * p) / q
 9     return count / y_true.shape[0]
10 print(F1Measure(y_true, y_pred))# 0.5

在上述4项指标中,都是值越大对应模型的分类效果越好。同时,从式(3-127)、(3-130)、(3-133)和(3-135)可以看出,在多标签场景下的各项指标尽管在计算步骤上与单标签场景有所区别,但是两者在计算各个指标时所秉承的思想却是类似的。

当然,对于后面3个指标的计算,还可以直接通过sklearn库中的对应方法来完成,示例代码如下所示:

1 from sklearn.metrics import precision_score, recall_score, f1_score
2 print(precision_score(y_true=y_true, y_pred=y_pred, average='samples'))# 0.5
3 print(recall_score(y_true=y_true, y_pred=y_pred, average='samples'))# 0.5
4 print(f1_score(y_true,y_pred,average='samples'))# 0.5

除了前面已经介绍的6种评估指标外,我们下面再介绍最后一种更加直观的衡量方法汉明损失(Hamming Loss)[1]。

5. 汉明损失

对于汉明损失来说,它的计算公式为:

$$ \text{Hamming Loss} = \frac{1}{m q} \sum_{i=1}^{m}\sum_{j=1}^{q} I\left( y^{(i)}_{j} \neq \hat{y}^{(i)}_{j} \right) \tag{3-137} $$

其中$y^{(i)}_j$表示第$i$个样本的第$j$个标签,$q$表示一种有多少个类别。

从式(3-137)可以看出,汉明损失衡量的是所有样本中,预测错的标签数在整个标签数中的占比。所以对于汉明损失来说,其值越小表示模型的表现结果越好。因此,对于如下真实结果和预测结果来说:

1 y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 1]])
2 y_pred = np.array([[0, 1, 1, 0], [0, 1, 1, 0], [1, 1, 0, 0]])

其汉明损失为:

$$ \text{Hamming Loss}=\frac{1}{3\times4}\times(2+0+4)\approx0.5\tag{3-138} $$

对于式(3-138)所示的计算过程来说,其对应的实现代码为:

1 def Hamming_Loss(y_true, y_pred):
2     count = 0
3     for i in range(y_true.shape[0]):
4         p = np.size(y_true[i] == y_pred[i])
5         q = np.count_nonzero(y_true[i] == y_pred[i])
6         count += p - q
7     return count / (y_true.shape[0] * y_true.shape[1])

同时也可以通过sklearn.metrics中的hamming_loss方法来进行计算,示例代码如下所示:

1 from sklearn.metrics import hamming_loss
2 print(hamming_loss(y_true, y_pred)) # 0.5

尽管在这里我们介绍了7种不同的评估指标,但是在多标签分类中仍然还有其它不同的评估方法,具体可以参见文献 [2]。例如还可以通过sklearn.metric模块中的multilabel_confusion_matrix方法来分别计算多标签中每个类别的准确率、召回率等;最后再来计算每个类别各项指标的平均值。有兴趣的读者可以自行去探索。

3.13.5 小结#

在本节内容中,我们首先介绍了两种在多标签分类场景中常用的模型损失函数,即Sigmoid损失和扩展交叉熵损失;接着分别介绍了不考虑部分正确和考虑部分正确的评估指标,包括绝对匹配率、0-1损失、准确率、召回率等等的原理和实现方法。

引用#

[1] Pedregosa F, Varoquaux G, Gramfort A, et al. Scikit-learn: Machine learning in Python[J]. the Journal of machine Learning research, 2011, 12: 2825-2830.

[2] Sorower M S. A literature survey on algorithms for multi-label learning[J]. Oregon State University, Corvallis, 2010, 18(1): 25.

[3] Godbole S, Sarawagi S. Discriminative Methods for Multi-labeled Classification. 2004, Lecture Notes in Computer Science, 22–30.

[4] https://mmuratarat.github.io/2020-01-25/multilabel_classification_metrics

您当前阅读的内容现已出版,点击右侧了解

10章教学课件,400余幅示意插图、40个示例源代码,助力读者轻松迈入深度学习的大门!

查看详情
阅读 --

3.5 从逻辑回归到Softmax回归

在本节内容中,我们首先通过一个例子引入了什么是分类任务,介绍了为什么不能用线性回归模型进行建模的原因;然后通过对线性回归的改进得到了逻辑回归模型,并直接地给出了逻辑回归模型的目标函数;接着介绍了如何通过多个逻辑回归模型来构建多分类任务的模型 …

3.9 分类模型评估指标

如同回归模型一样,分类模型在训练结束之后同样需要一种测度来对模型的结果进行评判,以便于我们进行下一步流程。相较于回归模型的评估指标,分类模型的评估指标则相对更多且考虑情况也更为繁杂。在接下来的这节内容中,我们将从零开始一步一步地详细介绍分类 …