Agent 工程学习站 做棵大树 出品 · beatree.cn 免费 · 无需登录 · 进度存本机
IV 版 · 知识引擎 第 03 章 Vectors and Similarity
IV · 知识引擎 03 / 22 进阶

Embedding 与向量检索

Vectors and Similarity
预计阅读 28 分钟
难度 进阶
关键词 Embedding · 余弦相似度
本机状态 未读

语义检索为什么会找错?因为「语义相近」和「对回答问题有用」是两件事。前者是向量空间的几何关系,后者是任务相关性的判断。理解这中间的落差,才能明白向量检索的边界在哪里、什么时候必须叠加别的检索手段。

编辑部注 · 本章的核心是「能力边界」而不是数学。重点是理解为什么纯向量检索在一些场景必然失效。

先把向量检索的物理本质讲清楚:它把文本映射到一个高维空间里的点,让「语义相近的文本」在空间里距离更近。检索就是找最近的邻居。这个机制非常有效,也有非常明确的边界——而多数误用都源于不清楚边界在哪。

一、嵌入做的事:从符号到坐标

文本在计算机里本来是离散符号序列,无法比较「相似」。嵌入模型把它变成连续向量,于是「相似」变成了「距离」这个可计算的东西。

关键在于「相似」是通过什么学到的。模型在大量文本对(相似的、不相似的)上训练,学会了把「意思接近」的文本映射到相近的位置。这带来两个直接结论:

嵌入擅长的嵌入不擅长的
同义表达:「转化率低」与「成交比例不高」精确标识符:订单号、错误码、函数名、型号(差一个字符就是另一个东西)
跨语言:「客户满意度」与「customer satisfaction」逻辑关系:「A 大于 B」与「B 大于 A」在向量空间里可能很近
语义概括:「怎么提升留存」与「提高用户复购的方法」否定与极性:「有效」与「无效」在向量空间里往往距离很近
主题归类:一段文字属于哪个业务领域时效与版本:「2024 口径」与「2025 口径」的向量几乎相同
否定词问题是向量检索最经典的失效模式

「这个方案有效」和「这个方案无效」在向量空间里通常非常接近,因为模型学到的主要是「方案 + 有效性评估」这个主题,而不是那个「否」字带来的极性反转。类似的还有「同比增长」与「同比下降」、「支持」与「不支持」。

后果很严重:用户明确问「哪些功能不支持 X」,检索回来的全是「支持 X」的文档。解法不是换嵌入模型(这是共性局限),而是在查询理解阶段把「不支持」改写为「缺少 / 未提供 / 不兼容」等正向表述,或者辅以关键词检索把「不支持」这个精确串匹配上。

二、相似度度量:怎么选

度量公式直觉何时用注意
余弦相似度只看两个向量的夹角,忽略长度默认选择,绝大多数文本嵌入归一化之后与点积等价
点积夹角 + 长度都算模型训练时就用的点积(如部分 OpenAI 模型)必须与训练时一致,否则排序会系统性偏移
欧氏距离空间中的直线距离向量已归一化时与余弦等价(单调对应)未归一化时对长度敏感,容易偏向短文本
一条实用纪律

使用哪个度量,取决于嵌入模型是怎么训练的,而不是取决于你的直觉。模型卡(model card)通常会写明推荐度量。用错了不会报错,只会让排序质量悄悄变差——这类问题极难发现,因为结果「看起来还算相关」。

验证方法:取 20 组已知的相关/不相关样本对,分别用两种度量算排序,看哪个更符合预期。这个测试大概花 20 分钟,能省下后面大量的排查时间。

三、能力边界:三类必然失效的场景

向量检索的能力边界 这三类场景不是「调参能解决」的,而是机制上的必然失效。识别出来,才能选对补救手段。 ① 精确匹配类 需要一字不差 查询:「ERR_45004 是什么原因」 期望:定位到报错码对应的文档条目 向量会把它匹配到讨论「错误处理」「异常码规范」的泛泛文档,而 ERR_45004 那条可能排在 50 名开外。 补救:关键词/BM25 检索精确串匹配,再与向量结果融合(见下一章)。 同类:订单号、函数名、配置项名、产品型号、人名、法律条文编号。 ② 极性 / 逻辑反转类 词几乎相同但含义相反 查询:「哪些接口不支持批量操作」 期望:只返回不支持的那些 「支持批量操作」与「不支持批量操作」的向量距离极近,检索会把两者混在一起。 补救:查询改写为正向表述(「缺少批量能力」),或用否定词规则在召回后做二次判别。 同类:有效/无效、同意/不同意、同比增长/同比下降、包含/不包含。 ③ 条件 / 数值约束类 信息在结构化字段里,不在语义里 查询:「2025 年 9 月之后更新的华东区口径文档」 期望:三个条件同时满足 向量只能表达「华东区口径文档」这个语义主题,无法表达「时间 > 2025-09」这个约束。 补救:条件抽出来做元数据过滤(结构化过滤下推到召回层),向量只负责语义部分。 同类:时间范围、版本号、状态、地区、金额区间、部门归属。
图 1 三类必然失效的场景与补救手段。共同规律是:失效都发生在「需要精确或结构化判断」的地方,而向量只在「语义模糊匹配」上强。所以正确的架构不是选一种,而是让每种手段处理它擅长的部分。

