news 2026/8/19 11:03:45

6.向量相似度是怎么计算的?从余弦距离到语义检索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
6.向量相似度是怎么计算的?从余弦距离到语义检索

向量相似度是怎么计算的?从余弦距离到语义检索

码海寻道 · 大模型、智能体与 RAG 工程组件系列第 6 篇

上一​篇我们把文字转换成了向量。接下来还有一个关键问题:

两个向量究竟怎样比较,才能判断它们的语义是否接近?

向量数据库的核心工作,可以概括成一句话:给定一个查询向量,从大量候选向量中找出“最相似”的 Top K 个结果。

这里的“相似”,不是凭直觉判断,而是由距离或相似度函数计算出来的。函数不同,分数含义不同,排序方向也可能相反。

在真实系统里,距离指标并不是只写在检索代码中的一个参数。它需要同时出现在 Embedding 服务配置、Milvus/pgvector Schema、索引操作类、评测脚本和监控日志中。只改其中一处,就可能出现“查询能返回结果,但排序和阈值都不可信”的问题。

一、先理解向量空间中的“近”

假设我们把一批文档转换成向量,每个向量都可以看成高维空间中的一个点:

文档 A → 向量 A → 空间中的点 A 文档 B → 向量 B → 空间中的点 B 用户问题 → 查询向量 Q → 空间中的点 Q

语义检索要做的事情,就是计算 Q 与所有候选点之间的关系,然后按结果排序。

但“距离近”并没有唯一答案:

  • 可以看两个点之间的直线距离;
  • 可以看两个向量的夹角;
  • 可以看两个向量对应坐标的乘积总和;
  • 也可以使用关键词或稀疏向量的匹配分数。

因此,选择距离指标,本质上是在定义:你的系统认为“相似”是什么。

二、余弦相似度:最常见的文本向量比较方式

余弦相似度关注两个向量的夹角,而不是它们距离原点有多远。公式是:

cos(A, B) = (A · B) / (||A|| × ||B||)

其中:

  • A · B是向量内积;
  • ||A||是向量 A 的长度;
  • ||B||是向量 B 的长度。

余弦相似度通常在[-1, 1]范围内:

  • 越接近 1,方向越接近;
  • 接近 0,方向接近正交;
  • 越接近 -1,方向越相反。

在很多文本 Embedding 场景中,方向比向量长度更重要,因此余弦相似度经常被采用。但不能只因为“文本检索常用余弦”就盲目设置,还应该看 Embedding 模型的说明和实际评测结果。

三、内积 IP:为什么归一化后它等价于余弦?

内积的公式很简单:

A · B = a1b1 + a2b2 + ... + anbn

内积同时受到两个因素影响:

  1. 两个向量的方向是否一致;
  2. 两个向量的长度是否较大。

如果先把向量归一化,使每个向量的长度都等于 1,那么:

A · B = cos(A, B)

这也是为什么一些系统会使用 IP 搜索归一化后的向量。

但要注意,归一化不是一句可有可无的预处理。如果向量没有归一化,却把 IP 当作余弦相似度使用,结果可能被向量长度影响,排序就会与预期不同。

Milvus 官方文档明确说明:归一化后的向量使用内积时,结果等价于余弦相似度;未归一化时,IP 和余弦的结果可能不同。

四、欧氏距离 L2:直线距离越短越相似

欧氏距离也叫 L2 距离,二维情况下就是我们熟悉的两点直线距离:

L2(A, B) = sqrt((a1-b1)^2 + ... + (an-bn)^2)

在 L2 检索中:

距离越小,结果越相似。

而在余弦和 IP 检索中,通常是分数越大,结果越相似。

这是工程中非常容易犯错的地方:如果业务代码统一把“分数越大越好”,却没有区分 L2 和 COSINE,可能把最不相关的结果排到前面。

对于长度已经归一化的向量,L2 与余弦之间存在单调关系,排序可能一致;但对于未归一化向量,二者的排序可能明显不同。不能仅凭“都是距离”来替换。

五、Milvus 中三种指标的直观对比

Milvus 对浮点向量常见的指标包括L2IPCOSINE。它们的排序方向需要明确:

指标衡量方式更相似的结果
COSINE向量夹角分数更大
IP向量内积分数更大
L2欧氏距离距离更小

Milvus 的搜索结果字段通常被称为 distance,但它在不同指标下可能表示距离或相似度分数。不要只看字段名,必须结合metric_type判断数值含义。

组件之间必须保持同一套约定

