Аннотация

Эта статья родилась из пары лекций, которые я прочитал студентам в рамках курса, посвященного вопросам машинного обучения. Почему именно PostgreSQL? Почему векторы? За последние два года тема языковых моделей стала невероятно популярной, и вместе с этим появилось множество инструментов, доступных даже начинающему инженеру, стремящемуся познакомиться с миром текстового анализа.

Доступность этих технологий открывает безграничные возможности для их применения в самых разных областях: от систем управления знаниями до «копилотов», помогающих более тщательно анализировать анамнез пациентов, или информационных киосков, позволяющих собрать идеальную корзину товаров для пикника.

Вряд ли данная работа может похвастаться полнотой или глубиной, однако, я надеюсь, что она предоставит те самые “хорошие” точки входа, которые позволят, погружаясь в детали, открыть для себя множество новых интересных и полезных тем для исследований и инженерных проектов.

Введение

Представьте, что вы можете искать информацию не просто по ключевым словам, но по смыслу — находить нужные данные даже в огромных массивах, будто подсвечивая их в темноте. Это и есть магия векторного поиска, которая привлекает разработчиков и исследователей. Приложения таких технологий уже охватывают самые разные области: от персональных рекомендаций и анализа текстов до классификации изображений. И теперь, когда языковые модели научились превращать слова и фразы в многомерные векторы, перед нами открываются новые горизонты поиска.

Но если для векторного поиска уже существуют специализированные базы данных, зачем тогда рассматривать PostgreSQL? Потому что PostgreSQL — это не просто база данных, а зрелый и надежный инструмент с богатыми возможностями. Подключение таких расширений, как pgvector, превращает PostgreSQL в настоящий кладезь для векторного поиска, совмещая производительность и гибкость с поддержкой множества типов данных и транзакций. Компании могут использовать PostgreSQL с pgvector как универсальный инструмент для хранения и поиска данных по смыслу, снижая затраты на инфраструктуру и обслуживание.

Эта статья — о том, как адаптировать PostgreSQL для векторного поиска, исследуя возможности и расширения, такие как pgvector, и показывая, как они работают на практике. В результате у вас будет полноценное руководство по тому, как развернуть смысловой поиск на PostgreSQL, обходя стороной дорогие и сложные специализированные решения.

Что общего у вектора и письменного стола?

Векторные представления, или эмбеддинги, представляют собой способ преобразования сложных объектов, таких как текст, изображения или аудиофайлы, в многомерное пространство. В этом пространстве каждый объект описывается набором числовых значений, каждое из которых (или измерение) отражает определённую характеристику объекта. Это позволяет представить объект как точку в пространстве, а близость точек указывает на схожесть объектов по смыслу или характеристикам.

Использование векторных представлений позволяет эффективно находить «близкие» данные и решать задачи семантического поиска, классификации, рекомендаций и многих других.

Примеры векторных представлений

Для текста векторное представление может отражать смысловые аспекты. Например, модель word2vec или BERT обучается находить такие векторы, где слова с похожими значениями имеют близкие вектора. Скажем, слова "король" и "королева" будут находиться рядом в этом пространстве, но также "король" и "принц" могут иметь близкие значения, поскольку относятся к одной тематике.

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

Рассмотрим простой датасет Iris — это один из самых известных наборов данных в машинном обучении и статистике, благодаря его простоте и возможности интуитивного понимания базовых алгоритмов классификации. Этот набор данных был собран и опубликован британским биологом и статистиком Рональдом Фишером в 1936 году. Фишер использовал данные для демонстрации линейного дискриминантного анализа, а мы рассмотрим, как можно на этих данных проиллюстрировать возможности модели k-NN (поиска k ближайших соседей).

Датасет содержит информацию о 150 образцах цветов ириса, которые разделены на три класса:

  • Iris setosa — Ирис щетинистый

  • Iris versicolor — Ирис разноцветный

  • Iris virginica — Ирис виргинский

Каждый класс содержит ровно 50 элементов.

Каждая запись датасета включает 4 числовых признака, которые представляют измерения различных частей цветка:

  • Длина чашелистика (sepal length) — измеряется в сантиметрах.

  • Ширина чашелистика (sepal width) — измеряется в сантиметрах.

  • Длина лепестка (petal length) — измеряется в сантиметрах.

  • Ширина лепестка (petal width) — измеряется в сантиметрах.

Атрибуты и классы цветков из датасета Iris:

Цветок 1:
  Длина чашелистика: 5.1
  Ширина чашелистика: 3.5
  Длина лепестка: 1.4
  Ширина лепестка: 0.2
  Класс: setosa

Цветок 2:
  Длина чашелистика: 4.9
  Ширина чашелистика: 3.0
  Длина лепестка: 1.4
  Ширина лепестка: 0.2
  Класс: setosa

Цветок 51:
  Длина чашелистика: 7.0
  Ширина чашелистика: 3.2
  Длина лепестка: 4.7
  Ширина лепестка: 1.4
  Класс: versicolor

Цветок 52:
  Длина чашелистика: 6.4
  Ширина чашелистика: 3.2
  Длина лепестка: 4.5
  Ширина лепестка: 1.5
  Класс: versicolor

Цветок 53:
  Длина чашелистика: 6.9
  Ширина чашелистика: 3.1
  Длина лепестка: 4.9
  Ширина лепестка: 1.5
  Класс: versicolor

Если рассматривать замеры, сделанные Рональдом Фишером, как точки на плоскости (или вектора), то можно графически представить распределение классов ирисов на двухмерной плоскости, комбинируя попарно атрибуты из датасета.

Например, на верхнем левом рисунке изображены различные виды ирисов в зависимости от длины и ширины их чашелистиков. Очевидно, что один вид выделяется на фоне остальных, в то время как два других кажутся почти неразличимыми.

Однако, если обратить внимание на правое нижнее изображение, где виды ирисов представлены на плоскости в виде двухмерного графика по значениям длины и ширины их лепестков, становится заметно, что все три класса имеют достаточно четкие различия.

Более того, можно использовать метод уменьшения размерности, такой как PCA (Principal Component Analysis), чтобы преобразовать 4-мерные векторы, представляющие линейные размеры цветков, в 2-мерные, содержащие всего две ключевые характеристики, которые однако позволяют различать виды. Это можно осуществить следующим образом.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA

# Загрузим датасет Iris
iris = load_iris()
X = iris.data
y = iris.target
target_names = iris.target_names

# Выполним снижение размерности методом PCA до 2 компонентов
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

# Построим график для всех трех классов ирисов
plt.figure(figsize=(10, 8))
colors = ['navy', 'turquoise', 'darkorange']
for i, color, target_name in zip(range(3), colors, target_names):
    plt.scatter(X_pca[y == i, 0], X_pca[y == i, 1], alpha=0.7, color=color, label=target_name)

plt.xlabel('Первая главная компонента')
plt.ylabel('Вторая главная компонента')
plt.title('PCA для датасета Iris')
plt.legend()
plt.grid(True)
plt.show()

В результате получим вот такую красивую картинку.

Очевидно, что при уменьшении количества измерений часть информации всё же была утрачена, и два вида ирисов уже не так легко различить, как это было до компрессии. Поэтому применять методы снижения размерности следует осторожно, учитывая особенности исследуемых данных.

Однако, возвращаясь к нашим ирисам, если мы встретим цветок и измерим его длину чашелистика, ширину чашелистика, длину лепестка и ширину лепестка, то сможем попытаться определить вид этого цветка, используя достаточно простой метод.

Коротко о k-NN

Алгоритм k-ближайших соседей (k-Nearest Neighbors, k-NN) — это один из самых простых и интуитивных алгоритмов машинного обучения, который используется как для классификации, так и для регрессии. Он относится к категории ленивого обучения, поскольку не требует предварительного обучения модели — все вычисления происходят во время запроса. Несмотря на свою простоту, k-NN часто показывает высокую точность на различных задачах, особенно в случаях, когда структура данных позволяет выявить группы схожих точек.

Идея k-NN основана на предположении, что объекты, находящиеся близко друг к другу в многомерном пространстве, имеют схожие характеристики:

  • для любого нового объекта алгоритм k-NN находит k ближайших объектов из обучающей выборки и использует их метки для предсказания. Алгоритм рассчитывает расстояние между точками, чтобы определить "близость".

  • в зависимости от задачи (классификация или регрессия) ближайшие k объектов "голосуют" за итоговый результат. При классификации выбирается класс, за который проголосовало большинство, а при регрессии — усредняется значение целевой переменной.

Единственное, что осталось упомянуть, это расстояние, точнее, что подразумевают под этим термином. На самом деле тут тоже все просто - обычно используется тривиальное евклидово расстояние, но выбор тоже есть:

  • L2 Distance (евклидово расстояние): этот метод вычисляет расстояние между векторами a и b как длину вектора a - b. Это расстояние часто используется в задачах, где важно учитывать фактическое расстояние между объектами в пространстве, и оно чувствительно к масштабу данных.

  • Cosine Similarity (косинусное сходство): этот метод измеряет угол θ между векторами a и b. Косинус угла используется для определения схожести направлений двух векторов. Чем ближе угол к нулю (то есть чем ближе косинус к 1), тем больше схожи направления векторов. Косинусное сходство часто применяется, когда важнее направление векторов, чем их длина (например, при сравнении текстов).

  • Inner Product (скалярное произведение): этот метод измеряет степень схожести векторов путём вычисления их скалярного произведения a⋅b. Векторное произведение используется, когда нужно учитывать как направление, так и длину векторов, так как оно будет больше, когда оба вектора имеют одинаковое направление и значительную длину. Этот метод находит применение в рекомендательных системах и других задачах, где нужно усилить вклад крупных значений.

Так вот, возвращаясь к нашему датасету ирисов, можно применить метод k-NN для определения вида найденного нами цветка. Выглядеть это будет примерно так.

Предположим, мы нашли цветок и, выполнив измерения, получили следующий вектор значений

[5.4 2.2 4.0 1.7]

Осталось только открыть консоль и написать небольшой скрипт на python

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score

# Загрузим датасет Iris
iris = load_iris()
X = iris.data
y = iris.target

# Разделим на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Пример для k=5
knn = KNeighborsClassifier(n_neighbors=5, metric='euclidean')
knn.fit(X_train, y_train)

y_pred = knn.predict(X_test)
print(f"Точность для k=5: {accuracy_score(y_test, y_pred)}")

# Новый вектор с измерениями
new_sample = [[5.4, 2.2, 4.0, 1.7]]

# Определение вида ириса
predicted_class = knn.predict(new_sample)

# Вывод названия класса
predicted_name = iris.target_names[predicted_class[0]]
print(f"Вы нашли ирис: {predicted_name}")

В результате вычислений получим

Точность для k=5: 1.0
Вы нашли ирис: versicolor

На самом деле, применяя такой простой метод как k-NN, можно решать весь интересные и весьма прикладные задачи.

Задачи, решаемые алгоритмом k-NN

И так, подводя небольшой итог, алгоритм ближайших соседей, или k-Nearest Neighbors (k-NN), — это такой способ решения задач, когда нужно быстро находить похожие на заданный запрос объекты. Его можно встретить в самых разных областях, начиная от классификации и регрессии, до рекомендательных систем, медицинской диагностики и анализа текстов.

Например, в рекомендациях, будь то фильмы на wink или плейлисты на Яндекс.Музыка, k-NN помогает "понять", что понравится пользователю, ориентируясь на то, что он уже смотрел или слушал. Алгоритм берет «вектор предпочтений» человека, находит похожие на него элементы и предлагает что-то из похожего, то есть из того, что находится "рядом" в этом векторном пространстве интересов.

В обработке естественного языка (NLP) этот алгоритм тоже весьма полезен. В случае чат-ботов и голосовых помощников, k-NN может помочь выбрать наиболее подходящий ответ на запрос пользователя. Берется текст запроса, преобразуется в вектор, и затем ищутся похожие по смыслу фразы из базы ответов. В итоге алгоритм находит ответы, близкие к запросу пользователя, даже если они формулированы иначе.

В медицинской диагностике, скажем, с помощью анализа снимков, k-NN тоже помогает находить "соседей". Например, изображения МРТ и рентгеновские снимки можно представить в виде векторов, и алгоритм уже ищет схожие с ними снимки с известными диагнозами. Это особенно полезно для врачей, когда нужно найти похожие случаи и сделать точный вывод.