四、动手:三种度量的对比与边界探测

embedding_bounds.pypython
import numpy as np
def l2_normalize(m: np.ndarray) -> np.ndarray:
    n = np.linalg.norm(m, axis=-1, keepdims=True)
    return m / np.where(n == 0, 1, n)
def cosine(a: np.ndarray, b: np.ndarray) -> np.ndarray:
    return l2_normalize(a) @ l2_normalize(b).T
def dot(a: np.ndarray, b: np.ndarray) -> np.ndarray:
    return a @ b.T
def euclidean(a: np.ndarray, b: np.ndarray) -> np.ndarray:
    # 返回负距离,便于与相似度统一为「越大越好」
    diff = a[:, None, :] - b[None, :, :]
    return -np.linalg.norm(diff, axis=-1)
METRICS = {"cosine": cosine, "dot": dot, "euclidean": euclidean}
def compare_metrics(query: str, docs: list[str], embed) -> None:
    """
    用一组已知样本对比三种度量的排序差异。
    20 分钟的成本,能避免后面几个月的排序质量问题。
    """
    qv = embed([query])
    dv = embed(docs)
    print(f"查询:{query}\n")
    for name, fn in METRICS.items():
        scores = fn(qv, dv)[0]
        order = np.argsort(-scores)[:5]
        print(f"--- {name} top5 ---")
        for i in order:
            print(f"  {scores[i]:+.4f}  {docs[i][:44]}")
    print()
def probe_polarity_and_exactness(embed) -> dict:
    """
    边界探测:亲手验证两类必然失效。
    这一步的目的是建立「什么时候不能只靠向量」的判断力。
    """
    pairs = [
        # (A, B, 期望:两者是否应该被区分开)
        ("接口支持批量操作", "接口不支持批量操作"),
        ("ERR_45004 参数超长", "常见的错误处理规范"),
        ("同比增长 12%", "同比下降 12%"),
        ("该字段可以为空", "该字段不可以为空"),
        ("华东区转化率 1.1%", "华东区转化率偏低,建议关注"),
    ]
    out = {}
    for a, b in pairs:
        va, vb = embed([a, b])
        out[f"{a[:14]}… vs {b[:14]}…"] = float(cosine(va, vb)[0, 0])
    return out
# --- 输出示例(数值会随模型不同而变,但规律稳定) ---
# 「支持批量操作」vs「不支持批量操作」            → 约 0.92  ← 距离极近,无法区分极性
# 「ERR_45004 参数超长」vs「常见的错误处理规范」   → 约 0.31  ← 精确串反而更远
# 结论:极性反转与精确标识符,必须靠向量之外的手段处理。
def hybrid_score(vec_score: np.ndarray, kw_score: np.ndarray,
                  alpha: float = 0.6) -> np.ndarray:
    """
    一个极简的混合打分示意。
    真实系统用 RRF 或带权重归一化的加权和(见下一章),
    这里只为说明「两路信号可以互补」。
    """
    v = (vec_score - vec_score.min()) / (vec_score.ptp() + 1e-9)
    k = (kw_score - kw_score.min()) / (kw_score.ptp() + 1e-9)
    return alpha * v + (1 - alpha) * k
实操任务
  • 跑一遍 probe_polarity_and_exactness(),亲眼看「支持/不支持」的余弦相似度有多高——这个数字会改变你对向量检索的信任度;
  • 用你自己的语料构造 10 组「应该被区分开但向量分不清」的样本,统计需要用到关键词检索的比例;
  • 回答一个问题:如果你的检索系统里 30% 的查询属于这三类失效场景,你应该优先改哪一环?

五、自测

