AnalyticDB PostgreSQL稀疏向量使用指南

当您需要高效存储大部分元素为零的向量时,创建稀疏向量表比稠密向量表能节省大量的存储空间。本文介绍云原生数据仓库AnalyticDB PostgreSQL版向量数据库如何使用稀疏向量,包括创建稀疏向量表、构建稀疏向量索引、执行稀疏向量检索,以及执行稀疏向量和稠密向量的混合查询等。

简介

在向量数据库中,通常将向量划分为稠密向量和稀疏向量。稀疏向量是一种大部分元素都是0的数据结构,通常有数万个维度,但是其中只有少数几个维度有值。当使用稀疏向量进行关键词搜索时,一个稀疏向量就表示一个文档,其中的维度通常表示字典(或词汇表)中的关键词,维度的值则表示这些关键词在文档中的重要性。如果使用BM25算法生成稀疏向量,维度的值则包含关键词匹配数量、词频和其他文本相关性因素。

在机器学习和自然语言处理中,如果使用普通的数组或列表来存储,会浪费大量的空间,为了高效存储和操作这类向量,引入了稀疏向量。稀疏向量常用于表示文本、图像或者其他类型的数据。这种数据结构在存储和处理高维数据时非常有用,可以显著减少存储空间和计算资源的消耗。

稠密向量(Dense)和稀疏向量(Sparse)的区别如下图所示:

image

前提条件

使用稀疏向量

创建稀疏向量表

语法

CREATE TABLE <SparseVectorTable> 
(  
    id int PRIMARY KEY,
    description text, 
    ...,
    sparse_vector svector(MAX_DIM), 
)DISTRIBUTED BY(id);

参数说明:

  • SparseVectorTable:向量表名称。

  • sparse_vector:稀疏向量列,为svector类型。

  • MAX_DIM:稀疏向量的最大维度(非有值维度数量)。

使用示例

创建一个名为svector_test的稀疏向量表,具体示例如下:

-- 创建一个带稀疏向量字段的表
CREATE TABLE <svector_test>(  
    id bigint,
    type int,
    tag varchar(10),
    document text,
    sparse_features svector(250000)
) DISTRIBUTED BY (id);

-- 设置稀疏向量列为Plain模式
ALTER TABLE <svector_test> ALTER COLUMN sparse_features SET storage plain;
说明

Plain模式:在PostgreSQL中 ,存储机制对于大对象、大字段或无法容纳在常规数据页内的数据采用了一种名为Toast的策略。这种策略允许数据的高效存储和访问。当字段使用Plain策略存储时,数据被存储为非压缩和非分裂的一整块,数据必须完全适应单个数据项,不会溢出到Toast表中。Plain模式通常用于需要避免Toast处理开销的小数据字段,或者可以被高效存储的数据类型,例如整数或小文本字段。

创建稀疏向量索引

语法

稀疏向量的索引仅支持内积距离度量,不支持PQ量化和MMAP模式。语法规则如下:

CREATE INDEX <idx_sparse_vector>
ON MyTable USING vector_index (sparse_vector_column);
WITH (DISTANCEMEASURE=IP,
      HNSW_M=$M,
      HNSW_EF_CONSTRUCTION=$EF_CONSTURCTION);

参数说明:

  • idx_sparse_vector:向量索引名称。

  • DISTANCEMEASURE:取值为IP,否则会出现参数报错。

  • HNSW_M和HNSW_EF_CONSTRUCTION:参考创建向量索引

使用示例

使用示例中的svector_test稀疏向量表为例,创建稀疏向量索引,具体示例如下:

-- 根据混合查询的结构化字段为其创建btree索引,如果结构化字段为数组类型,也可以添加gin索引
CREATE INDEX svector_test(type);
CREATE INDEX svector_test(tag);

-- 为稀疏向量列创建一个HNSW索引
CREATE INDEX ON svector_test USING ANN(sparse_features) WITH(DISTANCEMEASURE=IP,HNSW_M=64,pq_enable=0,external_storage=0);

稀疏向量数据的表示

云原生数据仓库AnalyticDB PostgreSQL版中,通过使用svector类型可以表示稀疏向量。svector类型支持以JSON格式的字符串输入,需要包含indices字段和values字段,分别表示下标数组(非负整数)和对应的值数组(浮点数)。一个20维的稀疏向量可以作如下数据表示:

稀疏向量数据示例

