# 向量搜索

向量搜索是一种将数据表示为向量的搜索方法。它通常用于图像搜索、视频搜索、文本搜索等应用,以及图像分类和聚类等机器学习应用。

本节介绍如何创建和操作向量索引以加速向量搜索,以及如何配置不同类型的向量索引。

# 命令参考

# 创建带有向量的表

目前,我们支持两种类型的向量:float32 数组和二进制字符串。相应地,在 MyScale 中,嵌入向量将表示为 Array(Float32)FixedString

注意

二进制字符串类型的向量仅适用于数据库版本 1.3.1 或更高版本。

提示

嵌入向量列的所有向量 必须 具有相同的维度。

  • 对于 float32 数组向量,请使用 CONSTRAINT 来避免错误。例如,CONSTRAINT constraint_name_1 CHECK length(data) = 128
  • 对于二进制字符串向量,请将它们表示为固定长度的 FixedString(N) 类型。因此,不必为这些向量指定维度约束,因为每个向量的维度必须保持一致且等于 8 * N

以下是为不同类型的向量数据创建表的示例。

-- 创建一个表格,其中包含一个128维的 float32 向量数组和一个具有长度约束的向量列
CREATE TABLE test_vector
(
    id    UInt32,
    data  Array(Float32),
    CONSTRAINT check_length CHECK length(data) = 128,
    date  Date,
    label Enum8('person' = 1, 'building' = 2, 'animal' = 3)
) ENGINE = MergeTree ORDER BY id
-- 创建一个表格,包含128维(16字节)的二进制字符串向量
CREATE TABLE test_binary_vector
(
    id    UInt32,
    binary_data  FixedString(16),
    date  Date,
    label Enum8('person' = 1, 'building' = 2, 'animal' = 3)
) ENGINE = MergeTree ORDER BY id

注意

数据类型名称区分大小写,如果大小写错误,将返回错误。

# 创建向量索引

在运行向量搜索之前,必须先创建向量索引。创建向量索引的语法如下:

ALTER TABLE [db.]table_name
ADD VECTOR INDEX index_name column_name
[TYPE index_type('param1 = value1', 'param2 = value2', ...)]
  • index_name: 向量索引的名称。
  • column_name: 将创建向量索引的列的名称。此列必须Array(Float32)FixedString 类型。当它是 Array(Float32) 类型时,必须有一个约束来指定数组的长度。
  • index_type: 向量索引的具体类型。

提示

对于 float32 向量数组,我们强烈建议使用 MSTG 算法以获得最佳结果。然而,以下其他索引类型也可用。

  • FLAT: 用于比较的暴力算法
  • ScaNN: 由 Google Research 开发的可扩展最近邻算法
  • IVF 系列:包括 IVFFLATIVFSQIVFPQ
  • HNSW 系列:包括 HNSWFLATHNSWSQHNSWPQ

对于二进制字符串向量,可用的索引类型为 BinaryMSTGBinaryFLATBinaryIVFBinaryHNSW。在这些中,我们强烈推荐使用 BinaryMSTG 算法以获得最佳结果。

NOTE

MSTGBinaryMSTG 的向量索引类型仅在 SaaS/BYOC 版本中提供,而所有其他类型都在所有版本中提供,包括开源、SaaS 和 BYOC。

还有两种方法可以创建默认的向量索引,而无需指定 index_type:

  1. 创建一个index_type等于DEFAULT的向量索引。
  2. 在创建向量索引时,无需指定 TYPE index_type(...) 字段。

提示

在开源版本中,我们默认创建 ScaNNBinaryIVF 向量索引。而在 SaaS/BYOC 版本中,默认创建 MSTGBinaryMSTG 向量索引。

例如,要在 SaaS 上为 test_float_vector 表中的 vector 列创建一个名为 idx 类型为 MSTG 的向量索引,请使用以下命令之一:

-- 指定向量索引类型。
ALTER TABLE test_float_vector ADD VECTOR INDEX idx vector TYPE MSTG
-- 创建一个index_type等于DEFAULT的向量索引。
ALTER TABLE test_float_vector ADD VECTOR INDEX idx vector TYPE DEFAULT
-- 未指定向量索引类型。
ALTER TABLE test_float_vector ADD VECTOR INDEX idx vector

