【搜索引擎】Elasticsearch(六):向量搜索深度解析:从参数原理到混合查询实战

目录

一、向量搜索基础概念

1、什么是向量搜索

向量搜索将非结构化数据(文本、图像、行为等)转换为高维浮点数数组,通过计算向量间距离(余弦相似度、欧氏距离等)找到最相似的内容。Elasticsearch 自 7.x 版本引入 dense_vector 类型与 knn 查询,原生支持大规模近似最近邻搜索。

1)应用场景举例

在直播推荐系统中,每个用户可抽象为高维行为向量。给定一个查询用户,需要从海量用户中快速找出最相似的若干人。这就用到了 knn 查询。


二、Elasticsearch 向量字段配置

1、dense_vector 类型映射

json 复制代码
"frame_vector": {
  "type": "dense_vector",
  "dims": 512,            // 向量维度
  "index": true,          // 启用 HNSW 索引
  "similarity": "cosine"  // 距离度量
}

1)参数详解

  • dims:固定维度,一旦创建不可修改。若实际向量为 32 维,必须重建索引。
  • index :是否构建近似索引。true 时使用 HNSW 图,查询毫秒级;false 时仅能暴力搜索,大规模数据下查询不可接受。
  • similarity :支持 cosinel2_normdot_product。需注意余弦相似度原始范围 [-1,1],ES 内部会做线性变换,使得分数可能大于 1。

2)HNSW 索引的高级参数

json 复制代码
"index_options": {
  "type": "hnsw",
  "m": 16,                // 每个节点最大连接数(默认 16)
  "ef_construction": 100  // 建图时的候选队列大小(默认 100)
}

增大 mef_construction 可提升召回率,但会增加内存和建索引时间。


三、KNN 查询的核心参数:knum_candidates

1、定义与区别

  • k:最终返回给用户的结果数量。
  • num_candidates :每个分片在 HNSW 图搜索中预先检索的候选向量数量,从中挑选最优 k 个。

1)为什么需要 num_candidates

HNSW 是近似算法,贪心路径可能遗漏真正的最近邻。通过增大 num_candidates(即 HNSW 中的 ef_search),先粗捞一批候选再精排,可显著提高召回率。公式:num_candidates 越大 → 召回越接近精确搜索,但查询耗时增加。

2)典型设置建议

数据规模 k num_candidates 建议 说明
小数据集(千级) 10 总数(或暴力搜索) 数据量很小,可直接精确计算
海量数据(千万级以上) 10 500 ~ 10000 ES 上限 10000,从 500 开始测试,逐步上调

1、作用阶段对比

参数 阶段 本质 对性能/召回的影响
ef_construction 索引构建 插入节点时的动态候选列表大小 越大 → 图质量越高(查询召回提升),但建索引越慢
ef_search 在线查询 搜索时的动态候选列表大小(即 num_candidates 越大 → 每次查询召回提升,但延迟增加

1)关系与调优口诀

  • 建图用大 ef_construction(如 200~500),一次成本换高质量
  • 查询时用合适的 num_candidates(如 500~5000),按 SLA 平衡

2)代码示例

json 复制代码
// 建索引时设置
"index_options": { "ef_construction": 300 }

// 查询时设置
{ "knn": { "num_candidates": 1000 } }

五、混合搜索:向量与文本并列查询

1、语法结构(ES 8.x 及以上)

json 复制代码
{
  "query": { "multi_match": { "query": "手机", "fields": ["title^2"] } },
  "knn": {
    "field": "frame_vector",
    "query_vector": [0.1, -0.2, ...],
    "k": 10,
    "num_candidates": 500
  }
}

knnquery 必须平级 ,不可嵌套在 bool 内部。

1)分数计算:累加关系

从实际 _explanation 可知,最终分数为:

\\text{final_score} = \\text{score}*{\\text{query}} + \\text{score}* {\\text{knn}}

未命中 query 的文档,score_query = 0,但仍然可能因高向量分数而出现。

2)查看分数构成

添加 "explain": true 并解析 _explanation

json 复制代码
"explanation": {
  "description": "sum of:",
  "details": [
    { "description": "score from multi_match query", "value": 2.03 },
    { "description": "within top k documents", "value": 5.02 }
  ]
}

若某部分为 0,则可能被省略。


六、实际案例:水煮鱼查询的分数分析

1、查询语句

bash 复制代码
curl -X GET "http://127.0.0.1:9200/live_room/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "multi_match": { "query": "水煮鱼", "fields": ["anchor_name^2", "room_title"] } },
  "knn": {
    "field": "frame_vector",
    "query_vector": [32维向量],
    "k": 3,
    "num_candidates": 100
  },
  "explain": true
}'

1)返回的三篇文档(简化)