В больших компаниях k-NN можно использовать для поиска нужной информации. Вбиваешь запрос на естественном языке, и алгоритм ищет документы, содержащие информацию, близкую по смыслу. Это позволяет быстро находить нужные инструкции и документы, ускоряя работу.

Так что k-NN — это своего рода универсальный "поисковик по соседям". Этот подход работает с разными типами данных и задачами, от рекомендаций до анализа текстов и изображений, позволяя находить объекты, похожие на запрос, и находить их быстро.

Зазеркалье данных в PostgreSQL

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

Например, мы можем заниматься разработкой системы управления знаний (СУЗ), похожей на такую.

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

Как это можно гипотетически реализовать? Приблизительно процесс можно представить вот так.

Верхнеуровнево такая система может выглядеть, например, так.

Как мы уже упоминали в предыдущей статье, PostgreSQL предоставляет достаточно неплохие возможности полнотекстового поиска и возможности ранжирования найденных записей (k-NN) на основе определенных эвристик. Однако этого оказывается недостаточно, когда требуется учитывать контекст содержимого документа на уровне смыслов. И тут нам может помочь, например, широко используемое на текущий момент расширение pgvector, которое добавляет поддержку операций с векторами и поиска по их сходству. Оно позволяет хранить, индексировать и выполнять запросы по векторным данным непосредственно в базе данных PostgreSQL, что особенно полезно в задачах, связанных с машинным обучением и обработкой естественного языка.

Что же именно мы получим, установив это расширение в свою инсталляцию PostgreSQL?

  • Хранение векторов: pgvector вводит новый тип данных vector, предназначенный для хранения числовых векторов размерностью до 2000 измерений. Он может использоваться для хранения векторных представлений данных (например, текстов, изображений и аудио), которые предварительно преобразованы в числовой формат с помощью эмбеддингов или других моделей.

  • Поиск ближайших соседей: pgvector предоставляет эффективные алгоритмы для поиска ближайших соседей по различным метрикам расстояния, таким как L2 (евклидово расстояние), косинусное расстояние и скалярное произведение. Это позволяет выполнять семантический поиск и рекомендации на основе сходства данных.

  • Индексация: для ускорения поиска pgvector поддерживает создание индексов типов HNSW и IVFFlat, что обеспечивает быстрый доступ к векторным данным при выполнении запросов.

  • Интеграция с SQL: расширение интегрируется с существующими возможностями PostgreSQL, позволяя комбинировать векторные операции с традиционными SQL-запросами, включая фильтрацию, агрегацию и объединение данных.

И так, что же нам потребуется сделать, чтобы реализовать описанный выше пользовательский опыт взаимодействия для нашего приложения СУЗ.

  • Подготовить векторы эмбеддингов, которые отражают основные характеристики объектов (в нашем случае текстов).

  • Загрузить этих векторы в PostgreSQL.

  • Создать индексы для быстрого и точного поиска.

  • Выполнить поисковые запросы по нашим документам, которые вернут релевантный результат.

  • Отправить полученные данные поисковой выдачи на обработку системе Нейрошлюз (LLM).

  • Полученные от Нейрошлюза результаты обработки предоставить пользователю в виде ответа системы.

Из этих перечисленных шагов мы рассмотрим подробно первые четыре.

Как подготовить эмбеддинги?

Вообще, конечно, лучше начать с вопроса, что такое эмбеддинг? На самом деле это просто buzzword, обозначающее векторное представление какого-либо объекта, построенное специализированной моделью, в отличие от, например, векторного представления в нашем датасете ирисов, которое мы получили выполнив банальные измерения цветка линейкой.

Так все-таки, как же подготовить эти самые эмбеддинги? Если отвечать на этот вопрос дилетантски, что мы и сделаем в контексте этой статьи, то можно просто взять одну из моделек отсюда и просто пропустить через нее наши тексты. В контексте нашего приложения СУЗ этот шаг будет соответствовать обращению бэкенда к сервису Нейрошлюз для преобразования данных предиката поиска или вставляемых в БД данных в эмбеддинг. В целом, механика этого преобразования будет выглядеть примерно так.

import matplotlib.pyplot as plt
import seaborn as sns
from sentence_transformers import SentenceTransformer, util

# Инициализация модели
model = SentenceTransformer('sergeyzh/LaBSE-ru-turbo')

# Список текстов статей на тему машинного обучения
sentences = [
    "Машинное обучение — это область компьютерных наук, изучающая алгоритмы, которые позволяют компьютерам находить закономерности в данных и принимать решения без явного программирования. Существует несколько видов машинного обучения, включая обучение с учителем, обучение без учителя и обучение с подкреплением. В последние годы машинное обучение активно используется в таких областях, как медицинская диагностика, прогнозирование финансов и компьютерное зрение.",
    "В машинном обучении существует множество алгоритмов, каждый из которых подходит для решения различных задач. Одними из самых популярных алгоритмов являются линейная регрессия, деревья решений, случайный лес и градиентный бустинг. Линейная регрессия используется для задач прогнозирования, тогда как случайный лес и градиентный бустинг являются эффективными методами для классификации и регрессии.",
    "Глубокое обучение — это подмножество машинного обучения, которое фокусируется на нейронных сетях. Современные модели глубокого обучения включают сверточные нейронные сети для анализа изображений и рекуррентные нейронные сети для обработки последовательностей, таких как текст или временные ряды. Глубокое обучение нашло применение в таких областях, как автоматическое распознавание лиц, обработка естественного языка и автономное вождение.",
    "Обработка естественного языка — это область искусственного интеллекта, которая позволяет компьютерам понимать и генерировать текст на естественном языке. Среди ключевых задач NLP выделяют распознавание именованных сущностей, анализ тональности текста, машинный перевод и чат-боты. Современные методы NLP включают трансформеры, такие как BERT и GPT, которые позволяют улучшить понимание текстов и создавать семантически осмысленные эмбеддинги.",
    "Рекомендательные системы — это системы, которые предсказывают предпочтения пользователей и помогают им находить интересный контент. Они используются на таких платформах, как YouTube, Netflix и Amazon. Основные подходы к созданию рекомендательных систем включают коллаборативную фильтрацию, контентные рекомендации и гибридные модели. В последние годы для улучшения точности рекомендаций активно применяются нейронные сети и глубокое обучение.",
    "Машинное обучение активно используется в медицине и здравоохранении для диагностики заболеваний, прогнозирования исходов лечения и анализа медицинских изображений. Например, алгоритмы могут помочь в ранней диагностике рака и других тяжелых заболеваний. В основе большинства таких систем лежат модели глубокого обучения, которые могут анализировать сложные структуры в медицинских изображениях и данные о пациентах.",
    "Классификация и регрессия — это два основных типа задач в машинном обучении. Задачи классификации включают определение класса объекта, например, 'спам' или 'не спам' в электронной почте. Задачи регрессии направлены на предсказание численного значения, такого как цена квартиры или температура. Алгоритмы, применяемые для этих задач, включают деревья решений, случайные леса и нейронные сети.",
    "С ростом применения машинного обучения возникают вопросы этики и безопасности. Машинное обучение может быть использовано в противозаконных целях или привести к дискриминации, если алгоритмы неправильно обучены. Компании должны уделять особое внимание вопросам этики, тестированию алгоритмов и обеспечению их справедливости и прозрачности. Этические аспекты особенно важны в таких областях, как финансы и здравоохранение.",
    "Компьютерное зрение — это область, которая позволяет компьютерам 'видеть' и анализировать изображения и видео. Машинное обучение и глубокое обучение активно применяются в компьютерном зрении для задач, таких как распознавание объектов, обнаружение аномалий и автономное вождение. Сверточные нейронные сети (CNN) — это популярные модели для задач компьютерного зрения, которые позволяют анализировать пиксельные данные и классифицировать объекты.",
    "Обучение с подкреплением — это вид машинного обучения, в котором агент взаимодействует со средой и учится достигать целей через получение вознаграждений за свои действия. Оно используется в таких областях, как робототехника, игры и автономные системы. Одним из известных примеров является алгоритм AlphaGo, который научился играть в го на уровне чемпиона, используя обучение с подкреплением."
]

