人工智能实践(语言智能)
第1讲:经典 NLP 任务

1.3 文本分类

情感 / 主题 / 意图 / 仇恨检测等分类任务谱系;特征工程 + 分类器 vs 微调 BERT vs 纯 prompt;Accuracy / Precision / Recall / F1、Macro vs Micro、类别不平衡

文本分类是 NLP 的"Hello World"

文本分类(Text Classification)是最经典也最落地的 NLP 任务:给一段文本分配一个或多个离散标签。它的数学形式异常简单:

f:texty{1,2,,K}f: \text{text} \to y \in \{1, 2, \ldots, K\}

但正是这种极简结构让它成为研究与工业界的试金石:任何新的表示方法、架构、优化技巧,第一件事就是跑一遍分类基准。本节的目标不是让你成为调 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=TP+TNTP+FP+TN+FN\text{Accuracy} = \frac{TP + TN}{TP + FP + TN + FN}

但在类别不平衡场景下 Accuracy 会误导人:99% 样本是正常、1% 是仇恨言论,模型全部预测"正常"可以拿到 99% 的 Accuracy,却在真正关心的仇恨检测上一个都没抓到。

Precision、Recall、F1

混淆矩阵(以二分类为例):

预测为正预测为负
实际为正TP(真正例)FN(假负例)
实际为负FP(假正例)TN(真负例)

三大指标:

Precision=TPTP+FP,Recall=TPTP+FN\text{Precision} = \frac{TP}{TP + FP}, \quad \text{Recall} = \frac{TP}{TP + FN} F1=2PrecisionRecallPrecision+RecallF_1 = \frac{2 \cdot \text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}
  • Precision 高:预测为"仇恨"的那批文本里,真的是仇恨言论的比例——误报低
  • Recall 高:所有真正的仇恨言论里,被模型抓到的比例——漏报低
  • F1:两者的调和平均,平衡衡量

不同业务对 P/R 的偏好不同:反垃圾邮件要 Recall 高(不能漏),金融风控要 Precision 高(不能误封)。

Macro vs Micro F1

多分类或多标签场景下,F1 有两种聚合方式:

Macro-F1=1Kk=1KF1(k)\text{Macro-F1} = \frac{1}{K} \sum_{k=1}^K F_1^{(k)} Micro-F1=F1(kTPk,kFPk,kFNk)\text{Micro-F1} = F_1\left(\sum_k TP_k, \sum_k FP_k, \sum_k FN_k\right)

直观区别:

  • Macro-F1:每个类别权重相同。适合你希望"小类别也算数"的场景(如意图分类,每类都要能识别)
  • Micro-F1:按样本数加权,大类别影响力大。在类别极度不平衡时会被大类主导

读论文时注意:很多分类 benchmark 默认报 Macro-F1,因为它能暴露"在小类别上表现差"的问题。看到"F1 = 0.85"不要立刻高兴,先问清楚是 Macro 还是 Micro。


类别不平衡怎么办

真实业务数据几乎永远不平衡。应对策略:

重采样(Resampling)

  • 上采样少数类(SMOTE 等)
  • 下采样多数类
  • 训练时用 class_weight='balanced' 让少数类样本的损失权重更大

损失函数改造

  • 加权交叉熵L=kwkyklogy^k\mathcal{L} = -\sum_k w_k \cdot y_k \log \hat{y}_kwkw_k 与类频率成反比
  • Focal LossLfocal=(1y^)γlogy^\mathcal{L}_{\text{focal}} = -(1-\hat{y})^\gamma \log \hat{y},自动抑制容易样本,关注难样本

评估指标选对

  • 不平衡分类一律用 Macro-F1 或 PR-AUC,不要只看 Accuracy
  • 关注少数类的 Recall(业务通常最在意漏报)

数据层面根治

  • 主动学习:让模型挑选"最不确定"的样本让人标注
  • 合成数据:用 LLM 为少数类生成样本(第 5 讲详讲)

一个容易忽略的细节:阈值

默认二分类阈值是 0.5——概率 0.5\ge 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 做决策

对很多业务模型,调阈值比换模型架构的收益更显著


本节小结

维度要点
三代 pipelineTF-IDF+LR(强基线)→ 微调 BERT → LLM prompt;按数据量和延迟选
Accuracy适合均衡二分类;不平衡场景会严重误导
Precision / Recall / F1必须看混淆矩阵,根据业务偏好权衡
Macro vs Micro F1Macro 关注小类,Micro 被大类主导,论文默认 Macro
类别不平衡重采样 / 加权损失 / Focal Loss / 合成数据;指标选对比模型更重要
阈值调节默认 0.5 几乎永远不是最优;用 PR 曲线找 F1 最大点