ID room_title query_score knn_score final_score 说明
1 iPhone促销 0 9.99 9.99 纯向量匹配(向量极相似)
4 川菜水煮鱼 2.04 5.03 7.07 文本+向量双命中
2 王者荣耀 0 4.62 4.62 纯向量中等相似

2)关键观察

  • 纯向量结果依然出现 ,因为 query 得分 0 时 knn 仍贡献总分。
  • 文本命中加分明显_id:4 因文本得分 2.04,排名超过了向量分数更高的 _id:2(4.62 vs 5.03?实际上 _id:2 向量 4.62 低于 _id:4 的 5.03,所以顺序正确)。
  • 向量分数可能大于 2 ,取决于 similarity 的内置变换和可能的 boost

七、常见问题与解决方案

1、knn 嵌套在 bool 中报错

错误示例

json 复制代码
{ "bool": { "should": [ { "knn": ... } ] } }

原因 :ES 8.x 中 knn 必须是顶级参数。
修正 :使用 knn.filter 实现"且"逻辑,或使用 query + knn 并列实现"或"逻辑。

2、query_vector 维度不匹配

现象illegal_argument_exception,提示向量维度不一致。
解决 :检查 mapping 中 dims,重建索引以修改维度(无法在线变更)。

3、RRF 需要商业许可证

错误current license is non-compliant for [Reciprocal Rank Fusion (RRF)]
替代方案

  • 使用 query + knn 并列(简单累加,无需许可证)。
  • 使用 knn.filter 实现前置过滤。
  • 在应用层手动实现 RRF(方案代码见附录)。

4、如何排除未命中文本的文档?

使用 knn.filter

json 复制代码
{
  "knn": {
    "field": "frame_vector",
    "query_vector": [...],
    "k": 10,
    "filter": { "multi_match": { "query": "水煮鱼", "fields": [...] } }
  }
}

此时 kNN 只在满足文本条件的文档中搜索,纯向量文档不会出现。


八、总结与最佳实践

1、参数速查表

目标 参数 推荐值(海量数据)
索引质量 ef_construction 200~500
索引内存 m 16~32
查询召回 num_candidates 500~10000
最终结果数 k 业务决定(如10)

2、查询逻辑选择

业务需求 正确语法
必须满足文本条件 knn.filter
文本与向量分别打分后累加 query + knn 并列
复杂的融合排序(如 RRF) 需商业许可证或应用层实现

3、调优流程

  1. 根据实际向量维度配置 mapping(index: true)。
  2. 建索引时设 ef_construction=300m=24
  3. 测试查询:从 num_candidates=500 开始,逐步增加,监控召回率与 P99 延迟。
  4. 利用 explain 分析分数构成,调整 boost 平衡文本与向量权重。
  5. 若需排除纯向量结果,改用 knn.filter

附录:应用层手动 RRF 示例(Python)

python 复制代码
def rrf_score(rank1, rank2, k=60):
    return 1/(rank1+k) + 1/(rank2+k)

text_hits = es.search(...)   # 全文检索结果
knn_hits = es.search(...)    # knn 结果
merged = {}
for r, hit in enumerate(text_hits['hits']['hits']):
    merged[hit['_id']] = rrf_score(r+1, 1000)  # 未出现在 knn 中的设置大 rank
for r, hit in enumerate(knn_hits['hits']['hits']):
    if hit['_id'] in merged:
        merged[hit['_id']] += rrf_score(1000, r+1)
    else:
        merged[hit['_id']] = rrf_score(1000, r+1)
sorted_ids = sorted(merged, key=merged.get, reverse=True)
相关推荐
会编程的土豆1 小时前
【数据结构与算法】二叉树深度
算法·深度优先
派大星酷2 小时前
MCP 工具介绍及编写指南
java·人工智能
财迅通Ai2 小时前
南矿集团:2026Q1营收增速超21% 海外业务翻倍增长
大数据·人工智能·南矿集团
knight_9___2 小时前
RAG面试篇9
java·人工智能·python·算法·agent·rag
AI人工智能+2 小时前
表格识别技术:通过深度学习与计算机视觉融合,实现复杂文档中表格的版面还原及数据的结构化转换。
深度学习·计算机视觉·ocr·表格识别
贾斯汀玛尔斯2 小时前
每天学一个算法--Top-K 查询(Top-K Retrieval)
算法
考勤技术解析2 小时前
外包技术人员打卡管理的技术痛点与轻量化解决方案
大数据·人工智能·ai
BizViewStudio2 小时前
甄选 2026:AI 重构新媒体代运营行业的三大核心变革与落地路径——附10家优质服务商
大数据·网络·人工智能·媒体
小兵张健2 小时前
武汉 AI 面试圈,真的很小
人工智能·面试·ai编程