1.3 文本分类
情感 / 主题 / 意图 / 仇恨检测等分类任务谱系;特征工程 + 分类器 vs 微调 BERT vs 纯 prompt;Accuracy / Precision / Recall / F1、Macro vs Micro、类别不平衡
文本分类是 NLP 的"Hello World"
文本分类(Text Classification)是最经典也最落地的 NLP 任务:给一段文本分配一个或多个离散标签。它的数学形式异常简单:
但正是这种极简结构让它成为研究与工业界的试金石:任何新的表示方法、架构、优化技巧,第一件事就是跑一遍分类基准。本节的目标不是让你成为调 BERT 的熟练工,而是让你在任何分类任务面前都能快速回答三个问题:用什么 pipeline?用什么指标?类别不平衡怎么办?
任务谱系
分类任务看似千变万化,在工业场景可以归为以下几类:
| 子类 | 标签示例 | 标签粒度 | 典型数据集 |
|---|---|---|---|
| 情感分析(Sentiment) | 正面 / 负面 / 中性 | 2-5 类 | IMDB、SST-2、ChnSentiCorp |
| 主题分类(Topic) | 政治 / 体育 / 娱乐 / 科技 | 10-100 类 | AG News、THUCNews |
| 意图识别(Intent) | 查询天气 / 订机票 / 闲聊 | 10-1000 类 | SNIPS、CLINC150 |
| 仇恨 / 毒性检测 | 正常 / 侮辱 / 仇恨 / 威胁 | 多标签 | Jigsaw、COLD |
| 多标签分类 | 一篇论文属于多个领域 | 10-10000 类 | arXiv 论文、Reuters |
不同子类的难度差异极大。情感二分类今天几乎"已解决"(90%+ 准确率),而意图识别在千类量级、每类只有几十条样本的真实业务场景仍相当挑战。
三代 pipeline 的演化
第一代:特征工程 + 浅层分类器(2000-2015)
典型代码不到 10 行:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
clf = Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2), max_features=20000)),
("lr", LogisticRegression(max_iter=1000))
])
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))这条 pipeline 至今仍是强基线。在英文情感分类、新闻主题分类这些数据量充足的任务上,它和 BERT 的差距往往只有 1-3 个点,但推理速度快几百倍。
第二代:微调 BERT / 预训练模型(2018-2022)
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer
model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-chinese", num_labels=num_classes
)
# ... 加载数据、配置 Trainer、trainer.train() ...BERT 时代的关键变化:不再从零学表示。tokenizer + 预训练模型已经把语义编码好,分类只需要在 [CLS] token 上接一个线性分类头,小数据也能训出好模型。
第三代:纯 Prompt / Zero-shot(2023-)
from transformers import pipeline
classifier = pipeline("zero-shot-classification")
result = classifier(
"这部电影的剧情设计非常精彩,演员演技也在线",
candidate_labels=["正面", "负面", "中性"]
)
# {'labels': ['正面', '中性', '负面'], 'scores': [0.91, 0.06, 0.03]}或者直接调用 LLM:请判断以下评论的情感(正面/负面/中性):<text>。
三代方法如何选
经验法则(2026 年左右的工业实践):
- 数据 ≥ 10k 条、延迟敏感 → TF-IDF + LR 先试,95% 场景直接可用
- 数据 1k-10k、追求极致精度 → 微调 BERT / RoBERTa / DeBERTa(中文用 MacBERT、Chinese-RoBERTa)
- 数据 ≤ 1k 或冷启动 → LLM zero-shot / few-shot prompt
- 标签动态变化(不断新增类别) → zero-shot NLI 模型或 embedding + 最近邻
评估指标:比你以为的复杂
Accuracy:二分类均衡场景的王者
但在类别不平衡场景下 Accuracy 会误导人:99% 样本是正常、1% 是仇恨言论,模型全部预测"正常"可以拿到 99% 的 Accuracy,却在真正关心的仇恨检测上一个都没抓到。
Precision、Recall、F1
混淆矩阵(以二分类为例):
| 预测为正 | 预测为负 | |
|---|---|---|
| 实际为正 | TP(真正例) | FN(假负例) |
| 实际为负 | FP(假正例) | TN(真负例) |
三大指标:
- Precision 高:预测为"仇恨"的那批文本里,真的是仇恨言论的比例——误报低
- Recall 高:所有真正的仇恨言论里,被模型抓到的比例——漏报低
- F1:两者的调和平均,平衡衡量
不同业务对 P/R 的偏好不同:反垃圾邮件要 Recall 高(不能漏),金融风控要 Precision 高(不能误封)。
Macro vs Micro F1
多分类或多标签场景下,F1 有两种聚合方式:
直观区别:
- Macro-F1:每个类别权重相同。适合你希望"小类别也算数"的场景(如意图分类,每类都要能识别)
- Micro-F1:按样本数加权,大类别影响力大。在类别极度不平衡时会被大类主导
读论文时注意:很多分类 benchmark 默认报 Macro-F1,因为它能暴露"在小类别上表现差"的问题。看到"F1 = 0.85"不要立刻高兴,先问清楚是 Macro 还是 Micro。
类别不平衡怎么办
真实业务数据几乎永远不平衡。应对策略:
重采样(Resampling)
- 上采样少数类(SMOTE 等)
- 下采样多数类
- 训练时用
class_weight='balanced'让少数类样本的损失权重更大
损失函数改造
- 加权交叉熵:, 与类频率成反比
- Focal Loss:,自动抑制容易样本,关注难样本
评估指标选对
- 不平衡分类一律用 Macro-F1 或 PR-AUC,不要只看 Accuracy
- 关注少数类的 Recall(业务通常最在意漏报)
数据层面根治
- 主动学习:让模型挑选"最不确定"的样本让人标注
- 合成数据:用 LLM 为少数类生成样本(第 5 讲详讲)
一个容易忽略的细节:阈值
默认二分类阈值是 0.5——概率 判为正类。但这在不平衡场景几乎永远不是最优:
from sklearn.metrics import precision_recall_curve
import numpy as np
precisions, recalls, thresholds = precision_recall_curve(y_true, y_proba)
f1s = 2 * precisions * recalls / (precisions + recalls + 1e-10)
best_threshold = thresholds[np.argmax(f1s)]
# 用 best_threshold 而非 0.5 做决策对很多业务模型,调阈值比换模型架构的收益更显著。
本节小结
| 维度 | 要点 |
|---|---|
| 三代 pipeline | TF-IDF+LR(强基线)→ 微调 BERT → LLM prompt;按数据量和延迟选 |
| Accuracy | 适合均衡二分类;不平衡场景会严重误导 |
| Precision / Recall / F1 | 必须看混淆矩阵,根据业务偏好权衡 |
| Macro vs Micro F1 | Macro 关注小类,Micro 被大类主导,论文默认 Macro |
| 类别不平衡 | 重采样 / 加权损失 / Focal Loss / 合成数据;指标选对比模型更重要 |
| 阈值调节 | 默认 0.5 几乎永远不是最优;用 PR 曲线找 F1 最大点 |