Elasticsearch 向量搜索:余弦相似度匹配
余弦相似度是一种常用的相似度度量方式,它通过计算两个向量之间的夹角余弦值来评估相似度,公式为:$\cos \theta = \frac{\mathbf{A} \cdot \mathbf{B}}{|\mathbf{A}| |\mathbf{B}|}$,其中$\mathbf{A}$和$\mathbf{B}$是两个向量,$\cdot$表示点积,$|\cdot|$表示向量的范数。余弦相似度范围在$[-1,
Elasticsearch 向量搜索:余弦相似度匹配
在Elasticsearch中,向量搜索是一种高效处理高维数据(如文本嵌入或图像特征)的方法。余弦相似度是一种常用的相似度度量方式,它通过计算两个向量之间的夹角余弦值来评估相似度,公式为:$\cos \theta = \frac{\mathbf{A} \cdot \mathbf{B}}{|\mathbf{A}| |\mathbf{B}|}$,其中$\mathbf{A}$和$\mathbf{B}$是两个向量,$\cdot$表示点积,$|\cdot|$表示向量的范数。余弦相似度范围在$[-1, 1]$之间,值越大表示相似度越高($1$表示完全相同)。
下面,我将逐步解释如何在Elasticsearch中实现余弦相似度匹配,包括设置索引、执行查询和注意事项。整个过程基于Elasticsearch 7.x 或更高版本(推荐使用8.x以获得更好的性能)。
步骤1: 理解余弦相似度在Elasticsearch中的原理
- Elasticsearch使用
dense_vector字段类型存储向量数据。 - 余弦相似度计算需要点积和范数,但Elasticsearch的向量默认未归一化(即范数不为1)。因此,在查询时,我们需要手动计算归一化版本或使用脚本处理。
- 核心公式推导:余弦相似度可重写为$\frac{\mathbf{A} \cdot \mathbf{B}}{|\mathbf{A}| |\mathbf{B}|} = \left( \frac{\mathbf{A}}{|\mathbf{A}|} \right) \cdot \left( \frac{\mathbf{B}}{|\mathbf{B}|} \right)$,这表示我们应先将向量归一化(单位化),然后计算点积。
- 在Elasticsearch中,常用
script_score查询来实现,因为它允许自定义相似度计算。
步骤2: 设置索引和存储向量
在Elasticsearch中,首先需要创建一个索引,并定义dense_vector字段来存储向量。以下是一个索引设置的JSON示例:
- 假设每个文档有一个向量字段
embedding,维度为128(根据您的数据调整)。 - 索引名称设为
vector_index。
PUT /vector_index
{
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"dims": 128 // 向量维度,例如128
},
"content": { // 可选:存储原始文本或其他元数据
"type": "text"
}
}
}
}
- 创建索引后,添加一些文档数据。例如:
确保向量值以数组形式提供。POST /vector_index/_doc/1 { "embedding": [0.1, 0.2, ..., 0.5], // 实际向量值,长度需匹配维度 "content": "示例文本1" }
步骤3: 执行余弦相似度搜索查询
使用script_score查询来计算余弦相似度并排序结果。查询时,需要提供一个查询向量(query vector),并编写Painless脚本(Elasticsearch的脚本语言)实现归一化和点积计算。
查询示例:
- 假设查询向量为$\mathbf{q} = [q_1, q_2, \dots, q_{128}]$。
- 脚本逻辑:
- 归一化查询向量和文档向量。
- 计算点积作为相似度分数。
- 公式在脚本中:$\text{score} = \frac{\mathbf{q}}{|\mathbf{q}|} \cdot \frac{\mathbf{doc_vector}}{|\mathbf{doc_vector}|}$。
GET /vector_index/_search
{
"query": {
"script_score": {
"query": {
"match_all": {} // 匹配所有文档,但根据分数排序
},
"script": {
"source": """
// 归一化查询向量
double qNorm = 0.0;
for (double v : params.query_vector) {
qNorm += v * v;
}
qNorm = Math.sqrt(qNorm);
// 归一化文档向量
double docNorm = 0.0;
for (double v : doc['embedding']) {
docNorm += v * v;
}
docNorm = Math.sqrt(docNorm);
// 计算点积并返回余弦相似度
double dotProduct = 0.0;
for (int i = 0; i < params.query_vector.length; i++) {
dotProduct += (params.query_vector[i] / qNorm) * (doc['embedding'][i] / docNorm);
}
return dotProduct;
""",
"params": {
"query_vector": [0.3, 0.4, ..., 0.6] // 替换为实际查询向量,长度128
}
}
}
}
}
- 解释:
script_score查询为每个文档计算一个自定义分数(即余弦相似度)。- 脚本中,先计算查询向量和文档向量的范数($|\mathbf{q}|$和$|\mathbf{doc_vector}|$),然后归一化并计算点积。
- 返回的分数用于排序:分数越高,相似度越大。
- 结果:Elasticsearch会返回按余弦相似度降序排列的文档列表。
注意事项
- 向量归一化:在索引前或查询时归一化向量可提高效率和准确性。建议在数据导入时预处理向量(例如,使用Python的scikit-learn库归一化),这样脚本中无需重复计算范数,从而提升性能。
- 性能优化:对于大规模数据集,使用Elasticsearch 8.x的KNN搜索特性更高效。它支持
dense_vector的index选项和近似最近邻算法,但需注意KNN默认使用欧氏距离;如果必须用余弦相似度,仍需自定义脚本。 - 维度限制:Elasticsearch的
dense_vector维度上限通常为1024(具体取决于版本),确保向量维度匹配设置。 - 错误处理:在脚本中添加范数检查(如
if (docNorm == 0) return 0;)避免除以零错误。 - 测试验证:在实际应用中,先用小数据集测试查询,确保脚本正确性。您可以使用Kibana或Elasticsearch客户端执行查询。
通过以上步骤,您可以在Elasticsearch中高效实现余弦相似度匹配。如果您有具体向量数据或问题,提供更多细节我可以进一步优化示例!
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)