Как получить косинусное расстояние между двумя векторами в postgres? - PullRequest
6 голосов
/ 29 июня 2019

Мне интересно, есть ли способ получить косинусное расстояние двух векторов в 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 для этого, нохотел избежать этого, так как это может быть неэффективно.

1 Ответ

3 голосов
/ 06 июля 2019

О вашем столе

Прежде всего, я считаю, что вы должны изменить тип данных на обычный массив.

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}

Возможно, не тот ответ, который вы ожидали, но учтите это

Как вы уже знаете, вычисление косинуса между векторами включает вычисление величин. Я думаю, что проблема не в алгоритме, а в его реализации; это требует вычисления квадратов и квадратных корней, что дорого для РСУБД.

Теперь поговорим об эффективности; серверный процесс не принимает нагрузку при вызове математических функций. В PostgreSQL математические функции ( смотрите здесь ) запускаются из библиотеки 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 раз, если вы хотите получить разность косинусов между любыми двумя векторами, используя исходные компоненты вектора.

Любая комбинация вышеперечисленного

Заключение

Когда мы стремимся к эффективности, в большинстве случаев нет канонического ответа. Вместо этого у нас есть компромиссы, которые мы должны рассмотреть и оценить. Это всегда зависит от конечной цели, которую мы должны достичь. Базы данных отлично подходят для хранения и извлечения данных; они могут определенно делать другие вещи, но это идет с добавленной стоимостью Если мы можем жить с дополнительными накладными расходами, тогда это нормально в противном случае мы должны рассмотреть альтернативы.

...