# Вычисление эмбеддингов
embeddings = model.encode(sentences)
# Посмотрим на один из полученных векторов
print(embeddings[0])
# А также узнаем его размерность, чтобы быть уверенными, что мы не превысим лимит pgvector
print(embeddings.shape[1])

# Расчет матрицы схожести
similarity_matrix = util.dot_score(embeddings, embeddings).cpu().numpy()

# Построение тепловой карты
plt.figure(figsize=(10, 8))
sns.heatmap(similarity_matrix, annot=True, cmap='viridis', xticklabels=range(1, 11), yticklabels=range(1, 11))
plt.title("Матрица схожести текстов статей")
plt.xlabel("Тексты статей")
plt.ylabel("Тексты статей")
plt.show()
Вектор: [-6.10602135e-03 -6.60162885e-03 -1.54547906e-02 -7.09088054e-03 ... ]
Размерность: 768

Если посмотреть на матрицу схожести, вычисленную на основе скалярного произведения эмбеддингов, можно заметить, что, например, документы 1, 3, 6, 8, 9 достаточно сильно схожи. А документы 4, 5, 8 не так сильно схожи. Однако, можно заметить, что все документы менее или более релевантны в совокупности, и это обусловлено наличием общей темы, которая присутствует в каждом тексте в том или ином виде.

Как полученные эмбеддинги обрабатывать в БД?

Для того чтобы сохранить полученные нами эмбеддинги, а также эффективно с ними работать в PostgreSQL, потребуется сделать ряд манипуляций со схемой БД, включая установку расширения pgvector.

Установим расширения pgvector

Чтобы установить расширение, лучше конечно же обратиться к соответствующему разделу официальной документации. В самом простом случае достаточно просто выполнить следующий скрипт в psql

CREATE EXTENSION vector;

Создадим схему базы данных

Теперь создадим таблицу documents, в которой будем хранить документы нашей системы управления знаниями:

  • id — уникальный идентификатор документа.

  • content — оригинальный текст документа.

  • embedding — векторное представление текста (эмбеддинг).

Для этого выполним SQL-запрос:

CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(768)  -- указываем размерность, например, 768 для LaBSE-ru-turbo
);

Примечание: укажите фактическую размерность эмбеддингов, полученную от вашей модели. Например, для модели LaBSE-ru-turbo 768, для других моделей может быть другое значение.

Сохраним все в БД

Осталось только сохранить текст и его векторное представление, полученное от модели в нашу БД.

from sentence_transformers import SentenceTransformer
import psycopg2

# Инициализация модели для вычисления эмбеддингов
model = SentenceTransformer('sergeyzh/LaBSE-ru-turbo')

# Тексты статей
texts = [
    "Машинное обучение — это область компьютерных наук, изучающая алгоритмы...",
    "В машинном обучении существует множество алгоритмов, каждый из которых...",
    "Глубокое обучение — это подмножество машинного обучения, которое фокусируется...",
    "Обработка естественного языка — это область искусственного интеллекта...",
    "Рекомендательные системы — это системы, которые предсказывают предпочтения...",
    "Машинное обучение активно используется в медицине и здравоохранении для диагностики...",
    "Классификация и регрессия — это два основных типа задач в машинном обучении...",
    "С ростом применения машинного обучения возникают вопросы этики и безопасности...",
    "Компьютерное зрение — это область, которая позволяет компьютерам 'видеть'...",
    "Обучение с подкреплением — это вид машинного обучения, в котором агент взаимодействует..."
]

# Вычисление эмбеддингов
embeddings = model.encode(texts)

# Подключение к базе данных PostgreSQL
conn = psycopg2.connect(
    dbname="postgres",
    user="postgres",
    password="postgres",
    host="localhost",
    port="5432"
)
cursor = conn.cursor()

# Сохранение текстов и эмбеддингов в базу данных
for text, embedding in zip(texts, embeddings):
    cursor.execute(
        "INSERT INTO documents (content, embedding) VALUES (%s, %s)",
        (text, embedding.tolist())  # Преобразуем эмбеддинг в формате numpy.ndarray в обычный массив
    )

# Подтверждение и закрытие соединения
conn.commit()
cursor.close()
conn.close()

Индексируем наши эмбеддинги

Осталось выполнить последний шаг, который позволит нам в дальнейшем эффективно искать похожие запросу записи в нашей системе. Формально это выглядит просто.

CREATE INDEX idx_embedding_hnsw ON documents USING hnsw (embedding);

Или так.

CREATE INDEX idx_embedding_ivfflat ON documents USING ivfflat (embedding);

Оба эти выражения создают индекс на колонке, содержащей эмбеддинги, вычисленные для наших текстов. Различаются лишь тип индекса. Расширение pgvector помимо всего прочего добавляет два типа индексов, которые можно использовать для индексации эмбеддингов и последующего поиска записей.

  • HNSW (Hierarchical Navigable Small World):

    • Основан на графах иерархии и подходит для быстрого и точного поиска ближайших соседей.

    • Эффективен для больших объемов данных, но требует больше памяти и времени на построение.

    • Поддерживает метрики vector_l2_ops (евклидово расстояние), vector_ip_ops (скалярное произведение) и vector_cosine_ops (косинусное расстояние).

  • IVFFlat (Inverted File with Flat Quantization):

    • Разбивает пространство на кластеры, рассматривая только ближайшие для оптимизации поиска.

    • Быстро строится и экономит память, но может быть менее точным.

    • Также поддерживает vector_l2_ops, vector_ip_ops и vector_cosine_ops.

Несмотря на то, что выглядит все это достаточно простенько, я бы хотел остановиться на этом этапе и рассмотреть его чуть более детально. Особенно интересует наиболее часто используемый индекс HNSW.

Алгоритм HNSW

Алгоритм HNSW (Hierarchical navigable small world), который используется для построения индекса с аналогичным названием, — это метод поиска ближайших соседей, который использует иерархическую графовую структуру, позволяющую быстро находить похожие объекты в большом наборе данных. Благодаря "приближению" и "отдалению" (zoom in и zoom out) между уровнями графа, HNSW эффективно сузит поиск, соединяя только наиболее похожие объекты и сокращая вычислительные затраты.

Основная идея HNSW похожа на то, как мы ищем место на электронной карте, например, в Yandex Maps, используя возможности zoom in(приближения) и zoom out (отдаления).

Представьте, что вы хотите найти легендарную Рюмку, все, что вам известно, что она находится где-то в Твери, в центральном районе, и это не то бизнес-центр, не то ресторан, не то гостиница. Если что, выглядит она вот так ;)

Если вы рассматриваете карту Восточной Сибири и увеличили масштаб до максимума, то поиски Рюмки могут стать очень неудобными. Прокручивать карту в масштабе 1:1 куда-то на северо-запад — не самое увлекательное занятие.

Однако, если подняться примерно на высоту геостационарной орбиты, то можно легко перенестись в Центральную Россию. Затем, немного приблизившись, вы окажетесь в Тверской области. Оттуда можно переместиться на точку над Тверью, снова приблизиться, рассмотреть Центральный район города и найти метки бизнес-центров, среди которых, наконец, обнаружить Рюмку.

Алгоритм HNSW работает так же: он начинает поиск на высоком уровне, где данных меньше, но они дают общее направление. Когда алгоритм "определяет" нужную область, он переходит на следующий, более детализированный уровень (zoom in), где находятся объекты, похожие точнее. Так, уровень за уровнем, он приближается к самому подходящему объекту, словно "находит Рюмку" на карте, используя "умное приближение".

Эта система "приближения" и "отдаления" помогает быстрее и точнее находить нужный объект, не перебирая все подряд.

HNSW основывается на принципах, схожих с принципами, используемыми в списках с пропусками (skip lists) и сетях малых миров (small-world network). Этот подход включает многоуровневую структуру графа, где верхние уровни имеют более редкие связи, в то время как нижние уровни состоят из более плотных областей. Такое иерархическое расположение позволяет эффективно перемещаться и выполнять поиск близости в пространстве данных, обеспечивая более быстрый доступ к релевантной информации.

Давайте рассмотрим, как происходит построение такого графа. В структуре Hierarchical Navigable Small World (HNSW) узлы добавляются поочередно. Каждому узлу случайным образом присваивается целочисленный параметр l, определяющий максимальный уровень, на котором узел может находиться в графе. Например, если l равно 1, узел будет присутствовать на уровнях 0 и 1. Определение l для каждого узла следует экспоненциально убывающему распределению вероятностей, нормированному с помощью ненулевого множителя mL. Установка mL в 0 ограничивает узлы одним уровнем, что приводит к неоптимальной сложности поиска в HNSW. Обычно большинство значений l равны 0, поэтому большинство узлов находятся только на нижнем уровне. Однако более высокие значения mL увеличивают вероятность размещения узлов на верхних уровнях, что потенциально повышает эффективность структуры.

Влияние параметра mL на распределение узлов по уровням HNSW приблизительно можно представить так.

Юрий Малков описывает следующим образом выбор значения mL

Чтобы достичь оптимальной производительности HNSW, необходимо минимизировать пересечение соседей на разных уровнях (т.е. процент соседей элемента, которые также принадлежат другим уровням). Для уменьшения этого пересечения следует снизить значение параметра mL. Однако уменьшение mL приводит к увеличению среднего числа переходов во время жадного поиска на каждом уровне, что негативно сказывается на производительности. Это указывает на существование оптимального значения для параметра mL. Простым выбором оптимального mL является 1/ln(M), что соответствует параметру p=1/M в списках с пропусками, обеспечивая среднее перекрытие одного элемента между уровнями.

Вставка узла в структуру HNSW осуществляется в два этапа после присвоения узлу значения уровня l:

  • Первый этап:

    • Алгоритм начинает поиск с верхнего уровня графа, стремясь найти ближайший узел к новому.

    • Найденный узел используется как точка входа для следующего, более низкого уровня.

    • Этот процесс продолжается до достижения уровня l, на котором будет добавлен новый узел.

  • Второй этап:

    • На уровне l алгоритм добавляет новый узел, повторяя процесс поиска, но с изменением: вместо одного ближайшего соседа ищутся efConstruction ближайших соседей (гиперпараметр).

    • Из этих efConstruction соседей выбираются M узлов, с которыми устанавливаются связи из нового узла.

    • Затем алгоритм спускается на уровень ниже, где каждый из выбранных efConstruction узлов служит точкой входа для дальнейшего поиска и вставки.

    • Процесс завершается, когда новый узел и его связи добавлены на самый нижний уровень, уровень 0.

