向量
了解如何在 Redis 中使用向量字段并执行向量搜索
Redis 包含一个高性能向量数据库,您可以在其上对向量嵌入执行语义搜索。您可以结合文本、数值、地理空间和标签元数据过滤来增强这些搜索。
为了快速入门,请查看Redis 向量快速入门指南和 Redis AI Resources Github 仓库。
概览
- 创建向量索引:Redis 维护数据上的辅助索引,具有定义的模式(包括向量字段和元数据)。Redis 支持
FLAT
和HNSW
向量索引类型。 - 存储和更新向量:Redis 将向量和元数据存储在 hashes 或 JSON 对象中。
- 用向量搜索:Redis 支持多种带有向量字段的高级查询策略,包括 k-近邻 (KNN)、向量范围查询和元数据过滤器。
- 运行时配置向量查询.
- 向量搜索示例:探索一些向量搜索示例,涵盖不同的用例和技术。
创建向量索引
当您为索引定义模式时,可以包含一个或多个向量字段,如下所示。
语法
FT.CREATE <index_name>
ON <storage_type>
PREFIX 1 <key_prefix>
SCHEMA ... <field_name> VECTOR <algorithm> <index_attribute_count> <index_attribute_name> <index_attribute_value>
[<index_attribute_name> <index_attribute_value> ...]
有关其他字段、选项和注意事项,请参阅完整的索引文档。
参数
参数 | 描述 |
---|---|
index_name |
索引名称。 |
storage_type |
存储选项(HASH 或 JSON )。 |
prefix (可选) |
用于选择应被索引的键的键前缀。如果省略,则默认为所有键。 |
field_name |
向量字段名称。 |
algorithm |
向量索引算法(FLAT 或 HNSW )。 |
index_attribute_count |
向量字段属性的数量。 |
index_attribute_name |
向量字段属性名称。 |
index_attribute_value |
向量字段属性值。 |
FLAT 索引
当数据集较小(< 1M 向量)或当完美搜索准确性比搜索延迟更重要时,请选择 FLAT
索引。
必需属性
属性 | 描述 |
---|---|
TYPE |
向量类型(BFLOAT16 、FLOAT16 、FLOAT32 、FLOAT64 )。BFLOAT16 和 FLOAT16 需要 v2.10 或更高版本。 |
DIM |
存储在此字段中的向量嵌入的宽度或维数。换句话说,构成向量的浮点元素数量。DIM 必须是正整数。用于查询此字段的向量必须与字段本身具有完全相同的维度。 |
DISTANCE_METRIC |
距离度量(L2 、IP 、COSINE )。 |
示例
FT.CREATE documents
ON HASH
PREFIX 1 docs:
SCHEMA doc_embedding VECTOR FLAT 6
TYPE FLOAT32
DIM 1536
DISTANCE_METRIC COSINE
在上面的示例中,创建了一个名为 documents
的索引,它索引键前缀为 docs:
的哈希,以及一个名为 doc_embedding
的 FLAT
向量字段,该字段具有三个索引属性:TYPE
、DIM
和 DISTANCE_METRIC
。
HNSW 索引
HNSW
,即分层可导航小世界,是一种近似最近邻算法,它使用多层图使向量搜索更具可伸缩性。
- 最低层包含所有数据点,每个更高层都包含一个子集,形成一个层次结构。
- 在运行时,搜索遍历每一层的图,从上到下,在下降到后续层之前找到局部最小值。
当您拥有更大的数据集(> 1M 文档)或当搜索性能和可伸缩性比完美搜索准确性更重要时,请选择 HNSW
索引类型。
必需属性
属性 | 描述 |
---|---|
TYPE |
向量类型(BFLOAT16 、FLOAT16 、FLOAT32 、FLOAT64 )。BFLOAT16 和 FLOAT16 需要 v2.10 或更高版本。 |
DIM |
存储在此字段中的向量嵌入的宽度或维数。换句话说,构成向量的浮点元素数量。DIM 必须是正整数。用于查询此字段的向量必须与字段本身具有完全相同的维度。 |
DISTANCE_METRIC |
距离度量(L2 、IP 、COSINE )。 |
可选属性
HNSW
支持许多附加参数来调整查询的准确性,同时权衡性能。
属性 | 描述 |
---|---|
M |
图层中每个节点的最大出边(连接)数量。在零层,最大连接数将是 2 * M 。更高的值会提高准确性,但也会增加内存使用量和索引构建时间。默认值为 16。 |
EF_CONSTRUCTION |
图构建过程中考虑的最大连接邻居数量。更高的值会提高准确性,但也会增加索引构建时间。默认值为 200。 |
EF_RUNTIME |
KNN 搜索期间的最大顶级候选数量。更高的值会提高准确性,但也会增加搜索延迟。默认值为 10。 |
EPSILON |
相对因子,用于设置范围查询可能搜索候选的边界。也就是说,距离查询向量的距离为 radius * (1 + EPSILON) 的向量候选可能会被扫描,从而允许更广泛的搜索和更准确的结果,但代价是运行时增加。默认值为 0.01。 |
示例
FT.CREATE documents
ON HASH
PREFIX 1 docs:
SCHEMA doc_embedding VECTOR HNSW 10
TYPE FLOAT64
DIM 1536
DISTANCE_METRIC COSINE
M 40
EF_CONSTRUCTION 250
在上面的示例中,创建了一个名为 documents
的索引,它索引键前缀为 docs:
的哈希,以及一个名为 doc_embedding
的 HNSW
向量字段,该字段具有五个索引属性:TYPE
、DIM
、DISTANCE_METRIC
、M
和 EF_CONSTRUCTION
。
距离度量
Redis 支持三种流行的距离度量来衡量两个向量 $u$, $v$ $\in \mathbb{R}^n$ 之间的相似度,其中 $n$ 是向量的长度
距离度量 | 描述 | 数学表示 |
---|---|---|
L2 |
两个向量之间的欧几里得距离。 | $d(u, v) = \sqrt{ \displaystyle\sum_{i=1}^n{(u_i - v_i)^2}}$ |
IP |
两个向量的内积。 | $d(u, v) = 1 -u\cdot v$ |
COSINE |
两个向量的余弦距离。 | $d(u, v) = 1 -\frac{u \cdot v}{\lVert u \rVert \lVert v \rVert}$ |
上述度量计算两个向量之间的距离,值越小,两个向量在向量空间中越接近。
存储和更新向量
在创建索引时,<storage_type>
决定了向量和元数据的结构以及如何加载到 Redis 中。
Hash
使用 HSET
命令在hashes中存储或更新向量和任何元数据。
示例
HSET docs:01 doc_embedding <vector_bytes> category sports
<vector_bytes>
表示向量的基础内存缓冲区。将向量转换为字节的一种常用方法是使用 redis-py 客户端库和 Python NumPy 库。
示例
import numpy as np
from redis import Redis
redis_client = Redis(host='localhost', port=6379)
# Create a FLOAT32 vector
vector = np.array([0.34, 0.63, -0.54, -0.69, 0.98, 0.61], dtype=np.float32)
# Convert vector to bytes
vector_bytes = vector.tobytes()
# Use the Redis client to store the vector bytes and metadata at a specified key
redis_client.hset('docs:01', mapping = {"vector": vector_bytes, "category": "sports"})
JSON
您可以使用 JSON.SET
命令在JSON 中存储或更新向量和任何关联的元数据。
要将向量作为 JSON 存储在 Redis 中,您将向量存储为浮点数的 JSON 数组。请注意,这与 Redis hashes 中向量存储方式不同,hashes 中向量存储为原始字节。
示例
JSON.SET docs:01 $ '{"doc_embedding":[0.34,0.63,-0.54,-0.69,0.98,0.61], "category": "sports"}'
JSON 的优点之一是模式灵活性。从 v2.6.1 开始,JSON 支持多值索引。这允许您在同一个 JSONPath 下索引多个向量。
以下是一些使用向量进行多值索引的示例
多值索引示例
JSON.SET docs:01 $ '{"doc_embedding":[[1,2,3,4], [5,6,7,8]]}'
JSON.SET docs:01 $ '{"chunk1":{"doc_embedding":[1,2,3,4]}, "chunk2":{"doc_embedding":[5,6,7,8]}}'
更多信息和示例可在索引 JSON 文档部分找到。
使用向量搜索
您可以使用 FT.SEARCH
或 FT.AGGREGATE
命令运行向量搜索查询。
要使用 FT.SEARCH
发出向量搜索查询,您必须将 DIALECT
选项设置为 >= 2
。有关详细信息,请参阅方言文档。
KNN 向量搜索
KNN 向量搜索查找查询向量的 top k 个最近邻。它具有以下语法
语法
FT.SEARCH <index_name>
<primary_filter_query>=>[KNN <top_k> @<vector_field> $<vector_blob_param> $<vector_query_params> AS <distance_field>]
PARAMS <query_params_count> [$<vector_blob_param> <vector_blob> <query_param_name> <query_param_value> ...]
SORTBY <distance_field>
DIALECT 2
参数
参数 | 描述 |
---|---|
index_name |
索引名称。 |
primary_filter_query |
过滤器条件。当不需要过滤器时使用 * 。 |
top_k |
从索引中获取的最近邻居的数量。 |
vector_field |
要搜索的向量字段的名称。 |
vector_blob_param |
查询向量,作为原始字节 blob 传递。blob 的字节大小必须与向量字段的维度和类型匹配。 |
vector_query_params (可选) |
一个可选部分,用于标记通过 PARAMS 部分传递的一个或多个向量查询参数。有效参数应以键值对的形式提供。请参阅每种向量索引类型支持哪些运行时查询参数。 |
distance_field (可选) |
响应中使用的可选距离字段名称和/或用于排序。默认情况下,距离字段名称为 __<vector_field>_score ,可以在不使用查询中的 AS <distance_field> 的情况下用于排序。 |
vector_query_params_count |
向量查询参数的数量。 |
vector_query_param_name |
向量查询参数的名称。 |
vector_query_param_value |
向量查询参数的值。 |
示例
FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" DIALECT 2
使用查询属性
或者,从 v2.6 开始,可以在运行时查询属性中指定 <vector_query_params>
和 <distance_field>
名称,如下所示。
[KNN <top_k> @<vector_field> $<vector_blob_param>]=>{$yield_distance_as: <distance_field>}
向量范围查询
向量范围查询允许您使用表示输入查询向量和索引向量字段之间的语义距离的 radius
参数来过滤索引。这在您不确切知道要获取多少最近(top_k
)邻居,但知道结果应该有多相似的场景中很有用。
例如,想象一个欺诈或异常检测场景,您不确定向量索引中是否有任何匹配项。您可以发出向量范围查询,以快速检查索引中指定半径内是否有任何感兴趣的记录。
向量范围查询的操作与 KNN 向量查询略有不同
- 向量范围查询可以在查询中作为过滤条件多次出现。
- 向量范围查询可以是 KNN 向量搜索中
<primary_filter_query>
的一部分。
语法
FT.SEARCH <index_name>
@<vector_field>:[VECTOR_RANGE (<radius> | $<radius_param>) $<vector_blob_param> $<vector_query_params>]
PARAMS <vector_query_params_count> [<vector_query_param_name> <vector_query_param_value> ...]
SORTBY <distance_field>
DIALECT 2
参数 | 描述 |
---|---|
index_name |
索引名称。 |
vector_field |
索引中向量字段的名称。 |
radius 或 radius_param |
查询向量和索引向量之间允许的最大语义距离。您可以在查询中直接提供值,传递给 PARAMS 部分,或作为查询属性。 |
vector_blob_param |
查询向量,作为原始字节 blob 传递。blob 的字节大小必须与向量字段的维度和类型匹配。 |
vector_query_params (可选) |
一个可选部分,用于标记通过 PARAMS 部分传递的一个或多个向量查询参数。有效参数应以键值对的形式提供。请参阅每种向量索引类型支持哪些运行时查询参数。 |
vector_query_params_count |
向量查询参数的数量。 |
vector_query_param_name |
向量查询参数的名称。 |
vector_query_param_value |
向量查询参数的值。 |
使用查询属性
向量范围查询子句后面可以跟一个查询属性部分,如下所示
@<vector_field>: [VECTOR_RANGE (<radius> | $<radius_param>) $<vector_blob_param>]=>{$<param>: (<value> |
$<value_attribute>); ... }
在这种情况下,相关的参数是 $yield_distance_as
和 $epsilon
。请注意,范围查询中没有默认的距离字段名称。
过滤器
Redis 支持包含过滤器的向量搜索,以根据定义的条件缩小搜索空间。如果您的索引包含可搜索字段(例如,TEXT
、TAG
、NUMERIC
、GEO
、GEOSHAPE
和 VECTOR
),您可以执行带过滤器的向量搜索。
支持的过滤器类型
您还可以组合多个查询作为过滤器。
语法
带过滤器的向量搜索查询遵循此基本结构
FT.SEARCH <index_name> <primary_filter_query>=>[...]
其中 <primary_filter_query>
定义文档选择和过滤。
示例
FT.SEARCH documents "(@title:Sports @year:[2020 2022])=>[KNN 10 @doc_embedding $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" DIALECT 2
过滤工作原理
Redis 使用内部算法优化向量搜索的过滤计算。运行时算法由启发式方法确定,旨在根据从查询和索引派生的几个因素最小化查询延迟。
批量模式
批量模式通过对来自索引的小批量最近邻进行分页来工作
- 检索向量索引中的一批高分文档。仅当满足
<primary_filter_query>
时才会返回这些文档。换句话说,文档必须包含相似的向量并满足过滤条件。 - 当返回通过过滤条件的
<top_k>
个文档,或者处理完索引中的所有向量后,迭代过程终止。 - 批量大小根据
<top_k>
以及通过<primary_filter_query>
的索引中预期文档数量与向量索引大小的比率自动由启发式方法确定。 - 目标是最小化获取
<top_k>
结果所需的总批量数量,同时保留尽可能小的批量大小。请注意,批量大小可能会根据前一批次中通过过滤的結果数量在每次迭代中动态变化。
临时暴力模式 (Ad-hoc brute force mode)
- 计算通过过滤的文档对应的每个向量的分数,然后选择并返回
<top_k>
个结果。 - 当通过
<primary_filter_query>
的文档数量相对较少时,此方法更可取。 - 即使底层向量索引算法是近似的,KNN 查询的结果在这种模式下也始终是准确的。
执行模式可能会在运行期间从批量模式切换到临时暴力模式,这取决于从一批到另一批相关因素的更新估计。
运行时查询参数
过滤模式
默认情况下,Redis 选择最佳过滤模式来优化查询执行。您可以使用这些可选参数覆盖自动选择的策略
参数 | 描述 | 选项 |
---|---|---|
HYBRID_POLICY |
指定在带过滤器的向量搜索(混合搜索)期间使用的过滤模式。 | BATCHES 或 ADHOC_BF |
BATCH_SIZE |
当自动选择或请求 BATCHES 策略时,每次迭代中使用的固定批量大小。 |
正整数。 |
索引特有查询参数
FLAT
目前,FLAT 索引没有可用的运行时参数。
HNSW
HNSW 索引的可选运行时参数包括
参数 | 描述 | 默认值 |
---|---|---|
EF_RUNTIME |
KNN 搜索期间保留的顶部候选项的最大数量。更高的值会带来更准确的结果,但代价是更长的查询运行时。 | 索引创建期间传递的值。默认值为 10。 |
EPSILON |
用于设置向量范围查询边界的相对因子。查询向量距离为 radius * (1 + EPSILON) 的向量候选项可能会被扫描,从而允许更广泛的搜索和更准确的结果,但代价是运行时增加。 |
索引创建期间传递的值。默认值为 0.01。 |
重要注意事项
-
执行 KNN 向量搜索时,您指定
<top_k>
个最近邻。但是,默认的 Redis 查询LIMIT
参数(用于分页)为 10。为了获取返回的<top_k>
个结果,您还必须在搜索命令中指定LIMIT 0 <top_k>
。请参阅下面的示例。 -
默认情况下,结果按文档分数排序。要按向量相似度分数排序,请使用
SORTBY <distance_field>
。请参阅下面的示例。 -
根据您选择的距离度量,索引中向量之间的计算距离具有不同的边界。例如,
Cosine
距离上限为2
,而L2
距离没有边界。执行向量范围查询时,最佳实践是根据您的用例和所需的召回率或精确度指标调整<radius>
参数。
向量搜索示例
以下是一些示例,帮助您入门。有关更全面的演练,请参阅Redis 向量快速入门指南和 Redis AI Resources Github 仓库。
KNN 向量搜索示例
返回 10 个最近邻文档,其中 doc_embedding
向量字段最接近以下 4 字节 blob 表示的查询向量
FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY __vector_score DIALECT 2
返回前 10 个最近邻,并使用查询参数自定义 K
和 EF_RUNTIME
参数。请参阅 FT.SEARCH 命令中的“可选参数”部分。假设 doc_embedding
是 HNSW 索引,将 EF_RUNTIME
值设置为 150
FT.SEARCH documents "*=>[KNN $K @doc_embedding $BLOB EF_RUNTIME $EF]" PARAMS 6 BLOB "\x12\xa9\xf5\x6c" K 10 EF 150 DIALECT 2
为距离字段(vector_distance
)分配一个自定义名称,然后使用该名称进行排序
FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB AS vector_distance]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2
使用查询属性语法指定可选参数和距离字段名称
FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB]=>{$EF_RUNTIME: $EF; $YIELD_DISTANCE_AS: vector_distance}" PARAMS 4 EF 150 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2
要探索其他 Python 向量搜索示例,请查看 Redis Python
客户端库和 Redis Vector Library
的配方。
过滤示例
对于这些示例,假设您创建了一个名为 movies
的索引,其中包含不同电影及其元数据的记录。
在 title
字段中包含 'Dune' 且 year
在 [2020, 2022] 范围内的电影中,返回前 10 个最近邻,按 movie_distance
排序
FT.SEARCH movies "(@title:Dune @year:[2020 2022])=>[KNN 10 @movie_embedding $BLOB AS movie_distance]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY movie_distance DIALECT 2
在类别标签包含 action 但不包含 drama 的电影中,返回前 10 个最近邻,按 movie_distance
排序
FT.SEARCH movies "(@category:{action} ~@category:{drama})=>[KNN 10 @doc_embedding $BLOB AS movie_distance]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY movie_distance DIALECT 2
在类别标签包含 drama 或 action 的电影中,返回前 10 个最近邻,并显式设置过滤模式(混合策略)为“临时暴力搜索”,而不是自动选择
FT.SEARCH movies "(@category:{drama | action})=>[KNN 10 @doc_embedding $BLOB HYBRID_POLICY ADHOC_BF]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY __vec_score DIALECT 2
在类别标签包含 action 的电影中,返回前 10 个最近邻,并使用查询参数显式设置过滤模式(混合策略)为“批量”且批量大小为 50
FT.SEARCH movies "(@category:{action})=>[KNN 10 @doc_embedding $BLOB HYBRID_POLICY BATCHES BATCH_SIZE $BATCH_SIZE]" PARAMS 4 BLOB "\x12\xa9\xf5\x6c" BATCH_SIZE 50 DIALECT 2
运行与上面相同的查询,并使用查询属性语法指定可选参数
FT.SEARCH movies "(@category:{action})=>[KNN 10 @doc_embedding $BLOB]=>{$HYBRID_POLICY: BATCHES; $BATCH_SIZE: 50}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" DIALECT 2
要探索其他 Python 向量搜索示例,请查看 Redis Python
客户端库和 Redis Vector Library
的配方。
范围查询示例
对于这些示例,假设您创建了一个名为 products
的索引,其中包含来自电子商务网站的不同产品及其元数据的记录。
返回 100 个产品,其中 description_vector
字段与指定的查询向量 blob 之间的距离最多为 5
FT.SEARCH products "@description_vector:[VECTOR_RANGE 5 $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" LIMIT 0 100 DIALECT 2
运行与上面相同的查询,并设置 EPSILON
参数为 0.5
(假设 description_vector
是 HNSW 索引),在名为 vector_distance
的字段中返回 description_vector
与查询结果之间的向量距离,并按该距离排序结果。
FT.SEARCH products "@description_vector:[VECTOR_RANGE 5 $BLOB]=>{$EPSILON:0.5; $YIELD_DISTANCE_AS: vector_distance}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance LIMIT 0 100 DIALECT 2
使用向量范围查询作为过滤器:返回所有文档,这些文档的 type
标签包含 'shirt' 且 year
值在 [2020, 2022] 范围内,或者存储在 description_vector
中的向量与查询向量的距离不超过 0.8;然后按向量距离排序结果(如果距离在范围内)
FT.SEARCH products "(@type:{shirt} @year:[2020 2022]) | @description_vector:[VECTOR_RANGE 0.8 $BLOB]=>{$YIELD_DISTANCE_AS: vector_distance}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2
要探索其他 Python 向量搜索示例,请查看 Redis Python
客户端库和 Redis Vector Library
的配方。
内存消耗比较
以下是支持的向量类型 BFLOAT16
、FLOAT16
、FLOAT32
和 FLOAT64
的向量大小的 Python+NumPy 示例。
import numpy as np
#install ml_dtypes from pip install ml-dtypes
from ml_dtypes import bfloat16
# random float64 100 dimensions
double_precision_vec = np.random.rand(100)
# for float64 and float32
print(f'length of float64 vector: {len(double_precision_vec.tobytes())}') # >>> 800
print(f'length of float32 vector: {len(double_precision_vec.astype(np.float32).tobytes())}') # >>> 400
# for float16
np_data_type = np.float16
half_precision_vec_float16 = double_precision_vec.astype(np_data_type)
print(f'length of float16 vector: {len(half_precision_vec_float16.tobytes())}') # >>> 200
# for bfloat16
bfloat_dtype = bfloat16
half_precision_vec_bfloat16 = double_precision_vec.astype(bfloat_dtype)
print(f'length of bfloat16 vector: {len(half_precision_vec_bfloat16.tobytes())}') # >>> 200
下一步
向量嵌入和向量搜索并非新概念。许多大型公司已使用向量来表示电子商务目录中的产品或广告流水线中的内容已有十多年的历史。
随着大型语言模型(LLMs)的兴起和需要高级信息检索技术的应用的普及,Redis 非常适合作为您用于语义搜索及其他功能的高性能查询引擎。
以下是一些将向量搜索应用于不同用例的附加资源
继续学习 Redis University
请参阅向量搜索入门课程以了解更多。