第二阶段:语义理解应用:文本分类与聚类 (Text Classification & Clustering)
"The best classifier is the one you understand." - Andrew Ng
即使在生成式 AI 时代,经典的分类和聚类依然是业务系统的基石。本章将从零样本到微调,从K-Means到BERTopic,构建完整的语义理解工程体系。
难度级别:⭐⭐(应用实战)
目录
- [一、判别式 AI vs. 生成式 AI](#一、判别式 AI vs. 生成式 AI)
- [1. 核心差异](#1. 核心差异)
- [2. 为什么判别式模型仍然重要?](#2. 为什么判别式模型仍然重要?)
- [二、Embedding 的本质与可视化](#二、Embedding 的本质与可视化)
- [1. 什么是 Embedding?](#1. 什么是 Embedding?)
- [2. 流行的 Embedding 模型(2025版)](#2. 流行的 Embedding 模型(2025版))
- [3. Embedding 可视化:t-SNE 与 UMAP](#3. Embedding 可视化:t-SNE 与 UMAP)
- [4. 完整代码:Embedding 可视化实战](#4. 完整代码:Embedding 可视化实战)
- 三、零样本分类 (Zero-Shot Classification)
- [1. 基于 NLI 的零样本分类](#1. 基于 NLI 的零样本分类)
- [2. 英文零样本分类实现](#2. 英文零样本分类实现)
- [3. 中文零样本分类(基于 Sentence-BERT)](#3. 中文零样本分类(基于 Sentence-BERT))
- [4. 2025年最佳实践:使用 LLM API](#4. 2025年最佳实践:使用 LLM API)
- [5. 高级策略:Embedding 粗筛 + LLM 精排](#5. 高级策略:Embedding 粗筛 + LLM 精排)
- 四、少样本分类 (Few-Shot Classification)
- [1. SetFit 框架深度解析](#1. SetFit 框架深度解析)
- [2. 完整代码实现](#2. 完整代码实现)
- [3. Few-Shot Prompt(使用 LLM)](#3. Few-Shot Prompt(使用 LLM))
- [五、全量微调 vs PEFT (LoRA) 在分类任务中的对比](#五、全量微调 vs PEFT (LoRA) 在分类任务中的对比)
- [1. 全量微调 (Full Fine-Tuning)](#1. 全量微调 (Full Fine-Tuning))
- [2. PEFT: LoRA (Low-Rank Adaptation)](#2. PEFT: LoRA (Low-Rank Adaptation))
- [3. 完整对比代码](#3. 完整对比代码)
- [4. 对比总结](#4. 对比总结)
- 六、文本聚类算法
- [1. K-Means 聚类](#1. K-Means 聚类)
- [2. DBSCAN (密度聚类)](#2. DBSCAN (密度聚类))
- [3. HDBSCAN (层次化 DBSCAN)](#3. HDBSCAN (层次化 DBSCAN))
- [4. 算法对比](#4. 算法对比)
- [七、BERTopic 原理与实战](#七、BERTopic 原理与实战)
- [1. BERTopic 流程](#1. BERTopic 流程)
- [2. 完整代码实战](#2. 完整代码实战)
- [3. 中文 BERTopic](#3. 中文 BERTopic)
- [4. 动态主题建模(时间维度)](#4. 动态主题建模(时间维度))
- 八、层次化聚类实战
- [1. 凝聚层次聚类 (Agglomerative)](#1. 凝聚层次聚类 (Agglomerative))
- [2. 完整代码](#2. 完整代码)
- [3. 层次化主题建模](#3. 层次化主题建模)
- 九、分类与聚类评估指标
- [1. 分类评估指标](#1. 分类评估指标)
- [2. 聚类评估指标](#2. 聚类评估指标)
- [3. 完整评估代码](#3. 完整评估代码)
- 十、实战案例:客服工单自动分类系统
- [1. 系统架构](#1. 系统架构)
- [2. 完整实现代码](#2. 完整实现代码)
- [3. 生产环境部署建议](#3. 生产环境部署建议)
- 十一、本章小结
一、判别式 AI vs. 生成式 AI
在 LLM 出现之前,NLP 的半壁江山是 BERT 打下的。BERT 是典型的判别式模型(Encoder-only)。
1. 核心差异
| 维度 | 判别式模型 (Discriminative) | 生成式模型 (Generative) |
|---|---|---|
| 目标 | P ( Y ∣ X ) P(Y \mid X) P(Y∣X) - 给定输入判断类别 | P ( X , Y ) P(X, Y) P(X,Y) 或 P ( Y ∣ X ) P(Y \mid X) P(Y∣X) 自回归 |
| 代表 | BERT, RoBERTa, DeBERTa | GPT-4, LLaMA, ChatGPT |
| 优势 | 准确率高、速度快、成本低 | 灵活性强、零样本能力 |
| 劣势 | 需要标注数据、类别固定 | 推理慢、成本高、易幻觉 |
2. 为什么判别式模型仍然重要?
虽然现在可以用 GPT-4 做分类(Prompt: "Is this positive? answer yes/no"),但在高并发、低延迟、高性价比 场景下,基于 Embedding 的分类器依然是最优选择。
典型场景:
- 客服工单自动分类(每秒数千请求)
- 垃圾邮件过滤(毫秒级响应)
- 舆情分析(固定类别:正/中/负)
- 内容审核(违规分类)
二、Embedding 的本质与可视化
1. 什么是 Embedding?
Embedding 是将文本映射到高维向量空间的过程,使得语义相似的文本在空间中彼此接近。
数学表示:
Encoder : T → R d \text{Encoder}: \mathbb{T} \rightarrow \mathbb{R}^d Encoder:T→Rd
其中:
- T \mathbb{T} T: 文本空间
- d d d: Embedding 维度(通常 768/1024/1536)
2. 流行的 Embedding 模型(2025版)
| 模型 | 维度 | 特点 | 使用场景 | 发布时间 |
|---|---|---|---|---|
| BGE-M3 | 1024 | 多语言、多粒度 | 跨语言检索 | 2024年 |
| bge-large-zh-v1.5 | 1024 | 中文SOTA | 中文检索/分类 | 2023年 |
| M3E-base | 768 | 中文轻量级 | 资源受限场景 | 2023年 |
| text-embedding-3-large | 3072 | OpenAI最新 | 英文任务 | 2024年 |
| GTE-Qwen2-7B | 3584 | 大模型级别 | 高精度场景 | 2024年 |
| jina-embeddings-v3 | 1024 | 多任务优化 | 通用场景 | 2024年 |
2025年推荐选择:
- ✅ 中文场景 :
bge-large-zh-v1.5或BGE-M3 - ✅ 英文场景 :
text-embedding-3-large(API) 或GTE-Qwen2-7B(本地) - ✅ 资源受限 :
M3E-base或bge-small-zh-v1.5 - ✅ 多语言 :
BGE-M3或jina-embeddings-v3
3. Embedding 可视化:t-SNE 与 UMAP
高维向量无法直接观察,需要降维到 2D/3D 空间。
3.1 t-SNE (t-Distributed Stochastic Neighbor Embedding)
原理:
- 在高维空间中,计算点对之间的相似度(高斯分布)
- 在低维空间中,保持相似度分布(t-分布)
- 通过梯度下降最小化 KL 散度
优点:
- 擅长保持局部结构
- 可视化效果好
缺点:
- 慢( O ( N 2 ) O(N^2) O(N2))
- 随机性强(每次结果不同)
- 不保持全局结构
3.2 UMAP (Uniform Manifold Approximation and Projection)
原理:
- 基于流形学习和拓扑数据分析
- 构建高维空间的拓扑图(k-近邻图)
- 在低维空间重建该拓扑结构
优点:
- 速度快(支持大规模数据)
- 保持全局结构
- 确定性强
缺点:
- 参数敏感(
n_neighbors,min_dist)
4. 完整代码:Embedding 可视化实战
python
"""
功能:Embedding可视化(t-SNE vs UMAP对比)
依赖:sentence-transformers>=2.3.0, scikit-learn>=1.3.0, umap-learn>=0.5.5
"""
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
import umap
from sentence_transformers import SentenceTransformer
# 1. 准备数据
texts = [
# 科技类
"人工智能正在改变世界",
"深度学习模型训练需要大量数据",
"Transformer架构是现代AI的基石",
"GPT-4展现了强大的语言理解能力",
# 体育类
"梅西带领阿根廷夺得世界杯冠军",
"NBA总决赛即将开打",
"马拉松比赛吸引数万名跑者参与",
"中国队在奥运会上取得佳绩",
# 美食类
"这家餐厅的烤鸭非常美味",
"川菜以麻辣著称",
"意大利面配番茄酱很经典",
"日本寿司讲究食材新鲜",
# 旅游类
"长城是中国的标志性景点",
"巴黎的埃菲尔铁塔令人震撼",
"马尔代夫的海滩美不胜收",
"京都的古寺展现日本文化"
]
labels = ["科技"]*4 + ["体育"]*4 + ["美食"]*4 + ["旅游"]*4
# 2. 生成 Embedding(使用2025年推荐模型)
print("加载 Embedding 模型...")
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
embeddings = model.encode(texts, normalize_embeddings=True)
print(f"Embedding 形状: {embeddings.shape}") # (16, 1024)
# 3. t-SNE 降维
print("执行 t-SNE 降维...")
tsne = TSNE(
n_components=2,
random_state=42,
perplexity=5, # 对于小数据集使用较小的perplexity
n_iter=1000
)
tsne_results = tsne.fit_transform(embeddings)
# 4. UMAP 降维
print("执行 UMAP 降维...")
umap_reducer = umap.UMAP(
n_components=2,
random_state=42,
n_neighbors=5,
min_dist=0.1,
metric='cosine'
)
umap_results = umap_reducer.fit_transform(embeddings)
# 5. 可视化对比
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 定义颜色映射
colors = {'科技': '#FF6B6B', '体育': '#4ECDC4', '美食': '#FFD93D', '旅游': '#95E1D3'}
# t-SNE 可视化
for label in set(labels):
mask = np.array(labels) == label
axes[0].scatter(
tsne_results[mask, 0],
tsne_results[mask, 1],
label=label,
color=colors[label],
s=150,
alpha=0.7,
edgecolors='black',
linewidths=1
)
axes[0].set_title("t-SNE 可视化", fontsize=16, fontweight='bold')
axes[0].legend(fontsize=12)
axes[0].grid(True, alpha=0.3, linestyle='--')
# UMAP 可视化
for label in set(labels):
mask = np.array(labels) == label
axes[1].scatter(
umap_results[mask, 0],
umap_results[mask, 1],
label=label,
color=colors[label],
s=150,
alpha=0.7,
edgecolors='black',
linewidths=1
)
axes[1].set_title("UMAP 可视化", fontsize=16, fontweight='bold')
axes[1].legend(fontsize=12)
axes[1].grid(True, alpha=0.3, linestyle='--')
plt.tight_layout()
plt.savefig('embedding_visualization.png', dpi=150, bbox_inches='tight')
plt.show()
print("\n✅ 可视化完成!")
print("💡 观察要点:")
print(" - 同类别文本是否聚在一起(类内紧密度)")
print(" - 不同类别是否分离明显(类间距离)")
print(" - t-SNE更关注局部结构,UMAP保留全局结构")
运行结果分析:
- ✅ 同类别聚集:相同主题的文本在空间中彼此接近
- ✅ 类别分离:不同主题之间有明显边界
- ✅ t-SNE vs UMAP :
- t-SNE:更紧凑的聚类,适合展示局部关系
- UMAP:保持全局结构,类别间距离更有意义
⚠️ 注意事项:
- 降维会损失信息,可视化仅供参考
- 小数据集(<50样本)降维效果不稳定
- 生产环境直接用高维向量计算相似度,无需降维
三、零样本分类 (Zero-Shot Classification)
零样本分类不需要训练数据,直接利用预训练模型的语义理解能力。
1. 基于 NLI 的零样本分类
核心思想:
将分类问题转化为**文本蕴含(Entailment)**问题。
公式:
P ( label ∣ text ) = P ( entailment ∣ premise=text , hypothesis="This text is about [label]" ) P(\text{label} \mid \text{text}) = P(\text{entailment} \mid \text{premise=text}, \text{hypothesis="This text is about [label]"}) P(label∣text)=P(entailment∣premise=text,hypothesis="This text is about [label]")
其中:
- Premise(前提):待分类文本
- Hypothesis(假设):构造的分类假设,如"This text is about 科技"
- Entailment(蕴含):前提是否支持假设
2. 英文零样本分类实现
python
"""
功能:基于NLI的零样本分类(英文)
模型:facebook/bart-large-mnli(2019年,依然有效)
依赖:transformers>=4.36.0
"""
from transformers import pipeline
# 1. 加载零样本分类器
classifier = pipeline(
"zero-shot-classification",
model="facebook/bart-large-mnli",
device=0 # 使用GPU加速(如果有)
)
# 2. 定义候选类别
candidate_labels = ["technology", "sports", "politics", "entertainment", "finance"]
# 3. 待分类文本
text = "Apple announced the new iPhone 15 series with improved cameras"
# 4. 进行分类
result = classifier(text, candidate_labels)
print("零样本分类结果:")
for label, score in zip(result['labels'], result['scores']):
print(f" {label}: {score:.3f}")
# 输出示例:
# technology: 0.952
# finance: 0.028
# entertainment: 0.012
# sports: 0.005
# politics: 0.003
3. 中文零样本分类(基于 Sentence-BERT)
python
"""
功能:基于语义相似度的中文零样本分类
模型:BAAI/bge-large-zh-v1.5(2025年推荐)
依赖:sentence-transformers>=2.3.0
"""
import numpy as np
from sentence_transformers import SentenceTransformer, util
# 1. 加载中文 Embedding 模型
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
# 2. 定义类别描述(使用完整描述而非单个词)
category_descriptions = {
"科技": "这段文本讨论的是人工智能、计算机、互联网、科学技术或IT行业相关的内容",
"体育": "这段文本讨论的是足球、篮球、比赛、运动员或体育赛事相关的内容",
"美食": "这段文本讨论的是餐饮、菜品、烹饪、美味或饮食文化相关的内容",
"旅游": "这段文本讨论的是景点、旅行、度假、观光或旅游体验相关的内容"
}
# 3. 待分类文本
text = "长城是中国古代的伟大建筑,吸引了无数游客前来参观"
# 4. 计算相似度
text_embedding = model.encode(text, convert_to_tensor=True, normalize_embeddings=True)
category_embeddings = model.encode(
list(category_descriptions.values()),
convert_to_tensor=True,
normalize_embeddings=True
)
# 5. 余弦相似度(归一化后等价于点积)
similarities = util.cos_sim(text_embedding, category_embeddings)[0]
# 6. 获取排序结果
scores = similarities.cpu().numpy()
ranked_categories = sorted(
zip(category_descriptions.keys(), scores),
key=lambda x: x[1],
reverse=True
)
print("中文零样本分类结果:")
for category, score in ranked_categories:
confidence = "高" if score > 0.6 else "中" if score > 0.4 else "低"
print(f" {category}: {score:.3f} [{confidence}置信度]")
# 输出示例:
# 旅游: 0.742 [高置信度]
# 科技: 0.312 [低置信度]
# 美食: 0.289 [低置信度]
# 体育: 0.254 [低置信度]
💡 关键技巧:
- ✅ 用完整描述代替单个类别词(准确率提升10-15%)
- ✅ 使用
normalize_embeddings=True确保余弦相似度一致性 - ✅ 选择专门的中文Embedding模型
- ⚠️ 类别描述的质量直接影响分类效果
4. 2025年最佳实践:使用 LLM API
现代LLM(如GPT-4、Claude、Qwen)具备强大的零样本分类能力,适合快速原型 和复杂分类场景。
python
"""
功能:使用OpenAI API进行零样本分类
模型:gpt-4o-mini(性价比最高,2024年发布)
依赖:openai>=1.10.0
"""
from openai import OpenAI
import json
client = OpenAI()
def classify_text_with_llm(text: str, categories: list[str]) -> dict:
"""
使用LLM进行零样本分类
参数:
text: 待分类文本
categories: 候选类别列表
返回:
{"category": str, "confidence": float, "reasoning": str}
"""
prompt = f"""你是一个专业的文本分类助手。请将以下文本分类到给定类别中。
候选类别:{', '.join(categories)}
文本:{text}
请以JSON格式返回结果,包含以下字段:
- category: 最匹配的类别
- confidence: 置信度(0-1之间的浮点数)
- reasoning: 简短的分类理由(20字以内)
示例输出:
{{"category": "科技", "confidence": 0.95, "reasoning": "讨论了AI技术发展"}}
"""
response = client.chat.completions.create(
model="gpt-4o-mini", # 2025年推荐:快速且便宜
messages=[{"role": "user", "content": prompt}],
temperature=0, # 确保结果稳定
response_format={"type": "json_object"} # 强制JSON输出
)
result = json.loads(response.choices[0].message.content)
return result
# 测试
categories = ["科技", "体育", "美食", "旅游"]
text = "OpenAI发布了最新的GPT-5模型,性能大幅提升"
result = classify_text_with_llm(text, categories)
print(f"分类结果: {result['category']}")
print(f"置信度: {result['confidence']}")
print(f"理由: {result['reasoning']}")
# 输出示例:
# 分类结果: 科技
# 置信度: 0.98
# 理由: 讨论了AI模型发布
对比:Embedding vs LLM
| 维度 | Embedding方法 | LLM方法 |
|---|---|---|
| 准确率 | 中(75-85%) | 高(85-95%) |
| 速度 | 快(10ms) | 慢(500-1000ms) |
| 成本 | 低(免费/自部署) | 高($0.0001-0.001/次) |
| 解释性 | 无 | 有(可提供理由) |
| 复杂场景 | 弱 | 强(可处理多层次分类) |
| 适用场景 | 高并发、固定类别 | 灵活分类、需要解释 |
2025年推荐策略:
- 原型阶段:使用LLM快速验证
- 生产环境 :
- 高并发(>100 QPS)→ Embedding + 分类器
- 低并发、高准确率要求 → LLM API
- 成本敏感 → 自部署开源模型(Qwen2.5-14B)
5. 高级策略:Embedding 粗筛 + LLM 精排 (Hybrid Classification)
当类别数量非常多(例如 1000+ 电商类目)时,我们无法将所有类别都放入 LLM 的 Prompt 中(上下文长度限制且费用昂贵)。此时,"粗筛 + 精排" 是标准解决方案。
流程图:
用户输入 → Embedding 检索(向量库) → Top-K 候选类别(如 Top 10)
↓
构建 Prompt (包含 Top-K 候选)
↓
LLM 最终裁决
代码实现:
python
"""
功能:Embedding 粗筛 + LLM 精排处理海量类别
"""
from sentence_transformers import SentenceTransformer, util
from openai import OpenAI
import torch
# 1. 模拟海量类别库 (实际场景可能有数千个)
all_categories = [
# 电子产品
"智能手机", "笔记本电脑", "平板电脑", "智能手表", "蓝牙耳机",
"单反相机", "无人机", "移动电源", "数据线", "手机壳",
# 家居用品
"沙发", "床垫", "衣柜", "餐桌", "办公椅",
"台灯", "窗帘", "地毯", "装饰画", "花瓶",
# ... 假设有很多 ...
]
# 2. 初始化模型
embedder = SentenceTransformer('BAAI/bge-large-zh-v1.5')
client = OpenAI()
# 3. 预计算类别 Embeddings (只需做一次)
category_embeddings = embedder.encode(all_categories, convert_to_tensor=True)
def hybrid_classify(text, top_k=5):
# Step 1: 粗筛 (Embedding Retrieval)
query_embedding = embedder.encode(text, convert_to_tensor=True)
# 计算相似度并获取 Top-K
cos_scores = util.cos_sim(query_embedding, category_embeddings)[0]
top_results = torch.topk(cos_scores, k=top_k)
candidate_indices = top_results.indices.tolist()
candidates = [all_categories[i] for i in candidate_indices]
print(f"Embedding 粗筛结果: {candidates}")
# Step 2: 精排 (LLM Decision)
prompt = f"""请从以下候选类别中,为文本选择最准确的一个类别。
文本:{text}
候选列表:
{', '.join(candidates)}
如果都不合适,请返回"其他"。只返回类别名称,不要解释。"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
final_category = response.choices[0].message.content
return final_category
# 测试
text = "我想买个大疆用来航拍婚礼"
result = hybrid_classify(text)
print(f"最终分类: {result}")
# 输出示例:
# Embedding 粗筛结果: ['无人机', '单反相机', '智能手机', '移动电源', '蓝牙耳机']
# 最终分类: 无人机
方案优势:
- ✅ 突破上下文限制:支持无限多的类别(只要向量检索能支持)。
- ✅ 降低成本:Prompt 中只需包含 5-10 个即使候选,大大减少 Token 消耗。
- ✅ 准确率高:LLM 擅长处理歧义,比单纯 Embedding 匹配更准(例如区分"苹果手机"和"苹果(水果)")。
四、少样本分类 (Few-Shot Classification)
只有少量标注样本(每类 5-50 个)时,使用少样本学习框架。
1. SetFit 框架深度解析
SetFit (Sentence Transformer Fine-Tuning) 是 HuggingFace 推出的少样本分类利器,专门解决标注数据稀缺问题。
1.1 为什么 SetFit 如此高效?
传统微调的困境:
- BERT 微调需要数千样本才能避免过拟合
- Prompt-based 方法需要精心设计提示词,效果不稳定
- 少样本场景下,全量微调容易"记住"训练样本(过拟合)
SetFit 的创新:
-
对比学习阶段:从每类 8 个样本中生成配对数据
- 同类样本对(正样本对):"text_A" 与 "text_B" 相似
- 异类样本对(负样本对):"text_A" 与 "text_C" 不相似
- 通过对比学习微调 Sentence Transformer,拉近同类,推远异类
-
分类头训练:在微调后的 Embedding 之上训练轻量级分类器
- 使用简单的 Logistic Regression 或浅层 MLP
- 因为 Embedding 已经学到了任务相关的语义,分类器只需学习决策边界
数学原理:
对比学习损失函数:
L = − log exp ( sim ( h i , h j + ) / τ ) exp ( sim ( h i , h j + ) / τ ) + ∑ k = 1 N exp ( sim ( h i , h k − ) / τ ) \mathcal{L} = -\log \frac{\exp(\text{sim}(h_i, h_j^+) / \tau)}{\exp(\text{sim}(h_i, h_j^+) / \tau) + \sum_{k=1}^{N} \exp(\text{sim}(h_i, h_k^-) / \tau)} L=−logexp(sim(hi,hj+)/τ)+∑k=1Nexp(sim(hi,hk−)/τ)exp(sim(hi,hj+)/τ)
其中:
- h i h_i hi: 锚点样本的 Embedding
- h j + h_j^+ hj+: 正样本(同类)的 Embedding
- h k − h_k^- hk−: 负样本(异类)的 Embedding
- τ \tau τ: 温度参数(通常 0.05)
- sim ( ⋅ , ⋅ ) \text{sim}(\cdot, \cdot) sim(⋅,⋅): 余弦相似度
优势总结:
- ✅ 数据高效:每类只需 8-16 个样本,在某些数据集上超过 3000 样本的全量微调
- ✅ 训练快速:对比学习+分类头训练通常 < 1 分钟(CPU)
- ✅ 效果接近全量微调:在 20 个数据集上平均准确率仅低 1-2%
- ✅ 多语言支持:可使用任何 Sentence Transformer 作为基座
- ✅ 无需 Prompt 工程:不像 GPT-3 需要精心设计提示词
2. 完整代码实现
python
from setfit import SetFitModel, SetFitTrainer
from datasets import Dataset
# 1. 准备少样本数据(每类只有 8 个样本)
train_data = {
"text": [
# 科技类(8个)
"OpenAI发布GPT-4模型", "量子计算机取得突破",
"芯片技术进入3nm时代", "人工智能辅助医疗诊断",
"5G网络覆盖全国", "云计算市场快速增长",
"机器人技术日新月异", "区块链应用场景扩大",
# 体育类(8个)
"中国队夺得奥运金牌", "世界杯决赛激动人心",
"NBA季后赛进入白热化", "马拉松赛事圆满结束",
"足球转会窗口关闭", "运动员打破世界纪录",
"羽毛球公开赛开幕", "网球大满贯赛事精彩"
],
"label": [0]*8 + [1]*8 # 0: 科技, 1: 体育
}
train_dataset = Dataset.from_dict(train_data)
# 2. 加载预训练模型并创建 SetFit 模型
model = SetFitModel.from_pretrained("moka-ai/m3e-base")
# 3. 创建训练器
trainer = SetFitTrainer(
model=model,
train_dataset=train_dataset,
num_iterations=20, # 对比学习的轮数
num_epochs=1, # 分类头训练的轮数
column_mapping={"text": "text", "label": "label"}
)
# 4. 训练模型
trainer.train()
# 5. 测试模型
test_texts = [
"谷歌推出新款智能手机",
"篮球比赛进入加时赛"
]
predictions = model(test_texts)
print("少样本分类结果:")
for text, pred in zip(test_texts, predictions):
label_name = "科技" if pred == 0 else "体育"
print(f" {text} -> {label_name}")
3. Few-Shot Prompt(使用 LLM)
对于更少的样本(每类 1-3 个),可以用 In-Context Learning:
python
from openai import OpenAI
client = OpenAI()
# Few-shot examples
examples = """
示例1:
文本: 人工智能改变世界
类别: 科技
示例2:
文本: 梅西带领球队夺冠
类别: 体育
示例3:
文本: 这道菜非常美味
类别: 美食
"""
# 待分类文本
text = "苹果发布新款笔记本电脑"
# 构建 Prompt
prompt = f"""{examples}
现在请分类以下文本:
文本: {text}
类别: """
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
print(f"分类结果: {response.choices[0].message.content}")
# 输出: 科技
五、全量微调 vs PEFT (LoRA) 在分类任务中的对比
当有足够数据时,可以选择微调模型。
1. 全量微调 (Full Fine-Tuning)
定义: 更新模型的所有参数。
优点:
- 效果最好(充分学习任务特征)
缺点:
- 显存占用大(需要存储全部梯度)
- 训练慢
- 容易过拟合(小数据集)
2. PEFT: LoRA (Low-Rank Adaptation)
核心思想:
冻结预训练权重 W 0 W_0 W0,只训练低秩分解矩阵 A , B A, B A,B:
W = W 0 + Δ W = W 0 + B A W = W_0 + \Delta W = W_0 + BA W=W0+ΔW=W0+BA
其中:
- W 0 ∈ R d × d W_0 \in \mathbb{R}^{d \times d} W0∈Rd×d: 冻结的原始权重
- B ∈ R d × r B \in \mathbb{R}^{d \times r} B∈Rd×r, A ∈ R r × d A \in \mathbb{R}^{r \times d} A∈Rr×d: 可训练的低秩矩阵
- r ≪ d r \ll d r≪d: 秩(通常 8/16/32)
参数量对比:
- 原始: d 2 d^2 d2
- LoRA: 2 d r 2dr 2dr (当 r = 8 r=8 r=8, d = 1024 d=1024 d=1024 时,减少 99%)
3. 完整对比代码
python
from transformers import AutoModelForSequenceClassification, AutoTokenizer, Trainer, TrainingArguments
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
# 准备数据
dataset = load_dataset("SetFit/amazon_counterfactual_en")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
def tokenize(batch):
return tokenizer(batch["text"], padding=True, truncation=True)
dataset = dataset.map(tokenize, batched=True)
# ========== 方法1: 全量微调 ==========
model_full = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased",
num_labels=2
)
print(f"全量微调参数量: {model_full.num_parameters():,}")
trainer_full = Trainer(
model=model_full,
args=TrainingArguments(
output_dir="./full_finetune",
num_train_epochs=3,
per_device_train_batch_size=16,
logging_steps=100
),
train_dataset=dataset["train"]
)
trainer_full.train()
# ========== 方法2: LoRA 微调 ==========
model_lora = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased",
num_labels=2
)
# 配置 LoRA
lora_config = LoraConfig(
r=8, # 秩
lora_alpha=32, # 缩放系数
target_modules=["query", "value"], # 只作用于 Q,V 矩阵
lora_dropout=0.1,
bias="none"
)
model_lora = get_peft_model(model_lora, lora_config)
model_lora.print_trainable_parameters()
# 输出: trainable params: 294,912 || all params: 109,483,778 || trainable%: 0.27%
trainer_lora = Trainer(
model=model_lora,
args=TrainingArguments(
output_dir="./lora_finetune",
num_train_epochs=3,
per_device_train_batch_size=16,
logging_steps=100
),
train_dataset=dataset["train"]
)
trainer_lora.train()
# ========== 性能对比 ==========
print("\n性能对比:")
print(f"全量微调 - 参数量: {model_full.num_parameters():,}, 显存: ~4.2GB")
print(f"LoRA微调 - 参数量: {model_lora.get_nb_trainable_parameters():,}, 显存: ~1.5GB")
4. 对比总结
| 维度 | 全量微调 | LoRA 微调 |
|---|---|---|
| 参数量 | 100% | 0.1%-1% |
| 显存占用 | 高 | 低(可用更大 Batch Size) |
| 训练速度 | 慢 | 快 1.5-2倍 |
| 效果 | 最优 | 接近全量(损失<1%) |
| 适用场景 | 大数据集 | 中小数据集 |
建议:
- 数据 < 10k:用 SetFit 或 Few-Shot
- 数据 10k-100k:用 LoRA
- 数据 > 100k:考虑 全量微调
六、文本聚类算法
当没有标注数据时,聚类可以自动发现文本的内在结构。
1. K-Means 聚类
原理:
- 随机初始化 k k k 个聚类中心
- 将每个样本分配到最近的中心
- 重新计算每个簇的中心
- 重复 2-3 直到收敛
代码实现:
python
from sklearn.cluster import KMeans
from sentence_transformers import SentenceTransformer
import matplotlib.pyplot as plt
# 1. 准备数据
texts = [
"人工智能改变世界", "深度学习突破", "神经网络应用",
"篮球比赛精彩", "足球世界杯", "奥运会开幕",
"美味的川菜", "意大利面很好吃", "日本料理精致",
"长城景色壮观", "巴黎铁塔美丽", "马尔代夫海滩"
]
# 2. 生成 Embedding
model = SentenceTransformer('moka-ai/m3e-base')
embeddings = model.encode(texts)
# 3. K-Means 聚类
kmeans = KMeans(n_clusters=4, random_state=42)
labels = kmeans.fit_predict(embeddings)
# 4. 输出结果
for i, (text, label) in enumerate(zip(texts, labels)):
print(f"簇{label}: {text}")
# 输出示例:
# 簇0: 人工智能改变世界
# 簇0: 深度学习突破
# 簇1: 篮球比赛精彩
# ...
问题:
- 需要预先指定 k k k(簇数)
- 对初始化敏感
- 高维空间效果下降(维度灾难)
2. DBSCAN (密度聚类)
优势:
- 自动发现簇数
- 可识别噪声点
- 能发现任意形状的簇
核心参数:
eps: 邻域半径min_samples: 核心点的最小邻居数
代码实现:
python
from sklearn.cluster import DBSCAN
from sklearn.decomposition import PCA
# 1. 降维(高维空间DBSCAN效果差)
pca = PCA(n_components=50)
embeddings_reduced = pca.fit_transform(embeddings)
# 2. DBSCAN 聚类
dbscan = DBSCAN(eps=0.5, min_samples=2)
labels = dbscan.fit_predict(embeddings_reduced)
print(f"发现 {len(set(labels)) - (1 if -1 in labels else 0)} 个簇")
print(f"噪声点数量: {list(labels).count(-1)}")
# 3. 输出结果
for i, (text, label) in enumerate(zip(texts, labels)):
cluster_name = f"簇{label}" if label != -1 else "噪声"
print(f"{cluster_name}: {text}")
3. HDBSCAN (层次化 DBSCAN)
改进:
- 自动确定
eps参数 - 更稳定的聚类结果
- 适合不同密度的簇
代码实现:
python
import hdbscan
# 1. HDBSCAN 聚类
clusterer = hdbscan.HDBSCAN(
min_cluster_size=2,
min_samples=1,
metric='euclidean'
)
labels = clusterer.fit_predict(embeddings_reduced)
print(f"HDBSCAN 发现 {len(set(labels)) - (1 if -1 in labels else 0)} 个簇")
# 2. 聚类概率(置信度)
probabilities = clusterer.probabilities_
for text, label, prob in zip(texts, labels, probabilities):
print(f"簇{label} ({prob:.2f}): {text}")
4. 算法对比
| 算法 | 需要指定簇数 | 噪声识别 | 高维适应 | 速度 |
|---|---|---|---|---|
| K-Means | 是 | 否 | 差 | 快 |
| DBSCAN | 否 | 是 | 差 | 中 |
| HDBSCAN | 否 | 是 | 中 | 慢 |
推荐策略:
- 先用 UMAP/PCA 降维到 50-100 维
- 用 HDBSCAN 自动发现簇
- 用 轮廓系数 (Silhouette Score) 评估聚类质量
七、BERTopic 原理与实战
BERTopic 是现代主题建模的标准工具,集成了 Embedding + 降维 + 聚类 + 关键词提取。
1. BERTopic 流程
文本 → Embedding → UMAP降维 → HDBSCAN聚类 → c-TF-IDF提取主题词
核心创新:c-TF-IDF (Class-based TF-IDF)
传统 TF-IDF 是文档级别,c-TF-IDF 是簇级别:
c-TF-IDF ( w , c ) = TF ( w , c ) × log N ∑ c ′ TF ( w , c ′ ) \text{c-TF-IDF}(w, c) = \text{TF}(w, c) \times \log \frac{N}{\sum_{c'} \text{TF}(w, c')} c-TF-IDF(w,c)=TF(w,c)×log∑c′TF(w,c′)N
其中:
- w w w: 词
- c c c: 簇
- TF ( w , c ) \text{TF}(w, c) TF(w,c): 词 w w w 在簇 c c c 中的频率
2. 完整代码实战
python
from bertopic import BERTopic
from sklearn.datasets import fetch_20newsgroups
# 1. 准备数据(20newsgroups 数据集)
docs = fetch_20newsgroups(subset='all', remove=('headers', 'footers', 'quotes'))['data'][:1000]
# 2. 创建 BERTopic 模型
topic_model = BERTopic(
language="english",
calculate_probabilities=True,
verbose=True
)
# 3. 训练模型(自动完成 Embedding + 降维 + 聚类)
topics, probs = topic_model.fit_transform(docs)
# 4. 查看主题
print(f"发现 {len(set(topics))} 个主题")
print("\n主题及其关键词:")
for topic_id in set(topics):
if topic_id == -1: # 噪声主题
continue
print(f"\n主题 {topic_id}:")
print(topic_model.get_topic(topic_id)[:5]) # 前5个关键词
# 输出示例:
# 主题 0:
# [('space', 0.012), ('nasa', 0.011), ('launch', 0.009), ...]
# 5. 可视化主题
topic_model.visualize_topics().show()
# 6. 查看文档属于哪个主题
sample_doc = "NASA launches new space mission"
topic, prob = topic_model.transform([sample_doc])
print(f"\n文档主题: {topic[0]}, 置信度: {prob[0]:.3f}")
3. 中文 BERTopic
python
from bertopic import BERTopic
from sentence_transformers import SentenceTransformer
# 1. 中文文档
docs = [
"人工智能技术快速发展",
"深度学习在图像识别中应用广泛",
"自然语言处理是AI的重要分支",
"篮球比赛非常激烈",
"足球世界杯吸引全球关注",
"奥运会运动员奋勇拼搏",
"川菜麻辣鲜香",
"粤菜清淡爽口",
"日本料理精致美观"
]
# 2. 使用中文 Embedding 模型
embedding_model = SentenceTransformer("moka-ai/m3e-base")
# 3. 创建模型
topic_model = BERTopic(
embedding_model=embedding_model,
language="chinese (simplified)",
min_topic_size=2 # 最小主题大小
)
# 4. 训练
topics, probs = topic_model.fit_transform(docs)
# 5. 查看主题
for topic_id in set(topics):
if topic_id != -1:
print(f"主题 {topic_id}: {topic_model.get_topic(topic_id)[:3]}")
4. 动态主题建模(时间维度)
跟踪主题随时间的演变:
python
from bertopic import BERTopic
import pandas as pd
# 1. 准备带时间戳的数据
docs = [...]
timestamps = ["2023-01-01", "2023-02-01", ...] # 对应每个文档的时间
# 2. 训练模型
topic_model = BERTopic()
topics, probs = topic_model.fit_transform(docs)
# 3. 随时间演变分析
topics_over_time = topic_model.topics_over_time(docs, timestamps)
# 4. 可视化
topic_model.visualize_topics_over_time(topics_over_time).show()
八、层次化聚类实战
层次化聚类构建树状结构(Dendrogram),适合发现嵌套的主题层次。
1. 凝聚层次聚类 (Agglomerative)
原理:
- 每个样本初始为一个簇
- 不断合并最相似的两个簇
- 直到所有样本合并为一个簇
2. 完整代码
python
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.cluster import AgglomerativeClustering
import matplotlib.pyplot as plt
# 1. 准备数据
texts = [
"深度学习", "神经网络", "机器学习",
"足球", "篮球", "排球",
"川菜", "粤菜", "湘菜"
]
model = SentenceTransformer('moka-ai/m3e-base')
embeddings = model.encode(texts)
# 2. 计算层次聚类链接矩阵
linkage_matrix = linkage(embeddings, method='ward')
# 3. 绘制树状图
plt.figure(figsize=(10, 6))
dendrogram(linkage_matrix, labels=texts, leaf_font_size=12)
plt.title("层次化聚类树状图")
plt.xlabel("样本")
plt.ylabel("距离")
plt.tight_layout()
plt.savefig('hierarchical_clustering.png', dpi=150)
plt.show()
# 4. 切分为指定数量的簇
agg_clustering = AgglomerativeClustering(n_clusters=3)
labels = agg_clustering.fit_predict(embeddings)
for text, label in zip(texts, labels):
print(f"簇{label}: {text}")
3. 层次化主题建模
结合 BERTopic 实现层次化主题:
python
from bertopic import BERTopic
from sklearn.cluster import AgglomerativeClustering
# 1. 训练 BERTopic
topic_model = BERTopic(hdbscan_model=AgglomerativeClustering(n_clusters=10))
topics, probs = topic_model.fit_transform(docs)
# 2. 构建主题层次
hierarchical_topics = topic_model.hierarchical_topics(docs)
# 3. 可视化层次结构
topic_model.visualize_hierarchy(hierarchical_topics=hierarchical_topics).show()
九、分类与聚类评估指标
评估是机器学习的生命线,没有评估就无法优化。
1. 分类评估指标
混淆矩阵与核心指标
准确率 (Accuracy) :整体正确率
Accuracy = T P + T N T P + T N + F P + F N \text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN} Accuracy=TP+TN+FP+FNTP+TN
精确率 (Precision) :预测为正的样本中,真正为正的比例
Precision = T P T P + F P \text{Precision} = \frac{TP}{TP + FP} Precision=TP+FPTP
召回率 (Recall) :实际为正的样本中,被正确预测的比例
Recall = T P T P + F N \text{Recall} = \frac{TP}{TP + FN} Recall=TP+FNTP
F1分数 :精确率和召回率的调和平均
F 1 = 2 × Precision × Recall Precision + Recall F1 = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} F1=2×Precision+RecallPrecision×Recall
指标选择指南:
| 场景 | 推荐指标 | 原因 |
|---|---|---|
| 数据平衡 | Accuracy | 简单直观 |
| 数据不平衡 | F1-score | 综合考虑 |
| 垃圾邮件检测 | Precision | 避免误杀 |
| 疾病诊断 | Recall | 避免漏诊 |
| 多分类 | Macro-F1 | 平等对待各类 |
2. 聚类评估指标
有标签评估
调整兰德指数 (ARI):范围[-1, 1],1表示完美聚类
归一化互信息 (NMI):范围[0, 1],1表示完美聚类
无标签评估
轮廓系数 (Silhouette Score)
s ( i ) = b ( i ) − a ( i ) max ( a ( i ) , b ( i ) ) s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))} s(i)=max(a(i),b(i))b(i)−a(i)
- 范围:[-1, 1],越接近1越好
- a ( i ) a(i) a(i):样本到同簇其他点的平均距离
- b ( i ) b(i) b(i):样本到最近其他簇的平均距离
3. 完整评估代码
python
"""
功能:分类与聚类评估工具
依赖:scikit-learn>=1.3.0, seaborn>=0.12.0
"""
from sklearn.metrics import (
classification_report, confusion_matrix,
silhouette_score, davies_bouldin_score
)
import seaborn as sns
import matplotlib.pyplot as plt
def evaluate_classification(y_true, y_pred, labels=None):
"""分类评估"""
print("=" * 50)
print("分类评估结果")
print("=" * 50)
print(classification_report(y_true, y_pred, target_names=labels))
# 混淆矩阵可视化
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=labels, yticklabels=labels)
plt.title('混淆矩阵')
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.tight_layout()
plt.savefig('confusion_matrix.png', dpi=150)
plt.show()
def evaluate_clustering(X, labels_pred):
"""聚类评估"""
silhouette = silhouette_score(X, labels_pred)
db_index = davies_bouldin_score(X, labels_pred)
print("=" * 50)
print("聚类评估结果")
print("=" * 50)
print(f"轮廓系数 (Silhouette): {silhouette:.3f} [越接近1越好]")
print(f"DB指数: {db_index:.3f} [越小越好]")
# 使用示例
y_true = [0, 1, 2, 0, 1, 2]
y_pred = [0, 1, 2, 0, 2, 2]
evaluate_classification(y_true, y_pred, ['科技', '体育', '美食'])
十、实战案例:客服工单自动分类系统
1. 系统架构
用户工单 → 文本清洗 → Embedding → 分类器 → 路由到对应客服组
↓
定期重训练(数据漂移检测)
2. 完整实现代码
python
"""
功能:客服工单自动分类系统(完整生产级实现)
特点:数据增强、模型持久化、性能监控
依赖:sentence-transformers>=2.3.0, scikit-learn>=1.3.0, joblib>=1.3.0
"""
import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix
import joblib
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
# ============ 1. 数据准备与增强 ============
def prepare_data():
"""准备训练数据(实际应从数据库读取)"""
data = pd.DataFrame({
'text': [
# 账号问题(扩展样本)
"账号无法登录", "密码忘记了", "登录失败", "账号被锁定",
"验证码收不到", "无法注册新账号", "账号异常",
# 商品问题
"商品质量差", "收到破损商品", "产品有瑕疵", "商品与描述不符",
"想要退货", "商品功能故障", "产品尺寸不对",
# 物流问题
"物流太慢", "快递丢失", "配送延迟", "快递还没到",
"物流信息不更新", "配送地址错误", "快递被签收但未收到",
# 售后问题
"如何申请退款", "退款进度查询", "发票开具", "保修期多久",
"售后服务态度差", "维修申请", "换货流程"
],
'label': [0]*7 + [1]*7 + [2]*7 + [3]*7 # 0:账号 1:商品 2:物流 3:售后
})
return data
# ============ 2. 模型训练 ============
def train_classifier():
"""训练分类器"""
print("=" * 60)
print("客服工单分类系统 - 模型训练")
print("=" * 60)
# 加载数据
data = prepare_data()
print(f"数据集大小: {len(data)} 条")
print(f"类别分布:\n{data['label'].value_counts()}\n")
# 生成 Embedding
print("加载 Embedding 模型...")
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
embeddings = model.encode(
data['text'].tolist(),
normalize_embeddings=True,
show_progress_bar=True
)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
embeddings, data['label'],
test_size=0.2,
random_state=42,
stratify=data['label'] # 保持类别比例
)
# 训练分类器
print("\n训练逻辑回归分类器...")
classifier = LogisticRegression(
max_iter=1000,
class_weight='balanced', # 处理类别不平衡
random_state=42
)
classifier.fit(X_train, y_train)
# 交叉验证
cv_scores = cross_val_score(classifier, X_train, y_train, cv=3)
print(f"交叉验证准确率: {cv_scores.mean():.3f} (+/- {cv_scores.std():.3f})")
# 测试集评估
y_pred = classifier.predict(X_test)
category_names = ['账号问题', '商品问题', '物流问题', '售后问题']
print("\n测试集评估:")
print(classification_report(y_test, y_pred, target_names=category_names))
# 保存模型
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
model_path = f"ticket_classifier_{timestamp}.pkl"
joblib.dump({
'classifier': classifier,
'embedding_model_name': 'BAAI/bge-large-zh-v1.5',
'categories': category_names
}, model_path)
print(f"\n✅ 模型已保存: {model_path}")
return model, classifier, category_names
# ============ 3. 在线推理 ============
class TicketClassifier:
"""工单分类器(生产环境)"""
def __init__(self, model_path):
"""加载模型"""
checkpoint = joblib.load(model_path)
self.classifier = checkpoint['classifier']
self.categories = checkpoint['categories']
self.embedding_model = SentenceTransformer(
checkpoint['embedding_model_name']
)
print(f"✅ 模型已加载,支持类别: {self.categories}")
def predict(self, text: str) -> dict:
"""
分类单条工单
返回:
{
'category': str, # 类别名称
'label': int, # 类别ID
'confidence': float, # 置信度
'all_probs': dict # 所有类别的概率
}
"""
# 生成 Embedding
embedding = self.embedding_model.encode(
[text],
normalize_embeddings=True
)
# 预测
label = self.classifier.predict(embedding)[0]
probs = self.classifier.predict_proba(embedding)[0]
return {
'category': self.categories[label],
'label': int(label),
'confidence': float(probs[label]),
'all_probs': {
cat: float(prob)
for cat, prob in zip(self.categories, probs)
}
}
def predict_batch(self, texts: list[str]) -> list[dict]:
"""批量分类(提升吞吐量)"""
embeddings = self.embedding_model.encode(
texts,
normalize_embeddings=True,
batch_size=32,
show_progress_bar=True
)
labels = self.classifier.predict(embeddings)
probs = self.classifier.predict_proba(embeddings)
results = []
for label, prob in zip(labels, probs):
results.append({
'category': self.categories[label],
'label': int(label),
'confidence': float(prob[label])
})
return results
# ============ 4. 使用示例 ============
if __name__ == "__main__":
# 训练模型
emb_model, clf, categories = train_classifier()
# 模拟推理
print("\n" + "=" * 60)
print("推理测试")
print("=" * 60)
test_tickets = [
"我的账号登不上去了",
"收到的商品有划痕",
"快递三天了还没送到",
"退款什么时候到账"
]
for ticket in test_tickets:
embedding = emb_model.encode([ticket], normalize_embeddings=True)
label = clf.predict(embedding)[0]
prob = clf.predict_proba(embedding)[0][label]
print(f"工单: {ticket}")
print(f" → {categories[label]} (置信度: {prob:.3f})\n")
3. 生产环境部署建议
3.1 性能优化
python
"""
优化策略:
1. 模型预加载(避免每次请求都加载)
2. Batch推理(累积请求批量处理)
3. GPU加速(sentence-transformers支持CUDA)
"""
# 使用 ONNX 加速推理(可选)
from optimum.onnxruntime import ORTModelForFeatureExtraction
model = ORTModelForFeatureExtraction.from_pretrained(
'BAAI/bge-large-zh-v1.5',
export=True,
provider='CUDAExecutionProvider' # GPU加速
)
3.2 监控与告警
python
"""
关键监控指标:
1. 分类准确率(定期人工标注验证)
2. 置信度分布(低置信度样本需人工复核)
3. 数据漂移检测(类别分布变化)
4. 推理延迟(P50/P95/P99)
"""
# 置信度阈值策略
def classify_with_threshold(text, threshold=0.7):
result = classifier.predict(text)
if result['confidence'] < threshold:
return {'category': '转人工', 'reason': '低置信度'}
return result
3.3 持续优化
| 时间节点 | 优化策略 |
|---|---|
| 上线第1周 | 每天人工标注100条,快速修正明显错误 |
| 第1个月 | 收集1000+标注数据,重新训练 |
| 第3个月 | 引入主动学习,优先标注困难样本 |
| 第6个月 | 评估是否需要更换Embedding模型 |
十一、本章小结
1. 核心要点回顾
| 场景 | 推荐方案 | 核心优势 | 成本/复杂度 |
|---|---|---|---|
| 探索分析 | BERTopic / HDBSCAN | 自动发现主题,无需标注 | 低 |
| 少量数据 (<100) | Few-Shot / LLM API | 启动快,无需训练 | 中/高(API费用) |
| 中等数据 (1k-10k) | SetFit / LoRA | 效果好,训练快 | 中 |
| 生产系统 (>10k) | Embedding + LR / MLP | 推理极快,并发高 | 低(推理成本) |
2. 2025年技术趋势
- RAG与分类融合:分类不再是终点,而是RAG的路由节点(语义路由)。
- 小模型崛起:对于特定分类任务,微调后的0.5B模型(如Qwen2-0.5B)效果由于通用大模型。
- 合成数据增强:使用GPT-4生成困难样本(Hard Negatives)来训练小模型,成为标准范式。
3. 应用建议
- ✅ 从简单开始:先用 LLM API 跑通流程,验证价值。
- ✅ 数据为王:与其纠结模型架构,不如清洗几百条高质量数据。
- ✅ 持续监控:分类模型会随时间退化,必须建立 Human-in-the-loop 机制。
4. 延伸阅读
- 论文 :SetFit: Efficient Few-Shot Learning Without Prompts
- 工具 :MaartenGr/BERTopic
- 教程 :Hugging Face Text Classification Guide
下一章预告: 第2章:检索增强生成 (RAG) 原理
在下一章中,我们将深入 RAG 架构,探讨如何让 LLM "外挂大脑",解决幻觉和知识时效性问题。