Визуально процесс добавления нового узла в HNSW можно представить так.

Поиск начинается с самого верхнего уровня и спускается вниз, на каждом уровне итеративно определяя одного ближайшего кандидата (ef = 1) из окрестности точки входа данного уровня, этот найденный узел используется в качестве точки входа после перехода на следующий уровень вниз. Этот процесс продолжается до тех пор, пока не будет достигнут нулевой уровень (l = 0). На самом нижнем уровне, начиная с точки входа, процесс поиска просматривает окрестности вершин, по которым он приближается к запросу, и формирует ef = efSearch кандидатов, которые используются для оценки сходимости метода, а также из которых будут отобраны k ближайших соседей.

  • Инициализация поиска:

    • Поиск начинается с верхнего уровня иерархии графа.

    • В качестве начальной точки (entry point) выбирается узел, который был добавлен последним на этом уровне.

  • Поиск на верхних уровнях:

    • На каждом уровне, начиная с верхнего, алгоритм выполняет жадный поиск ближайшего соседа к вектору запроса.

    • От текущего узла алгоритм переходит к соседям, выбирая каждый раз узел, минимизирующий расстояние до запроса.

    • Этот процесс продолжается до тех пор, пока не будет достигнут уровень 0.

  • Поиск на нулевом уровне (базовый уровень):

    • На уровне 0 алгоритм использует параметр ef = efSearch, определяющий количество кандидатов для поиска.

    • Инициализируется список кандидатов ближайших соседей, содержащий начальную точку и ее соседей.

    • Алгоритм итеративно обновляет этот список, добавляя новых кандидатов и удаляя менее подходящих, основываясь на расстоянии до вектора запроса.

    • Процесс продолжается до тех пор, пока не будет обработано ef кандидатов или не будет достигнута сходимость (отсутствие улучшений).

  • Формирование результата:

    • После завершения поиска на уровне 0 из списка кандидатов выбираются k ближайших соседей.

    • Эти k соседей возвращаются в качестве результата поиска.

Как при построении HNSW, так и при поиске мы использовали гиперпараметры, влияющие на качество поиска в HNSW:

  • M(max, max0):

    • Определяет максимальное количество соседей для каждой точки на каждом уровне.

    • Высокие значения M улучшают точность (больше потенциальных связей), но увеличивают время построения и размер графа.

  • efConstruction:

    • Определяет количество ближайших соседей, которые рассматриваются при добавлении нового элемента в граф (влияет на качество построенного индекса).

    • Чем выше efConstruction, тем лучше качество индекса и точность поиска, но время и ресурсы на построение возрастают.

  • efSearch:

    • Используется при выполнении поиска для управления размером списка кандидатов.

    • Высокие значения efSearch повышают точность поиска, так как проверяется больше кандидатов, но увеличивают время выполнения.

Для оценки влияния этих параметров на качество построенного индекса обычно используют такую метрику как полнота (recall), потому что она показывает, сколько из истинных ближайших соседей было найдено. В задаче поиска ближайших соседей (k-NN) для каждого запроса важно найти как можно больше точных соответствий среди k ближайших, поскольку цель — максимально полно покрыть истинное множество соседей.

И, конечно, высокая полнота влияет на ряд показателей производительности, поэтому нужно понимать, где найти баланс. Вот что показывает страница с бенчмарками для HNSW.

Полнота vs. производительность (Запросы в секунду, QPS)

На графике "Полнота vs. QPS" можно увидеть, как увеличение полноты влияет на скорость обработки запросов (QPS). Чем выше полнота, тем меньше запросов в секунду удается обработать. Почему? Чтобы достичь более высокой точности, алгоритму приходится проверять больше данных и проводить более глубокий поиск, что увеличивает нагрузку на систему и замедляет её. Вывод: повышение полноты позволяет точнее находить соседей, но снижает пропускную способность.

Полнота vs. время построения индекса

На графике "Полнота vs. Время построения индекса" мы видим, что на построение индекса требуется больше времени, если мы стремимся к высокой полноте.

Чем выше целевая полнота, тем больше усилий требуется на построение связей в индексе, чтобы обеспечить высокое качество поиска. Это приводит к увеличению времени построения. Индексы с высокой полнотой требуют больше ресурсов на этапе создания, зато могут быть более точными при поиске.

Полнота vs. размер индекса

На графике "Полнота vs. Размер индекса" демонстрируется, как увеличивается объем памяти, занимаемый индексом HNSW, по мере роста полноты.

Чтобы повысить полноту, часто приходится добавлять дополнительные связи между элементами, что увеличивает объем индекса. Однако степень этого увеличения может варьироваться в зависимости от настроек и структуры данных.

Как легко заметить, при настройке индекса HNSW важно найти баланс между полнотой и производительностью. С ростом полноты повышается качество поиска, но это также может привести к увеличению затрат на вычисления, времени построения индекса и потребляемой памяти. Оптимальные параметры зависят от требований к точности поиска и доступных ресурсов.

Отражение обретает форму: плоды нашего поиска по БД

А теперь, господа, пристегнитесь. Сейчас со всей этой хренотенью мы попробуем взлететь.

Собственно, остался последний шажочек - просто выполнить запрос к БД, которая вернет нам k ближайших соседей.

Вспомним, что ранее в таблицу documents мы уже сохранили статьи с эмбеддингами. Теперь, преобразовав текстовый запрос в вектор (query_embedding) попробуем получить список релевантных статей.

SELECT id, content
FROM documents
ORDER BY embedding <-> '[0.1, 0.2, 0.3, ..., 0.128]'::vector  -- Векторный запрос
LIMIT 5;

Здесь [0.1, 0.2, 0.3, ..., 0.128] — это примерное значение query_embedding, которое вам следует заменить реальным вектором запроса.

В результате выполнения этой команды получим.

+----+---------------------------------------------------------------------------------------------------------+
| ID | Content                                                                                                 |
+----+---------------------------------------------------------------------------------------------------------+
| 1  | "Машинное обучение активно используется в медицине для диагностики и прогнозирования заболеваний..."    |
| 3  | "Рекомендательные системы помогают пользователям находить контент на основе их предпочтений..."         |
| 5  | "Обработка естественного языка (NLP) позволяет компьютерам анализировать текст и понимать намерения..." |
| 7  | "Глубокое обучение используется в таких областях, как компьютерное зрение и обработка изображений..."   |
| 9  | "Обучение с подкреплением позволяет агентам взаимодействовать со средой и достигать целей через..."     |
+----+---------------------------------------------------------------------------------------------------------+