[0, 0, 1.1, 0, 0, 0, 2.2, 0, 0, 3.3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

稀疏向量数据表示

{"indices":[2, 6, 9], "values":[1.1, 2.2, 3.3]}

参数说明:

  • indices:[2, 6, 9]表示稀疏向量中非0值的维度为第2,6,9维。

  • values:[1.1, 2.2, 3.3]表示对应的三个非0维度的取值为1.1,2.2,3.3。

云原生数据仓库AnalyticDB PostgreSQL版数据库中,使用svector可以将字符串转换为稀疏向量类型,SQL语句如下所示:

postgres=# SELECT '{"indices":[2, 6, 9], "values":[1.1, 2.2, 3.3]}'::svector;
svector
--------------------------------------------
{"indices":[2,6,9],"values":[1.1,2.2,3.3]}
(1 ROW)

稀疏向量数据的导入

稀疏向量的数据导入支持INSERT和COPY语法,以使用示例中的svector_test稀疏向量表为例,介绍INSERT的使用方法,具体示例如下:

-- 插入稀疏向量
INSERT INTO svector_test VALUES (1, 1, 'a', 'xxx', '{"indices":[2, 6, 9], "values":[1.1, 2.2, 3.3]}'::svector);
INSERT INTO svector_test VALUES (2, 2, 'b', 'xxx', '{"indices":[50, 100, 200], "values":[2.1, 3.2, 4.3]}'::svector);
INSERT INTO svector_test VALUES (3, 3, 'b', 'xxx', '{"indices":[150, 60, 90], "values":[5, 1e3, 1e-3]}'::svector);

稀疏向量的检索

语法

对于稀疏向量表来说,稀疏向量检索只支持内积距离度量的检索方式。稀疏向量检索也分为精确检索和近似检索,精确检索和近似检索的具体语法如下:

精确检索

SELECT id, inner_product(<sparse_vector>, '{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector) 
AS score FROM <SparseVectorTable> ORDER BY negative_inner_product(<sparse_vector>, 
'{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector) LIMIT <topk>;

参数说明:

  • sparse_vector:向量列名称。

  • SparseVectorTable:向量表名称。

  • topk:需要检索的结果集topk。

近似检索

SELECT id, inner_product(<sparse_vector>, '{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector) 
AS score FROM <SparseVectorTable> ORDER BY <sparse_vector>
'{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector LIMIT <topk>;

参数说明:

  • sparse_vector:向量列名称。

  • SparseVectorTable:向量表名称。

  • topk:需要检索的结果集topk。

使用示例

使用示例中的svector_test稀疏向量表为例,使用稀疏向量检索的示例如下:

稀疏向量检索

SELECT id, sparse_features, 
'{"indices":[2,60,50], "values":[2,0.01, 0.02]}' <#> sparse_features AS score 
FROM svector_test
ORDER BY score
LIMIT 3;

检索结果

   id   |                 sparse_features                 |        score
--------+-------------------------------------------------+---------------------
      3 | {"indices":[60,90,150],"values":[1000,0.001,5]} |                 -10
      1 | {"indices":[2,6,9],"values":[1.1,2.2,3.3]}      |   -2.20000004768372
      2 | {"indices":[50,100,200],"values":[2.1,3.2,4.3]} | -0.0419999957084656
(3 ROWS)
说明

稀疏向量的向量检索召回率,可以通过fastann.sparse_hnsw_max_scan_points和fastann.sparse_hnsw_ef_search进行调节。具体内容,请参见相关参考

稠密向量与稀疏向量的混合查询

创建一个同时包含稀疏向量和稠密向量的数据表HYBRID_SEARCH_TEST,并以此为基础介绍如何实现稠密向量和稀疏向量的混合查询,具体建表SQL语句示例如下:

-- 创建混合查询的数据表
CREATE TABLE IF NOT EXISTS HYBRID_SEARCH_TEST (
   id integer PRIMARY key,
   corpus text,
   filter integer,
   dense_vector float4[],
   sparse_vector svector(250003)
) distributed BY (id);

-- 设置向量列为Plain模式
ALTER TABLE hybrid_search_test ALTER COLUMN dense_vector SET storage plain;
ALTER TABLE hybrid_search_test ALTER COLUMN sparse_vector SET storage plain;

创建索引的SQL语句示例如下:

-- 创建结构化索引
CREATE INDEX ON hybrid_search_test(FILTER);

-- 创建稠密向量索引
CREATE INDEX ON hybrid_search_test USING ANN(dense_vector) WITH(DISTANCEMEASURE=IP, DIM=1024, HNSW_M=64, HNSW_EF_CONSTRUCTION=600, EXTERNAL_STORAGE=1);

-- 创建稀疏向量索引
CREATE INDEX ON hybrid_search_test USING ANN(sparse_vector) WITH(DISTANCEMEASURE=IP, HNSW_M=64, HNSW_EF_CONSTRUCTION=600);

稠密向量查询、稀疏向量查询以及混合查询的SQL语句示例如下:

-- 向量检索 + filter
SELECT id, corpus FROM hybrid_search_test WHERE FILTER IN (0, 100, 200, 300, 400, 500, 600, 700, 800, 900) ORDER BY dense_vector <#> ARRAY[1,2,3,...,1024]::float4[] LIMIT 100;

-- sparse检索 + filter
SELECT id, corpus FROM hybrid_search_test WHERE FILTER IN (0, 100, 200, 300, 400, 500, 600, 700, 800, 900) ORDER BY sparse_vector <#> '{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector LIMIT 100;
  
-- 混合检索 + filter
WITH combined AS (
(SELECT id, corpus, inner_product_distance(dense_vector, ARRAY[1,2,3,...,1024]::float4[]) AS dist1, sparse_vector <#> '{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector AS dist2 FROM hybrid_search_test WHERE FILTER IN (0, 100, 200, 300, 400, 500, 600, 700, 800, 900) ORDER BY dist2 LIMIT 100)
  UNION ALL
(SELECT id, corpus, inner_product(sparse_vector, '{"indices":[1,2,..], "values":[1.1,2.2...]}'::svector) AS dist1, dense_vector <#> ARRAY[1,2,3,...,1024]::float4[] AS dist2 FROM hybrid_search_test WHERE FILTER IN (0, 100, 200, 300, 400, 500, 600, 700, 800, 900) ORDER BY dist2 LIMIT 100 )
) SELECT DISTINCT first_value(id) OVER (PARTITION BY id) AS id,  first_value(corpus) OVER (PARTITION BY id) AS corpus, MAX(dist1 - dist2) OVER (PARTITION BY id) AS dist FROM combined ORDER BY dist DESC LIMIT 100;

相关参考

稀疏向量检索相关的内核参数

稀疏向量检索相关的内核参数

功能说明

默认值

取值范围

fastann.sparse_hnsw_max_scan_points

在HNSW索引中进行稀疏向量检索时,最大扫描点个数,用于提前结束搜索,可用于召回率测试。

8000

[1, 8000000]

fastann.sparse_hnsw_ef_search

在HNSW索引中进行稀疏向量检索时,搜索候选集大小,可用于召回率测试。

400

[10, 10000]

说明

上述内核参数可以在会话级别设置生效。

稀疏向量支持的向量函数

函数作用

向量函数

返回值类型

含义

支持的数据类型

l2_distance

double_precision

欧氏距离(开方值),通常用于衡量两个稀疏向量的大小,表示两个稀疏向量的距离。

svector

计算

inner_product

double precision

内积距离,在向量归一化之后等于余弦相似度,通常用于在向量归一化之后替代余弦相似度。

svector

dp_distance

double precision

点积距离,和内积距离完全一致。

svector

cosine_similarity

double precision

余弦相似度,取值范围:[-1, 1],通常用于衡量两个稀疏向量在方向上的相似性,而不关心两个稀疏向量的实际长度。

svector

svector_add

svector

两个稀疏向量的加法。

svector

svector_sub

svector

两个稀疏向量的减法。

svector

svector_mul

svector

两个稀疏向量的乘法。

svector

svector_norm

double precision

计算单个稀疏向量的模长。

svector

svector_angle

double precision

计算两个稀疏向量的夹角。

svector

排序

l2_squared_distance

double precision

欧氏距离(平方值),由于比欧氏距离(开方值)少了开方的计算,因此主要用于对欧氏距离(开方值)的排序逻辑,以减少计算量。

svector

negative_inner_product

double precision

反内积距离,为内积距离取反后的结果,主要用于对内积距离的排序逻辑,以保证排序结果按内积距离从大到小排序。

svector

cosine_distance

double precision

余弦距离,取值范围:[0, 2],主要用于对余弦相似度的排序逻辑,以保证排序结果按余弦相似度从大到小排序。

svector

向量函数的使用示例

-- 欧氏距离
SELECT l2_distance('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 内积距离
SELECT inner_product('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 余弦相似度
SELECT l2_distance('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 向量加法
SELECT svector_add('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 向量减法
SELECT svector_sub('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 向量乘法
SELECT svector_mul('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 欧氏平方距离
SELECT l2_squared_distance('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 反内积距离
SELECT negative_inner_product('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);
-- 余弦距离
SELECT cosine_distance('{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector, '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector);

稀疏向量支持的向量操作符

操作符

计算含义

排序含义

支持的数据类型

<->

获取欧氏距离(平方),结果等同于l2_squared_distance。

svector

<#>

获取反内积,结果等同于negative_inner_product

按点积距离从大到小排序。

svector

<#>

获取余弦距离,结果等同于cosine_distance。

svector

+

两个稀疏向量的加法。

svector

-

两个稀疏向量的减法。

svector

*

两个稀疏向量的乘法。

svector

向量操作符示例

-- 欧氏平方距离
SELECT '{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector <-> '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector AS score;

-- 反内积距离
SELECT '{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector <#> '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector AS score;

-- 余弦距离
SELECT '{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector <=> '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector AS score;

-- 加法
SELECT '{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector + '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector AS value;

-- 减法
SELECT '{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector + '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector AS value;

-- 乘法
SELECT '{"indices":[1,2,3,4,5,6], "values":[1,2,3,4,5,6]}'::svector * '{"indices":[1,2,3,4,5,6], "values":[2,3,4,5,6,7]}'::svector AS value;