多分类与多标签学习实战:OvR、softmax 与多输出评估

多分类与多标签学习实战:二分类到多分类、OvR/OvO 策略、softmax 与交叉熵、多标签分类(问题转换/链式/原生)、多输出回归、多分类评估指标(macro/micro/weighted)、不均衡多分类、多标签阈值调优、工程落地与常见陷阱。

引言

现实问题很少是「是/否」——图片有十类、新闻打多个标签、一张病历同时命中多个诊断。从二分类到多分类(一个样本一个类)再到多标签(一个样本多个类),评估方式和训练目标都在变化。本文覆盖 OvR/OvO 策略、softmax 与交叉熵、多标签的问题转换法、宏/微/加权平均指标,以及多标签阈值调优的实战。

前置:/ml-supervised-classification/(分类算法与二分类指标)、/ml-neural-networks-basics/(softmax 与交叉熵)、/ml-pipelines-feature-selection/(流水线落地)。


目录


1. 三种问题的边界

问题输出形态示例
二分类1 个标签(0/1)垃圾邮件?
多分类1 个标签(K 选 1)图片是猫/狗/鸟?
多标签多个标签(任意组合)文章含「科技」+「AI」

关键区别:多分类各类互斥(softmax 总和为 1),多标签各标签可共存(每标签独立 sigmoid)。

记忆:多分类「K 选 1」用 softmax 让概率和为 1;多标签「每标签独立」用 sigmoid;别把互斥任务当多标签做。


2. OvR 与 OvO:把多分类拆成二分类

2.1 两种拆法

策略拆法训练数适用
OvR(One-vs-Rest)K 个「对全体」分类器K 个数据少/类多时常用
OvO(One-vs-One)K(K-1)/2 个两两分类器多类少、svm 等核方法

2.2 sklearn 调用

from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

# OvR 包装
clf = OneVsRestClassifier(LogisticRegression(max_iter=1000))
clf.fit(X_train, y_train)

# 逻辑回归原生支持多分类(内部 OvR 或 multinomial)
clf2 = LogisticRegression(multi_class="multinomial")   # softmax 形式

记忆:OvR 训练 K 个「对全体」分类器、OvO 训练两两分类器;逻辑回归的 multinomial 直接用 softmax 形式做多分类。


3. softmax 与交叉熵:多分类的神经网络目标

3.1 softmax 把 logits 变成概率分布

import torch
import torch.nn as nn

logits = torch.tensor([[2.0, 1.0, 0.1]])
probs = torch.softmax(logits, dim=-1)   # [0.67, 0.24, 0.09],和为 1

3.2 交叉熵损失

loss_fn = nn.CrossEntropyLoss()          # 内部含 softmax
y = torch.tensor([0])                    # 真实类
loss = loss_fn(logits, y)                # 只有真实类那一项被惩罚

CrossEntropyLoss 把 softmax 与负对数似然合二为一——训练时直接用 logits 即可,别在损失前手动再 softmax。

3.3 为什么不用 MSE

交叉熵对「错得很自信」惩罚更大、梯度更陡,且只惩罚真实类的概率——MSE 会平等惩罚所有输出、收敛更慢。

记忆:多分类神经网络标配 softmax(输出概率和为 1)+ 交叉熵(只惩罚真实类);PyTorch 的 CrossEntropyLoss 已内置 softmax,直接喂 logits。


4. 多标签:一个样本多个类

4.1 数据形态

每行样本对应一个标签集合:

# y 是 0/1 矩阵:每行每个标签 0/1
# [[1, 0, 1],   # 样本1:标签A + 标签C
#  [0, 1, 0],   # 样本2:标签B
#  [1, 1, 0]]   # 样本3:标签A + 标签B

4.2 两种建模思路

  • 每个标签独立二分类(最常用):K 个 sigmoid 输出,每个标签看自己的概率
  • 标签依赖建模:用链式/图方法考虑标签相关性(如「病毒性」常与「发热」共存)

记忆:多标签 y 是 0/1 矩阵;默认做法是每个标签独立二分类(K 个 sigmoid),需要时再建模标签相关性。


5. 问题转换法:OneVsRest 与链式分类

5.1 OneVsRestClassifier 直接支持多标签

from sklearn.multiclass import OneVsRestClassifier

clf = OneVsRestClassifier(LogisticRegression(max_iter=1000))
clf.fit(X_train, y_multi)             # y_multi 是 0/1 矩阵
probs = clf.predict_proba(X_test)     # 每个标签一个概率

5.2 ClassifierChain:给标签之间「串线索」

ClassifierChain 按顺序训练,后一个标签的输入包含前一个标签的预测,捕捉相关性:

from sklearn.multioutput import ClassifierChain

chain = ClassifierChain(LogisticRegression(), order=[0, 1, 2])
chain.fit(X_train, y_multi)

代价:链式依赖串行、对顺序敏感;标签多时优先 OneVsRest,标签相关性明确时才上链。

记忆:OneVsRest 是多标签的默认转换(每标签独立二分类);ClassifierChain 用前标签预测做特征捕捉相关性,代价是串行与顺序敏感。


6. 多输出回归:连续多目标

当目标不是 0/1 而是多个连续值(如同时预测房价与面积、多个传感器读数):

from sklearn.multioutput import MultiOutputRegressor
from sklearn.ensemble import RandomForestRegressor