提示

有关特定类型的参数详细信息,请参阅向量索引配置选项说明部分。有关性能调优建议,请参阅性能调优建议部分。

从 DB 版本 v1.4.0 或更高版本开始,MyScale 允许在表上创建多个向量索引。但是,在一个向量列上只允许创建一个向量索引。例如,可以创建一个具有多个向量列的表:

-- 创建具有两个不同维度的float32向量列的表
CREATE TABLE test_multiple_vectors
(
    id    UInt32,
    vec1  Array(Float32),
    vec2  Array(Float32),
    CONSTRAINT check_length CHECK length(vec1) = 128,
    CONSTRAINT check_length CHECK length(vec2) = 64,
    date  Date,
    label Enum8('person' = 1, 'building' = 2, 'animal' = 3)
) ENGINE = MergeTree ORDER BY id

然后可以在每个向量列上创建一个向量索引:

-- 在 vec1 列上添加 MSTG 索引类型
ALTER TABLE test_multiple_vectors ADD VECTOR INDEX idx1 vec1 TYPE MSTG
-- 在 vec2 列上添加另一个 MSTG 向量索引
ALTER TABLE test_multiple_vectors ADD VECTOR INDEX idx2 vec2 TYPE MSTG

# 删除向量索引

可以使用以下语法删除向量索引。它将删除索引并释放相关的内存和磁盘资源。如果索引当前正在构建中,则构建过程也将立即停止。

ALTER TABLE [db.]table_name DROP VECTOR INDEX index_name

# 检查向量索引的状态

要查看向量索引的当前状态,可以使用 system.vector_indices 系统表。以下语法允许您查看所有现有的向量索引:

SELECT * FROM system.vector_indices

您可以使用 WHERE 子句按表名或其他条件筛选结果。例如,要查看特定表(如 test_float_vector)的向量索引,可以使用以下命令:

SELECT table, name, status FROM system.vector_indices
WHERE table = 'test_float_vector'

这将输出有关向量索引的信息,包括其当前状态,可能的状态有以下几种:

  1. Built:此状态表示索引已成功构建并准备就绪。
  2. InProgress:此状态表示索引当前正在构建或更新。在此期间,索引可能不完整,未被索引的向量搜索将回退到蛮力算法,速度较慢。
  3. Error:如果索引在构建或使用过程中遇到错误,将进入 Error 状态。这可能是由于各种原因,如无效的输入数据或系统故障。当索引处于此状态时,通常无法使用,直到错误解决为止。

对于处于 Error 状态的向量索引,可以使用以下命令查看失败原因:

SELECT table, name, latest_failed_part, latest_fail_reason
FROM system.vector_indices WHERE status = 'Error'

# 基本向量搜索

在 MyScale 中,使用 distance() 函数执行向量搜索。它计算指定向量与指定列中的所有向量数据之间的距离,并返回前几个候选结果。distance() 函数的基本语法如下:

distance('param1 = value1', 'param2 = value2')(column_name, query_vector)
  • params 表示特定于搜索的参数。params 还可以包括特定于索引的参数,例如 nprobe = 1(对于 IVFFLAT 向量索引)来定义搜索范围。
  • column_name 是要搜索的包含向量数据的列的名称。
  • query_vector 是将要被搜索的向量。

