稠密向量字段类型

编辑

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_hnswint4_hnswbbq_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(默认值)、byte 和 bit。
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

* 仅当 indextrue 时才能指定此参数。

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_typefloat 时,所有向量都必须是单位长度,包括文档向量和查询向量。文档 _score 计算为 (1 + dot_product(query, vector)) / 2

element_typebyte 时,所有向量都必须具有相同的长度,包括文档向量和查询向量,否则结果将不准确。文档 _score 计算为 0.5 + (dot_product(query, vector) / (32768 * dims)),其中 dims 是每个向量的维数。

cosine
计算余弦相似度。在索引期间,Elasticsearch 会自动将具有 cosine 相似度的向量归一化为单位长度。这允许在内部使用 dot_product 来计算相似度,这更有效。原始的非归一化向量仍然可以通过脚本访问。文档 _score 计算为 (1 + cosine(query, vector)) / 2cosine 相似度不允许零幅值的向量,因为在这种情况下未定义余弦值。
max_inner_product
计算两个向量的最大内积。这类似于 dot_product,但不要求向量被归一化。这意味着每个向量的大小都会显著影响得分。文档 _score 会进行调整以防止出现负值。对于 max_inner_product< 0_score1 / (1 + -1 * max_inner_product(query, vector))。对于非负的 max_inner_product 结果,_score 的计算公式为 max_inner_product(query, vector) + 1

尽管它们在概念上相关,similarity 参数与 text 字段的 similarity 不同,并且接受一组不同的选项。

index_options

(可选*,对象)一个可选部分,用于配置 kNN 索引算法。HNSW 算法有两个内部参数,会影响数据结构的构建方式。可以调整这些参数以提高结果的准确性,但代价是索引速度会变慢。

* 仅当 indextrue 时才能指定此参数。

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。仅适用于 hnswint8_hnswint4_hnswbbq_hnsw 索引类型。
ef_construction
(可选,整数)在为每个新节点组装最近邻居列表时要跟踪的候选数量。默认为 100。仅适用于 hnswint8_hnswint4_hnswbbq_hnsw 索引类型。
confidence_interval
(可选,浮点数)仅适用于 int8_hnswint4_hnswint8_flatint4_flat 索引类型。量化向量时要使用的置信区间。可以是 0.901.0 之间(含)的任何值,或正好为 0。当值为 0 时,表示应计算动态分位数以进行优化的量化。当介于 0.901.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"} 

5 个字节代表 40 维向量

代表 40 维向量的十六进制字符串

然后,在搜索时,可以使用 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 类型(hnswint8_hnswint4_hnsw),连接数 m 必须保持不变或增加。对于标量量化格式(int8_flatint4_flatint8_hnswint4_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 存在多少段(和文档)。