多标签分类与单标签最根本区别在于一个样本可对应多个标签,须用MultiLabelBinarizer转为二值矩阵,不可用LabelEncoder或OneHotEncoder;训练测试需共用同一mlb实例,评估宜用hamming_loss和jaccard_score。

用 MultiLabelBinarizer 处理标签格式最稳妥
多标签分类和单标签最根本的区别在于:一个样本可能对应多个标签,比如一篇新闻同时属于 ["科技", "人工智能", "政策"]。直接用 LabelEncoder 或 OneHotEncoder 会出错——前者把整个列表当做一个类别编码,后者不支持嵌套列表输入。
正确做法是先用 MultiLabelBinarizer 把标签列表转成二值矩阵:
from sklearn.preprocessing import MultiLabelBinarizer mlb = MultiLabelBinarizer() y_bin = mlb.fit_transform([["A", "B"], ["B"], ["A", "C"]]) # 输出:[[1 1 0], [0 1 0], [1 0 1]]
-
mlb.classes_记录了所有唯一标签,顺序固定,后续预测后要用它反查原始标签名 - 如果原始标签是字符串形式的逗号分隔(如
"A,B"),得先用.split(",")拆成列表,再喂给fit_transform - 训练集和测试集必须共用同一个
mlb实例,不能分别fit,否则维度对不上
选 LogisticRegression 还是 ClassifierChain?看标签相关性
简单场景下,LogisticRegression 配合 OneVsRestClassifier 是最常用组合,本质是对每个标签单独训练一个二分类器:
from sklearn.multiclass import OneVsRestClassifier from sklearn.linear_model import LogisticRegression clf = OneVsRestClassifier(LogisticRegression()) clf.fit(X_train, y_bin)
但如果标签之间存在强依赖(比如出现 "深度学习" 很可能也带 "PyTorch"),ClassifierChain 更合适——它按预设顺序串起多个分类器,前一个的预测结果会作为后一个的额外特征:
立即学习“Python免费学习笔记(深入)”;
- 顺序影响效果,可用交叉验证试几种排列,或按标签频率/共现频率排序
- 链式结构让模型更重全局标签分布,但训练和预测都变慢,且不可并行
- 默认不校准概率,如果需要每个标签的置信度,得手动给每个子分类器加
CalibratedClassifierCV
评估不能只看准确率,hamming_loss 和 jaccard_score 更关键
准确率(accuracy)在多标签里意义很弱:全预测为负就可能高达 90%+,实际毫无价值。真正反映性能的是两个指标:
-
hamming_loss:错误预测的标签占总标签数的比例,越低越好(0 表示全对) -
jaccard_score(平均模式选average="samples"):对每个样本算预测集合与真实集合的交并比,再取均值,更贴近“每个样本整体准不准” - 如果关注某个特定标签(如稀有标签
"量子计算"),得单独提取那一列,用precision_recall_fscore_support算
注意 jaccard_score 的 average 参数:用 "samples" 是样本级,用 "macro" 是标签级,二者结果常差很大,报告时必须写明。
用 transformers + TextClassificationPipeline 做端到端微调要绕过默认单标签限制
Hugging Face 的 pipeline("text-classification") 默认只支持单标签,直接传多标签数据会报错 ValueError: Expected target to be a 1D tensor。必须换用底层 API,并手动处理标签:
- 模型输出层要改成
num_labels维,损失函数用BCEWithLogitsLoss(不是CrossEntropyLoss) - 标签必须是 float 类型的二值张量(
torch.float32),不能是 long - 预测时用
torch.sigmoid转概率,再按阈值(如 0.5)截断,不能用argmax - 别漏掉
tokenizer的padding=True, truncation=True,不然 batch 内长度不一致会崩
真正麻烦的不是代码,而是验证集上怎么设阈值——固定 0.5 常常不是最优,得用 average_precision_score 扫一遍阈值找最佳点,这点容易被忽略。


