提示

  • 对于float32向量数组,重要的是要在查询向量中包含一个小数点,以防止它被识别为 Array(UInt64) 类型,这将导致执行查询时出现错误。例如,一个128D的float32向量数组的格式为 [3.0, 9, ..., 4]
  • 对于二进制字符串向量,可能不像其他类型那样容易显示和输入,但我们可以借助特定函数来操作它。
    • 使用 char() 函数产生一个二进制向量。这个函数返回二进制字符串,其长度为传递参数的数量,每个字节的值为相应参数的值。例如,一个128D的二进制字符串向量的格式为 char(128, 254, ... 127, 100)
    • 使用 unbin() 函数产生一个二进制向量。这个函数将参数中的每一对二进制数字解释为一个数字,并将其转换为该数字所代表的字节。例如,一个128D的二进制字符串向量的格式为 unbin('01010...01010')
    • 使用 unhex() 函数产生一个二进制向量。这个函数将参数中的每一对十六进制数字解释为一个数字,并将其转换为该数字所代表的字节。例如,一个128D的二进制字符串向量的格式为 unhex('FFEEDDCC...112233')
  • distance() 函数应与 order by 和 limit 子句一起使用,以获取前几个候选结果。
  • 在 order by 子句中,distance 函数列的排序方向需要与向量索引的度量类型(Cosine、IP 等)相对应,否则将报错。当度量类型为 IP 时,排序方向必须为 DESC

典型的 float32 向量数组搜索查询看起来像这样:

