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

时间:2019-06-29 10:33:19

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

我想知道是否有一种方法可以获取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函数,但由于它可能效率不高,因此希望避免这种情况。

2 个答案:

答案 0 :(得分:4)

您可以参考我的代码。

--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 :(得分:3)

关于您的餐桌

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

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次。

以上任意组合

结论

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