Embedding 服务:是否归一化 ↓ 向量字段:维度和数据类型 ↓ 索引:metric / operator class ↓ 查询:距离方向、阈值和排序 ↓ Reranker:候选片段的二次排序

例如,pgvector 中使用余弦距离时,索引操作类应使用vector_cosine_ops;Milvus 中则需要让 Collection 的索引和搜索参数使用相匹配的 metric。不能只在应用层把“距离越小”改成“分数越大”,而不检查底层操作符和索引。

六、Top K 到底是什么?

Top K 表示从候选结果中返回排名最靠前的 K 条。

查询向量 ↓ 计算候选相似度 ↓ 排序 ↓ 返回 Top K

例如top_k=5,意味着系统返回前 5 条,不代表这 5 条都一定足够相关。

K 太小会怎样?

可能漏掉真正有用的片段,尤其是文档切分不理想或问题表达比较模糊时。

K 太大会怎样?

会带来:

  • 更多无关内容进入上下文;
  • Prompt 变长,成本上升;
  • 大模型注意力被噪声分散;
  • 后续生成延迟增加。

因此,常见做法不是简单地把 K 设置得很大,而是“先多召回,再重排序,再截取少量结果”。这就是下一篇 Reranker 文章会讨论的两阶段检索。

七、相似度阈值:为什么 Top K 还不够?

如果只使用 Top K,即使查询和所有文档都不相关,系统仍然会返回 K 条“相对最接近”的结果。

例如知识库里根本没有答案,但系统仍可能返回 5 个分数最高的无关片段,最后让模型据此编造答案。

因此可以增加阈值:

只保留 score >= threshold 的结果

但阈值不是越高越安全:

  • 阈值过高,会漏掉表达方式不同但实际相关的文档;
  • 阈值过低,会让大量噪声进入 Prompt;
  • 不同模型、不同数据集和不同指标的分数分布不同。

尤其要注意,余弦分数、IP 分数和 L2 距离不能直接使用同一个阈值。阈值必须在真实评测集上标定。

八、近似最近邻 ANN:为什么不直接比较全部向量?

如果只有几百个向量,逐个计算距离并不困难。但当向量数量达到百万、千万甚至更大规模时,每次查询都与所有向量比较,成本会很高。

向量数据库通常使用 ANN,也就是 Approximate Nearest Neighbor,近似最近邻搜索:

全部向量 ↓ 索引定位可能相关的候选区域 ↓ 只对候选进行精细比较 ↓ 返回近似 Top K

ANN 的目标不是在任何情况下保证数学意义上的绝对最优,而是在召回率、延迟和资源消耗之间取得平衡。

Milvus 的基础向量搜索文档说明,ANN 会先定位候选子集,再比较查询向量与候选向量,从而减少全量比较带来的开销。

常见索引包括:

  • FLAT:接近暴力搜索,结果精确,适合数据量较小或评测基准;
  • IVF:先把向量分到若干簇,只搜索部分簇;
  • HNSW:通过图结构寻找近邻,常用于低延迟检索;
  • DiskANN 等:面向更大规模和磁盘场景。

索引参数越激进,速度可能越快,但召回率可能下降。向量数据库的“快”通常不是没有代价的快。

九、过滤条件会改变检索结果

企业知识库不能只按向量相似度检索,还要增加元数据过滤:

向量相似度检索 + tenant_id = 当前租户 + department_id in 用户可访问部门 + document_status = published + version = 当前有效版本

过滤可以发生在向量检索之前、检索过程中或检索之后,具体取决于数据库能力和实现方式。关键是不能先召回无权文档,再把它们交给大模型后才试图补救。

如果过滤条件过于严格,候选集合可能变小,召回率下降;如果过滤条件缺失,则会产生严重的数据泄露风险。

十、为什么检索分数不能直接当作“答案可信度”?

向量分数只表示查询向量与候选向量在某种指标下的接近程度,不等于:

  • 文档一定包含答案;
  • 文档内容一定正确;
  • 文档对当前用户可见;
  • 大模型一定能正确利用它;
  • 最终回答一定没有幻觉。

一个高分片段可能只是与问题主题相近,却没有回答关键条件。一个相对低分的片段可能包含真正重要的限定语。

所以实际 RAG 系统还需要:

  1. 元数据过滤;
  2. 多路召回;
  3. Reranker 重排序;
  4. 答案引用和事实校验;
  5. 低置信度时拒答或转人工。

十一、一个简单的相似度计算示例

下面用 Python 展示余弦相似度的核心计算逻辑:

