这篇学完你能回答什么
- Embedding 是什么?为什么语义相近的两句话,向量距离就近?
- 「哪些费用能报销」和「哪些费用不能报销」,向量距离远吗?
- 换一个 embedding 模型的代价有多大?为什么它是检索优化里最后才动的一步?
从一个真实故障讲起
一个企业内部知识库的检索服务,上线一周后收到三条投诉,运维一开始以为是三个不同的 bug:
- 用户问「哪些费用不能报销」,召回的第一条是《可以报销的费用清单》——答案正好相反;
- 用户问「A 型号和 B 型号有什么区别」,召回的全是 A 型号介绍和 B 型号介绍,没有一条讲对比;
- 用户问「2025 年的差旅标准」,召回了 2023 版的同名文档,内容高度相似,年份不对。
三个症状,一个根因:向量相似度衡量的是「话题接近」,不是「回答了你的问题」。
否定词在向量空间里几乎不改变方向(整句话谈的还是报销);「区别」这种关系型意图,在只有单篇文档向量的空间里根本没有对应的点;年份这种精确 token,在稠密向量里被稀释成了几乎无差别的信号。
这三类失真不是模型没训好,是这套机制的设计边界。搞清边界,比记住哪个模型排名高有用得多。
用户提问
「哪些费用不能报销」
按余弦相似度排序
整段话谈的还是报销,话题一致
否定几乎不改方向断点
「不」只是一个低权重信号
召回《可以报销的费用清单》
投诉里的第一条,答案正好相反
核心概念:先打比方,再给定义
Embedding(嵌入 / 向量表示)。把一段文本压成一个定长的实数向量,比如 1024 个数字。比喻:给每段话在一张巨大的地图上钉一个坐标,意思相近的话钉得近。
但比喻到这里必须补一句关键的:这张地图上「什么算近」,是训练时定义出来的,不是天然的。 这句话是本篇的主轴。
Semantic space(语义空间 / 向量空间)。所有这些坐标构成的高维空间。
Cosine similarity(余弦相似度)。只看两个向量的方向夹角,不看长度。这是检索里最常用的度量——因为文本长度不该影响「说的是不是一回事」。
Contrastive learning(对比学习)。训练 embedding 模型的主流方法:给一大堆(查询,相关文档)配对作为正样本,再配上不相关的作为负样本,用损失函数把正样本拉近、负样本推远。
稠密向量 vs 稀疏向量。稠密向量(本篇主角)每一维都是有值的实数,捕捉语义;稀疏向量(如 BM25 的词频表示)绝大多数维是 0,捕捉字面命中。二者互补,这是混合检索的地基(T2-5 展开)。
意思相近的两段话,钉得也近
但「什么算近」不是天然的 —— 比喻到这里就该停,后半句才是这一篇的主轴
把一段文本压成定长实数向量
比如 1024 个数字;所有坐标构成的高维空间就是语义空间。检索最常用余弦相似度:只看方向夹角,不看长度
该挨着的拉到一起,不该挨着的推开
没人纠正过的那种关系,它就学不会 —— 比如一句话和它的否定形式
正样本拉近,负样本推远
训练 embedding 模型的主流方法:(查询,相关文档)配对作正样本,不相关的作负样本,交给损失函数
原理拆解
| 失真 | 为什么会这样 | 该由谁来接 |
|---|---|---|
| 否定不敏感 | 整段话题一致,否定只是一个低权重信号 | 结构化过滤,别指望向量 |
| 精确匹配被稀释 | 年份、型号、金额、人名、错误码在稠密向量里没有专属维度 | 这才是必须搭配 BM25 的根本原因 |
| 一段多义被平均 | 一个 chunk 讲三件事,向量落在三件事的平均位置 | 分块策略:chunk 讲一件事(T2-2) |
所以「近」等于训练数据里被标成正样本的那种关系。理解这一点,开篇三个故障就全部自解:训练数据里几乎没有「一句话和它的否定形式应该被推远」这样的样本,模型自然没学会区分;也几乎没有「查询是关系、答案要跨两篇文档」这样的样本,空间里根本没有对应的点。
为什么语义相近的两句话向量就近?
这是本篇最容易答得含糊的一问。正确答案不是「因为模型理解了语义」,而是:
训练阶段
正样本对:("怎么申请年假", "年假申请流程说明") → 损失函数把它们拉近
负样本对:("怎么申请年假", "打印机维修指南") → 损失函数把它们推远
│
▼
反复优化后,编码器学到一种映射:
"训练数据里被判为相关的文本对" → 方向接近
│
检索阶段 ▼
query ──► 编码 ──► 向量 ──┐
├──► 余弦相似度 ──► Top-K
文档 ──► 编码 ──► 向量 ──┘ (文档侧离线算好,存进向量库)所以「近」的定义,等于训练数据里被标成正样本的那种关系。理解这一点,前面三个故障就全部自解了:训练数据里几乎没有「一句话和它的否定形式应该被推远」这样的样本,模型自然没学会区分;也几乎没有「查询是关系,答案要跨两篇文档」这样的样本,模型自然给不出。
import numpy as np
def cosine(a, b):
a, b = np.asarray(a), np.asarray(b)
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))
# 归一化之后,余弦相似度等价于点积——
# 这就是为什么向量库通常要求你先归一化,然后用内积检索:更快,且等价。
def normalize(v):
v = np.asarray(v)
return v / np.linalg.norm(v)三个必须记住的已知失真
- 否定不敏感。「能报销」和「不能报销」余弦相似度极高,因为整段话的话题一致,否定只是一个低权重信号。
- 精确匹配被稀释。年份、型号、金额、人名、错误码这类 token,在稠密向量里没有专属维度。这正是必须搭配 BM25 的根本原因,而不只是「工程上稳一点」。
- 一段多义会被平均掉。如果一个 chunk 同时讲了三件事,它的向量落在三件事的「平均位置」,结果是对哪一件都不够像。这条把 Embedding 和分块策略直接绑在一起(T2-2)。
工程实践(截至 2026-08)
表 1:两种「相似」,别混用
| 类型 | 场景 | 说明 |
|---|---|---|
| 对称相似(symmetric) | 找相似问题、去重、聚类 | 两侧是同类文本,「像不像」 |
| 非对称检索(asymmetric) | query → 文档 | 两侧不同类:短问句 vs 长段落,要的是「答不答得上」 |
主流 embedding 模型多为指令感知:query 侧和文档侧要加不同的前缀或指令模板。前缀用错、或者只给一侧加,掉点会很明显,而且这种掉点不会报错,只会悄悄拉低召回。这是新手最常踩、也最难自查的一个坑——务必对着模型卡确认。
表 2:几个容易答错的选择
| 问题 | 结论 |
|---|---|
| 维度越大越好吗 | 不是。维度上去了,存储、内存、检索耗时都涨,收益递减。部分模型支持维度截断(套娃式表示),可以按需要在精度和成本之间取一档 |
| 用什么距离度量 | 必须和模型训练时一致。训练用余弦却在库里配 L2,结果会莫名其妙地差 |
| 向量能跨模型混用吗 | 绝对不能。不同模型的空间毫无可比性,混用等于随机检索 |
| 换模型代价多大 | 全库重新编码 + 重建索引 + 重跑评测。所以它是检索优化里最后才动的一步 |
避坑清单
- 先确认前缀/指令用法,再谈调优。这一条能救回相当一部分「模型不行」的误判。
- 归一化和度量方式在离线建库和在线检索两侧必须一致,包括重建索引时。
- 别指望 embedding 处理否定和精确值,那是 BM25 和结构化过滤的活(T2-5)。
- chunk 讲一件事。一段多义是向量检索最隐蔽的效果杀手。
- 换模型前先证伪其他假设(覆盖率、分块、rerank、query 改写),因为它的迁移成本最高。
本篇只打地基:Embedding 是什么、边界在哪。具体怎么选型、看哪些评测指标、中英文场景差异,见 T2-3。
| 要定的事 | 结论 | 代价与依据 |
|---|---|---|
| query 和文档同一个前缀吗最难自查的一个坑 | 主流模型多为指令感知,两侧要加不同的前缀或指令模板 | 用错、或只给一侧,掉点很明显,而且不会报错 |
| 维度越大越好吗 | 不是,收益递减 | 存储、内存、检索耗时都涨;部分模型支持维度截断(套娃式表示),可按需取一档 |
| 用什么距离度量 | 必须和模型训练时一致 | 训练用余弦却在库里配 L2,结果会莫名其妙地差 |
| 向量能跨模型混用吗 | 绝对不能 | 不同模型的空间毫无可比性,混用等于随机检索 |
| 换模型代价多大 | 全库重新编码 + 重建索引 + 重跑评测 | 所以它是检索优化里最后才动的一步 |
| 对称相似还是非对称检索 | 找相似问题、去重、聚类是对称;query → 文档是非对称 | 非对称两侧不同类:短问句 vs 长段落,要的是「答不答得上」 |
- 先确认前缀用法
- 再谈调优 —— 这一条能救回相当一部分「模型不行」的误判
- 两侧必须一致
- 归一化和度量方式,离线建库与在线检索两侧一致,重建索引时也一样
- 别指望它做的事
- 否定和精确值是 BM25 与结构化过滤的活(T2-5)
- chunk 讲一件事
- 一段多义是向量检索最隐蔽的效果杀手
- 换模型前先证伪
- 覆盖率、分块、rerank、query 改写全排查完再动它,它的迁移成本最高
- 本篇的边界
- 只打地基;怎么选型、看哪些评测指标、中英文场景差异见 T2-3
面试视角
- 一句定义定长向量表示,比如 1024 个数字
- 讲清「近」是训练出来的对比学习的正负样本,别说「模型理解了语义」
- 主动给出边界否定、精确值、一段多义
- 落到工程动作所以配了 BM25 / 改了分块 / 上了 rerank
- 「把文字变成向量,模型理解了语义所以就近了」
- 认为维度越大越好,没听说过收益递减和维度截断
- 不知道 query 和文档要用不同前缀
- 被追问「否定怎么办」就卡住 —— 大多数人卡在这一问
- 相信「换个排名更高的模型就能解决召回问题」
- 能说出「近的定义来自训练数据里的正样本关系」
- 知道维度上去了,存储、内存、检索耗时一起涨
- 说得出自己项目里前缀用法踩过什么坑
- 主动提否定不敏感和精确值稀释,并给得出工程解法
- 知道换 embedding 要全库重建,把它排在优化序列最后
Q1-06。面试官怎么问。 Q1-06 是一面送分题;但凡是 RAG 相关岗位,一定会追到「为什么向量就近」和「否定怎么办」。答得出前者的人不多,答得出后者的人更少——后者是能不能进入 T2 章节深聊的门票。
答题结构建议
- 一句定义(定长向量表示);
- 讲清「近」是训练出来的(对比学习,正负样本),不要说「模型理解了语义」;
- 主动给出边界(否定、精确值、一段多义);
- 落到工程动作(所以我们配了 BM25 / 改了分块 / 上了 rerank)。
分水岭信号
- 只读过:说「embedding 就是把文字变成向量,模型理解了语义所以就近了」;认为维度越大越好;不知道 query 和文档要用不同前缀;相信「换个排名更高的模型就能解决召回问题」。
- 真做过:能说出「近的定义来自训练数据里的正样本关系」;一被问就主动提否定不敏感和精确值稀释这两个坑,并且给得出对应的工程解法;知道换 embedding 要全库重建,因此把它排在优化序列的最后;说得出自己项目里前缀用法踩过什么坑。
小结与延伸
继续深入
本篇归属第 1 章「LLM 与 Prompt 基础」,去做这一章的题。