引言
现实问题很少是「是/否」——图片有十类、新闻打多个标签、一张病历同时命中多个诊断。从二分类到多分类(一个样本一个类)再到多标签(一个样本多个类),评估方式和训练目标都在变化。本文覆盖 OvR/OvO 策略、softmax 与交叉熵、多标签的问题转换法、宏/微/加权平均指标,以及多标签阈值调优的实战。
前置:/ml-supervised-classification/(分类算法与二分类指标)、/ml-neural-networks-basics/(softmax 与交叉熵)、/ml-pipelines-feature-selection/(流水线落地)。
目录
- 1. 三种问题的边界
- 2. OvR 与 OvO:把多分类拆成二分类
- 3. softmax 与交叉熵:多分类的神经网络目标
- 4. 多标签:一个样本多个类
- 5. 问题转换法:OneVsRest 与链式分类
- 6. 多输出回归:连续多目标
- 7. 多分类评估:macro-micro-weighted
- 8. 多标签评估与阈值调优
- 9. 不均衡与工程落地
- 10. 速查表与一句话记忆
- 延伸阅读
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 | 拆成二分类 |
| 多标签 | 每标签独立 sigmoid | OneVsRest 默认 |
| 标签相关 | 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 多输出文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。