如何获得postgres中两个向量之间的余弦距离?
How to get cosine distance between two vectors in postgres?
我想知道是否有办法在 postgres 中获取两个向量的余弦距离。
为了存储向量,我使用 CUBE 数据类型。
下面是我的table定义:
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 函数,但我想避免这种情况,因为它可能效率不高。
关于你的table
首先,我认为您应该将数据类型更改为普通数组。
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次量级。
以上任意组合
结论
当我们追求效率的时候,很多时候并没有一个标准的答案。相反,我们必须考虑和评估 trade-offs。它始终取决于我们需要实现的最终目标。数据库非常适合存储和检索数据;他们绝对可以制作其他东西,但这会增加成本。如果我们可以忍受增加的开销,那很好;否则我们必须考虑替代方案。
你可以参考我的代码
--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';
我想知道是否有办法在 postgres 中获取两个向量的余弦距离。 为了存储向量,我使用 CUBE 数据类型。
下面是我的table定义:
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 函数,但我想避免这种情况,因为它可能效率不高。
关于你的table
首先,我认为您应该将数据类型更改为普通数组。
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次量级。
以上任意组合
结论
当我们追求效率的时候,很多时候并没有一个标准的答案。相反,我们必须考虑和评估 trade-offs。它始终取决于我们需要实现的最终目标。数据库非常适合存储和检索数据;他们绝对可以制作其他东西,但这会增加成本。如果我们可以忍受增加的开销,那很好;否则我们必须考虑替代方案。
你可以参考我的代码
--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';