稠密向量字段类型
编辑稠密向量字段类型
编辑dense_vector
字段类型存储数值的稠密向量。稠密向量字段主要用于 k 近邻 (kNN) 搜索。
dense_vector
类型不支持聚合或排序。
您可以根据 element_type
(默认为 float
),将 dense_vector
字段添加为数值数组。
resp = client.indices.create( index="my-index", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 3 }, "my_text": { "type": "keyword" } } }, ) print(resp) resp1 = client.index( index="my-index", id="1", document={ "my_text": "text1", "my_vector": [ 0.5, 10, 6 ] }, ) print(resp1) resp2 = client.index( index="my-index", id="2", document={ "my_text": "text2", "my_vector": [ -0.5, 10, 10 ] }, ) print(resp2)
response = client.indices.create( index: 'my-index', body: { mappings: { properties: { my_vector: { type: 'dense_vector', dims: 3 }, my_text: { type: 'keyword' } } } } ) puts response response = client.index( index: 'my-index', id: 1, body: { my_text: 'text1', my_vector: [ 0.5, 10, 6 ] } ) puts response response = client.index( index: 'my-index', id: 2, body: { my_text: 'text2', my_vector: [ -0.5, 10, 10 ] } ) puts response
const response = await client.indices.create({ index: "my-index", mappings: { properties: { my_vector: { type: "dense_vector", dims: 3, }, my_text: { type: "keyword", }, }, }, }); console.log(response); const response1 = await client.index({ index: "my-index", id: 1, document: { my_text: "text1", my_vector: [0.5, 10, 6], }, }); console.log(response1); const response2 = await client.index({ index: "my-index", id: 2, document: { my_text: "text2", my_vector: [-0.5, 10, 10], }, }); console.log(response2);
PUT my-index { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 3 }, "my_text" : { "type" : "keyword" } } } } PUT my-index/_doc/1 { "my_text" : "text1", "my_vector" : [0.5, 10, 6] } PUT my-index/_doc/2 { "my_text" : "text2", "my_vector" : [-0.5, 10, 10] }
与大多数其他数据类型不同,稠密向量始终是单值的。不可能在一个 dense_vector
字段中存储多个值。
为 kNN 搜索索引向量
编辑k 近邻 (kNN) 搜索根据相似度度量查找与查询向量最接近的 k 个向量。
稠密向量字段可用于在 script_score
查询中对文档进行排名。这允许您通过扫描所有文档并按相似度对其进行排名来执行暴力 kNN 搜索。
在许多情况下,暴力 kNN 搜索效率不够高。因此,dense_vector
类型支持将向量索引到专门的数据结构中,以支持通过搜索 API 中的 knn
选项进行快速 kNN 检索。
大小在 128 到 4096 之间的浮点元素未映射的数组字段被动态映射为 dense_vector
,默认相似度为 cosine
。您可以通过将字段显式映射为具有所需相似度的 dense_vector
来覆盖默认相似度。
默认情况下,为稠密向量字段启用索引,并将其索引为 int8_hnsw
。启用索引后,您可以定义在 kNN 搜索中使用的向量相似度。
resp = client.indices.create( index="my-index-2", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 3, "similarity": "dot_product" } } }, ) print(resp)
response = client.indices.create( index: 'my-index-2', body: { mappings: { properties: { my_vector: { type: 'dense_vector', dims: 3, similarity: 'dot_product' } } } } ) puts response
const response = await client.indices.create({ index: "my-index-2", mappings: { properties: { my_vector: { type: "dense_vector", dims: 3, similarity: "dot_product", }, }, }, }); console.log(response);
PUT my-index-2 { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 3, "similarity": "dot_product" } } } }
为近似 kNN 搜索索引向量是一个代价高昂的过程。摄取包含启用 index
的向量字段的文档可能需要相当长的时间。请参阅 k 近邻 (kNN) 搜索以了解有关内存要求的更多信息。
您可以通过将 index
参数设置为 false
来禁用索引。
resp = client.indices.create( index="my-index-2", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 3, "index": False } } }, ) print(resp)
response = client.indices.create( index: 'my-index-2', body: { mappings: { properties: { my_vector: { type: 'dense_vector', dims: 3, index: false } } } } ) puts response
const response = await client.indices.create({ index: "my-index-2", mappings: { properties: { my_vector: { type: "dense_vector", dims: 3, index: false, }, }, }, }); console.log(response);
PUT my-index-2 { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 3, "index": false } } } }
Elasticsearch 使用 HNSW 算法 来支持高效的 kNN 搜索。与大多数 kNN 算法一样,HNSW 是一种近似方法,它牺牲结果的准确性来提高速度。
自动量化用于 kNN 搜索的向量
编辑dense_vector
类型支持量化,以减少在 搜索 float
向量时所需的内存占用。支持以下三种量化策略:
-
int8
- 将向量的每个维度量化为 1 字节整数。这以牺牲一些精度为代价将内存占用减少 75%(或 4 倍)。 -
int4
- 将向量的每个维度量化为半字节整数。这以牺牲精度为代价将内存占用减少 87%(或 8 倍)。 -
bbq
- [预览] 此功能为技术预览版,可能会在将来的版本中更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。 更好的二进制量化,将每个维度降低到单比特精度。这以牺牲更大的精度为代价将内存占用减少 96%(或 32 倍)。通常,在查询期间进行过采样和重新排名可以帮助减轻精度损失。
当使用量化格式时,您可能需要过采样并重新评估结果以提高精度。有关详细信息,请参阅 过采样和重新评分。
要使用量化索引,您可以将索引类型设置为 int8_hnsw
、int4_hnsw
或 bbq_hnsw
。在索引 float
向量时,当前默认索引类型为 int8_hnsw
。
量化将继续在磁盘上保留原始浮点向量值,以便在数据的生命周期内进行重新排名、重新索引和量化改进。这意味着由于存储量化向量和原始向量的开销,磁盘使用量对于 int8
将增加约 25%,对于 int4
将增加约 12.5%,对于 bbq
将增加约 3.1%。
int4
量化需要偶数个向量维度。
[预览] 此功能为技术预览版,可能会在将来的版本中更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。 bbq
量化仅支持大于 64 的向量维度。
以下是如何创建字节量化索引的示例:
resp = client.indices.create( index="my-byte-quantized-index", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 3, "index": True, "index_options": { "type": "int8_hnsw" } } } }, ) print(resp)
response = client.indices.create( index: 'my-byte-quantized-index', body: { mappings: { properties: { my_vector: { type: 'dense_vector', dims: 3, index: true, index_options: { type: 'int8_hnsw' } } } } } ) puts response
const response = await client.indices.create({ index: "my-byte-quantized-index", mappings: { properties: { my_vector: { type: "dense_vector", dims: 3, index: true, index_options: { type: "int8_hnsw", }, }, }, }, }); console.log(response);
PUT my-byte-quantized-index { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 3, "index": true, "index_options": { "type": "int8_hnsw" } } } } }
以下是如何创建半字节量化索引的示例:
resp = client.indices.create( index="my-byte-quantized-index", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 4, "index": True, "index_options": { "type": "int4_hnsw" } } } }, ) print(resp)
const response = await client.indices.create({ index: "my-byte-quantized-index", mappings: { properties: { my_vector: { type: "dense_vector", dims: 4, index: true, index_options: { type: "int4_hnsw", }, }, }, }, }); console.log(response);
PUT my-byte-quantized-index { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 4, "index": true, "index_options": { "type": "int4_hnsw" } } } } }
[预览] 此功能为技术预览版,可能会在将来的版本中更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。 以下是如何创建二进制量化索引的示例:
resp = client.indices.create( index="my-byte-quantized-index", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 64, "index": True, "index_options": { "type": "bbq_hnsw" } } } }, ) print(resp)
const response = await client.indices.create({ index: "my-byte-quantized-index", mappings: { properties: { my_vector: { type: "dense_vector", dims: 64, index: true, index_options: { type: "bbq_hnsw", }, }, }, }, }); console.log(response);
PUT my-byte-quantized-index { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 64, "index": true, "index_options": { "type": "bbq_hnsw" } } } } }
稠密向量字段的参数
编辑接受以下映射参数:
element_type
的有效值:
-
float
- 索引每个维度的 4 字节浮点值。这是默认值。
-
byte
- 索引每个维度的 1 字节整数值。
-
bit
- 索引每个维度的单个位。对于非常高维度的向量或专门支持位向量的模型非常有用。注意:当使用
bit
时,维度数必须是 8 的倍数,并且必须表示位数。
-
dims
- (可选,整数)向量维数。不能超过
4096
。如果未指定dims
,则会将其设置为添加到该字段的第一个向量的长度。 -
index
- (可选,布尔值)如果为
true
,则可以使用 kNN 搜索 API 搜索此字段。默认为true
。
-
similarity
-
(可选*,字符串)在 kNN 搜索中使用的向量相似度度量。文档按其向量字段与查询向量的相似度进行排名。每个文档的
_score
将从相似度中派生,以确保分数是正的,并且较高的分数对应于较高的排名。当element_type: bit
时,默认为l2_norm
,否则默认为cosine
。* 仅当
index
为true
时才能指定此参数。bit
向量仅支持l2_norm
作为其相似度度量。
similarity
的有效值:
-
l2_norm
- 基于向量之间的 L2 距离(也称为欧几里得距离)计算相似度。文档
_score
计算为1 / (1 + l2_norm(query, vector)^2)
。
对于 bit
向量,不使用 l2_norm
,而是使用向量之间的 hamming
距离。_score
转换为 (numBits - hamming(a, b)) / numBits
。
-
dot_product
-
计算两个单位向量的点积。此选项提供了一种执行余弦相似度的优化方法。约束和计算的分数由
element_type
定义。当
element_type
为float
时,所有向量都必须是单位长度,包括文档向量和查询向量。文档_score
计算为(1 + dot_product(query, vector)) / 2
。当
element_type
为byte
时,所有向量都必须具有相同的长度,包括文档向量和查询向量,否则结果将不准确。文档_score
计算为0.5 + (dot_product(query, vector) / (32768 * dims))
,其中dims
是每个向量的维数。 -
cosine
- 计算余弦相似度。在索引期间,Elasticsearch 会自动将具有
cosine
相似度的向量归一化为单位长度。这允许在内部使用dot_product
来计算相似度,这更有效。原始的非归一化向量仍然可以通过脚本访问。文档_score
计算为(1 + cosine(query, vector)) / 2
。cosine
相似度不允许零幅值的向量,因为在这种情况下未定义余弦值。 -
max_inner_product
- 计算两个向量的最大内积。这类似于
dot_product
,但不要求向量被归一化。这意味着每个向量的大小都会显著影响得分。文档_score
会进行调整以防止出现负值。对于max_inner_product
值< 0
,_score
为1 / (1 + -1 * max_inner_product(query, vector))
。对于非负的max_inner_product
结果,_score
的计算公式为max_inner_product(query, vector) + 1
。
尽管它们在概念上相关,similarity
参数与 text
字段的 similarity
不同,并且接受一组不同的选项。
-
index_options
-
(可选*,对象)一个可选部分,用于配置 kNN 索引算法。HNSW 算法有两个内部参数,会影响数据结构的构建方式。可以调整这些参数以提高结果的准确性,但代价是索引速度会变慢。
* 仅当
index
为true
时才能指定此参数。index_options
的属性-
type
-
(必需,字符串)要使用的 kNN 算法的类型。可以是以下任意一种:
-
hnsw
- 这利用 HNSW 算法 进行可扩展的近似 kNN 搜索。这支持所有element_type
值。 -
int8_hnsw
- 浮点向量的默认索引类型。这利用 HNSW 算法,并为float
类型的element_type
自动进行标量量化,以实现可扩展的近似 kNN 搜索。这可以将内存占用减少 4 倍,但会牺牲一些精度。请参阅 自动量化向量以进行 kNN 搜索。 -
int4_hnsw
- 这利用 HNSW 算法,并为float
类型的element_type
自动进行标量量化,以实现可扩展的近似 kNN 搜索。这可以将内存占用减少 8 倍,但会牺牲一些精度。请参阅 自动量化向量以进行 kNN 搜索。 -
[预览] 此功能为技术预览版,未来版本可能会更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。
bbq_hnsw
- 这利用 HNSW 算法,并为float
类型的element_type
自动进行二进制量化,以实现可扩展的近似 kNN 搜索。这可以将内存占用减少 32 倍,但会牺牲精度。请参阅 自动量化向量以进行 kNN 搜索。 -
flat
- 这利用蛮力搜索算法进行精确的 kNN 搜索。这支持所有element_type
值。 -
int8_flat
- 这利用蛮力搜索算法并自动进行标量量化。仅支持float
类型的element_type
。 -
int4_flat
- 这利用蛮力搜索算法并自动进行半字节标量量化。仅支持float
类型的element_type
。 -
[预览] 此功能为技术预览版,未来版本可能会更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。
bbq_flat
- 这利用蛮力搜索算法并自动进行二进制量化。仅支持float
类型的element_type
。
-
-
m
- (可选,整数)HNSW 图中每个节点将连接到的邻居数量。默认为
16
。仅适用于hnsw
、int8_hnsw
、int4_hnsw
和bbq_hnsw
索引类型。 -
ef_construction
- (可选,整数)在为每个新节点组装最近邻居列表时要跟踪的候选数量。默认为
100
。仅适用于hnsw
、int8_hnsw
、int4_hnsw
和bbq_hnsw
索引类型。 -
confidence_interval
- (可选,浮点数)仅适用于
int8_hnsw
、int4_hnsw
、int8_flat
和int4_flat
索引类型。量化向量时要使用的置信区间。可以是0.90
到1.0
之间(含)的任何值,或正好为0
。当值为0
时,表示应计算动态分位数以进行优化的量化。当介于0.90
和1.0
之间时,此值限制在计算量化阈值时使用的值。例如,值0.95
将仅在计算量化阈值时使用中间 95% 的值(例如,最高和最低 2.5% 的值将被忽略)。对于int8
量化向量,默认为1/(dims + 1)
,对于int4
,则默认为0
,用于动态分位数计算。
-
合成 _source
编辑合成 _source
仅对 TSDB 索引(index.mode
设置为 time_series
的索引)正式可用。对于其他索引,合成 _source
处于技术预览阶段。技术预览版中的功能可能会在未来的版本中更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。
dense_vector
字段支持 合成 _source
。
索引和搜索位向量
编辑当使用 element_type: bit
时,这将把所有向量视为位向量。位向量每维度仅使用一个位,并在内部编码为字节。这对于非常高维的向量或模型非常有用。
当使用 bit
时,维数必须是 8 的倍数,并且必须表示位的数量。此外,对于 bit
向量,典型的向量相似度值实际上都得分相同,例如使用 hamming
距离。
让我们比较两个 byte[]
数组,每个数组代表 40 个单独的位。
[-127, 0, 1, 42, 127]
的位为 1000000100000000000000010010101001111111
[127, -127, 0, 1, 42]
的位为 0111111110000001000000000000000100101010
当比较这两个位向量时,我们首先计算 hamming
距离。
xor
结果
1000000100000000000000010010101001111111 ^ 0111111110000001000000000000000100101010 = 1111111010000001000000010010101101010101
然后,我们收集 xor
结果中 1
位的计数:18
。为了缩放得分,我们从总位数中减去该计数,然后除以总位数:(40 - 18) / 40 = 0.55
。这将是这两个向量之间的 _score
。
以下是索引和搜索位向量的示例
resp = client.indices.create( index="my-bit-vectors", mappings={ "properties": { "my_vector": { "type": "dense_vector", "dims": 40, "element_type": "bit" } } }, ) print(resp)
const response = await client.indices.create({ index: "my-bit-vectors", mappings: { properties: { my_vector: { type: "dense_vector", dims: 40, element_type: "bit", }, }, }, }); console.log(response);
PUT my-bit-vectors { "mappings": { "properties": { "my_vector": { "type": "dense_vector", "dims": 40, "element_type": "bit" } } } }
resp = client.bulk( index="my-bit-vectors", refresh=True, operations=[ { "index": { "_id": "1" } }, { "my_vector": [ 127, -127, 0, 1, 42 ] }, { "index": { "_id": "2" } }, { "my_vector": "8100012a7f" } ], ) print(resp)
const response = await client.bulk({ index: "my-bit-vectors", refresh: "true", operations: [ { index: { _id: "1", }, }, { my_vector: [127, -127, 0, 1, 42], }, { index: { _id: "2", }, }, { my_vector: "8100012a7f", }, ], }); console.log(response);
POST /my-bit-vectors/_bulk?refresh {"index": {"_id" : "1"}} {"my_vector": [127, -127, 0, 1, 42]} {"index": {"_id" : "2"}} {"my_vector": "8100012a7f"}
然后,在搜索时,可以使用 knn
查询来搜索相似的位向量
resp = client.search( index="my-bit-vectors", filter_path="hits.hits", query={ "knn": { "query_vector": [ 127, -127, 0, 1, 42 ], "field": "my_vector" } }, ) print(resp)
const response = await client.search({ index: "my-bit-vectors", filter_path: "hits.hits", query: { knn: { query_vector: [127, -127, 0, 1, 42], field: "my_vector", }, }, }); console.log(response);
POST /my-bit-vectors/_search?filter_path=hits.hits { "query": { "knn": { "query_vector": [127, -127, 0, 1, 42], "field": "my_vector" } } }
{ "hits": { "hits": [ { "_index": "my-bit-vectors", "_id": "1", "_score": 1.0, "_source": { "my_vector": [ 127, -127, 0, 1, 42 ] } }, { "_index": "my-bit-vectors", "_id": "2", "_score": 0.55, "_source": { "my_vector": "8100012a7f" } } ] } }
可更新的字段类型
编辑为了更好地满足扩展和性能需求,可以使用 更新映射 API 根据以下图表(允许跳跃)更新 index_options
中的 type
设置
flat --> int8_flat --> int4_flat --> hnsw --> int8_hnsw --> int4_hnsw
对于更新所有 HNSW 类型(hnsw
、int8_hnsw
、int4_hnsw
),连接数 m
必须保持不变或增加。对于标量量化格式(int8_flat
、int4_flat
、int8_hnsw
、int4_hnsw
),confidence_interval
必须始终保持一致(一旦定义,就无法更改)。
在所有其他情况下,更新 index_options
中的 type
都会失败。
切换 types
不会重新索引已索引的向量(它们将继续使用其原始 type
),更改后索引的向量将使用新的 type
。
例如,可以定义一个密集向量字段,该字段利用 flat
类型(原始 float32 数组)用于要索引的第一批数据。
resp = client.indices.create( index="my-index-000001", mappings={ "properties": { "text_embedding": { "type": "dense_vector", "dims": 384, "index_options": { "type": "flat" } } } }, ) print(resp)
const response = await client.indices.create({ index: "my-index-000001", mappings: { properties: { text_embedding: { type: "dense_vector", dims: 384, index_options: { type: "flat", }, }, }, }, }); console.log(response);
PUT my-index-000001 { "mappings": { "properties": { "text_embedding": { "type": "dense_vector", "dims": 384, "index_options": { "type": "flat" } } } } }
将 type
更改为 int4_hnsw
可确保更改后索引的向量将使用 int4 标量量化表示和 HNSW(例如,用于 KNN 查询)。这包括通过合并先前创建的段而创建的新段。
resp = client.indices.put_mapping( index="my-index-000001", properties={ "text_embedding": { "type": "dense_vector", "dims": 384, "index_options": { "type": "int4_hnsw" } } }, ) print(resp)
const response = await client.indices.putMapping({ index: "my-index-000001", properties: { text_embedding: { type: "dense_vector", dims: 384, index_options: { type: "int4_hnsw", }, }, }, }); console.log(response);
PUT /my-index-000001/_mapping { "properties": { "text_embedding": { "type": "dense_vector", "dims": 384, "index_options": { "type": "int4_hnsw" } } } }
在此更改之前索引的向量将继续使用 flat
类型(原始 float32 表示形式和用于 KNN 查询的蛮力搜索)。
为了将所有向量更新为新类型,应使用重新索引或强制合并。
为了进行调试,可以使用 索引段 API 检查每个 type
存在多少段(和文档)。