本章自测第 2 题是经典考点
Q1用哪种相似度度量,主要取决于什么?
A。用错度量不会报错,只会让排序质量悄悄变差——这类问题极难发现,因为结果「看起来还算相关」。验证方式:取 20 组已知相关性的样本对,对比不同度量的排序是否符合预期。
Q2为什么向量检索难以区分「支持批量操作」与「不支持批量操作」?
C。这是机制性的局限,不是模型不够好——换任何通用嵌入模型都会遇到。所以对策必须来自向量之外:查询改写(把否定转成「缺少/未提供」的正向表述)或叠加关键词检索对否定词做精确匹配。
Q3查询「2025 年 9 月之后更新的华东区口径文档」,向量检索的正确分工是什么?
B。结构化的条件(时间、版本、地区、状态)应该交给元数据过滤,语义部分才交给向量——让每种机制处理它擅长的东西。A 的问题是把数值约束交给了一个无法表达它的机制;D 就是「后过滤」,会因 top-k 额度被占满而导致可用结果过少。

六、小结

议题结论
嵌入的本质把语义相近映射为距离相近;编码的是「语义场」而非精确信息
度量选择依据嵌入模型的训练方式(查模型卡),并用已知样本对比验证
三类失效精确匹配类、极性反转类、条件约束类——都是机制性失效
应对方向精确串用关键词检索;极性用查询改写;条件用元数据过滤下推
一条纪律不要指望换嵌入模型解决边界问题;要靠混合手段

向量检索不是「更好的搜索」,它是「另一种搜索」。它的价值在于补上了关键词检索找不到的语义相近,代价是丢掉了关键词检索最擅长的精确。本刊编辑部

最后一章把关键词检索与向量检索拼在一起,解决「召回要宽、排序要准、引用要能被验证」这三件事。

面试官会怎么问

共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照

语义检索为什么会找错? 高频
根本原因是:"语义相近"和"对回答问题有用"是两件不同的事
四类具体错法:
① 语义相似但答案无关——查询"如何申请年假"召回《请假制度总则》的相似段落,但真正有用的是《年假实施细则》,两者语义高度接近。
② 精确标识失效——向量对数字、工号、型号、专有名词不敏感,"型号 A123" 和 "型号 A132" 在向量空间里几乎没区别,但答案完全不同。
③ 否定与条件丢失——"不含"、"除……之外"这类逻辑关系在稠密向量里表达很弱。
④ 频次与时效被稀释——出现 20 次的关键条款,可能比只出现 1 次的临时通知排在更前,但后者才是当前有效的版本。
所以纯向量检索在真实系统里几乎总是配合关键词检索(补精确匹配)+ 元数据过滤(补时效与权限)+ Rerank(补真实相关性)使用。
余弦相似度、点积、欧氏距离该怎么选?
取决于向量是否归一化
向量已归一化(模长为 1)——余弦相似度与点积完全等价,此时用点积更快(省掉除法)。大多数现代 embedding 模型输出的向量本身就是归一化的,所以实践中常见"用点积近似余弦"。
向量未归一化——点积会把模长也计入相似度,如果向量模长本身有含义(例如代表置信度或频次),这可能是特性;如果没有含义,那就是噪声,应该先归一化。
欧氏距离——在归一化向量上,欧氏距离与余弦相似度是单调等价的(d² = 2 − 2·cos),所以排序结果相同。它的好处是直觉上更好解释,坏处是对模长变化更敏感。
工程上的建议是:跟你的向量库与索引实现保持一致——很多 ANN 库针对特定度量做了优化(比如内积),混用不同度量的预计算会导致结果与暴力检索不一致,这种 bug 很难发现。
向量检索的能力边界在哪?什么场景不该用它?
不该用(或必须配合其他手段)的场景有四类:
① 需要精确匹配——编号、单号、金额、日期范围、代码符号。这些必须走结构化查询或关键词精确匹配。
② 需要聚合与统计——"上个月有多少人入职"。向量检索只能找相似文本,不能做聚合,这本质上是数据库问题。
③ 需要严格权限与合规——向量检索没有天然的权限语义,必须在检索前做元数据过滤(pre-filter),而不是检索后再筛(否则会泄漏被过滤内容的存在性,也浪费召回额度)。
④ 需要多跳推理——"和 A 合作过项目的人,他们的共同上级是谁"。这是图查询擅长的事,向量检索只能拿到与 A 相关的文本片段。
一句话总结:向量检索擅长"找相似的说法",不擅长"精确匹配、聚合、关系推理、权限控制"——所以真实系统里它只是检索层的一个组件,而不是全部。
加分点:把"pre-filter 而非 post-filter"这一点讲出来,会显得你真做过带权限的检索系统。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes