搜索“年假如何申请”,系统找回一篇“病假如何申请”;两段话都谈请假,却不一定适用于同一问题。向量检索经常出现这种“意思接近,但答案不对”的情况。理解 Embedding 和相似度,能帮助你判断问题出在资料、检索配置,还是回答阶段。
本文先用不联网的二维数字练习算余弦相似度,再用三条虚构制度检查检索结果。适合准备使用知识库、想读懂检索分数的普通用户。练习向量由人工指定,不是任何模型对句子的实际输出;本文不展示伪造的真实召回分数。
一、Embedding 把内容变成可比较的数字
Embedding 可以理解为模型把内容映射到一组数字,让系统能够进行相似度计算。它常用于语义搜索、分类和聚类。实际向量通常有很多维,不能把每一维都直接命名成“积极”“价格”“质量”等人类概念。
向量模型与聊天模型承担的任务不同:前者给出适合比较的表示,后者生成回答。拿到相近文档后,仍要检查文档是否包含所需条件和结论。Embedding 本身不会替你判断制度是否有效、资料是否真实。
同样长度的数字列表也不意味着可以比较。不同模型或不同配置生成的向量,可能属于不同的表示空间。不能仅凭“都是七百多维”就混入同一个索引。
二、余弦相似度比较方向,不是正确概率
余弦相似度用两个非零向量的点积,除以它们长度的乘积。用口语说,就是比较它们的方向有多接近。计算范围通常是负一到一,具体模型的实际分布未必覆盖整个范围。
相似度 = 点积 ÷(向量A的长度 × 向量B的长度)
点积 = 各个对应位置相乘后求和
长度 = 各位置平方求和后开平方分数接近一表示在这个比较方式下方向接近,不表示“答案有百分之九十九的概率正确”。相反方向得到负值,也不能直接理解为自然语言中的“逻辑反对”。数字几何关系和语言逻辑关系不是一回事。
还要留意工具显示的是 similarity 还是 distance。SciPy 的 cosine 函数返回余弦距离,即一减去余弦相似度。相似度常按大到小排序,距离常按小到大排序;看错方向会把最不接近的条目放到前面。
三、用 Python 做一个离线计算练习
准备 Python 3,下面程序只用标准库 math,不需要账号、密钥、第三方包或模型下载。将代码保存为 cosine_demo.py,在该目录终端执行 python cosine_demo.py。
import math
def cosine(a, b):
if not a or len(a) != len(b):
raise ValueError("向量不能为空,且维度必须一致")
if not all(math.isfinite(x) for x in a + b):
raise ValueError("向量必须是有限数字")
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(x * x for x in b))
if na == 0 or nb == 0:
raise ValueError("零向量不能计算余弦相似度")
return sum(x * y for x, y in zip(a, b)) / (na * nb)
query = [1.0, 0.0]
candidates = {
"同方向": [2.0, 0.0],
"略微偏转": [0.9, 0.1],
"垂直方向": [0.0, 1.0],
"反方向": [-1.0, 0.0],
}
for name, vector in candidates.items():
print(name, f"{cosine(query, vector):.4f}")预期依次得到 1.0000、0.9939、0.0000、-1.0000。本文已在本地执行这段独立的数学示例并检查结果;这不代表调用过真实 Embedding API,也不代表任何模型具备这样的语言理解准确率。
把第一项改成 [100.0, 0.0],结果仍为一,因为方向未变。再用 [0.0, 0.0] 测试,程序应拒绝计算,而不是给零向量安排一个看似有效的分数。长度不同也应报错,不能让 zip 默默忽略多出来的位置。
四、把概念带回三个制度条目
准备三个独立的虚构资料文件,保留编号和适用范围:
- D01/年假:“正式员工申请年假,至少提前三个工作日提交申请,由直属负责人审批。”
- D02/病假:“员工申请病假,应按公司要求补交相关证明;紧急情况可先通知负责人。”
- D03/会议室:“会议室使用前需完成预约,取消使用时及时释放时段。”
再写四个查询,不要先看模型答案再改标准:
- Q01:“正式员工年假要提前多久申请?”应找到 D01,答案是三个工作日。
- Q02:“临时生病来不及提交怎么办?”应检查 D02,不能引用年假的提前期限。
- Q03:“实习生有几天年假?”现有材料没有答案,应说明证据不足。
- Q04:“会议室不去了要处理预约吗?”应找到 D03,重点是释放时段。
在你自己的知识库里用检索预览或召回测试运行这些问题,记录返回的文档编号、原文和分数。不同平台入口不同,不能只看聊天窗口最终回答。先确认找回了什么,再判断回答写得好不好。
五、真实模型接入前检查这几项
本练习不要求购买 API。若后续使用平台提供的向量模型,先核对官方文档中的模型名、输出维度、任务设置、输入限制和计费方式,再导入这三条样本。只选定一套配置,把查询和文档放入相匹配的工作流程。
例如 Google 当前文档区分两代 Embedding 的任务设置:gemini-embedding-001 支持 task_type;gemini-embedding-2 使用指令方式表达任务,不能照搬前者的同名字段。这个差别说明“看到旧教程就复制配置”并不可靠,本文不把某一代的参数写成所有模型通用规则。
保存索引配置时至少记录:模型标识、维度、任务设置、切分规则、资料版本,以及使用的距离或相似度类型。模型或输出空间改变后,应按平台迁移要求重新生成相应索引;不要保留一半旧向量,再把另一半换成新模型。
六、相似内容为何仍然答错
年假和病假都包含员工、申请、负责人等词,即使在语义上接近,也有不同的适用条件。处理这类问题,要保留制度名称、人员范围和时间条件,不能把它们在切分时丢掉。
检查 D01 是否被切成孤立的“提前三个工作日提交申请”。如果片段没有年假标题,系统就更难判断它适用于哪类请假。需要时把标题和适用范围随正文一同保存,而非只靠提高候选数量。
同理,旧版制度和新版制度可能非常相似。把版本和有效状态作为资料属性,先筛选适用范围,再比较候选,是一种更容易核查的工程思路。是否支持属性过滤、如何配置,应由所用平台的文档确定。
七、Top K 和阈值怎么理解
Top K 表示最多保留若干候选,不是“前几项一定正确”。库里没有实习生年假天数,取第一条也不能凭空得到证据;一味增加候选,还可能把不适用的病假制度一起送进回答。
阈值也不是全行业统一数字。不要把某个平台的 0.8 原样移到另一个模型或不同距离方式中。先收集可回答、不可回答、用词接近但条件不符的样本,比较实际分布,再依据漏检和误检的代价调整。
小型练习可以先固定 Top K 和其他配置,只改一项切分规则,重新跑 Q01 到 Q04。记录改动前后是否找到正确文档、是否夹带不适用资料、是否对 Q03 保持证据不足。没有记录的反复调参,很难知道真正改善了什么。
八、把检索表现与回答表现分别记分
四个查询可以每条记录两项:正确证据是否进入候选,以及最终回答是否符合证据。Q01 找到了 D01,却答成提前五天,应把检索记为命中、回答记为错误。Q03 没有答案,如果系统坦言资料未说明,就不该因为“没有返回具体天数”而给差评。
如果候选同时包含 D01 和 D02,要记录不适用的干扰条目,不能只写“有正确文档,所以完全通过”。候选越多,越需要关注是否把病假证明要求错误嫁接给年假。少量人工样本只是发现问题的起点,不足以宣称整体准确率达到某个水平。
准备上线时再扩充同义表达、错别字、范围不符、版本冲突和无答案问题。每次更换模型或切分方式,都保留同一份基准样本重新检查。新增样本和配置变更分别记录,避免把测试集变简单误认为系统变强。
九、建立一张可复查的检查单
- 向量层:维度是否一致,是否有空值、非有限数或零向量,模型与任务配置是否匹配。
- 资料层:标题、适用范围、版本和关键条件是否保留,是否导入重复或过期资料。
- 检索层:排序方向正确吗,返回了哪些编号,正确证据是否在候选中。
- 回答层:是否引用正确片段,是否把范围扩大,是否承认现有资料没有答案。
若正确文档已经被找回,回答却编造了天数,就不要继续盲目更换向量模型;需要检查答案生成和引用约束。若正确文档根本未进入候选,应先回到索引、过滤和切分检查。
完成练习后,最有用的成果是四个查询的原始检索记录和人工判定。分数帮助你排候选,最终结论仍要落在能回查、适用且足够的证据上。

还没有公开评论,欢迎分享你的想法。