# y 是多列连续值矩阵
mor = MultiOutputRegressor(RandomForestRegressor(n_estimators=100))
mor.fit(X_train, y_multi_cont)

MultiOutputRegressor 为每个输出包一个独立的回归器——与多标签分类思路同构。

记忆:多输出回归用 MultiOutputRegressor 给每个连续目标各包一个回归器,与多标签的「每标签独立」思路一致。


7. 多分类评估:macro-micro-weighted

7.1 三种平均方式

方式计算含义
macro各类指标先算再平均各类等权,小类被放大
micro汇总所有样本再算样本等权,被大类主导
weighted按各类样本数加权综合,最常用

7.2 场景选择

# 类别均衡:macro / weighted 差别不大
# 类别不均衡、小类重要(如罕见病):优先 macro(或单独看小类的 recall)
# 全站报告:weighted f1 最贴合整体业务
from sklearn.metrics import classification_report, f1_score

print(classification_report(y_test, y_pred))          # 每类的 P/R/F1 + macro/weighted
macro = f1_score(y_test, y_pred, average="macro")
weighted = f1_score(y_test, y_pred, average="weighted")

记忆:macro 各类等权(关注小类用)、micro 样本等权(被大类主导)、weighted 按样本加权(默认综合报告用);不均衡且小类重要时看 macro 和小类自己的 recall。


8. 多标签评估与阈值调优

8.1 多标签指标

指标含义
精确匹配(exact match)全部标签猜对的样本比例,极苛刻
汉明损失(hamming loss)平均预测错的标签比例
标签子集 F1每个标签独立算 F1 再平均
每标签 ROC-AUC每标签一个 AUC,画 micro/macro 聚合

8.2 阈值不只是 0.5

多标签每个 sigmoid 概率的阈值可单独调——标签稀疏(很少命中)时,0.5 可能永远不激活,要调低阈值:

from sklearn.metrics import f1_score

best = 0.0
for thr in np.arange(0.2, 0.8, 0.05):
    preds = (probs > thr).astype(int)
    score = f1_score(y_val, preds, average="micro")
    if score > best:
        best, best_thr = score, thr
# 用验证集选出的 best_thr,别在测试集上调

记忆:多标签评估看汉明损失与每标签 F1/AUC;阈值别死守 0.5——稀疏标签要调低,用验证集网格搜最优点。


9. 不均衡与工程落地

9.1 多分类不均衡

# 多分类类别数可能极不均衡(如长尾分类)
# 手段:类别加权(class_weight)、重采样(过采样少数类)、分层抽样保每类
# 评估:不只看 macro,单独监控每个关键类的 recall

9.2 工程落地要点

# 1) 标签矩阵稀疏存储(多标签常 >99% 为 0)
# 2) 多标签模型输出 K 个概率,序化返回 Top-K 标签给业务
# 3) 阈值随业务口径调整(宁可多召回 / 宁可多精确)
# 4) 与 Pipeline 结合:标签编码器(MultiLabelBinarizer)纳入流程
from sklearn.preprocessing import MultiLabelBinarizer

mlb = MultiLabelBinarizer()
y_bin = mlb.fit_transform([["科技", "AI"], ["AI"]])   # 标签 → 0/1 矩阵

记忆:多分类不均衡用类别加权+分层抽样、监控每个关键类;多标签落地要点——稀疏存储、返回 Top-K、阈值随业务口径调、MultiLabelBinarizer 纳入 Pipeline。


10. 速查表与一句话记忆

场景做法一句话
多分类(互斥)softmax + 交叉熵K 选 1,概率和为 1
经典模型多分类OvR / OvO拆成二分类
多标签每标签独立 sigmoidOneVsRest 默认
标签相关ClassifierChain前标签做特征
多输出回归MultiOutputRegressor每输出一个模型
多分类评估macro/micro/weighted按关注点选平均
多标签阈值验证集搜最优别死守 0.5

一句话记忆:多分类「K 选 1」用 softmax+交叉熵(概率和为 1),经典模型拆成 OvR/OvO 二分类;多标签「每标签独立」用 OneVsRest(K 个 sigmoid),标签相关性明确时才上 ClassifierChain,多输出回归同理用 MultiOutputRegressor;评估上 macro 关注小类、micro 被大类主导、weighted 默认综合,多标签看汉明损失与每标签 F1/AUC 并在验证集上搜最优阈值(别死守 0.5,稀疏标签要调低);不均衡多分类用类别加权+分层抽样——「先分清互斥还是共存,再选训练目标与评估口径」。


延伸阅读

  • /ml-supervised-classification/ — 二分类算法与指标基础
  • /ml-neural-networks-basics/ — softmax、交叉熵与训练循环
  • /ml-anomaly-detection/ — 类别不平衡与阈值调优
  • /ml-pipelines-feature-selection/ — Pipeline 与标签编码落地
  • [[ai-ml]] — 长尾分类与多任务学习
  • sklearn 多类多标签文档
  • sklearn 多输出文档

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ml」更多文章

  1. 模型压缩与推理优化实战:量化、剪枝与知识蒸馏
  2. 强化学习入门实战:MDP、Q-learning 与 DQN 概览
  3. 模型监控与数据漂移检测:PSI、KS 与概念漂移实战