Использование pgvector
Обзор
Расширение pgvector реализует поиск по сходству векторов для PostgreSQL, что предполагает использование эмбеддинга (embedding). Эмбеддинг — это метод, который отображает сложные многомерные данные, включая текст и изображения, в векторном пространстве числовых векторов. Объекты с похожим смыслом располагаются рядом друг с другом в этом пространстве.
Такой поиск полезен в следующих сценариях:
-
Семантический поиск. Позволяет понять суть запроса пользователя, даже если слова не совпадают с ключевыми словами в документе.
-
Системы RAG (Retrieval Augmented Generation). Помогает языковым моделям ИИ искать релевантную информацию в базе знаний компании или в интернете для точных ответов.
-
Рекомендательные системы. Онлайн-кинотеатры, маркетплейсы и другие подобные системы используют поиск по сходству векторов, чтобы предлагать фильмы, музыку и товары на основе похожих интересов пользователей и свойств самих продуктов.
-
Мультимедийный поиск. Дает возможность загрузить изображение или ввести текстовый запрос и найти фотографии или видео, ему соответствующие.
-
Классификация. Помогает быстро определять тематику и категорию документов, а также обнаруживать дубликаты текстов и изображений.
Пакет, требуемый для установки pgvector, поставляется с ADP. Чтобы использовать pgvector, выполните команду CREATE EXTENSION в той базе данных, в которую необходимо поставить расширение:
CREATE EXTENSION vector;
|
ПРИМЕЧАНИЕ
Если расширение pgvector создано в базе данных template1, используемой в качестве шаблона БД по умолчанию, во всех вновь создаваемых базах данных будет установлено это расширение.
|
ADP использует версию
0.8.0
расширения pgvector. Чтобы это проверить, выполните следующий запрос:
SELECT extversion FROM pg_extension
WHERE extname = 'vector';
extversion ------------ 0.8.0
Расширение pgvector позволяет хранить векторы вместе с другими данными. Оно поддерживает:
-
точный и приблизительный поиск ближайших соседей;
-
векторы одинарной точности, половинной точности, двоичные и разреженные векторы;
-
расчет метрик, перечисленных в таблице ниже.
Название Синтаксис Расстояние L2 (евклидово)
<->
Скалярное произведение (внутреннее произведение). Имеет отрицательное значение
<#>
Косинусное расстояние
<=>
Расстояние L1 (расстояние между точками, имитирующее путь такси по сетке улиц, или Манхэттенское расстояние)
<+>
Расстояние Хэмминга (бинарные векторы)
<~>
Расстояние Жаккара (бинарные векторы)
<%>
Расширение pgvector поддерживает следующие типы векторов:
-
vector -
halfvec -
bit -
sparsevec
Для того чтобы протестировать функции pgvector, создайте таблицу со столбцом типа vector. Укажите количество измерений вектора в круглых скобках:
CREATE TABLE test (
id serial PRIMARY KEY,
embedding vector(3)
);
Добавьте в таблицу векторные данные:
INSERT INTO test (embedding) VALUES ('[1,2,3]'), ('[4,5,6]'), ('[1,3,8]'), ('[4,4,4]'), ('[3,3,3]'), ('[1,1,3]');
Рассчитайте расстояние L2 между векторами в таблице и вектором [1,1,1] и отсортируйте по этому расстоянию результаты:
SELECT embedding FROM test ORDER BY embedding <-> '[1,1,1]';
embedding ----------- [1,1,3] [1,2,3] [3,3,3] [4,4,4] [4,5,6] [1,3,8]
Из вывода видно, что ближайший вектор — [1,1,3], а наиболее удаленный — [1,3,8].
Рассчитайте расстояние L1 между векторами в таблице и вектором [1,1,1]:
SELECT embedding FROM test ORDER BY embedding <+> '[1,1,1]';
embedding ----------- [1,1,3] [1,2,3] [3,3,3] [1,3,8] [4,4,4] [4,5,6]
Вывод отличается: теперь самый удаленный вектор — [4,5,6].
Индексы
По умолчанию расширение pgvector выполняет точный поиск ближайшего соседа, что обеспечивает высокую точность и достаточно низкую скорость. Чтобы повысить скорость, вы можете добавить индекс, который активирует использование алгоритма приблизительного поиска ближайшего соседа. В отличие от обычных индексов, добавление индекса pgvector приведет к другим результатам запроса.
Поддерживаемые типы индексов:
HNSW
Индекс HNSW создает многослойный граф. Он имеет лучшую производительность запросов, чем IVFFlat, но требует больше времени для построения и использует больше памяти. Этот индекс можно создавать без данных в таблице.
Индекс HNSW поддерживает следующие типы векторов:
-
vector— до 2000 измерений (dimensions); -
halfvec— до 4000 измерений; -
bit— до 64000 измерений; -
sparsevec— до 1000 ненулевых элементов.
Необходимо добавить индекс для каждой функции расстояния, которую вы хотите использовать, и указать соответствующий класс оператора для каждой функции и типа вектора.
| Функция | Vector | Halfvec | Bit | Sparsevec |
|---|---|---|---|---|
Расстояние L2 |
vector_l2_ops |
halfvec_l2_ops |
— |
sparsevec_l2_ops |
Скалярное произведение |
vector_ip_ops |
halfvec_ip_ops |
— |
sparsevec_ip_ops |
Косинусное расстояние |
vector_cosine_ops |
halfvec_cosine_ops |
— |
sparsevec_cosine_ops |
Расстояние L1 |
vector_l1_ops |
halfvec_l1_ops |
— |
sparsevec_l1_ops |
Расстояние Хэмминга |
— |
— |
bit_hamming_ops |
— |
Расстояние Жаккара |
— |
— |
bit_jaccard_ops |
— |
Следующий код создает индекс для функции расстояния L2 и типа vector (имя таблицы — table1, имя столбца — embedding):
CREATE INDEX ON table1 USING hnsw (embedding vector_l2_ops);
Код ниже добавляет индекс для функции расстояния Хэмминга и типа bit:
CREATE INDEX ON table2 USING hnsw (embedding bit_hamming_ops);
IVFFlat
Индекс IVFFlat разбивает векторы на "списки" (list) и ищет в подмножестве "списков" те элементы, которые наиболее близки к запрашиваемому вектору. Этот индекс быстрее создается и использует меньше памяти, чем HNSW, но имеет более низкую производительность запросов. Его следует создавать после заполнения таблицы данными.
Индекс IVFFlat поддерживает следующие типы:
-
vector— до 2000 измерений (dimensions); -
halfvec— до 4000 измерений; -
bit— до 64000 измерений.
Для поддерживаемых типов векторов используйте те же классы операторов, что и для индекса HNSW.
Также можно указать параметр lists, который влияет на производительность. Обозначим количество строк в таблице как rows, тогда рекомендуемое значение:
-
для количества строк до 1 миллиона;
-
для более миллиона строк.
Следующий код создает индекс для функции расстояния L2 и типа vector, а также устанавливает параметр lists равным 100:
CREATE INDEX ON table1 USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
Чтобы определить, сколько списков будет просматриваться в поисках ближайших соседей, укажите параметр probes. Большее значение обеспечивает лучшую полноту поиска, меньшее значение увеличивает скорость. Используйте команду SET, чтобы указать параметр probes:
SET ivfflat.probes = 10;
Примеры
Данные примеры используют эмбеддинги. Чтобы получить их, воспользуйтесь любой моделью для их создания (некоторые из моделей доступны онлайн). В приведенных примерах каждый эмбеддинг имеет 3072 измерения. Для их хранения применяется столбец типа vector(3072).
Расстояние L2
Создайте таблицу со столбцом типа vector:
CREATE TABLE description (
id serial PRIMARY KEY,
title text,
embedding vector(768)
);
Заполните таблицу description данными. В приведенном ниже коде значения поля embedding сокращены, чтобы сохранить читаемость.
INSERT INTO description (title, embedding) VALUES
('A solution for managing PostgreSQL clusters', '[-0.009550615, -0.012897374, ... 0.011087871]'),
('A binary backup management system', '[-0.010036648, 0.011803862, ... 0.036223497]'),
('A separate product designed for simple, convenient, and fast software deployment and exploitation', '[0.020033414, -0.001558028, ... -0.00035061908]'),
('A tool for backup creation', '[0.011411955, -0.0039920947, ... 0.013054479]'),
('Easy cluster installation', '[0.0138596445, 0.0040847682, ... -0.0062671695]'),
('A tool to install and manage PostgreSQL clusters', '[-0.004355611, -0.0049024294, ... 0.004386888]');
Полную версию SQL-операторов можно найти в файле description.sql.
Сравните значения title в парах и отсортируйте их по расстоянию L2:
SELECT
d1.title AS title_d1,
d2.title AS title_d2,
d1.embedding <-> d2.embedding AS distance
FROM description d1
JOIN description d2 ON d1.id < d2.id
ORDER BY distance;
title_d1 | title_d2 | distance ---------------------------------------------+--------------------------------------------------+--------------------- A solution for managing PostgreSQL clusters | A tool to install and manage PostgreSQL clusters | 0.2973072644418864 A binary backup management system | A tool for backup creation | 0.4217937355892653 Easy cluster installation | A tool to install and manage PostgreSQL clusters | 0.4695271407723517 A solution for managing PostgreSQL clusters | Easy cluster installation | 0.4905144093027282 A separate product designed for simple ... | A tool for backup creation | 0.5002226333872557 A tool for backup creation | A tool to install and manage PostgreSQL clusters | 0.5172138587711625 A separate product designed for simple ... | Easy cluster installation | 0.5173604255461222 A solution for managing PostgreSQL clusters | A binary backup management system | 0.5213006656823225 A binary backup management system | A separate product designed for simple... | 0.5278033761319432 A solution for managing PostgreSQL clusters | A tool for backup creation | 0.5297814997533667 A separate product designed for simple ... | A tool to install and manage PostgreSQL clusters | 0.5426847414928297 A binary backup management system | A tool to install and manage PostgreSQL clusters | 0.5449763171711131 A solution for managing PostgreSQL clusters | A separate product designed for simple... | 0.5463234163351685 A binary backup management system | Easy cluster installation | 0.5631321163341745 A tool for backup creation | Easy cluster installation | 0.5684921266921485
Ближайшие по смыслу значения A solution for managing PostgreSQL clusters и A tool to install and manage PostgreSQL clusters.
Скалярное произведение
Создайте новую таблицу и заполните ее данными (значения поля embedding приведены с сокращениями):
CREATE TABLE items (
id serial PRIMARY KEY,
item text,
embedding vector(768)
);
INSERT INTO items (item, embedding) VALUES
('cookies', '[-0.002850077, -0.0040400615, ... 0.0043995334]'),
('apples', '[0.020033414, -0.001558028, ... 0.01091539]'),
('chocolate', '[-0.012762195, 0.023601593, ... -0.010077247]'),
('croissant', '[0.011127311, -0.008577864, ... 0.01457113]'),
('carrot', '[-0.0061358884, 0.027397538, ... 0.008722801]'),
('cheesecake', '[-0.01261848, -0.027980627, ... -0.005194875]'),
('flour', '[-0.013135226, 0.011132385, ... 0.00960297]'),
('bakery', '[0.00017604351, -0.012099541, ... 0.03317081]');
Полную версию SQL-операторов можно найти в файле items.sql.
Вычислите скалярное произведение между вектором, соответствующим значению bakery, и остальными векторами значений. Умножьте его на -1, поскольку <#> возвращает отрицательное скалярное произведение.
SELECT item,
(embedding <#> (SELECT embedding FROM items WHERE item = 'bakery')) * -1 AS inner_product
FROM items
ORDER BY inner_product DESC;
item | inner_product ------------+--------------------- bakery | 0.33495470881462097 croissant | 0.22958263754844666 flour | 0.22580863535404205 cookies | 0.21599330008029938 cheesecake | 0.2063595950603485 chocolate | 0.20056280493736267 carrot | 0.19186848402023315 apples | 0.19073499739170074
В данном примере самым релевантным значением bakery является croissant.