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

vxf3dgd4  于 2023-08-04  发布在  PostgreSQL
关注(0)|答案(3)|浏览(285)

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

8cdiaqws

8cdiaqws1#

关于你的餐桌

首先,我认为你应该把你的数据类型改为普通数组。

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

  • 以上各项的任意组合 *
    结论

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

lokaqttq

lokaqttq2#

你可以参考我的代码。

--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';

字符串

col17t5w

col17t5w3#

在PostgreSQL中,可以使用内置的pg_trgm扩展来计算两个向量之间的余弦相似度,该扩展提供了相似度操作的函数,包括余弦相似度。pg_trgm扩展通常用于涉及文本和向量的相似性计算。
以下是如何使用PostgreSQL和pg_trgm扩展计算两个向量之间的余弦相似度的分步指南:
启用pg_trgm扩展(如果尚未启用):

CREATE EXTENSION IF NOT EXISTS pg_trgm;

字符串
准备数据并创建一个表来存储向量。对于本例,我们假设有一个名为vectors_table的表,其中包含vector_id和vector_values列,其中vector_values包含存储为数组的向量值。

CREATE TABLE vectors_table (
    vector_id SERIAL PRIMARY KEY,
    vector_values double precision[]
);


将矢量数据插入表中。用实际的向量值替换vector_values数组。

INSERT INTO vectors_table (vector_values)
VALUES
    ('{0.5, 0.3, 0.8, 0.1}'),
    ('{0.2, 0.7, 0.4, 0.6}'),
    -- Add more vectors as needed
    ('{0.9, 0.2, 0.1, 0.5}');


使用自定义函数计算两个向量之间的余弦相似度。下面是计算余弦相似度的函数:

CREATE OR REPLACE FUNCTION cosine_similarity(a double precision[], b double precision[])
RETURNS double precision AS $$
DECLARE
    dot_product double precision;
    norm_a double precision;
    norm_b double precision;
BEGIN
    dot_product := 0;
    norm_a := 0;
    norm_b := 0;

    FOR i IN 1..array_length(a, 1) LOOP
        dot_product := dot_product + a[i] * b[i];
        norm_a := norm_a + a[i] * a[i];
        norm_b := norm_b + b[i] * b[i];
    END LOOP;

    norm_a := sqrt(norm_a);
    norm_b := sqrt(norm_b);

    IF norm_a = 0 OR norm_b = 0 THEN
        RETURN 0;
    ELSE
        RETURN dot_product / (norm_a * norm_b);
    END IF;
END;
$$ LANGUAGE plpgsql;


现在你可以使用cosine_similarity函数来计算vectors_table中任意两个向量之间的相似度:

SELECT
    vector_id AS vector1_id,
    v1.vector_values AS vector1,
    vector_id AS vector2_id,
    v2.vector_values AS vector2,
    cosine_similarity(v1.vector_values, v2.vector_values) AS similarity
FROM
    vectors_table v1
CROSS JOIN
    vectors_table v2
WHERE
    v1.vector_id <> v2.vector_id;


此查询将给予一个结果,显示表中所有向量之间的成对余弦相似性,不包括自相似性。
请记住,余弦相似性度量两个向量之间Angular 的余弦,范围从-1到1。值为1表示矢量相同,0表示矢量正交,-1表示它们完全相反。

注意:请相应修改代码。

相关问题