Embeddings 模型技术实践:从语义检索到智能客服意图路由
一、什么是 Embeddings 模型?
在自然语言处理领域,我们经常需要判断两段文本之间的语义关系。
例如:
- 用户搜索“怎么把买的东西退掉”,系统需要找到“商品退货及退款操作流程”。
- 用户向客服发送“我的快递怎么还没到”,系统需要将其识别为“物流查询”意图。
- 两个用户分别提交“账户无法登录”和“输入密码后一直进不去”,系统需要发现它们可能属于相同的问题。
- 面对数十万条客服历史对话,系统希望自动发现不同类型的问题及其分布。
这些任务的共同特点是:系统需要理解文本之间的语义关系,而不仅仅是比较关键词是否相同。
Embeddings(嵌入)模型就是解决这一问题的重要技术。
与大语言模型(LLM)生成文字不同,文本 Embeddings 模型将输入文本转换为固定维度的数值向量,使计算机可以通过数学运算比较文本之间的语义相似程度。
例如:
输入文本 A:
"我想把买来的耳机退掉"
输入文本 B:
"如何办理商品退货退款?"
输入文本 C:
"今天北京的天气怎么样?"
经过 Embeddings 模型编码:
文本 A → [0.12, -0.08, 0.32, ...]
文本 B → [0.15, -0.06, 0.29, ...]
文本 C → [-0.23, 0.41, 0.07, ...]
这里的数值仅用于展示数据形式,不是真实模型的输出。
如果模型训练得足够好,那么 A 和 B 的向量应该具有较高的语义相似度,而 A 和 C 的相似度通常较低。
1.1 Embeddings 模型的核心工作原理
我们可以将 Embeddings 模型抽象为一个函数:
[
f(x) \rightarrow \mathbf{v}\in\mathbb{R}^{d}
]
其中:
- (x):输入的文本。
- (f):Embeddings 模型。
- (\mathbf{v}):输出的文本向量。
- (d):向量维度,例如 384、512、1024 或 1536。
假设模型输出维度为 512,那么无论输入是一句话还是一段文章,都会得到一个包含 512 个数值的向量,前提是输入没有超过模型支持的长度范围。
这个向量并不是文本的压缩副本,也无法通过简单的逆向转换直接恢复原始文字。它是模型学习得到的一种语义表示。
1.2 如何计算文本相似度?
最常见的方法是余弦相似度(Cosine Similarity)。
对于向量 A 和 B:
[
\operatorname{sim}(A,B)=
\frac{A\cdot B}{|A||B|}
]
余弦相似度衡量的是两个向量在空间中的方向接近程度。
理论范围为 -1 到 1:
| 余弦相似度 | 数学含义 |
|---|---|
| 接近 1 | 向量方向非常接近 |
| 接近 0 | 向量近似正交 |
| 接近 -1 | 向量方向相反 |
但必须注意,余弦相似度不是语义一致的概率。
在部分 Embeddings 模型中,大量不相关文本的余弦相似度也可能明显大于 0。
因此,不能简单规定“相似度超过 0.8 就一定属于同一类别”。
这种阈值需要根据模型和实际业务数据进行验证。
1.3 Embeddings、LLM 和决策模型有什么不同?
| 对比维度 | Embeddings 模型 | 生成式 LLM | 决策模型 |
|---|---|---|---|
| 核心输出 | 数值向量 | 自然语言或结构化文本 | 预定义答案及概率 |
| 主要任务 | 语义表示与相似性计算 | 理解、推理、内容生成 | 分类、评分、路由 |
| 是否直接产生自然语言回答 | 否 | 是 | 通常否 |
| 是否直接输出分类概率 | 否 | 需额外设计和校准 | 部分模型支持 |
| 是否适合大规模预计算 | 是 | 通常不适合 | 视任务而定 |
| 典型应用 | 搜索、聚类、推荐、候选召回 | 问答、总结、复杂推理 | 意图识别、审核与决策 |
值得强调的是:
Embeddings 模型并不直接告诉我们某段文本属于哪个类别,而是提供一种可以用来计算相似度的表示。
要完成分类任务,还需要额外设计类别表示、相似度比较及分类策略。
二、主流纯文本 Embeddings 模型与调用方式
2.1 常见模型选择
当前比较有代表性的纯文本 Embeddings 模型包括:
| 模型 | 向量维度 | 主要特点 |
|---|---|---|
| BGE-small-zh-v1.5 | 512 | 中文模型,小型,适合本地部署及快速实验 |
| BGE-base-zh-v1.5 | 768 | 中文语义检索,可作为中等规模基线 |
| BGE-M3 | 1024 | 多语言,支持密集、稀疏和多向量检索 |
| Qwen3-Embedding-0.6B | 最高 1024 | 多语言,支持指令与可配置输出维度 |
| Qwen3-Embedding-4B | 最高 2560 | 较大规模的多语言文本向量模型 |
| text-embedding-3-small | 默认 1536 | 托管 API,支持调整输出维度 |
| text-embedding-3-large | 默认 3072 | 托管 API,适合测试更高容量的表示能力 |
这里的维度、模型参数量和实际分类准确率是不同概念。
维度更高,并不必然意味着效果更好。
例如,一个针对中文文本优化的 512 维模型,在特定中文客服数据集上,完全可能优于某个更高维但训练领域不同的模型。
选择 Embeddings 模型时,建议优先考虑:
- 中文语义表示质量。
- 文本长度和截断限制。
- 向量维度与存储成本。
- 单条文本推理延迟。
- 批量编码吞吐量。
- 本地部署或 API 部署的需求。
接下来两个实践案例,我们统一使用:
BAAI/bge-small-zh-v1.5
该模型输出 512 维向量,适合演示中文语义检索及基础分类任务。
它不是当前所有任务中性能最好的模型,选择它主要是为了让示例容易理解和复现。
2.2 Embeddings API 的基本形式
对于托管式 Embeddings 服务,常见的调用方式是:
POST /v1/embeddings
请求体一般包括模型名称和输入文本。
例如:
{
"model": "text-embedding-3-small",
"input": [
"我想查询订单物流",
"如何申请商品退款"
]
}
接口返回的数据主要是向量,而不是回答文本。
简化后的响应结构如下:
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.012, -0.031, 0.024]
},
{
"object": "embedding",
"index": 1,
"embedding": [-0.041, 0.017, 0.036]
}
],
"model": "text-embedding-3-small"
}
这里的三个数值只是为了简化展示,实际返回的是完整维度的向量。
如果需要使用托管 API,可以将 POST /v1/embeddings 与服务提供商给出的 API 基础地址组合起来。
不过,本文将以本地模型为主,避免 API 网络请求及密钥管理对学习过程造成干扰。
2.3 Python 环境准备
安装 Sentence Transformers:
pip install -U sentence-transformers numpy
加载模型:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer(
"BAAI/bge-small-zh-v1.5"
)
text = "我想查询订单物流"
vector = model.encode(
text,
normalize_embeddings=True
)
print("向量维度:", vector.shape)
print("前五个数值:", vector[:5])
输出结构类似:
向量维度:(512,)
前五个数值:[...]
其中 normalize_embeddings=True 表示对向量执行 L2 归一化,使其长度为 1。
这样,两个归一化向量的点积,就等于它们的余弦相似度。
需要注意,第一次通过模型名称加载模型时,通常需要下载权重文件。后续可以使用本地模型目录,在断网环境中运行。
三、实践案例一:基于 Embeddings 的企业知识库语义检索
3.1 案例背景
这是 Sentence Transformers 官方 Semantic Search 实践所介绍的典型应用方向。
假设我们正在开发一个企业内部知识库。
知识库中保存着大量业务制度、操作手册和常见问题解答。
例如:
文档1:
员工可以通过企业办公平台提交差旅报销申请。
申请时需要上传有效的发票和出差审批单。
文档2:
员工离职后,应在规定时间内办理设备归还,
包括笔记本电脑、工牌及其他办公资产。
文档3:
如遇企业邮箱无法登录,可以先检查账户密码,
若仍无法登录,应联系IT服务台处理。
文档4:
员工年度带薪休假天数根据入职时间及累计工作
年限确定,具体规则参照公司休假管理制度。
现在,用户输入:
我出差回来,机票和酒店费用要怎么报?
传统关键词搜索可能找不到理想答案,因为搜索词使用的是“出差”“机票”“酒店费用”,而知识库中使用的是“差旅报销”“发票”“审批单”。
虽然两者存在明显的语义联系,但字面表达并不完全一致。
Embeddings 语义检索的目标,就是让系统根据语义关系找到最相关的文档。
3.2 整体技术流程
知识库文档
│
▼
文本清洗与分块
│
▼
Embeddings 模型
│
▼
文档向量集合
│
▼
向量索引或向量数据库
│
│
│ 用户输入查询
│ │
│ ▼
│ Embeddings 模型
│ │
│ ▼
└──────── 向量相似度检索
│
▼
返回 Top-K
│
▼
相关知识库文本片段
这里有一个非常重要的架构特点:
知识库文档不需要在每次查询时重新计算 Embeddings。
它们可以预先计算并持久化保存。
只有新查询需要实时进行编码。
因此,在线检索的主要过程可以简化为:
[
q \rightarrow f(q) \rightarrow TopK
]
其中,(f(q)) 表示查询文本的向量表示。
3.3 完整 Python 实践
先构造一个简单的知识库:
from sentence_transformers import SentenceTransformer
import numpy as np
# 1. 加载 Embeddings 模型
model = SentenceTransformer(
"BAAI/bge-small-zh-v1.5"
)
# 2. 模拟企业知识库
documents = [
{
"id": 1,
"title": "差旅报销",
"content": (
"员工出差结束后,可以通过企业办公平台"
"提交差旅报销申请。机票、酒店住宿费用"
"按照财务制度报销,需要提供有效发票"
"及出差审批单。"
)
},
{
"id": 2,
"title": "离职设备归还",
"content": (
"员工离职前需要归还公司设备,包括"
"笔记本电脑、工牌和其他办公资产。"
)
},
{
"id": 3,
"title": "邮箱账户问题",
"content": (
"如果企业邮箱无法登录,可以先检查"
"账户密码,仍无法解决时联系IT服务台。"
)
},
{
"id": 4,
"title": "年假管理",
"content": (
"员工年度带薪休假天数根据累计工作"
"年限确定,具体以休假管理制度为准。"
)
}
]
# 3. 需要编码的文档正文
passages = [
doc["content"]
for doc in documents
]
# 4. 为知识库预先生成向量
doc_vectors = model.encode(
passages,
normalize_embeddings=True,
batch_size=32
)
# 5. 用户输入查询
query = "我出差回来,机票和酒店费用要怎么报?"
# BGE 中文检索模型推荐的查询指令
instruction = (
"为这个句子生成表示以用于检索相关文章:"
)
# 6. 生成查询向量
query_vector = model.encode(
instruction + query,
normalize_embeddings=True
)
# 7. 计算余弦相似度
scores = doc_vectors @ query_vector
# 8. 获取排名前三的文档
top_k = 3
top_indices = np.argsort(scores)[::-1][:top_k]
for rank, index in enumerate(top_indices, start=1):
doc = documents[index]
print(f"排名:{rank}")
print(f"标题:{doc['title']}")
print(f"相似度:{scores[index]:.4f}")
print(f"内容:{doc['content']}")
print("-" * 40)
3.4 如何解读结果?
对于上述查询,我们希望模型把“差旅报销”排在前面。
最终输出格式如下:
排名:1
标题:差旅报销
相似度:运行后得到的实际数值
内容:员工出差结束后,可以通过企业办公平台...
排名:2
标题:其他知识库文档
相似度:运行后得到的实际数值
...
排名:3
标题:其他知识库文档
相似度:运行后得到的实际数值
...
这里没有填入人为编造的相似度,因为真实数值必须由实际模型推理获得。
上述代码的核心只有三步:
# 文档编码
doc_vectors = model.encode(
passages,
normalize_embeddings=True
)
# 查询编码
query_vector = model.encode(
instruction + query,
normalize_embeddings=True
)
# 相似度计算
scores = doc_vectors @ query_vector
这就是一个最小化的语义检索系统。
3.5 扩展:将语义检索与 RAG 结合
Embeddings 模型只能返回相关文档,无法直接基于这些文档生成完整回答。
如果希望实现类似智能知识库问答的效果,就可以在其后接入 LLM。
用户问题
│
▼
Embeddings 查询编码
│
▼
向量检索 Top-K
│
▼
检索到相关文档
│
▼
可选:Reranker 精细排序
│
▼
问题 + 检索文档
│
▼
LLM 生成回答
│
▼
返回答案
这就是典型的 RAG(Retrieval-Augmented Generation,检索增强生成)架构。
这里有两个角色:
Embeddings 模型负责找到资料。
生成式 LLM 负责理解资料并组织回答。
在实际系统中,如果知识库较大,通常还需要:
- 使用文本分块避免超出模型输入长度。
- 使用 FAISS、Milvus 等建立向量索引。
- 将向量与文档 ID、来源、权限信息关联。
- 对检索结果执行重排序。
- 使用混合检索补充关键词、编号和专业术语匹配。
值得注意的是,RAG 的最终效果不仅取决于 Embeddings 模型,还取决于分块、召回、重排序和生成模型。
四、实践案例二:基于 Embeddings 的智能客服意图识别与路由
4.1 案例背景
第二个案例参考 OpenAI 开发者文档中 Zero-shot Classification with Embeddings 的经典方法。
该方法的核心思想是:
将文本与类别描述分别编码为向量,通过比较相似度完成零样本分类。
假设我们正在开发一个电商智能客服系统。
用户可能表达以下意图:
| 意图类别 | 用户表达示例 |
|---|---|
| 退款退货 | 我要把商品退掉 |
| 物流查询 | 我的快递到哪里了 |
| 修改订单 | 我想修改收货地址 |
| 优惠价格 | 这个商品有没有优惠券 |
| 发票业务 | 我想申请电子发票 |
传统关键词分类器可以根据“退款”“快递”“发票”等关键词进行匹配。
但用户实际表达可能具有明显的多样性。
例如:
买来的东西不喜欢,能不能把钱退给我?
这句话并未直接使用“退货退款”作为固定标签名称,但其语义与退款退货高度相关。
Embeddings 模型可以用于解决这种类别表达与用户表达不一致的问题。
4.2 核心原理:类别语义匹配
首先,为每一个意图类别定义一段文字描述。
例如:
类别:退款退货
描述:
用户希望取消订单、退回已经购买的商品、
获得退款或者办理售后退货。
然后对每个类别描述生成 Embeddings。
退款退货 ──> 类别向量 A
物流查询 ──> 类别向量 B
修改订单 ──> 类别向量 C
优惠价格 ──> 类别向量 D
发票业务 ──> 类别向量 E
用户输入新文本:
我买的耳机不想要了,能退钱吗?
通过相同的模型编码:
用户文本
│
▼
Embeddings 模型
│
▼
用户文本向量
│
▼
与所有类别向量比较
│
▼
类别相似度排名
│
▼
输出候选意图类别
这种分类方式不需要先训练一个专门的意图分类头,因此可以作为零样本意图分类的基线。
4.3 完整 Python 实践
from sentence_transformers import SentenceTransformer
import numpy as np
# 1. 加载模型
model = SentenceTransformer(
"BAAI/bge-small-zh-v1.5"
)
# 2. 定义意图类别及语义描述
intent_definitions = {
"退款退货": (
"用户希望取消已经支付的订单、"
"退回购买的商品、申请售后退款"
"或者要求商家退还货款。"
),
"物流查询": (
"用户希望查询快递物流信息、"
"配送进度、发货时间、包裹位置"
"或者预计送达时间。"
),
"修改订单": (
"用户希望修改已经提交的订单,"
"包括收货地址、联系电话、"
"商品规格或收件人信息。"
),
"优惠价格": (
"用户希望了解优惠券、折扣活动、"
"商品价格、满减优惠或者价格保护。"
),
"发票业务": (
"用户希望申请发票、补开发票、"
"修改发票抬头或查询开票信息。"
)
}
# 3. 提取类别信息
intent_names = list(intent_definitions.keys())
intent_texts = list(intent_definitions.values())
# 4. 预先计算类别向量
intent_vectors = model.encode(
intent_texts,
normalize_embeddings=True
)
def classify_intent(text, top_k=3):
"""
对文本进行意图相似度排序。
返回候选意图,不把相似度
直接解释为分类概率。
"""
# 编码用户文本
text_vector = model.encode(
text,
normalize_embeddings=True
)
# 计算所有类别的相似度
scores = intent_vectors @ text_vector
# 按相似度从高到低排序
indices = np.argsort(scores)[::-1][:top_k]
results = []
for index in indices:
results.append({
"intent": intent_names[index],
"similarity": float(scores[index])
})
return results
# 5. 模拟用户输入
user_text = "买来的耳机我不想要了,能退钱吗?"
# 6. 意图识别
results = classify_intent(user_text)
for item in results:
print(
f"{item['intent']}: "
f"{item['similarity']:.4f}"
)
这段代码完成了一个基础的零样本意图分类系统。
输入是一段文本,输出是最相近的三个意图及其相似度。
4.4 如何将相似度排名转化为业务路由?
假设某次实际运行得到以下分数:
以下分数仅用于解释逻辑,不代表实际模型测量结果。
退款退货:0.91
修改订单:0.73
优惠价格:0.65
系统可以认为“退款退货”是首选候选类别。
但这不等于模型有 91% 的正确概率。
对于生产环境,可以进一步设计:
- 最高相似度达到业务阈值。
- 第一类别和第二类别具有足够大的相似度差距。
- 不满足条件的请求进入更强的分类模型或人工审核。
例如:
results = classify_intent(user_text, top_k=2)
best = results[0]
second = results[1]
# 以下阈值为演示值,实际应在验证集上调优
min_similarity = 0.85
min_margin = 0.08
margin = (
best["similarity"]
- second["similarity"]
)
if (
best["similarity"] >= min_similarity
and margin >= min_margin
):
action = f"自动路由到:{best['intent']}"
else:
action = "进入进一步意图识别流程"
print(action)
这种方法可以把 Embeddings 模型从单纯的相似度计算器,变成智能客服系统中的候选意图召回器或轻量路由模块。
不过,自动化阈值应根据误分类代价、拒识率以及实际标签分布确定。
4.5 扩展:多意图识别怎么办?
实际客服场景经常不是单意图。
例如:
我要把昨天买的耳机退掉,另外上周买的键盘快递到哪里了?
这句话至少包含两个意图:
- 退款退货
- 物流查询
如果仅仅选择最高相似度类别,就会丢失另一个意图。
此时,我们可以采用两个阶段:
第一阶段:Embeddings 召回候选意图。
例如:
输入:
我要把昨天买的耳机退掉,
另外上周买的键盘快递到哪里了?
Embeddings 候选召回:
1. 退款退货
2. 物流查询
3. 修改订单
第二阶段:使用多标签分类器或者决策模型进行判定。
用户文本
│
▼
Embeddings 模型
│
▼
候选意图 Top-K
│
▼
多标签分类模型 / 决策模型
│
▼
最终意图集合
│
├── 退款退货
└── 物流查询
这样做的优势是:可以先用轻量 Embeddings 模型缩小候选集合,再由更擅长精确分类的模型完成最终判断。
当然,也可以尝试对每个类别分别设定相似度阈值,直接输出多个标签。
但这种方法存在明显问题:
- 不同类别的相似度分布可能不一致。
- 类别之间可能语义高度重叠。
- 文本包含某个关键词,并不意味着用户具有对应意图。
- 一个“其他”类别通常没有清晰统一的语义中心。
因此,对于复杂的多标签系统,直接对所有类别使用相同的余弦相似度阈值,往往不是最稳妥的做法。
更合理的方案是通过业务标注数据,为不同类别分别评估阈值,或者引入监督式多标签分类器。
4.6 当意图类别达到数百个时
对于大型客服系统,可能存在:
- 数十个一级意图。
- 数百个二级意图。
- 部分二级意图之间只有非常细微的区别。
此时,可以使用一种分层架构:
用户对话文本
│
▼
Embeddings 模型
│
▼
一级意图候选召回
│
▼
关联二级意图候选召回
│
▼
精细分类模型
│
▼
多标签意图集合
需要强调,一级意图和二级意图不一定必须分开召回。也可以直接把全部二级意图编码后进行统一 Top-K 检索,再根据层级关系组织输出。
这取决于业务标签体系和各类意图的语义相似程度。
对于 100~200 个左右的类别,仅仅计算一次查询向量与全部预先编码类别向量的点积,通常并不是主要计算瓶颈。
真正需要关注的往往是:
- 用户文本的 Embeddings 推理时间。
- 候选意图召回率。
- 相似类别之间的区分能力。
- 最终分类阶段的端到端延迟。
所以,在规模不大的意图分类系统中,Embeddings 更值得被优化的是表示质量和候选召回效果,而不只是向量检索速度。
五、工程实践:如何提高 Embeddings 系统的效果?
5.1 区分对称检索与非对称检索
Embeddings 检索并不总是相同的任务。
对称检索(Symmetric Search)
输入和匹配对象具有相似的文本形式。
例如:
句子 A:
我想退货。
句子 B:
这件商品能退款吗?
这种情况下,可以使用相同的编码方式。
非对称检索(Asymmetric Search)
查询往往很短,而待检索内容可能是一段完整的说明文字。
例如:
查询:
差旅怎么报销?
文档:
员工完成出差任务后,应通过财务报销
系统提交申请,并上传差旅审批记录...
对于非对称检索,部分 Embeddings 模型会针对 Query 和 Document 使用不同的指令或编码机制。
例如,BGE 中文模型推荐给短查询添加:
为这个句子生成表示以用于检索相关文章:
但通常不需要给文档添加该指令。
不同模型的要求不一样。
对于 Sentence Transformers,部分支持专门查询和文档提示词的模型,可以通过:
query_vector = model.encode_query(
query,
normalize_embeddings=True
)
document_vectors = model.encode_document(
documents,
normalize_embeddings=True
)
使用模型自身配置的查询与文档编码方式。
但具体模型是否配置了这些指令,需要查看对应的模型说明。对于本文使用的 BGE 模型,手动添加官方建议的查询指令是一种明确可控的实现方式。
5.2 Embeddings 不等于语义逻辑推理
考虑两句话:
A:用户已经完成实名认证。
B:用户尚未完成实名认证。
它们包含大量相同的词汇,主题也高度一致。
Embeddings 模型可能给出较高的语义相似度。
但两句话的事实含义恰好相反。
因此,Embedding Similarity 更适合衡量文本的语义接近程度,而不是严格的逻辑蕴含、事实一致或真假关系。
对于涉及:
- 否定词
- 时间条件
- 数值限制
- 权限与身份
- 法律条款
- 金融交易条件
的任务,不建议只靠向量相似度做最终决策。
5.3 为什么需要 Reranker?
Embeddings 模型通常采用双塔或 Bi-Encoder 结构。
它分别编码查询和文档:
Query ──> Encoder ──> Query Vector
Document ──> Encoder ──> Document Vector
优点是可以预计算文档向量,检索速度快。
缺点是,查询和文档之间没有在最终编码阶段进行充分的逐项交互。
Reranker 则可以直接联合处理查询与候选文档,对相关性进行更精细的判断。
因此,常见组合是:
Embeddings 模型
│
▼
初步召回 Top-50
│
▼
Reranker
│
▼
重新排序
│
▼
最终 Top-5
这里的 Top-50 和 Top-5 只是示例参数。
这种方式特别适合知识库检索和相似意图较多的分类系统。
5.4 向量数据库什么时候有必要?
假设使用 512 维、Float32 向量。
每个向量所需原始存储空间约为:
[
512\times4=2048\text{ bytes}
]
如果有 10 万条文档:
[
100000\times2048
204800000\text{ bytes}
]
约为 195 MiB。
这还不包括向量索引、文档正文和元数据。
对于几百条或几千条数据,直接使用 NumPy 计算点积就可以完成原型验证。
但当数据量扩大、并发请求增加时,可以考虑:
- FAISS:本地向量索引和近似最近邻检索。
- Milvus:面向更大规模向量检索的数据库。
- PostgreSQL + pgvector:在关系型数据库中增加向量检索能力。
向量数据库主要解决高效存储、索引和近邻检索,并不会自动提高 Embeddings 模型自身的语义理解能力。
5.5 模型切换时是否需要重新生成向量?
通常需要。
因为不同 Embeddings 模型学习到的向量空间并不相同。
例如:
模型 A:
文本 → 512 维向量
模型 B:
文本 → 1024 维向量
二者不仅维度不同,向量空间的语义结构也不同。
即使两个模型输出维度一样,也不能直接把它们的向量混合比较。
因此,从一个 Embeddings 模型切换到另一个模型时,通常需要重新生成知识库或类别向量,并重建相关索引。
六、如何评估 Embeddings 模型?
模型效果应当根据具体任务分别评估,而不是只比较公开排行榜上的综合分数。
6.1 语义检索任务
建议使用:
| 指标 | 含义 |
|---|---|
| Recall@K | 正确文档是否出现在前 K 个检索结果中 |
| MRR | 第一个正确结果在排名中有多靠前 |
| NDCG@K | 综合考虑排序位置与相关性等级 |
| P95 Latency | 95% 请求可以在多少时间内完成 |
对于知识库问答系统,Recall@K 往往是非常重要的指标。
因为如果正确资料未被召回,后续再强的 Reranker 或 LLM 也无法通过这些结果找到它。
6.2 意图分类任务
建议使用:
| 指标 | 含义 |
|---|---|
| Top-1 Accuracy | 第一候选类别是否正确 |
| Recall@K | 正确意图是否包含在前 K 个候选中 |
| Macro-F1 | 对各类意图平均计算的综合分类表现 |
| Micro-F1 | 汇总各类别样本预测后的综合分类表现 |
| 自动路由覆盖率 | 有多少请求能够直接处理 |
| 自动路由准确率 | 自动处理请求中实际分类正确的比例 |
对于一个 Embeddings + 决策模型的两阶段架构,应该分别评估:
- Embeddings 候选召回阶段的 Recall@K。
- 下游分类器在候选集合上的 Precision、Recall 和 F1。
- 完整链路的端到端准确率、P95 延迟和成本。
只有这样,才能判断 Embeddings 是否真正为整体系统带来了收益。
6.3 特别关注“其他”类与拒识能力
在意图识别中,经常需要支持:
意图A
意图B
意图C
其他
但 Embeddings 最近邻分类往往会遇到一个问题:
无论用户输入什么,系统总能找到最相似的已知类别。
即使用户输入:
今天公司的空调为什么这么冷?
在一个只包含退款、物流、发票的电商意图分类器中,系统仍然会返回某个最高相似度类别。
但这不意味着它真的属于该类别。
因此,需要独立设计拒识机制,例如:
- 最低相似度阈值。
- 第一与第二候选的分数间隔。
- 额外的域内/域外分类器。
- 决策模型或 LLM 的最终复核。
拒识能力对于大型意图识别系统尤其重要。
七、总结:Embeddings 模型最适合解决什么问题?
综合两个案例,可以把 Embeddings 模型的主要用途归纳如下:
| 场景 | Embeddings 的作用 |
|---|---|
| 知识库语义搜索 | 根据问题找到相关文档 |
| RAG | 为生成式 LLM 提供相关知识 |
| 智能客服意图识别 | 召回最可能的意图类别 |
| 相似文本检测 | 发现重复或近似的表达 |
| 文本聚类 | 将语义相近的文本组织在一起 |
| 内容推荐 | 根据文本语义寻找相关内容 |
| 大规模分类 | 生成可复用的文本特征或候选类别 |
本文的第一个案例展示了 Embeddings 最经典的应用:把文本转化为向量,通过相似度计算实现语义检索。
第二个案例展示了另一种重要用途:将类别描述与用户文本映射到同一向量空间,从而构建无需专门训练分类头的初步意图识别系统。
这两个场景看起来不同,但底层方法相同:
[
\text{文本}
\rightarrow
\text{向量}
\rightarrow
\text{相似度}
\rightarrow
\text{排序或候选召回}
]
进一步说,Embeddings 模型的真正价值并不在于输出了一组浮点数,而在于它使大量原本难以直接比较的自然语言内容,变成了可以进行数学计算和快速索引的数据。
最后,如果将 Embeddings 模型、决策模型和生成式 LLM 结合,可以形成一种分层的智能处理架构:
用户输入
│
▼
Embeddings 模型
│
▼
语义检索 / 候选召回
│
▼
结构化决策模型
│
┌─────────┴──────────┐
│ │
▼ ▼
简单明确任务 复杂或不确定任务
│ │
▼ ▼
自动处理 生成式 LLM
│
▼
进一步分析
│
▼
输出最终结果
这并不是所有应用都必须采用的架构,但它提供了一个有价值的工程思路:
- Embeddings 负责高效的语义表示与候选召回。
- 决策模型负责在明确的候选空间内判断和分类。
- 生成式 LLM 负责开放式理解、复杂推理及文本生成。
三个模型分别承担适合自己的工作,有机会在准确率、推理成本和端到端延迟之间取得更好的平衡。
Embeddings 模型不一定是能够直接完成业务决策的模型,却往往是构建高效语义检索、智能分类和复杂 AI 工作流的重要基础组件。
- 点赞
- 收藏
- 关注作者
评论(0)