Осталось всё это добро интегрировать в наше игрушечное приложение. Сделать это можно примерно так.

import psycopg2
import requests
from sentence_transformers import SentenceTransformer

# Настройки подключения к PostgreSQL
conn = psycopg2.connect(
    dbname="postgres",
    user="postgres",
    password="postgres",
    host="localhost",
    port="5432"
)
cursor = conn.cursor()

# Инициализация модели для преобразования запроса пользователя в эмбеддинг
model = SentenceTransformer('sergeyzh/LaBSE-ru-turbo')

# Пример запроса пользователя
user_query = "Как машинное обучение используется в медицине?"

# Преобразование запроса в вектор (эмбеддинг)
query_embedding = model.encode(user_query).tolist()

# SQL-запрос для поиска релевантных документов
cursor.execute("""
    SELECT id, content
    FROM documents
    ORDER BY embedding <-> %s::vector
    LIMIT 5;
""", (query_embedding,))

# Получение результатов запроса
results = cursor.fetchall()

# Закрываем соединение с БД
cursor.close()
conn.close()

# Настройки Нейрошлюза для получения саммари
neiro_url = "https://ai.rt.ru/summarize"
headers = {"Authorization": "Bearer YOUR_API_KEY"}

# Формируем данные для ответа
summaries = []
for doc_id, content in results:
    # Запрос на саммари в Нейрошлюз
    response = requests.post(neiro_url, json={"text": content}, headers=headers)
    summary = response.json().get("summary", "Нет саммари")

    summaries.append({
        "id": doc_id,
        "summary": summary,
        "content": content
    })

# Выводим результаты для пользователя
response_to_user = {
    "query": user_query,
    "results": [
        {"id": item["id"], "summary": item["summary"], "link": f"https://make.ai.great.again.ru/docs/{item['id']}"}
        for item in summaries
    ]
}

print(response_to_user)

Пара ложек дёгтя

Как и любая другая технология pgvector не без изъяна. Одним из ограничений pgvector является то, что при использовании условий WHERE в запросах с векторными данными могут возникнуть проблемы. Это происходит из-за того, что индексы, созданные с помощью pgvector, оптимизированы для поиска ближайших соседей по векторному полю, но не всегда могут корректно сочетаться с фильтрацией по другим условиям из-за процесса post-filtering. Собственно, что может пойти не так?

Когда PostgreSQL встречает запрос с условием поиска ближайших соседей по вектору и дополнительными условиями WHERE, он анализирует доступные индексы. Если для векторного поля создан индекс pgvector, PostgreSQL использует его для поиска ближайших соседей, так как это оптимизированный способ выполнения данной операции.

Таким образом PostgreSQL выполняет поиск ближайших соседей на основе векторного поля, используя индекс pgvector. Результаты поиска возвращаются без учета дополнительных условий WHERE, так как индекс не поддерживает такие условия.

После того как ближайшие соседи найдены, PostgreSQL применяет дополнительные условия WHERE к результатам поиска, чтобы отфильтровать их. Этот шаг выполняется в оперативной памяти и не использует индекс, что может значительно замедлить выполнение запроса, особенно если итоговый набор данных велик. Более того, так как пост-фильтрация выполняется после поиска ближайших соседей, это может привести к ситуации, когда часть (или все) найденных ближайших соседей отбрасывается из-за условий WHERE. В результате запрос может вернуть менее релевантные данные, чем мог бы при оптимальной фильтрации.

Например, для запроса

SELECT id, content
FROM documents
WHERE category = 'машинное обучение'
ORDER BY embedding <-> '[0.1, 0.2, 0.3, ..., 0.128]'::vector
LIMIT 5;

PostgreSQL сначала выполнит поиск по индексу pgvector и получит ближайших соседей без учета условия WHERE. Затем он отфильтрует результаты по значению category = 'машинное обучение'. Это может привести к тому, что некоторые строки будут исключены уже после поиска, что замедляет запрос и может вернуть менее релевантные результаты.

В простых случаях это можно решить, используя CTE (Common Table Expression), например, так

WITH nearest_neighbors AS (
    SELECT id, content, category
    FROM documents
    ORDER BY embedding <-> '[0.1, 0.2, 0.3, ..., 0.128]'::vector
    LIMIT 10 -- увеличили выборку ближайших соседей
)
SELECT *
FROM nearest_neighbors
WHERE category = 'машинное обучение';

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

Вторая, еще менее приятная особенность заключается в том, что тип данных vector в PostgreSQL добавляется через расширение pgvector. Это означает, что тип vector не является встроенным в сам PostgreSQL, а предоставляется исключительно этим расширением. Такое устройство приводит к ряду потенциальных проблем, особенно в производственных средах.

Когда расширение pgvector удаляется из базы данных PostgreSQL (например, при обновлении версии СУБД или версии самого расширения), все связанные с ним объекты также удаляются, включая:

  • Поля с типом vector: любое поле с типом vector перестает существовать после удаления расширения, что приводит к потере данных в этих полях.

  • Индексы, основанные на типе vector: индексы, которые были созданы для оптимизации векторного поиска, также удаляются вместе с расширением.

Такое поведение связано с тем, что PostgreSQL не поддерживает объекты, типы и функции, определенные в удаленных расширениях. В результате удаление pgvector приводит к полной утрате данных в столбцах типа vector, что может быть крайне нежелательным в производственной среде и потребовать полного пересчета всех вычисленных ранее эмбеддингов на таблице из 100500 квадриллионов строк, а это деньги (compute) и время senior инженеров (снова многа дениг).

Поэтому, прежде чем удалить расширение pgvector,

DROP EXTENSION IF EXISTS vector CASCADE;

позаботьтесь о сохранности своих данных, например, сохранив их в файлик и преобразовав в тип, который не зависит от расширения.

Например, можно экспортировать данные в CSV или другой формат для последующего восстановления.

COPY (SELECT id, content, embedding::text FROM documents) TO '/path/to/backup/documents_backup.csv' WITH CSV;

