【问题标题】:How to get cosine distance between two vectors in postgres?如何在postgres中获得两个向量之间的余弦距离?
【发布时间】:2019-11-10 23:56:15
【问题描述】:

我想知道是否有办法在 postgres 中获取两个向量的余弦距离。 为了存储向量,我使用了 CUBE 数据类型。

下面是我的表定义:

test=# \d vectors                                                                                                                                
                            Table "public.vectors"
 Column |  Type   | Collation | Nullable |               Default               
--------+---------+-----------+----------+-------------------------------------
 id     | integer |           | not null | nextval('vectors_id_seq'::regclass)
 vector | cube    |           |          | 

另外,示例数据如下:

test=# select * from vectors order by id desc limit 2;
   id    |                  vector                  
---------+------------------------------------------
 2000000 | (109, 568, 787, 938, 948, 126, 271, 499)
 1999999 | (139, 365, 222, 653, 313, 103, 215, 796)

我实际上可以为此编写自己的 PLPGSql 函数,但想避免这种情况,因为它可能效率不高。

【问题讨论】:

    标签: postgresql vector euclidean-distance cosine-similarity postgresql-11


    【解决方案1】:

    关于您的餐桌

    首先,我认为您应该将数据类型更改为纯数组。

    CREATE TABLE public.vector ( 
      id serial NOT NULL,
      vctor double precision [3] --for three dimensional vectors; of course you can change the dimension or leave it unbounded if you need it.
     );
    
    INSERT INTO public.vector (vctor) VALUES (ARRAY[2,3,4]);
    INSERT INTO public.vector (vctor) VALUES (ARRAY[3,4,5]);
    

    所以

    SELECT * FROM public.vector;
    

    会产生以下数据

       id |   vctor
    ------|---------
        1 | {2,3,4}
        2 | {3,4,5}
    

    也许不是您期望的答案,但请考虑一下

    您可能已经知道,计算向量之间的余弦涉及计算幅度。我认为问题不在于算法,而在于实现;它需要计算对 RDBMS 来说代价高昂的平方和平方根。

    现在,谈谈效率;调用数学函数时,服务器进程不承担负载。在 PostgreSQL 中,数学函数 (look here) 从 C 库运行,因此它们非常高效。然而,最终,宿主必须分配一些资源来进行这些计算。

    在服务器内部实施这些相当昂贵的操作之前,我确实会仔细考虑。但是没有一个正确的答案;这取决于您如何使用数据库。例如,如果它是一个有数千个并发用户的生产数据库,我会将这种计算移到其他地方(中间层或用户应用程序)。但是如果用户很少,并且您的数据库是用于小型研究操作,那么它可以将其实现为存储过程或在服务器内部运行的进程,但请记住,这将影响可伸缩性或可移植性。当然,还有更多考虑因素,例如将处理多少行,或者您是否打算触发触发器等。

    考虑其他替代方案

    制作客户端应用

    您可以用 VB 或您选择的语言编写一个快速而体面的程序。并让客户端应用程序进行繁重的计算,并将数据库用于它最擅长的存储和检索数据。

    以不同方式存储数据

    对于这个特定的示例,您可以存储单位向量加上幅度。这样,求任意两个向量之间的余弦就可以简单地简化为单位向量的点积(只有乘除,没有平方也没有平方根。)

    CREATE TABLE public.vector ( 
         id serial NOT NULL,
         uvctor double precision [3], --for three dimensional vectors; of course you can change the dimension or make it decimal if you need it
         magnitude double precision
     ); 
    
    INSERT INTO public.vector (vctor) VALUES (ARRAY[0.3714, 0.5571, 0.7428], 5.385); -- {Ux, Uy, Uz}, ||V|| where V = [2, 3, 4];
    INSERT INTO public.vector (vctor) VALUES (ARRAY[0.4243, 0.5657, 0.7071], 7.071); -- {Ux, Uy, Uz}, ||V|| where V = [3, 4, 5];
    
    SELECT a.vctor as a, b.vctor as b, 1-(a.uvctor[1] * b.uvctor[1] + a.uvctor[2] * b.uvctor[2] + a.uvctor[3] * b.uvctor[3]) as cosine_distance FROM public.vector a
    JOIN public.vector b ON a.id != b.id;
    

    导致

                              a  |                           b  | cosine_distance
    -----------------------------|------------------------------|------------------
    {0.3714,0.5571,0.7428,5.385} | {0.4243,0.5657,0.7071,7.071} |      0.00202963
    {0.4243,0.5657,0.7071,7.071} | {0.3714,0.5571,0.7428,5.385} |      0.00202963
    

    即使您必须计算服务器内部向量的大小,您也需要为每个向量计算一次,而不是每次都需要计算其中两个之间的距离。随着行数的增加,这变得更加重要。例如,对于 1000 个向量,如果要使用原始向量分量获得任意两个向量之间的余弦差,则必须计算 999000 次。

    以上任意组合

    结论

    当我们追求效率时,大多数时候并没有一个规范的答案。相反,我们必须考虑和评估权衡取舍。它始终取决于我们需要实现的最终目标。数据库非常适合存储和检索数据;他们肯定可以制造其他东西,但这会带来额外的成本。如果我们可以忍受增加的开销,那很好;否则我们必须考虑替代方案。

    【讨论】:

    • 您好,谢谢您的回答。让我告诉你我的最终目标。我在数据库中存储了 200 万条记录,我需要找到给定向量的相似向量。所以我正在考虑计算向量之间的不同距离,例如欧几里得、余弦等,使用立方体数据类型,找到欧几里得距离的性能是可以接受的。我只是想要余弦距离的类似性能。如您所见,数据量很大,因此在客户端应用程序中计算余弦距离可能不是一个好主意。
    • 是的,对于 2M 条记录,针对所有集合对任何给定向量进行即时计算是非常昂贵的;为此,我倾向于单位向量表示。通过两个向量之间的余弦相似度(1-cosine_distance),您可以使用余弦定律计算欧几里得距离;您甚至可以将每个向量的大小的平方存储在表中。我想知道,数据是如何生成的?您将如何使用结果(即,您将通过查询或报告计算指标;这是否是使用应用程序的更大系统的一部分?)
    • 这真的很有见地。我一定会试一试的。 1. 数据是如何生成的 >> 有一些第三方库可以生成向量(例如:OpenCV),2. 你将如何使用结果 >> 我正在研究 POC,我有一个使用 sql 的初步计划查询以获取报告。另外,我将把这个答案标记为已接受,因为它可能不是达到预期结果的最佳方式,但它绝对是适合我的解决方案之一。
    • @Krauss 为什么要从 1 中减去?
    • @user554481 两个单位向量的标量积是相似度(或角度的余弦)。距离 = 1 - 相似度。两个方向相同的单位向量相似度=1,距离=0;到正交单位向量的相似度 = 0 和距离 = 1:两个相对的单位向量的相似度 = -1 和距离 = 2。
    【解决方案2】:

    你可以参考我的代码。

    --for calculation of norm vector --
    CREATE or REPLACE FUNCTION public.vector_norm(IN vector double precision[])
        RETURNS double precision AS 
    $BODY$
    
    BEGIN
    
        RETURN(SELECT SQRT(SUM(pow)) FROM (SELECT POWER(e,2) as pow from unnest(vector) as e) as norm);
    END;
    $BODY$ LANGUAGE 'plpgsql'; 
    ALTER FUNCTION public.vector_norm(double precision[]) OWNER TO postgres;
    
    COMMENT ON FUNCTION public.vector_norm(double precision[]) IS 'This function is used to find a norm of vectors.';
    
    --call function--
    select public.vector_norm('{ 0.039968978613615,0.357211461290717,0.753132887650281,0.760665621142834,0.20826127845794}')
    
    
    
    
    --for caculation of dot_product--
    CREATE OR REPLACE FUNCTION public.dot_product(IN vector1 double precision[], IN vector2 double precision[])
        RETURNS double precision    
    AS $BODY$
    BEGIN
        RETURN(SELECT sum(mul) FROM (SELECT v1e*v2e as mul FROM unnest(vector1, vector2) AS t(v1e,v2e)) AS denominator);
    END;
    $BODY$ LANGUAGE 'plpgsql';
    
    ALTER FUNCTION public.dot_product(double precision[], double precision[]) OWNER TO postgres;
    
    COMMENT ON FUNCTION public.dot_product(double precision[], double precision[])
        IS 'This function is used to find a cosine similarity between two multi-dimensional vectors.';
    
    
    --call fuction--
    SELECT public.dot_product(ARRAY[ 0.039968978613615,0.357211461290717,0.753132887650281,0.760665621142834,0.20826127845794],ARRAY[ 0.039968978613615,0.357211461290717,0.753132887650281,0.760665621142834,0.20826127845794])
    
    
    
    --for calculatuion of cosine similarity--
    CREATE OR REPLACE FUNCTION public.cosine_similarity(IN vector1 double precision[], IN vector2 double precision[])
        RETURNS double precision
        LANGUAGE 'plpgsql'
    
    AS $BODY$
    BEGIN
        RETURN(select ((select public.dot_product(ARRAY[ 0.63434,0.23487,0.324323], ARRAY[ 0.63434,0.23487,0.324323]) as dot_pod)/((select public.vector_norm(ARRAY[ 0.63434,0.23487,0.324323]) as norm1) * (select public.vector_norm(ARRAY[ 0.63434,0.23487,0.324323]) as norm2))) AS similarity_value) 
    END;
    $BODY$;
    
    ALTER FUNCTION public.cosine_similarity(double precision[], double precision[])
        OWNER TO postgres;
    
    COMMENT ON FUNCTION public.cosine_similarity(double precision[], double precision[])
        IS 'this function is used to find a cosine similarity between two vector';
    

    【讨论】:

    • +1 为答案。如果您可以简要说明您的方法并提供此功能的一些性能统计信息,它将增加更多价值。
    • 您的函数cosine_similarity 使用一些任意向量而不是其参数。你忘了什么吗?
    • 还有语法错误。我在这里为你修好了:CREATE OR REPLACE FUNCTION public.cosine_similarity(IN vector1 double precision[], IN vector2 double precision[]) RETURNS double precision LANGUAGE 'plpgsql' AS $BODY$ BEGIN RETURN ( select ((select public.dot_product(vector1, vector2) as dot_pod)/((select public.vector_norm(vector1) as norm1) * (select public.vector_norm(vector2) as norm2))) AS similarity_value ); END; $BODY$;
    猜你喜欢
    • 2021-07-15
    • 1970-01-01
    • 2020-04-20
    • 1970-01-01
    • 2020-06-18
    • 2019-10-15
    • 2015-12-17
    • 2020-02-11
    • 2016-08-23
    相关资源
    最近更新 更多