📰 来源:Towards Data Science | 📅 翻译日期:2026年5月31日
🔗 原文:查看原文
🤖 翻译:DeepSeek AI · 仅供参考
两个场景,都很熟悉。
场景一:RAG系统初显神通
一个基于 RAG 的系统,覆盖了几百页政策文档,在一个小团队中上线。
首先让大家印象深刻的是:它能够处理 同义改写。有人问“如何取消?”,文档中从未出现“取消”一词,而是用了“终止程序”,但系统仍然找到了它。
另一位用户用法语提问,而政策文档是英文的,系统依然返回了正确的页面。这里有一个拼写错误,那里有一个语音拼写,都没问题。几天后,团队确实被折服了。RAG 最接近魔法的东西就在他们面前,而且不需要任何手工编码的同义词表。
场景二:两周后的失灵
同样的系统,两周后。
用户问:“关于合同工加班的规则是什么?”系统回答:“未找到相关信息。”而这位用户恰好是编写了这本手册一半内容的业务专家,他皱起眉头,打开 PDF,在 Ctrl+F 中输入“非雇员劳动”,三秒钟就找到了确切的段落。正确的关键词不是“加班”,而是文档实际使用的术语。专家知道这一点,但嵌入模型不知道。
很快,更多类似的情况浮现出来。否定句处理出错。确切的合同编号失败。一个内部产品代码返回了错误的等级。这些问题都无法通过更换嵌入提供商来解决。
本系列文章的观点提前说明:大多数企业级可靠性提升来自于强大的上游过滤(专家关键词、文档结构),而不是在薄弱的检索之上堆叠重排序器。
经典架构按成本分层
- 最底层:廉价的嵌入相似度
- 中间层:可选的交叉编码器重排序器
- 最上层:聊天补全的 LLM
它们都不是魔法;每一种都有特定的失效方式。
本文是更广泛的 企业文档智能系列 的一部分,该系列从基线管道到语料规模架构,逐步构建企业级 RAG。
1. 嵌入擅长的领域
在讨论失败之前,先看看嵌入真正让人印象深刻的地方。失败只有对比之下才有意义。
嵌入 将一段文本转换成一个向量。具有相似术语的文本在向量空间中会靠得很近。嵌入是一组数字,捕捉了一段文本的含义:更长的列表可以携带更多的细微差别。每一代嵌入都在改进。下面的每个案例都在四个模型上运行,从最弱到最强:
- GloVe-avg (2014):平均化的 300 维词向量。无上下文。严重偏向于字面词重叠。Apache 2.0 许可,在 HuggingFace 模型卡上声明。
- all-MiniLM-L6-v2 (2021):小型开源上下文句子编码器,2200 万参数,384 维。Apache 2.0 许可,在 HuggingFace 模型卡上声明。
- text-embedding-ada-002 (OpenAI 2022):1536 维。专有;受 OpenAI 使用条款约束。
- text-embedding-3-large (OpenAI 2024):3072 维。专有;受 OpenAI 使用条款约束。
加载每个模型只需一行代码。两个本地模型来自 sentence-transformers(首次调用时从 HuggingFace 下载权重到磁盘);两个 OpenAI 模型通过 API 客户端调用。所有四个模型的调用方式相同,返回一个向量。
from sentence_transformers import SentenceTransformer
from openai import OpenAI
# Local models: weights downloaded from HuggingFace, run in-process.
glove = SentenceTransformer("average_word_embeddings_glove.6B.300d") # 2014, 300-dim
minilm = SentenceTransformer("all-MiniLM-L6-v2") # 2021, 384-dim
# OpenAI models: called through the API.
client = OpenAI()
def openai_embed(text: str, model: str) -> list[float]:
return client.embeddings.create(input=text, model=model).data[0].embedding
# Same call shape across all four; each returns a vector of its own dimension.
v_glove = glove.encode("policy renewal")
v_minilm = minilm.encode("policy renewal")
v_ada = openai_embed("policy renewal", "text-embedding-ada-002") # 2022, 1536-dim
v_large = openai_embed("policy renewal", "text-embedding-3-large") # 2024, 3072-dim每个模型都存在于自己的向量空间中,具有各自的余弦分布,因此各列之间的原始分数不可比较。有意义的是同一列内的分离度:目标是否胜出干扰项,以及胜出多少?观察差距在梯度上扩大,是嵌入确实变好的经验证据。
下面每个比较表使用的基本原语相同:用四个模型嵌入查询和每个候选文本,用余弦相似度评分,每个候选返回一行:
def _cos(u, v):
"""Cosine similarity : dot-product of two vectors, normalised by their lengths."""
return float(np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v)))
def compare_models(query, candidates, target=None):
qg = glove.encode(query)
qm = minilm.encode(query)
qa = openai_embed(query, "text-embedding-ada-002")
ql = openai_embed(query, "text-embedding-3-large")
rows = []
for c in candidates:
rows.append({
"candidate": c,
"GloVe-avg": _cos(qg, glove.encode(c)),
"MiniLM": _cos(qm, minilm.encode(c)),
"ada-002": _cos(qa, openai_embed(c, "text-embedding-ada-002")),
"3-large": _cos(ql, openai_embed(c, "text-embedding-3-large")),
})
return pd.DataFrame(rows).set_index("candidate")1.1 概念近似
“车”匹配关于“车辆”、“汽车”、“机动车”的段落。“火灾损坏”找到“烟雾损坏”和“烧焦”的段落。“经理审批”匹配关于“高管批准”的条款。模型捕捉的是语义场,而不仅仅是表面词汇。这就是嵌入让人感觉强大的原因:用户不需要猜测文档的词汇;嵌入架起了桥梁。
随意查询桥接至正式释义。所有四个模型都找到了目标;更大的模型扩大了差距。
1.2 同义词与释义
“电话号码”匹配“电话”。“政策取消”匹配标题为“终止程序”的章节。“费用”匹配“收费”。“月成本”匹配“保费”。“到期”匹配“保单结束日期”。
评论已关闭