Если вам нужно временно удалить расширение, можно заранее преобразовать данные из vector в формат, поддерживаемый PostgreSQL по умолчанию, например, в массивы float8[].

ALTER TABLE documents
ALTER COLUMN embedding TYPE float8[]
USING embedding::float8[];

После этого можно удалить расширение без потери данных. Позднее можно восстановить vector-тип, если снова установить pgvector, и преобразовать данные обратно.

Новая надежда

Недавно на конференции PGConf было представлено расширение GANN (Generalized Approximate Nearest Neighbor), которое представляет собой перспективное дополнение к экосистеме PostgreSQL, значительно расширяющее возможности для работы с векторными данными. Это расширение устраняет ключевые ограничения pgvector, позволяя решать более сложные задачи и выполнять гибкую фильтрацию данных при поиске.

Одним из важных преимуществ GANN является поддержка условий в запросах через WHERE, что позволяет точно отбирать данные по дополнительным критериям, таким как права доступа или временные ограничения. В отличие от pgvector, GANN способен корректно обрабатывать такие запросы, избегая ситуаций, когда строки могут не возвращаться из-за наложенных условий. Также среди планов по развитию GANN значится внедрение многоколоночных индексов, что будет полезно для сложных запросов и фильтрации по нескольким параметрам одновременно — функция, отсутствующая в pgvector.

Гибкость GANN также проявляется в возможности подключения новых алгоритмов поиска. Используя единый метод доступа, GANN позволяет разработчикам добавлять собственные алгоритмы, оптимизируя их под конкретные потребности приложений. Это делает GANN более универсальным инструментом, в отличие от pgvector, который ограничен двумя алгоритмами — HNSW и IVFFLAT — и не планирует их расширение.

Тем не менее, как относительно молодой проект, GANN еще нуждается в доработках и оптимизациях. Но даже на текущей стадии он открывает возможности, которые делают его привлекательным выбором для задач, требующих более высокой точности и гибкости при работе с векторными данными.

GANN является шагом вперед в развитии PostgreSQL как платформы для векторных данных, давая пользователям инструменты для создания более адаптируемых и производительных решений.

Заключение

“Пойди туда, не знаю куда, найди то, не знаю что” вовсе не сюжет сказки о Федоте, — это фундаментальный мотив как в жизни, так и в обработке данных. PostgreSQL, оставаясь одной из самых надежных и зрелых реляционных СУБД, с векторным поиском расширяет свои границы, превращаясь из классического хранилища данных в систему, способную улавливать глубину и контекст, хранимой в ней информации. На протяжении статьи мы рассмотрели, как векторный поиск, реализованный через расширение pgvector, позволяет PostgreSQL работать не только с формальными запросами, но и с семантикой — с тем, что выходит за пределы ключевых слов и простых фильтров.

Тем не менее, как и любая сложная технология, векторный поиск в PostgreSQL требует осознанного подхода. Хотя возможности расширения открывают перед нами новые горизонты, важно понимать, что PostgreSQL не заменяет специализированные векторные базы данных — он обогащает уже знакомый инструмент, добавляя ему гибкости. Векторный поиск может значительно усилить возможности СУБД в контексте обработки больших объемов данных и поиска по смыслу, но для достижения этого требуются настройка и компромиссы, такие как выбор правильных индексов и оптимальных параметров для обеспечения баланса между быстродействием и точностью.

Что почитать?

Efficient and Accurate Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs
Авторы: Ю. А. Малков, Д. А. Яшунин
Источник: arXiv, 2016

В статье представлен алгоритм HNSW (Hierarchical Navigable Small World), высокоэффективный метод для приближенного поиска ближайших соседей. HNSW создает многоуровневый граф, организующий точки данных на основе их "близости", что обеспечивает быстрый и масштабируемый поиск. Этот подход стал популярным в векторных базах данных, позволяя обрабатывать большие объемы данных в пространствах высокой размерности.

Navigation in a small world
Авторы: Джон Клейнберг
Источник: Nature, 2000

В этой статье Джон Клейнберг исследует феномен "малого мира" — концепцию, согласно которой любые два человека на Земле связаны через короткие цепочки общих знакомых. Автор анализирует механизмы, позволяющие людям эффективно находить кратчайшие пути в социальных сетях, несмотря на их огромный масштаб и сложность. Исследование проливает свет на фундаментальные свойства сетевых структур и их влияние на процессы коммуникации и распространения информации.

Зачем мне векторная база данных, если уже есть PostgreSQL?
Докладчик: Владлен Пополитов
Мероприятие: PGConf.Russia 2024

Доклад Владлена Пополитова на PGConf.Russia 2024 посвящен возможностям векторного поиска в PostgreSQL. Он рассматривает использование расширений PostgreSQL, таких как pgvector и новейшее GANN, для эффективного поиска по векторным данным, что позволяет PostgreSQL поддерживать современные приложения в области анализа данных и искусственного интеллекта, работающие с высокоразмерными данными.

Word2Vec: Подробное руководство с примерами кода

В этой статье подробно рассматривается работа модели Word2Vec, ее архитектуры и практическое применение с примерами на Python.

Word2Vec: как работать с векторными представлениями слов

Статья объясняет алгоритмы Word2Vec и их применение в системах обработки естественного языка, таких как Amazon Alexa и Google Translate.

A detailed review on word embedding techniques with emphasis on word2vec
Авторы: Joshua Johnson, M. Ramakrishna Murty, I. Navakanth
Источник: Multimed Tools Appl 83, 37979–38007 (2024)

Обзор различных техник векторного представления слов с акцентом на Word2Vec, включая их преимущества и недостатки.

На грани ИИ: пример поиска и обработки векторов в PostgreSQL + pgvector

В этой статье рассматривается практическое применение расширения pgvector для хранения и поиска векторов в PostgreSQL. Автор демонстрирует, как использовать pgvector для кластеризации данных и поиска по векторному сходству.

Прощайте, базы данных, да здравствуют векторные базы данных

В статье рассматриваются преимущества векторных баз данных перед традиционными реляционными СУБД, а также их применение в современных приложениях.

Deep Learning for Search
Автор: Томас Дюран.

Книга рассматривает применение глубокого обучения для улучшения поисковых систем, включая использование векторных представлений и моделей, подобных word2vec.

Комментарии (0)