Postgres:用于一对多搜索的浮点数组的余弦相似度索引

Posted

技术标签:

【中文标题】Postgres:用于一对多搜索的浮点数组的余弦相似度索引【英文标题】:Postgres: index on cosine similarity of float arrays for one-to-many search 【发布时间】:2017-12-01 05:03:58 【问题描述】:

Cosine similarity 两个相等大小的向量(实数)之间的定义为点积除以范数的乘积。

为了表示向量,我有一个float 数组的大表,例如CREATE TABLE foo(vec float[])'。给定某个 float 数组,我需要通过余弦相似度快速(使用索引,而不是 seqscan)找到该表中最接近的数组,例如SELECT * FROM foo ORDER BY cos_sim(vec, ARRAY[1.0, 4.5, 2.2]) DESC LIMIT 10; 但是我用什么?

pg_trgm 的余弦相似度支持不同。它比较文本,我不确定它到底做了什么。一个名为smlar (here) 的扩展也支持浮点数组的余弦相似度,但又做了一些不同的事情。我所描述的通常用于数据分析来比较文档的特征,所以我认为 Postgres 会支持它。

【问题讨论】:

你能解释一下你所说的“有索引”是什么意思吗?余弦相似度是一种二元运算,在您描述的结构中,它将对成对的行进行运算。索引不采用成对的行。 @rd_nielsen < 也是二元运算符,但 Postgres 中支持 btree 索引以通过过滤和排序加快查询。 那些俄罗斯人不会创建不支持索引的扩展 :) 他们是全文搜索(使用 GiST、GIN insexes)、数组索引功能(通过 GiST 的 GIN 和 RD-tree)的作者、GiST 版本的 R-tree(替换原始 R-tree 实现)、hstore、对 jsonb 的索引支持等等。如果在不支持索引的情况下实现“smlar”,我会感到非常惊讶。 通常,距离定义为 1/相似度。所以更大的相似性,“更相似”的对象——它们之间的距离更小。 哦。 “N_i 是交点中唯一元素的数量,N_a 和 N_b 是每个向量中唯一元素的数量”看起来更像 Jaccard 相似度(不完全是一些奇怪的变体),绝对不像余弦相似度......你在哪里接受那个定义?源代码或文档?能给个链接吗? 【参考方案1】:

我认为没有任何扩展可以做到这一点,所以我找到了一个有限的解决方法:

如果 A 和 B 都被归一化(长度为 1),cos(A, B) = 1 - 0.5 * ||A - B||^2||A - B|| 是欧几里得距离,cos(A, B) 是余弦相似度。所以更大的欧几里得距离 更小的余弦相似度(如果你想象一个单位圆,直觉上是有意义的),如果你有非法线向量,改变它们的大小而不改变它们的方向不会影响它们的余弦相似度。太好了,所以我可以标准化我的向量并比较它们的欧几里得距离...

There's a nice answer here about Cube,它支持 欧几里得 距离上的 n 维点和 GiST 索引,但它只支持 100 或更少的维度(可以破解更高,但我有一些问题135及更高,所以现在我很害怕)。还需要 Postgres 9.6 或更高版本。

所以:

    确保我不关心最多 100 个维度。升级到 Postgres 9.6 或更高版本。 用数组填充我的表格来表示向量。 标准化向量以创建一个额外的cube 点列。在此列上创建一个 GiST 索引。 按欧几里得距离升序得到余弦相似度降序:EXPLAIN SELECT * FROM mytable ORDER BY normalized <-> cube(array[1,2,3,4,5,6,7,8,9,0]) LIMIT 10;

如果我需要超过 100 个维度,我也许可以使用多个索引列来实现这一点。在这种情况下会更新答案。

更新: 可以肯定的是,我无法将 >100 维向量拆分为多列。我最终不得不扫描整个表。

【讨论】:

有进展吗?欧几里得距离关系很好地将其简化为最近的点对问题,但索引问题似乎相当困难。也许局部敏感散列可能有效? @ShellRox 嗨!好吧,那个扩展做了索引,他们有一篇关于散列机制的论文,除了 Postgres 对索引的每行大小有任意限制。如果我编辑扩展以使用 float4 而不是 float8(又名双精度),我可以达到大约 180 个维度。这个项目因为不相关的原因被放弃了,所以从那以后我就没有再讨论过索引问题了。另外,我认为 Postgres 不是人们用于延迟敏感的机器学习模型推理的工具;) 感谢您的回复!我也使用 Postgres,并希望它在 512 维向量上进行余弦相似度搜索。不幸的是,我找不到任何有效的东西(除了全扫描)——这个问题是唯一的希望。因此,我相信我必须编写自己的搜索功能。 Henry Conklin 的回答可能适合您的需求。【参考方案2】:

如果您可以接受不精确的解决方案,您可以使用随机投影:https://en.wikipedia.org/wiki/Random_projection。

随机生成k 与其他向量长度相同的不同向量并将它们存储在某处。您将使用这些对数据进行空间分箱。对于表中的每个向量,对每个随机向量进行点积并存储乘积的符号。

对于每个随机向量具有相同符号的向量进入同一个 bin,通常具有高余弦相似度的向量最终会进入同一个 bin。您可以将符号作为位打包成一个整数,并使用普通索引将向量提取到与查询相同的 bin 中,然后进行顺序搜索以找到具有最高余弦相似度的那些。

【讨论】:

我听说过降维,但没见过这种。这很酷,因为如果您不想编写扩展程序,您甚至可以只使用 Postgres 表/查询来完成。但它不适用于我的情况,因为我的数据已经使用 SVD 或 Glove 从更高维度(它是自然语言)“压缩”下来。我发现 300d 给出的结果比 100d 更准确​​。所以真的是我想要的完整的 300 个维度。 随机投影可以用作降维,但在这种情况下,我们将保持向量不变并使用随机投影进行分箱。随机投影被用作空间哈希表的键,而不是低维表示。因此,您的向量将保持完整的 300 个维度,但在索引上拉取一组相似向量会更容易。 2D 中的类比是使用 x 和 y 轴作为“随机”向量,并按向量所在的象限对向量进行分组。 这看起来确实类似于new scientific document,它利用随机二元森林将向量空间划分为由超平面界定的凸超多面体的非重叠单元。我一定会尝试这两个,谢谢! @HenryConklin 哦,对了,我自己搞糊涂了。这是有道理的,我同意这应该可行。 几年后,我在翻阅我的 SO 资料并认为这是更正确的答案,尽管我的工作无需编写自定义扩展。考虑到你们在这里提供了多少帮助,给自己打勾也感觉很糟糕。

以上是关于Postgres:用于一对多搜索的浮点数组的余弦相似度索引的主要内容,如果未能解决你的问题,请参考以下文章

一对多连接在 postgres 中返回单行

如何在postgres中获得两个向量之间的余弦距离?

使用数组搜索 Postgres 数组

Django外键不适用于一对多关系

R语言使用lsa包计算余弦相似度(Cosine Similarity)实战:两个向量的余弦相似度矩阵的余弦相度

将浮点值存储到无符号字符数组