AI 学习手记一个普通学习者的 AI 知识笔记
首页/学习笔记/Embedding 与相似度:机器眼里的"意思相近"是什么

学习笔记

Embedding 与相似度:机器眼里的"意思相近"是什么

把"语义向量"这个词背后的几何、训练方式和一堆容易翻车的工程细节讲清楚。

"把文本变成向量,相似的就离得近"——这句话我抄过很多次,直到自己拿数据跑了一遍检索才发现,它正确得几乎没有任何信息量。这篇记录我补的那几课。

一、向量本身不神秘,神秘的是那个距离

一个 embedding 模型做的事,是把一段文本映射成一个固定维度的浮点数组。常见的中文向量模型维度在 768 到 1024 之间,国外一些 API 模型是 1536 或 3072。维度高不是缺点,但维度和"聪明程度"没有直接关系,它只决定表达空间的大小。

比较两段文本像不像,通常用余弦相似度:看两个向量夹角的余弦,范围 −1 到 1,越接近 1 越像。实践中大多数模型输出会做 L2 归一化,此时余弦相似度和点积等价,内积还能再乘个模长的影响,所以看到"用点积更快"这种说法,先确认向量是不是归一化过的——没归一化的时候,点积会偏向长文本。

我最初的误解是把相似度数值当物理量,以为 0.8 就是"八成相似"。实际上相似度的绝对值只在你自己那套语料和那一个模型内部有意义,换模型、换切块方式,同一对文本的分数会整体漂移。所以阈值一定要在自己的数据上重新标定,跨项目抄来的 0.75 毫无价值。

二、它是被什么训练出来的

现在主流是对比学习:准备大量"锚句 + 正例 + 负例"三元组,训练目标是让锚句和正例靠近、和负例拉远。负例里最有用的是"hard negative"——字面很像但语义不同的句子,因为这正是检索最容易搞错的地方。

由此能推出几个实用结论:

  • 模型擅长区分它训练分布里出现过的差异。法律条文之间的细微差别、医疗术语、方言、代码,这些领域的"相近"和通用语料的"相近"不是一回事,通用模型在这里容易失效。
  • 同一个模型内比较是硬要求。v1 和 v1.5 的向量空间完全不相通,混在一起算距离,结果不是噪声,是彻底错乱。我的做法是把模型名和版本号写进每条向量的元数据里,入库时校验,不一致直接拒绝。
  • 问句和文档的分布不对称。用"查询向量"去匹配"文档向量"时,理想情况是模型针对 query 和 passage 用了不同的编码前缀(非对称模型)。如果拿对称模型做非对称检索,召回会莫名变差。

三、向量库那些索引,本质都在拿精度换速度

暴力检索(每条都算一遍)在十万级就已经开始难受了,于是有了近似最近邻(ANN)。我实际接触的有这几类:

做法一句话原理代价
HNSW建多层图的"高速公路",从粗到细贪心走近邻内存偏高,建索引慢,召回通常很好
IVF-Flat先聚类,查询时只扫最近的几个簇内存省,nprobe 调小会漏
PQ / 标量量化把向量本身压缩存放省内存,精度损失叠加
全文检索 BM25词面匹配,完全不懂语义快、可解释,吃同义词和黑话

这张表里最反直觉的一条经验是:纯向量检索经常输给"向量 + BM25 混合,再加一层重排"。原因是向量擅长抓"意思像",但对精确实体、型号、编号这类必须字面命中的东西很不可靠——你搜"Qwen2.5-7B-Instruct",它能给你召回一堆 Qwen 的其他型号。混合检索取两边之长,工程上主流框架基本都支持。

重排(reranker)是另一件值得单独说的事。向量召回是"双塔"结构,query 和文档各自编码,彼此看不见,快但粗;cross-encoder 把 query 和文档拼在一起过一遍模型,慢但准。所以常见搭配是召回 50~100 条,重排留下 3~5 条。多这一步通常显著提升质量,代价是几十到几百毫秒。

四、真正翻车的地方,几乎都在切块

模型和索引我都换过,效果差异远不如"怎么切块"这一项大。踩过的坑:

  1. 按固定字数硬切,会把一句话的两个部分劈开,前半段失去指代对象,向量跑偏。至少要做到按句子/段落边界对齐。
  2. overlap(重叠)不是可选项。我默认留 10%~15% 的重叠,代价是存储变多,换来的是跨块信息不断链。
  3. 表格、代码、列表被拍平成纯文本之后,语义基本毁了。表格的行列关系一旦变成一串字符,向量完全学不到结构。这类内容要么转成带上下文的自然语言描述,要么走结构化检索。
  4. 块太小召回噪声,块太大稀释语义。经验区间是几百 token 一块,但这个值必须在自己的数据上扫一遍再定,别信任何人的默认值。
  5. 没有上下文的短块会失忆。给每个块前面拼上"文档标题 + 所属章节"再编码,是我做过收益最明显的一个改动。

五、评测:先攒 30 条"金标准"再谈调参

这一步我以前总是跳过,结果所有"感觉变好了"都站不住。现在的最小做法:

  • 手工写 30~50 个真实问题,为每个问题标出"正确应该召回哪几块"。
  • 只看两个指标:recall@k(前 k 条里覆盖了多少正确答案)和 MRR(第一条正确答案排第几,取倒数平均)。
  • 之后每次改动——换模型、改切块、加混合检索、调 top-k——都跑一遍这两个数。

有了这 30 条,争论就有落脚点了;没有的话,参数就是在原地表演。至于端到端的"答案质量",那是另一层评测(生成有没有忠实于检索到的内容),得单独看,不能被召回指标替代。

六、一句话总结

Embedding 不是"让模型理解语义的魔法",它是一种用几何距离近似语义相关性的检索手段,近似得好不好,取决于训练数据分布、切块方式和你的评测集。把它当检索组件去调,而不是当智能去崇拜,很多困惑就消失了。

内容标识:本文为本人学习笔记,未使用 AI 生成正文,仅在定稿后用模型检查过一遍错别字。

本文出处:http://honeysss.cn/post/embedding-and-similarity.html 转载请注明出处。