Технологии

Эмбеддинги простыми словами: как текст превращается в числа

Эмбеддинг — координаты фразы на карте смыслов. Показываем на трёх фразах, как считается близость, и разбираем главную ловушку: близко не значит релевантно.

Возьмите три фразы:

  1. «Завтра еду в горы кататься на лыжах».
  2. «Планирую зимний отпуск в Альпах».
  3. «Завтра еду в горы, но кататься не буду — лечу спину».

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

#Что такое вектор смысла

Эмбеддер — это отдельная модель, которая принимает кусок текста и выдаёт массив чисел фиксированной длины. Не текст, не сжатый текст, не пересказ — просто числа. Длина массива задаётся моделью: например, nomic-embed-text выдаёт 768 чисел, bge-m3 — 1024. Это число называют размерностью.

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

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

#Как это выглядит на наших трёх фразах

Проверить утверждение «близость по смыслу превращается в близость координат» можно самому, за пять минут, без библиотек машинного обучения. Метод такой:

  1. Поднимите Ollama и скачайте модель-эмбеддер: ollama pull nomic-embed-text.
  2. Для каждой из трёх фраз получите вектор запросом к /api/embeddings.
  3. Посчитайте косинус между парами: сумма произведений соответствующих чисел, делённая на произведение длин векторов.

Что вы увидите — предсказуемо по направлению и непредсказуемо по величине: пара «лыжи — Альпы» окажется ближе, чем пара «лыжи — молоко», а пара «еду в горы кататься» и «еду в горы, но кататься не буду» окажется близкой, хотя смысл противоположный. Конкретные числа приводить бессмысленно: они зависят от модели, от версии и от того, добавили ли вы служебные префиксы. Важна не величина, а порядок.

Про префиксы стоит сказать отдельно, потому что на этом теряют качество молча. Модели семейства nomic обучены с пометками роли текста: хранимый документ подаётся как search_document: …, поисковый запрос — как search_query: …. Ollama сама их не добавляет. Persona добавляет их в app/memory_vec.py перед вызовом эмбеддера — и там же в комментарии зафиксировано следствие: при смене схемы префиксов индекс нужно строить заново, иначе запросы и документы окажутся в разных системах координат.

#Главная ловушка: близко не значит релевантно

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

Класс Пример запроса Что вернётся вместо ответа
Отрицание «встреча не состоялась» записи про состоявшиеся встречи
Близкие сущности «ошибка E-4021» абзацы про другие коды ошибок
Смена роли «что я должен Олегу» «что Олег должен мне»

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

Второй класс объясняет, почему поиск по идентификаторам, артикулам и кодам ошибок нужно вести не векторами. Эмбеддер видит в E-4021 не уникальный ключ, а строку, похожую на E-4022. Разбор того, какие типы запросов кому отдавать, — в сравнении семантического поиска и ключевых слов.

Третий класс — потеря направления связи. Векторное представление плохо удерживает, кто субъект, а кто объект, потому что оба слова в предложении присутствуют.

#Что из этого следует при выборе модели

Эмбеддер — отдельная модель, и её берут по остаточному принципу чаще всего. Три практических правила:

Размерность нужно решить один раз. Она попадает в схему хранилища. В Persona векторная таблица объявлена как FLOAT[768] в миграции 186_vec_memory.sql — под nomic-embed-text. Модель с другой размерностью потребует новой таблицы и полной переиндексации, это не настройка, а миграция.

Многоязычность не бесплатна. Модель, обученная преимущественно на английском, на русском тексте даёт заметно менее осмысленные соседства. Проверять это надо на своём корпусе — сравнение моделей и их поведения на русском есть в обзоре embed-моделей.

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

#Нужна ли под это отдельная база

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

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

#Частые вопросы

#Можно ли по эмбеддингу восстановить исходный текст?

Точно — нет, это не обратимое сжатие. Но и считать вектор анонимным нельзя: по нему восстановима тема и во многих случаях достаточно деталей, чтобы понять, о чём был фрагмент. Вектор личной переписки — такие же личные данные, как и сама переписка.

#Почему два разных эмбеддера нельзя сравнивать между собой?

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

#Сколько места занимают векторы?

Один вектор из 768 чисел одинарной точности — это около трёх килобайт. На десять тысяч записей выходят десятки мегабайт: заметно больше текста, но не критично для персонального архива. Считать надо от числа записей, а не от их длины.

#Нужен ли GPU, чтобы считать эмбеддинги?

Нет. Небольшие модели-эмбеддеры считаются на процессоре с приемлемой скоростью. Узкое место обычно другое: первый запрос после простоя грузит модель в память, и это может занять десятки секунд — поэтому в Persona для запроса к эмбеддеру выставлен жёсткий потолок ожидания, после которого поиск деградирует на ключевые слова, а не подвешивает чат.

#Что делать, если поиск по смыслу находит не то?

Сначала проверьте, не относится ли запрос к одному из трёх классов из таблицы выше — тогда лечится не настройкой, а добавлением поиска по ключевым словам рядом. Если запрос обычный, а результаты всё равно мимо, вероятнее всего проблема в модели-эмбеддере или в том, что индекс построен другой моделью.

#Вывод

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

Хотите ИИ, который помнит вас?

Persona держит контекст между чатами и работает на вашем железе — если развернуть её у себя и подключить локальную модель.

Создать аккаунт