importmathdefcosine_similarity(a:list[float],b:list[float])->float:iflen(a)!=len(b):raiseValueError("向量维度不一致")dot=sum(x*yforx,yinzip(a,b))norm_a=math.sqrt(sum(x*xforxina))norm_b=math.sqrt(sum(y*yforyinb))ifnorm_a==0ornorm_b==0:raiseValueError("零向量不能计算余弦相似度")returndot/(norm_a*norm_b)

生产环境不会用 Python 循环逐条扫描百万向量,而是交给向量数据库和索引完成。但理解这段逻辑,有助于排查维度错误、零向量和排序方向问题。

用执行计划确认数据库是否真的使用了向量索引

在 PostgreSQL + pgvector 中,可以用EXPLAIN (ANALYZE, BUFFERS)检查查询计划和实际耗时:

EXPLAIN(ANALYZE,BUFFERS)SELECTid,content,embedding<=>'[0.1,0.2,0.3]'::vectorASdistanceFROMknowledge_chunksWHEREtenant_id='tenant-a'ORDERBYembedding<=>'[0.1,0.2,0.3]'::vectorLIMIT5;

如果增加过滤条件后召回数量明显下降,不要先假定距离公式错了。先分别检查过滤字段的选择性、索引是否被使用、候选是否足够,以及是否需要在数据库过滤后进行应用层重排。

十二、上线前的向量检索检查清单

  • 文档向量和查询向量使用兼容模型;
  • 所有向量维度一致;
  • 距离指标与模型说明匹配;
  • 如果使用 IP,明确是否做了归一化;
  • 业务代码正确处理 L2 的“小分更好”;
  • Top K 经过真实评测,不是随意设置;
  • 阈值根据真实分数分布标定;
  • 检索加入租户、权限和版本过滤;
  • ANN 索引有召回率与延迟对比数据;
  • 不把向量分数直接当作答案可信度。

结语:距离指标决定了系统如何理解“相似”

向量检索不是简单地“把文本放进数据库然后搜索”。它至少包含四个决定:

  1. 用什么模型生成向量;
  2. 用什么指标比较向量;
  3. 返回多少候选结果;
  4. 如何在速度、召回率和上下文长度之间取平衡。

余弦、内积和 L2 没有绝对的“最好”,只有与模型、数据和业务目标是否匹配。下一篇我们继续讨论:

《如何选择 Embedding 模型?从维度、中文能力到成本》


参考资料

  1. Milvus Documentation:Similarity Metrics
  2. Milvus Documentation:Basic Vector Search
  3. pgvector 官方项目:HNSW 与距离操作符
  4. Milvus Documentation:Index Explained

本文为“码海寻道”原创技术文章。具体指标、索引和参数应结合 Embedding 模型官方说明及真实数据集进行评测。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/19 11:03:37

douyin-downloader 从零跑通:3 步完成抖音批量下载与无水印备份

douyin-downloader 从零跑通&#xff1a;3 步完成抖音批量下载与无水印备份 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallb…

作者头像 李华
网站建设 2026/8/19 11:01:01

用 CoolProp 完成一次制冷循环计算:从安装到COP的完整实践指南

用 CoolProp 完成一次制冷循环计算&#xff1a;从安装到COP的完整实践指南 【免费下载链接】CoolProp Thermophysical properties for the masses 项目地址: https://gitcode.com/gh_mirrors/co/CoolProp 如果你做过制冷系统或热泵的课程设计&#xff0c;大概率经历过这…

作者头像 李华
网站建设 2026/8/19 11:00:45

Springboot4项目构建原生镜像的问题解决(持续更新,请收藏)

一个项目升级到springboot4&#xff0c;构建成原生镜像后各种问题&#xff0c;经过一番摸索&#xff0c;终于解决了。 1、ehcache配置问题&#xff1a; 异常信息&#xff1a; Caused by: org.ehcache.xml.exceptions.XmlConfigurationException: Error parsing XML configurati…

作者头像 李华
网站建设 2026/8/19 10:55:48

MultiFixer:基于协调者-提议者架构的多智能体代码修复框架

1. 项目概述&#xff1a;当多行Bug修复遇上“协调者-提议者”架构 在软件开发的日常中&#xff0c;修复一个Bug&#xff0c;尤其是那些涉及多行代码、跨越多个函数甚至文件的“多块”&#xff08;Multi-Hunk&#xff09;Bug&#xff0c;从来都不是一件轻松的事。传统的单点修复…

作者头像 李华