RAG 中的 Embedding 是什么?余弦相似度、点积和欧氏距离怎么选?
简化版
Embedding 把查询和文档映射到同一个高维向量空间,让语义相近的文本距离更近。相似度度量必须匹配模型的训练方式:向量已 L2 归一化时,余弦相似度与点积排序等价;未归一化时点积还受向量模长影响,欧氏距离衡量几何距离。铁律——建库和查询必须用同一模型、同一维度、同一归一化方式,否则向量空间对不上。
详细版
RAG 通常用双编码器分别编码查询和文档,离线保存文档向量,在线只算查询向量做近邻搜索。三种度量:
- 余弦相似度:比较方向,范围约 −1~1,不关心模长;
- 点积:同时受方向和模长影响,计算简单,多数索引原生支持;
- 欧氏距离:两点直线距离,越小越相似。
若向量都做 L2 归一化,点积就是余弦相似度,欧氏距离排序也与它们单调对应。选择应遵循 Embedding 模型说明。
完整版教学
一、Embedding 学到的是什么
Embedding 模型把可变长文本压成固定维向量。训练时拉近相关的查询-文档/语义相近文本,推远不相关样本。关键认知:它不是关键词表,也不是把事实无损存进向量,而是面向”相似性比较”的稠密表示。
所以「向量相近」只表示模型认为语义或任务相关,不保证两个文本事实一致,更不代表「一个文本能完整回答另一个」——这是很多 RAG 召回不准的认知根源。
二、双编码器为什么适合召回
双编码器(Bi-Encoder)独立算查询和文档向量,文档可提前离线编码:
离线:把百万文档全部编码成向量,存进索引(一次性)
在线:查询来了只编码 1 次 → 向量索引搜索 → 毫秒级返回 Top-k
速度快、可扩展。代价是查询和文档编码阶段没有逐 token 交互,复杂匹配能力弱于 Cross-Encoder——所以常用双编码器做大规模召回,再用 Reranker(Cross-Encoder)精排少量候选。
三、三个距离公式的关系(带推导)
余弦相似度:cosine(a,b) = a·b / (‖a‖·‖b‖) —— 只看夹角
点积: a·b —— 方向+模长
欧氏距离: ‖a−b‖
当 a、b 都归一化为单位向量(‖a‖=‖b‖=1)时:
‖a−b‖² = ‖a‖² + ‖b‖² − 2(a·b) = 2 − 2(a·b)
→ 三者产生"相同排序",只是分数方向和尺度不同
未归一化时不能假定等价——点积会被长向量「刷高」,可能把冗长但不相关的文档排前面。
四、模型选择比向量维度更重要
一个高频误区:以为维度越高越好。其实模型是否匹配任务更关键:是否支持目标语言、是否针对检索训练(而非通用语义)、查询和文档是否需要不同前缀(如 query:/passage:)、输入长度是否覆盖业务文本。代码、法律、多语言、电商可能需要不同模型。
代价提醒:换 Embedding 模型后,旧文档向量通常必须全部重建——除非新模型明确提供并验证了兼容映射,否则不同模型的向量空间不可直接比较。
五、相似度分数不是概率
另一个高频坑:把 0.8 的余弦相似度当成「80% 相关」。它不是概率:
模型A 的 0.8 ≈ 模型B 的 0.6(不同模型分数尺度完全不同)
同一模型,不同查询类型的分数分布也不一样
所以阈值必须在真实标注集上校准,不能从一个模型直接搬到另一个。生产系统通常组合 Top-k + 最低阈值 + 元数据过滤 + Reranker,而非只凭一个分数决定答案。
向量距离只有在“同一编码模型、同一归一化约定、同一索引口径”下才可比较;相似度高也不等于答案事实正确。
六、常见误区与追问
- 误区:向量相近就代表能回答问题。 只代表语义/任务相关,不保证事实一致或能完整回答。
- 误区:相似度 0.8 = 80% 相关。 分数不是概率,不同模型/查询尺度不同,阈值要标注集校准。
- 误区:维度越高越好。 模型是否匹配语言/领域/检索任务更重要,别只看维度。
- 误区:换个 Embedding 模型不用重建库。 常规情况下要全量重建;只有模型明确提供并验证兼容性时才可例外。
- 追问:三种度量什么时候等价? 向量 L2 归一化后,余弦、点积、欧氏距离排序一致。
- 追问:为什么用双编码器而不是 Cross-Encoder 召回? 双编码器文档可离线编码、在线只编码查询,快且可扩展;Cross-Encoder 太慢,用于精排。
- 追问:建库和查询归一化不一致会怎样? 向量空间对不上,相似度失真、召回质量崩,这是常见事故。
七、距离选择判断表
| 相似度 | 是否受向量模长影响 | 使用前提 |
|---|---|---|
| Cosine | 否 | 关注方向相似 |
| Inner Product | 是 | 模型按点积训练或已归一化 |
| L2 Distance | 是 | 索引与训练度量一致 |
| Hybrid Score | 取决于融合 | 先做分数校准 |
八、加强记忆
Embedding 记「把语义放进地图,距离函数量两点多近」:它是面向相似性比较的稠密表示,向量相近只代表相关、不代表事实一致或能完整回答。度量选择遵循模型训练方式——单位向量下余弦/点积/欧氏距离排序等价,未归一化时点积受模长影响。三条铁律钉死:建库与查询采用兼容的模型/维度/归一化、常规模型替换要重建文档向量、相似度不是概率(阈值要标注集校准)。双编码器负责大规模召回,Cross-Encoder 负责精排。
项目实战落地
项目里怎么做的
《AI Agentic RAG高级企业知识库平台》的片段向量是 1024 维,存在 PostgreSQL + PGVector 的 knowledge_chunk_vector 表里。PGVector 的 <=> 运算符算的是余弦距离,检索 SQL 里只算距离、按距离排序,换算成相似度放在代码里:
sql = f"""
select chunk_id, kb_id, document_id, parent_chunk_id, chunk_type, title_path, content,
embedding <=> %(embedding)s as distance
from knowledge_chunk_vector
where {where}
order by embedding <=> %(embedding)s
limit %(top_k)s
"""
# ……执行查询后,每一行结果这样换算:
# 余弦距离范围是 0 到 2,1 减去距离得到相似度,
# 页面上展示相似度比展示距离直观:数字越大越相关
"score": round(1 - distance, 6),
查询向量要包一层 Vector 再传:写库时向量写进的是 vector(1024) 列,数据库能从列类型推断出来;检索时它是 <=> 的操作数,两边都没有类型上下文,裸的 Python 列表会被当成 float8[],直接报 operator does not exist: vector <=> double precision[]。
《AI企业知识库》把换算和阈值都写进了 SQL:1 - 距离 算出相似度,where 里按相似度阈值过滤,再按距离取前 TopK:
select segment_id, knowledge_base_id, document_id, content,
(1 - (embedding <=> ?::vector)) as similarity
from knowledge_segment_vector
where (1 - (embedding <=> ?::vector)) >= ?
order by embedding <=> ?::vector
limit ?
《AI Agent岗位匹配与求职规划系统》没有用向量数据库,余弦相似度在 Java 里自己算:
- 向量存成文本。 向量化结果每一维四舍五入到 8 位小数、去掉末尾的 0,用逗号拼成一串存进
knowledge_chunk.embedding_text,检索时再拆回数字; - 手写余弦。 点积除以两个向量模长的乘积;任一向量为空、两边维度不同或模长为 0 时,相似度直接记 0,不抛异常。
《AI Agent电商平台导购与运营增长系统》在没配向量模型时有一个本地回退:文本转小写后按相邻两个字切成 2-gram,每个 2-gram 的哈希值对 64 取模,落到 64 维向量的对应位置上加 1,最后归一化。入库和检索用同一套规则,有启用的向量模型就都走真实模型,否则都走本地哈希;余弦按「点积除以两个模长」算,兼容真实模型返回的未归一化向量。源码注释写明本地哈希「检索质量有限,仅用于无 Key 时也能演示 RAG 流程」。
为什么这样取舍
- 给人看相似度,不给人看距离。 余弦距离 0 表示完全一致、越大越不相关,页面上显示「相似度 0.87」比「距离 0.13」直观得多,阈值也按相似度来配。
- 低于阈值的片段直接不要。 企业知识库在检索这一步就按阈值把相关度太低的片段过滤掉,拼给模型的资料里只剩达到阈值的那些。
面试官还会追问
- 删文档、重新切分、删片段的时候,向量库里对应的向量怎么同步清掉?不清会出现什么现象?
- 怎么发现 MySQL 里的片段和向量库里的向量不一致了?
学完《AI Agentic RAG高级企业知识库平台》,上面这些追问你都会迎刃而解。