Сравнение различных векторных эмбеддингов
Эта статья была первоначально опубликована в The New Stack и перепечатывается здесь с разрешения.
Чем отличаются векторные вложения, сгенерированные разными нейронными сетями, и как их можно оценить в вашем Jupyter Notebook?
Большие языковые модели (LLM) набирают популярность, и мы сталкиваемся с новой парадигмой языковых приложений, таких как ChatGPT. Векторные базы данных станут ключевой частью стека. Поэтому важно понимать, что такое векторы и почему они так важны.
Этот проект демонстрирует различия в векторных вложениях между моделями и показывает, как использовать несколько коллекций векторных данных в одном Jupyter Notebook. В этой статье мы рассмотрим, что такое векторные вложения, почему они важны и как сравнивать различные векторные вложения в вашем Jupyter Notebook.
Что такое векторные вложения и почему они важны?
Откуда берутся векторные вложения?
Проще говоря, векторные вложения — это числовые представления данных. Они в основном используются для представления неструктурированных данных. Неструктурированные данные — это изображения, видео, аудио, текст, молекулярные изображения и другие виды данных, не имеющие формальной структуры. Векторные вложения генерируются путем пропускания входных данных через предварительно обученную нейронную сеть и взятия выходных данных предпоследнего слоя.
Нейронные сети имеют разные архитектуры и обучаются на разных наборах данных, что делает векторное вложение каждой модели уникальным. Именно поэтому работа с неструктурированными данными и векторными вложениями сложна. Позже мы увидим, как модели с одной и той же базой, дообученные на разных наборах данных, могут давать разные векторные вложения.
Различия в нейронных сетях также означают, что мы должны использовать отдельные модели для обработки разнообразных форм неструктурированных данных и генерации их вложений. Например, нельзя использовать модель sentence transformer для генерации вложений для изображения. С другой стороны, вы не захотели бы использовать ResNet50, модель для изображений, для генерации вложений для предложений. Поэтому важно находить модели, подходящие для вашего типа данных.
Как мы сравниваем векторные вложения?
Далее давайте посмотрим, как их сравнивать. В этом разделе сравниваются три разные многоязычные модели на основе MiniLM от Hugging Face. Существует много способов сравнивать векторы. В этом примере мы используем метрику расстояния L2 и инвертированный файловый индекс в качестве векторного индекса.
Для этого проекта я разработал часть своих данных, вдохновившись недавним релизом альбома Taylor Swift, и определил их прямо в Jupyter Notebook. Вы можете использовать мои примеры или придумать собственные предложения. Когда у нас есть данные, мы получаем разные вложения и сохраняем два набора вложений в векторной базе данных, такой как Milvus. Мы выполняем запросы к ним, чтобы провести сравнения с использованием вложений третьей модели.
Мы хотим посмотреть, отличаются ли результаты поиска и насколько далеко результаты поиска находятся друг от друга. В производственной среде вы бы хотели знать, какие результаты ожидаете увидеть, а затем сверить их с возвращенными результатами.
Сравнение векторных вложений из разных моделей
Три модели, которые мы сравниваем, — это базовая многоязычная модель перефразирования, использующая MiniLM из Sentence Transformers, версия, которая была дообучена для определения намерений, и модель, которую Sprylab дообучила без подробностей о том, для чего она была настроена. Найти три совместимые модели, которые я мог бы запустить на своем ноутбуке, было одной из самых сложных частей этого проекта.
Нам нужны векторы одинаковой длины/размерности, чтобы сравнивать векторные эмбеддинги. В этом примере мы используем 384-мерные векторы согласно модели MiniLM Sentence Transformer. Обратите внимание, что это не означает, что мы создаем 384 «признака» или «категории» для каждого фрагмента данных, а скорее генерируем абстрактное представление данных в 384-мерном пространстве. Например, ни одно из измерений не будет представлять что-либо вроде части речи, количества слов в предложении, того, является ли что-то именем собственным, или чего-либо концептуального в этом духе.
Данные для сравнения векторных эмбеддингов
Мы используем модели sentence transformer, а это значит, что наши данные должны быть в форме предложений. Я рекомендую иметь как минимум 50 предложений для сравнения. Пример notebook содержит 51. Я также рекомендую использовать данные, которые имеют между собой некоторое сходство. В этом примере я использовал тексты четырех песен Taylor Swift: “Speak Now,” “Starlight,” “Sparks Fly,” и “Haunted.”
Я выбрал эти песни, потому что многие строки образуют полные предложения, поэтому их легко преобразовать из формата текста песни в формат предложений. Я также хотел проверить гипотезу. Первые три песни более или менее являются песнями о любви, тогда как последняя, “Haunted,” больше похожа на песню о расставании. Поэтому я предполагаю, что вероятность появления “Haunted” в результатах выдачи для первых трех песен ниже, чем у них друг для друга.
Сравнение векторных эмбеддингов в вашем Jupyter Notebook
Перейдем к коду. С Milvus Lite, облегченной версией Milvus, вы можете сравнивать свои векторные эмбеддинги прямо в Jupyter Notebook. Для этого примера кода вам нужно будет выполнить ! pip install pymilvus milvus sentence-transformers. Для этого проекта нужно всего несколько импортов; библиотека time необязательна. После выполнения импортов запустите default_server из Milvus и настройте соединение.
from sentence_transformers import SentenceTransformer
from milvus import default_server
from pymilvus import connections, utility, FieldSchema, CollectionSchema, DataType, Collection
from time import time
default_server.start()
connections.connect(host="127.0.0.1", port=default_server.listen_port)
Получите модели sentence transformer с Hugging Face
Шаг 1 — получить наши модели векторных эмбеддингов. В этом примере мы используем серию paraphrase multilingual MiniLM. Первая — каноническая версия. Следующие две — по-разному дообученные версии. Такой выбор модели дает нам наглядный пример того, как дообучение может заметно изменить ваши векторы.
v12 = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
ft3_v12 = SentenceTransformer("Sprylab/paraphrase-multilingual-MiniLM-L12-v2-fine-tuned-3")
ft5_v12 = SentenceTransformer("hroth/psais-paraphrase-multilingual-MiniLM-L12-v2-5shot")
Загрузите наборы данных в две коллекции в векторной базе данных
Ниже я привел 51 предложение из альбома Taylor Swift “Speak Now”. Можете свободно скопировать и вставить этот фрагмент кода. Или прочитать его, если хотите; она отличный автор текстов.
# вдохновлено Speak Now (Taylor's Version)
# Текст из: Speak Now, Starlight, Sparks Fly, Haunted
sentences = [
"Я не из тех девушек, которым следует грубо врываться на церемонию с белой фатой, но ты не из тех парней, которым следует жениться не на той девушке",
"Я пробираюсь внутрь и вижу твоих друзей и ее чванливую семейку, все одеты в пастельные тона, а она где-то в глубине комнаты кричит на подружку невесты, одетая в платье в форме пирожного",
"Это точно не то, чего ты ожидал.",
"Я теряюсь в грезах, где встаю и говорю: 'Не говори «да», беги сейчас, я встречу тебя, когда ты выйдешь из церкви у задней двери.'",
"Не жди и не произноси ни одной клятвы, ты должен меня выслушать, и они сказали: 'Говори сейчас'.",
"Обмениваются нежными жестами.",
"И орган начинает играть песню, похожую на похоронный марш.",
"И я прячусь за занавесками, похоже, я не была приглашена твоей милой будущей невестой.",
"Она плывет по проходу, словно королева конкурса красоты.",
"Но я знаю, ты хотел бы, чтобы это была я, ты хотел бы, чтобы это была я, не так ли?",
"Я слышу, как проповедник говорит: 'Говори сейчас или навеки храни молчание'",
"Вот тишина, вот мой последний шанс.",
"Я встаю с дрожащими руками, все глаза устремлены на меня",
"Ужас на лицах всех в комнате, но я смотрю только на тебя.",
"И ты скажешь: 'Давай убежим сейчас', я встречу тебя, когда выйду из своего смокинга у задней двери",
"Малыш, я не произнес своих клятв, так рад, что ты была рядом, когда они сказали: 'Говори сейчас'",
"Я сказала: 'О боже, какая чудесная мелодия'",
"Это была лучшая ночь, никогда не забуду, как мы двигались.",
"Все вокруг было разодето в пух и прах, и мы танцевали, танцевали, словно сделаны из звездного света",
"Я встретила Бобби на променаде летом 45-го",
"Он забрал меня поздно ночью через окно, нам было семнадцать, и мы были безумны, неслись на волю, на волю.",
"Не помню, какую песню он играл, когда мы вошли.",
"В ту ночь мы пробрались на вечеринку в яхт-клуб, притворяясь герцогиней и принцем.",
"Он сказал: 'Посмотри на себя, ты так переживаешь о вещах, которые не можешь изменить, ты проведешь всю жизнь, распевая блюз, если продолжишь думать так'",
"Он пытался пускать камешки по океану, говоря мне: 'Разве ты не видишь звездный свет, звездный свет, разве ты не мечтаешь о невозможном'",
"У-у, у-у, он говорит безумные вещи, у-у, у-у, танцует со мной, у-у, у-у, мы могли бы пожениться, завести десять детей и научить их мечтать",
"То, как ты двигаешься, словно настоящий ливень.",
"А я — карточный домик",
"Ты из тех безрассудных, от которых мне следовало бы бежать, но я как-то знаю, что далеко не уйду",
"И ты стоял там передо мной, достаточно близко, чтобы коснуться",
"Достаточно близко, чтобы надеяться, что ты не видишь, о чем я думаю",
"Брось все прямо сейчас",
"Встреть меня под проливным дождем",
"Поцелуй меня на тротуаре",
"Унеси боль",
"Потому что я вижу, как искры летят, каждый раз, когда ты улыбаешься",
"Покори меня этими зелеными глазами, малыш, когда огни гаснут",
"Дай мне что-нибудь, что будет преследовать меня, когда тебя нет рядом",
"Мой разум забывает напомнить мне, что ты плохая идея",
"Ты касаешься меня один раз, и это действительно нечто, ты понимаешь, что я даже лучше, чем ты представлял",
"Я настороже со всем остальным миром, но с тобой я знаю, это бесполезно"
"И я могла бы ждать терпеливо, но мне правда хочется, чтобы ты это сделал"
"Я провожу пальцами по твоим волосам и смотрю, как огни сходят с ума",
"Просто продолжай смотреть на меня, это достаточно неправильно, чтобы казаться правильным",
"И веди меня вверх по лестнице, не прошепчешь ли ты нежно и медленно, я очарована тобой, малыш, словно фейерверком",
"Мы с тобой идем по хрупкой черте, я знала это все это время, но я никогда не думала, что доживу до того, как она сломается",
"Темнеет, и все слишком тихо, и я теперь не могу доверять ничему, и это накатывает на тебя, словно все это одна большая ошибка",
"О, я затаила дыхание, не потеряю тебя снова",
"Что-то сделало твои глаза холодными",
"Ну же, ну же, не оставляй меня так, я думала, что разобралась в тебе",
"Что-то пошло ужасно не так, ты — все, чего я хотела",
"Не могу дышать, когда тебя нет, теперь не повернуть назад, я одержима",
"Я просто знаю: ты не ушел, ты не можешь уйти, нет",
]
Сначала мы должны определить схемы, чтобы поместить данные в векторную базу данных. В этом примере у нас одинаковая схема для обеих коллекций, поэтому нам нужно создать только одну. Однако не забудьте создать две коллекции.
Когда коллекции готовы, мы кодируем все предложения в эмбеддинги для их моделей и определяем параметры векторного индекса. В качестве метрики расстояния мы используем L2 и индекс inverted file с четырьмя центроидами. В конце концов, записей всего 51. Затем мы форматируем данные и загружаем их в Milvus.
# object should be inserted in the format of (title, date, location, speech embedding)
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="sentence", dtype=DataType.VARCHAR, max_length=500),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=DIMENSION),
]
schema = CollectionSchema(fields=fields)
collection_v12 = Collection(name=COLLECTION_V12, schema=schema)
collection_v12_ft5 = Collection(name=COLLECTION_V12_Q, schema=schema)
v12_embeds = {}
v12_q_embeds = {}
for sentence in sentences:
v12_embeds[sentence] = v12.encode(sentence)
v12_q_embeds[sentence] = ft5_v12.encode(sentence)
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 4},
}
collection_v12.create_index(field_name="embedding", index_params=index_params)
collection_v12.load()
collection_v12_ft5.create_index(field_name="embedding", index_params=index_params)
collection_v12_ft5.load()
for sentence in sentences:
v12_insert = [
{
"sentence": sentence,
"embedding": v12_embeds[sentence]
}
]
ft_insert = [
{
"sentence": sentence,
"embedding": v12_q_embeds[sentence]
}
]
collection_v12.insert(v12_insert)
collection_v12_ft5.insert(ft_insert)
collection_v12.flush()
collection_v12_ft5.flush()
Запрос к векторным хранилищам для сравнения эмбеддингов
Далее пора сравнить. В этом примере мы будем использовать первые два предложения. Мы используем третью модель, чтобы сгенерировать для них векторные эмбеддинги.
search_embeds = {}
search_data = []
for sentence in sentences[0:2]:
vector_embedding = ft3_v12.encode(sentence)
search_embeds[sentence] = vector_embedding
search_data.append(vector_embedding)
Теперь мы выполняем запрос к Milvus. Я также отслеживаю время. Время для поисков, которое я получил, показано ниже. Обязательно передайте тот же тип метрики в параметрах поиска.
start1 = time()
res_v12 = collection_v12.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 2}},
limit = 3, # Limit to top_k results per search
output_fields=["sentence"])
time1 = time() - start1
print(f"Time for first search: {time1}")
start2 = time()
res_v12_ft5 = collection_v12_ft5.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 2}},
limit = 3, # Limit to top_k results per search
output_fields=["sentence"])
time2 = time() - start2
print(f"Time for second search: {time2}")
Теперь давайте посмотрим на результаты и сравним. Мы видим удивительно похожие результаты для исходной модели и для модели Sprylab, прошедшей fine-tuning. Единственное различие в том, что первым возвращенным результатом является само предложение. Это говорит нам о том, что результаты под номерами два и три больше похожи на два примерных поисковых предложения, чем друг на друга в этих двух векторных пространствах.
for i, hits in enumerate(res_v12):
for hit in hits:
print(f"Query sentence: {sentences[i]}")
print(f"Nearest Neighbor Number {i}: {hit.entity.get('sentence')} ---- {hit.distance}\n")
Далее давайте сравним две дообученные модели. По этим результатам мы увидим, что предложение, начинающееся с «I’m on my guard for the rest of the world …», семантически похоже на наши поисковые предложения, потому что оно появляется в обоих сравнениях. Здесь есть два интересных момента: 1) разные результаты по первому запросу и 2) предложение второго запроса не попадает в первую тройку для первого, но обратное верно.
for hit in hits:
print(f"Query sentence: {sentences[i]}")
print(f"Nearest Neighbor Number {i}: {hit.entity.get('sentence')} ---- {hit.distance}\n")
Итоги
В этом руководстве мы узнали о векторных эмбеддингах и о том, как их сравнивать, а также сами провели несколько сравнений с нашими пользовательскими данными. В качестве бонуса мы также показали пример того, как работать с двумя разными коллекциями одновременно. Именно так можно выполнять запросы к разным скрытым векторным пространствам.
Мы показали разницу между моделью и несколькими ее дообученными версиями. Мы также увидели, как один результат появился в обоих пространствах эмбеддингов. Результат запроса в нескольких векторных представлениях означает, что этот запрос должен быть семантически похож во многих отношениях. В качестве следующих шагов попробуйте сделать это с моделями изображений, языковыми моделями разной размерности или со своими данными.
Читать далее

What Is a Vector Lakebase?
A Vector Lakebase is a unified, lake-native data architecture for AI that combines vector-database-grade serving with open lake storage, reusable lake-level indexes, and a shared semantic layer.

Democratizing AI: Making Vector Search Powerful and Affordable
Zilliz democratizes AI vector search with Milvus 2.6 and Zilliz Cloud for powerful, affordable scalability, cutting costs in infrastructure, operations, and development.

DeepRAG: Thinking to Retrieval Step by Step for Large Language Models
Discover DeepRAG, an advanced retrieval-augmented generation (RAG) model that improves LLM accuracy by retrieving only essential data through step-by-step reasoning.



