23-Embedding与混合检索
为什么语义相似仍然找不到?Embedding、关键词与混合检索
用户问一个产品编号,向量检索返回语义相近但编号不同的文档。语义相似适合处理不同表达,却不天然擅长所有精确条件;关键词和结构化过滤仍然有自己的位置。
1. 向量空间怎样表达相似
余弦、内积与距离不能脱离模型和索引配置讨论。
Embedding 将什么变成向量?
Embedding 模型把文本等输入映射为数值向量,通常让训练目标下相关内容具有某种接近关系。向量各维一般不是可以直接解释的“第一维是 Java,第二维是数据库”。
查询与文档要使用匹配的表示协议。有些模型要求不同查询、文档前缀,或者区分任务类型;只要接口都返回相同维数,就把两种模型混在一起,通常不具备合理比较基础。
相同模型名下的版本、归一化和维数设置也需要记录,不能只保存一个裸数组。
余弦、内积和距离有什么区别?
余弦比较方向,内积同时受方向与长度影响,欧氏距离衡量点之间距离。单位归一化向量的某些排序关系可以等价,但不是任意数据都能互换。
向量库可能返回距离或转换后的分数,不同适配器的范围与排序方向需要核对。相似度 0.8 不是“答案有 80% 概率正确”,阈值也不能跨模型直接复制。
Spring AI SimpleVectorStore 1.1.8 使用余弦计算作为演示实现;生产向量库的索引和过滤行为则要看具体实现。对应源码
一个独立余弦计算示例
使用 Java 17,保存为 CosineDemo.java。人工二维向量只用于公式说明,不是实际 Embedding 输出。
public class CosineDemo {
static double cosine(double[] left, double[] right) {
if (left == null || right == null || left.length == 0 || left.length != right.length) {
throw new IllegalArgumentException("equal non-empty dimensions required");
}
double dot = 0, normLeft = 0, normRight = 0;
for (int i = 0; i < left.length; i++) {
if (!Double.isFinite(left[i]) || !Double.isFinite(right[i])) {
throw new IllegalArgumentException("finite values required");
}
dot += left[i] * right[i];
normLeft += left[i] * left[i];
normRight += right[i] * right[i];
}
if (normLeft == 0 || normRight == 0 || !Double.isFinite(normLeft)
|| !Double.isFinite(normRight) || !Double.isFinite(dot)) {
throw new IllegalArgumentException("invalid vector magnitude");
}
return dot / Math.sqrt(normLeft) / Math.sqrt(normRight);
}
public static void main(String[] args) {
double[] query = {1, 0};
System.out.println("old policy: " + cosine(query, new double[]{0.9, 0.1}));
System.out.println("current policy: " + cosine(query, new double[]{0.8, -0.2}));
}
}
由公式推导,旧制度约为 0.994,新制度约为 0.970,因此旧制度排序更靠前。这不表达真实模型质量,只说明业务有效性没有包含在这组向量距离里。
程序检查维度、有限值和零范数;极端数值的稳定处理仍不是它的用途。不能用二维示意程序估算真实索引吞吐或召回率。
哪些条件不能只靠语义表示?
租户、权限、有效版本和明确业务 ID 通常应通过结构化条件限制。用户问订单 12345,语义相关的订单说明不能替代该订单事实。
日期范围、产品型号和否定条件也可能难以仅靠相似度表达。关键词或结构检索可以补充准确约束,再用语义表示召回更广泛的相关内容。
对金额、代码、编号和特定错误码,保留原始字段很重要。清洗时把所有数字去掉,会破坏本应精确匹配的信息。
近似索引为何可能漏掉相关项?
生产向量库常采用近似最近邻索引,以降低查询成本。索引参数、候选预算、过滤方式和数据分布都会影响召回,不是只要存了正确向量就一定进入 topK。
可以用较小数据集上的精确检索作对照,区分 Embedding 表示问题与索引搜索问题。增加索引搜索预算可能改善召回,但也增加耗时与资源,需要一起比较。
metadata 过滤发生在候选搜索前、过程中还是后,也会改变结果数量与性能。授权设计不能仅从统一 Java 接口推断。
更换 Embedding 模型要做什么?
新模型通常需要重新向量化文档,并将查询指向匹配的新索引。即便维数相同,也不能随意混用旧向量和新查询。
保留 embeddingProfile,记录模型版本、维数、预处理、前缀和距离方式。可以构建新索引、完成覆盖检查与评估,再切换读取,保留回滚所需的旧配置。
2. 两条召回路径各擅长什么
精确名词与语义表达可以分路召回,再处理候选。
关键词与向量各擅长什么?
关键词检索强调词项匹配,可以较好保留型号、编号和精确术语;向量检索帮助处理同义表达和语义相关内容。中文分词、领域词典与模型训练范围都会影响实际表现。
| 问题特征 | 需要重点保留 |
|---|---|
| 错误码、商品型号 | 原始编号与准确匹配 |
| 同义表达 | 语义相关性 |
| 时间、版本、租户 | 结构化过滤 |
| 综合解释 | 多种召回与完整证据 |
混合检索不保证一定更好。某一路召回大量无关片段,融合后可能引入噪声;需要在固定问题集上与单路基线比较。
为什么不能直接相加两个原始分数?
BM25 类分数和向量相似度通常不在同一尺度。直接相加,可能让数值较大的一路主导结果,而不是相关性更好的一路。
一种选择是校准和归一化后加权,但需要明确分布和稳定性。另一种是 Reciprocal Rank Fusion,简称 RRF,按每一路的排名贡献分数:某文档的贡献为 1 / (c + rank),缺席该路则贡献零。
c 是平滑参数,rank 从一开始。RRF 不要求原始分数同尺度,但会丢掉原始分数差距,不能当作答案置信度。Elasticsearch RRF 文档
3. RRF 怎样融合排名
用完整程序说明排名融合,避免直接相加不同尺度的原始分数。
一个完整的融合程序
下面 Python 示例使用人工排序,不连接搜索引擎。保存为 rrf_demo.py。
def fuse(rankings, constant=60, limit=10):
if constant < 0 or limit < 1:
raise ValueError("invalid parameters")
scores = {}
for ranking in rankings:
seen = set()
rank = 0
for document_id in ranking:
if document_id in seen:
continue
seen.add(document_id)
rank += 1
scores[document_id] = scores.get(document_id, 0.0) + 1 / (constant + rank)
return sorted(scores.items(), key=lambda item: (-item[1], item[0]))[:limit]
if __name__ == "__main__":
dense = ["A", "B", "C"]
lexical = ["B", "D", "A"]
for document_id, score in fuse([dense, lexical]):
print(document_id, round(score, 6))
由公式计算,预期顺序为 B、A、D、C。B 在两路排名都靠前,因此融合分数高于只出现一路的 D。相同分数按 ID 排序,这是示例的确定性约定,不是业务相关性的证明。
去重应使用同一个片段或文档的稳定 ID,不能用正文字符串随意合并。不同版本或不同租户的相同文字,也不应变成同一授权对象。
候选窗口为什么影响结果?
每路只取前五条,排在第六的正确证据就完全没有参与融合。增大候选窗口可以带来更多机会,但也增加网络、排序和后续重排成本。
融合后的 topK 与每路候选数是不同参数。需要保存每路候选及最终排序,才能解释某片段是没有召回,还是融合后被挤出。
两路都必须使用当前身份、有效版本和可访问文档条件。不能向量路受限、关键词路查全库,再希望融合自动消除泄漏。
编号与查询改写要怎样处理?
把 DB-042 改写成“数据库故障”可能丢失精确锚点。可以保留原始查询进行关键词检索,再增加独立的语义改写召回,而不是用改写完全替代原文。
分词器是否保留连字符、大小写与数字,需通过实际索引规则确认。产品名称和错误码可以建立独立字段,使用适合其语义的匹配方式。
查询条件缺少版本或产品线时,可以先澄清,避免召回多个相似对象后由模型自行猜测。
怎样比较混合方案?
固定问题集,分别比较关键词、向量、两路融合,以及融合加重排。按编号类、同义表达类和跨段问题分别查看证据覆盖、引用准确性、耗时与成本。
并行发出两路查询的总延迟不能简单相加,但协调、超时与失败处理仍有成本。某一路失败时,是返回部分结果还是拒绝,需要有明确规则并记录实际使用了哪几路。
本文的排名只来自人工列表和公式,没有宣称混合检索在真实语料上提升了多少。
4. 设计判断与适用边界
把精确过滤留给可证明的条件,把语义表示用于相关性。租户、日期和有效状态不能只写在自然语言查询里,期待向量自然排除。编号多的资料可以加入词法召回,但分词、字段和候选窗口仍要配置与验证。
相似度不是答案置信度。混合检索改善的是候选集合,回答仍需要完整证据。更换 Embedding 时保留版本与索引重建计划,不能把两个空间的向量随意混在同一检索过程。
Contextual Retrieval 讨论为片段补充文档上下文。是否适用仍要通过本项目样本比较,本文没有移用其效果指标。 Anthropic:Introducing Contextual Retrieval。
5. 问答
问:相似度高就是事实可信度高吗?
答:不是,它主要反映向量表示关系,版本、权限和证据支持需另外判断。
问:两个模型维数一样就能混用吗?
答:不能保证处于同一表示空间,需要匹配模型与输入协议。
问:混合检索就是 BM25 分数加余弦分数吗?
答:两种尺度不同,需要校准或采用排名融合等明确方法。
问:RRF 分数能表示回答正确概率吗?
答:不能,它是融合排序信号。