SELECT id, date, label,
  distance(data, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist
FROM test_float_vector
ORDER BY dist LIMIT 10

此查询将返回test_float_vector表中向量列与查询向量[3.0, 9, ..., 4]之间的iddatelabel和距离。ORDER BY dist LIMIT 10子句指定应返回前10个最接近的结果。

输出:

id date label dist
3 "2024-08-11" "animal" 0
790110 "2001-10-14" "person" 102904
396372 "1987-12-15" "animal" 108579
401952 "1975-08-24" "animal" 117388
603558 "1999-09-26" "animal" 118487
25589 "1978-08-29" "animal" 119259
12632 "2019-02-25" "animal" 119662
800289 "2000-07-09" "building" 119673
16298 "1997-03-11" "animal" 120011
395903 "2020-08-19" "animal" 121352

查询结果将是一个包含三列的表:iddatelabeldist,分别显示向量的id、日期、标签和最接近的向量结果与查询向量之间的距离。

相应地,典型的二进制字符串向量搜索查询看起来像这样:

SELECT id, date, label,
  distance(binary_data, unhex('ABCDEF0123456789ABCDEF0123456789')) AS dist
FROM test_binary_vector
ORDER BY dist LIMIT 10

# 带有过滤器的向量搜索

带有过滤器的向量搜索允许您根据其他列的值或距离值缩小结果范围。例如,以下查询将返回test_float_vector表中向量列与查询向量[3.0, 9, ..., 4]之间的id、向量和距离,但仅限于id列大于100000的行:

SELECT id, date, label,
  distance(data, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist
FROM test_float_vector
WHERE id > 100000
ORDER BY dist LIMIT 10

输出:

id date label dist
790110 "2001-10-14" "person" 102904
396372 "1987-12-15" "animal" 108579
401952 "1975-08-24" "animal" 117388
603558 "1999-09-26" "animal" 118487
800289 "2000-07-09" "building" 119673
395903 "2020-08-19" "animal" 121352
600737 "1972-08-25" "animal" 125027
790101 "1990-02-22" "person" 129224
790265 "2019-05-26" "building" 133267
198290 "1974-04-22" "building" 134178

要按distance值进行过滤,请使用以下方式的WHERE子句:

SELECT id, date, label,
  distance(data, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist
FROM test_float_vector
WHERE dist < 110000
ORDER BY dist LIMIT 10

此查询将返回test_float_vector表中向量列与查询向量[3.0, 9, ..., 4]之间的iddatelabel和距离,但仅限于距离小于110000的行。

输出:

id date label dist
3 "2024-08-11" "animal" 0
790110 "2001-10-14" "person" 102904
396372 "1987-12-15" "animal" 108579

# 多列向量搜索

注意

该功能仅在 DB 版本 v1.8 或更高版本中可用。

多列向量搜索允许您在向量搜索查询中使用多个 distance() 函数。多个 distance() 函数的前几个候选项是基于 part 和行 ID 进行合并的。distance() 函数的结果类型为 Float32。因此,对于具有两个向量 vec1 和 vec2 的行,只有一个向量出现在前几个候选项中,另一个向量的 distance() 值将为 NaN。 典型的多列向量搜索查询如下所示:

SELECT id, date, label, distance(vec1, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist1,
distance(vec2, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3]) AS dist2
FROM test_multiple_vectors
ORDER BY dist1 + dist2
LIMIT 10;
  • 向量搜索查询应该结合使用 order by 和 limit 子句以获取前几个候选项。
  • 对于 distance() 值包含 NaN 的情况,您可以使用类似 ifNotFinite(dist1, <default_value>) 的表达式为 NaN 值提供适当的默认值。

# 向量索引配置选项的解释

向量索引有两种类型的参数:索引创建参数搜索参数

索引创建参数在索引创建时指定,例如

ALTER TABLE [db.]table_name
ADD VECTOR INDEX index_name column_name
[TYPE index_type
(
  'creation_param1 = value1',
  'creation_param2 = value2',
  ...
)]

在搜索过程中,可以指定搜索参数,例如:

SELECT
  id,
  distance('search_param1 = value1', 'search_param2 = value2')(column_name, query_vector) as dist
FROM [db.]table_name
ORDER BY dist LIMIT 10

# 通用索引创建参数

可以在创建任何类型的向量索引时使用以下参数:

  • metric_type: 该参数确定了向量搜索中使用的距离度量。
    • 对于 float32 向量数组,有三个选项可用,默认值为 L2
      • L2: L2 度量,也称为欧几里得距离。
      • Cosine: 余弦距离,基于余弦相似度。余弦距离公式计算如下:

        通过将距离值从 1 减去可以得到余弦相似度。
      • IP: 内积(IP)度量。请注意,在使用 IP 度量时,需要使用 ORDER BY ... DESC,因为更高的 IP 值表明相似度更大。
    • 对于二进制字符串向量,有一个选项可用,默认值为 Hamming
      • Hamming: 两个等长向量之间的汉明距离是相应位置上不同符号的计数。
      • Jaccard: 杰卡德距离度量不相似度。杰卡德距离公式计算如下:

        通过将距离值从 1 减去可以得到杰卡德系数。

# ScaNN / MSTG / BinaryMSTG 参数

ScaNN 是谷歌为高维向量空间开发的用于快速且可扩展的最近邻搜索的算法。

MyScale开发的多尺度树图(MSTG)算法是一种专有解决方案,旨在为标准和过滤向量搜索操作提供高数据密度和高性能。

索引创建参数:

ScaNNMSTGBinaryMSTG 在索引创建过程中除了上述的 metric_type 外,不接受任何参数。

搜索参数:

  • alpha = float: 此参数控制搜索操作的准确性。值越高,搜索越准确,但QPS越低。默认值为3,有效范围为1到4。

# FLAT / BinaryFLAT 参数

FLATBinaryFLAT 是向量索引的最简单形式,它们直接基于原始数据计算距离,不涉及任何额外的优化参数。这对于原型设计和确保搜索结果的准确性很有用,但由于其相对较慢的性能,不建议在生产环境中使用。

# IVFFLAT 参数

IVFFLAT是一种分层索引,利用聚类将向量分成较小的簇,以实现更高效的搜索。

索引创建参数:

  • ncentroids = int: 确定将所有向量数据划分为的簇的数量。较大的ncentroids值会导致较慢的表构建时间。默认值为1024。

搜索参数:

  • nprobe = int: 指定搜索操作期间要搜索的簇的数量。较大的值会导致较慢的搜索但更高的准确性。默认值为1。

建议的参数值:

建议为ncentroids选择1000-10000之间的值,偏好接近数据量的平方根的值。如果ncentroids太大,可能会影响性能。建议为nprobe选择0.1-10%的ncentroids值。

# IVFPQ 参数

索引创建参数:

  • ncentroids = int: 参见IVFFLAT

  • M = int: 将原始向量维度减小为MM必须能够整除原始向量维度。默认值为16。

  • bit_size = int: 指定用于替换原始向量的Product Quantization(PQ)编码表的大小。有效值是4的倍数,默认值为8。

搜索参数:

建议的参数值:

ncentroidsnprobe的推荐值与IVFFLAT相似。重要的是要注意,PQ的压缩比率计算公式为(bit_size * M) / (original_dimension * original_element_size)。对于128维的float32向量,当M = 16bit_size = 8时,对应的压缩比率为(16*8)/(128*32) = 1/32。过高的压缩比率可能会严重影响搜索结果的准确性,因此建议将bit_size保持在8,并将M控制在原始维度的1/4以内,以避免此问题。

# IVFSQ 参数

索引创建参数:

  • ncentroids = int: 参考 IVFFlat

  • bit_size = string: 可接受的值为 8bit6bit4bit8bit_uniform8bit_direct4bit_uniformQT_fp16,默认值为 8bit

标量量化(SQ)算法用于在保留维度数量的同时压缩每个向量维度。当 bit_size 设置为 8 时,压缩率约为 25%。算法的精度按照 8bit_direct8bit_uniform8bit 的顺序递增,但索引构建速度与精度成反比。8bit_direct 使用 static_cast 将浮点数转换为 uint_88bit_uniform 将所有浮点数均匀分成 256 个区间,8bit 将每个维度均匀分成 256 个区间。4bit_uniform 将数据分成 16 个区间进行量化。QT_fp16 是 SQ 算法的一种变体,使用半精度浮点数,详细信息可以在链接 https://gist.github.com/rygorous/2156668 (opens new window) 中找到。

搜索参数:

# HNSWFLAT 参数

分层可导航小世界(HNSW)算法是一种用于快速在高维空间中找到给定查询点的最近邻的近似最近邻搜索算法。它通过将数据点组织成多级图数据结构来实现这一目标。HNSW 算法利用“小世界”网络的原理,其中大多数节点之间只有很少的步骤,以导航图并高效地找到与查询点最接近的数据点。它以在大型数据集和高维空间中的高性能和可扩展性而闻名。

索引创建参数:

  • m = int: 该参数确定 HNSW 图中每个数据点的邻居数量,并影响索引的质量。较大的 m 值将导致搜索结果的准确性更高,但也会增加构建索引所需的时间。默认值为 16。
  • ef_c = int: 该参数确定在创建索引时 HNSW 使用的优先队列的大小,并影响索引的质量。较大的 ef_c 值将导致搜索结果的精度更高,但也会增加构建索引所需的时间。默认值为 100。

搜索参数:

  • ef_s = int: 该参数确定在搜索操作期间 HNSW 使用的优先队列的大小。较大的 ef_s 值将导致搜索结果的精度更高,但也会增加搜索时间。默认值为 50。

建议的参数值:

通常建议将 m 设置在 8-128 之间,将 ef_c 设置在 50-400 之间。将 ef_c 值翻倍将大致使索引构建时间翻倍。ef_s 的值应根据搜索需求进行调整,建议使用与 ef_c 相同的值范围。如果需要低延迟要求,可以选择较低的值。

# HNSWSQ 参数

索引创建参数:

搜索参数:

建议的参数值:

参考 IVFSQ 获取 bit_size 的选择,其余参数参考 HNSWFLAT

# 性能调优建议

通常,我们建议使用 MSTG/BinaryMSTG 索引和索引创建的默认值来优化性能。在搜索时,您可以根据所需的吞吐量和精度调整 alpha 值。

在 MyScale 中,数据会自动分成多个部分,并在内部为每个部分创建一个向量索引。为了获得最佳性能,我们建议在创建向量索引之前将表优化为一个数据部分。您可以使用以下命令来优化表:

OPTIMIZE TABLE test_vector FINAL

因此,操作的推荐顺序是:

  1. 使用 CREATE TABLE ... 创建表;
  2. 使用 INSERT INTO ... 插入数据;
  3. 优化表;
  4. 使用 ALTER TABLE ... ADD VECTOR INDEX 创建向量索引;
  5. 等待向量索引构建完成;
  6. 开始搜索。

需要注意的是,在索引创建之后优化表可能会消耗大量内存。因此,请不要在索引创建之后优化表。

Last Updated: Fri Nov 01 2024 09:38:04 GMT+0000