MAATRIX / Блог / Как embedding превращает смысл в координаты

Как embedding превращает смысл в координаты

MAATRIX

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

Что такое embedding и почему это просто числа

Embedding (эмбеддинг) — это массив чисел с плавающей точкой фиксированной длины, который модель сопоставляет куску текста: слову, предложению, абзацу или целому документу. Технически это просто вектор — например, 768 чисел вида [0.021, -0.153, 0.87, ..., -0.004]. Сам по себе такой набор чисел ничего не говорит человеку: в отличие от текста, его нельзя прочитать. Но у него есть свойство, которое и делает его полезным: два вектора, полученные из семантически близких текстов, оказываются близко друг к другу в этом многомерном пространстве, а вектора текстов с разным смыслом — далеко.

Важно сразу разделить два процесса, которые часто путают: обучение модели — тяжёлый разовый процесс, который проводят разработчики модели на огромных объёмах текста, и результат которого — веса нейросети, зафиксированные в файле; и вычисление эмбеддинга для конкретного текста — лёгкая операция инференса, когда вы прогоняете свой текст через уже обученную модель и на выходе получаете вектор. Именно второй процесс вы будете использовать каждый день — модель тут готовый чёрный ящик, который превращает «смысл» в «координаты», и его можно применять локально на CPU, без GPU и без какого-либо переобучения.

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

Как модель учится располагать точки рядом

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

Это называют контрастивным обучением (contrastive learning) — суть в контрасте между «похоже» и «непохоже». Итоговая модель не «понимает» текст в человеческом смысле — она выучила статистическую закономерность: какие сочетания слов и конструкций обычно встречаются в похожих по смыслу контекстах.

Отсюда два практических следствия, которые стоит держать в голове:

  1. Качество эмбеддингов целиком зависит от того, на каких данных и как обучали модель. Модель, обученную преимущественно на английских текстах, не стоит ждать одинаково хорошей на русском — если явно не заявлена многоязычность.
  2. Вектора от разных моделей эмбеддингов несовместимы друг с другом. Нельзя посчитать один текст моделью A, другой — моделью B и сравнивать полученные вектора: у каждой модели своё, отдельно выученное пространство координат. Если вы поменяли модель эмбеддингов в проекте — придётся пересчитать (переиндексировать) все вектора в базе заново.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Как измеряется близость: косинусное расстояние на примере

Раз смысл кодируется как близость точек в пространстве, нужна метрика, которая эту близость считает. Самая распространённая для эмбеддингов текста — косинусное сходство (cosine similarity). Она смотрит не на абсолютное расстояние между точками, а на угол между двумя векторами, если провести их из начала координат.

Формула для двух векторов A и B:

cosine_similarity(A, B) = (A · B) / (||A|| * ||B||)

где A · B — скалярное произведение (сумма произведений соответствующих координат), а ||A|| и ||B|| — длины (нормы) векторов. Результат лежит в диапазоне от -1 до 1: чем ближе к 1 — тем меньше угол между векторами и тем «более похожими по смыслу» модель считает тексты; значения около 0 говорят о смысловой несвязанности; отрицательные значения на практике для текстовых эмбеддингов встречаются редко.

Разберём на упрощённом примере с двумерными векторами (в реальности их сотни или тысячи измерений, но принцип тот же):

Вектор "отменить платёж"        A = [0.9, 0.2]
Вектор "вернуть деньги"         B = [0.85, 0.3]
Вектор "рецепт борща"           C = [-0.1, 0.95]

cosine(A, B) ≈ 0.99   -- почти совпадающее направление, тексты близки по смыслу
cosine(A, C) ≈ 0.28   -- векторы направлены по-разному, смысл далёк

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

Почему это даёт поиск по смыслу, а не по словам

Классический полнотекстовый поиск (full-text search, например на движке вроде PostgreSQL tsvector или Elasticsearch/BM25) ищет буквальные совпадения слов и их словоформ. Он отлично находит документ по запросу «настройка WireGuard», если в тексте есть именно эти слова или их близкие формы. Но он бессилен, если пользователь спросил «как поднять VPN туннель» — синонимия и перефразирование его не касаются, ведь для него это просто разные наборы токенов.

Векторный (семантический) поиск работает иначе: и запрос, и документы в базе превращаются в эмбеддинги одной и той же моделью, а дальше система ищет в базе не совпадение слов, а ближайшие по расстоянию вектора. «Как поднять VPN туннель» и «настройка WireGuard» в семантическом пространстве вполне могут оказаться близко, если модель эмбеддингов выучила связь между этими понятиями на обучающих данных — притом что ни одного общего слова в запросах нет.

Отсюда и практический вывод: семантический поиск не заменяет полнотекстовый полностью, а решает свою специфическую проблему — вариативность формулировок, синонимы, перефразирование, вопросы на естественном языке вместо ключевых слов. Многие продакшн-системы комбинируют оба подхода (гибридный поиск, hybrid search): полнотекстовый компонент хорошо ловит точные термины, коды ошибок, названия моделей оборудования — то, что векторный поиск иногда «размывает» по смыслу; а семантический ловит перефразированные запросы. Как это встраивается в работу моделей и агентов, разобрано в статье про tool calling в LLM — там векторный поиск часто выступает одним из инструментов, которые модель вызывает сама.

Векторные базы данных: как Qdrant и Chroma ищут вектора быстро

Хранить миллион 768-мерных векторов и искать среди них ближайшие к запросу перебором — вычислительно дорого: наивный полный перебор (brute-force) означает сравнение запроса со всеми векторами по очереди, и с ростом базы это линейно растёт по времени. Для этого существуют специализированные векторные базы данных — Qdrant, Chroma, Milvus, Weaviate, а также векторные расширения обычных СУБД вроде pgvector для PostgreSQL.

Внутри они используют приближённый поиск ближайших соседей (ANN, Approximate Nearest Neighbor) — чаще всего на основе индекса HNSW (Hierarchical Navigable Small World). Идея: вектора организуются в многослойный граф, где верхние слои содержат немного «опорных» точек для грубой навигации, а нижние — плотную сеть связей для точного поиска. Запрос быстро находит примерный район в верхних слоях, а потом уточняет позицию, спускаясь вниз. Это находит близкие вектора значительно быстрее полного перебора, жертвуя долей точности — отсюда слово «приближённый»: HNSW не гарантирует математически точно ближайший вектор, но на практике для большинства задач разница пренебрежимо мала.

Минимальный пример работы с Qdrant через Python-клиент:

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="docs",
    vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)

client.upsert(
    collection_name="docs",
    points=[
        PointStruct(id=1, vector=[0.021, -0.153, 0.87, "..."], payload={"text": "как отменить платёж"}),
    ],
)

result = client.search(
    collection_name="docs",
    query_vector=[0.019, -0.140, 0.81, "..."],
    limit=5,
)

Параметр size=768 — размерность вектора, она должна строго совпадать с тем, что выдаёт ваша модель эмбеддингов; distance=Distance.COSINE — метрика близости при поиске. Chroma устроена концептуально похоже, но проще в развёртывании для небольших проектов — умеет работать как встраиваемая библиотека внутри процесса Python, без отдельного сервера. Какую из двух баз выбрать, разобрано в статье Qdrant против Chroma: что выгоднее и когда.

RAG и разница между моделями эмбеддингов

Самое частое практическое применение всей этой механики — RAG (Retrieval-Augmented Generation, генерация с дополнением поиском). Документы режутся на куски (чанки), каждый чанк превращается в вектор одной моделью эмбеддингов и кладётся в векторную базу вместе с исходным текстом в payload. Вопрос пользователя превращается в вектор той же моделью, база находит ближайшие по смыслу куски, и они вместе с вопросом передаются языковой модели как контекст для ответа — это снижает выдумки и позволяет отвечать по данным, которых не было в обучении модели. Как собрать такой пайплайн целиком, разобрано в статье RAG pipeline с нуля: практический пример.

Ключевой развилка в этом пайплайне — выбор модели эмбеддингов, и модели сильно различаются по нескольким осям:

ПараметрЧто значитПример
Размерность вектораСколько чисел в одном эмбеддинге384, 768, 1024, 1536, 3072
Максимальная длина входаСколько токенов текста модель обработает за разот нескольких сотен до нескольких тысяч токенов
ЯзыкОбучена ли модель на многоязычных данныханглоязычные-only vs multilingual
Способ запускаЛокально (self-hosted) или через облачный APIsentence-transformers локально vs API-вызов

Размерность вектора — не абстрактный параметр, а прямая статья расходов на хранение и вычисления: вектор из 1536 чисел занимает вдвое больше места и вдвое дольше сравнивается, чем вектор из 768. Большая размерность не гарантирует автоматически более высокое качество поиска для вашей задачи — это стоит проверять на своих данных, а не полагаться на общие таблицы рейтингов моделей (например, MTEB), которые считаются на чужих датасетах и языках. Конкретные цифры «точности» разных моделей я намеренно не привожу — такие бенчмарки сильно зависят от домена и языка, и без проверки на вашей выборке мало что скажут про ваш случай.

Локальный запуск моделей эмбеддингов (например, через библиотеку sentence-transformers или FastEmbed) не требует GPU для большинства современных компактных моделей — вычисление эмбеддингов на CPU для умеренных объёмов текста вполне рабочий сценарий, GPU ускоряет процесс, но не является обязательным условием. Подробный разбор того, как считать эмбеддинги без видеокарты и чего ждать от производительности, — в статье Как считать эмбеддинги на CPU без GPU. А если нужен предметный гайд по выбору конкретной модели под задачу RAG — смотрите Embeddings-модели: какую выбрать для RAG.

Типичные ошибки при работе с эмбеддингами

Механика простая, но на практике несколько граблей встречаются постоянно:

  • Смешение векторов от разных моделей в одной коллекции. Проиндексировали часть моделью A, потом сменили на B и не пересчитали старые данные — поиск начнёт давать случайный мусор: расстояния между векторами из разных пространств не имеют смысла в принципе, даже если размерность совпала.
  • Слишком крупные или слишком мелкие чанки текста. Эмбеддинг усредняет смысл всего куска в один вектор. Чанк на десять страниц разных тем даёт «размытый» вектор, не соответствующий точно ни одной из тем; чанк в одно короткое предложение без контекста лишает модель информации для верного смысла.
  • Игнорирование лимита длины входа модели. Текст длиннее максимальной длины входа обычно молча обрезается — часть смысла документа теряется без предупреждения об ошибке.
  • Несовпадение метрики расстояния между обучением модели и настройками базы. Часть моделей нормализована конкретно под косинусное сходство; если в базе по ошибке выставлена евклидова метрика, ранжирование заметно ухудшается без явной ошибки — база продолжает что-то находить.
  • Отсутствие реранкинга там, где он нужен. Векторный поиск находит кандидатов быстро, но не всегда предельно точно — для задач, где важна точность верхних позиций, после него добавляют шаг переранжирования более тяжёлой моделью. Тема разобрана в статье Reranker-модели: зачем нужны в RAG.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Эмбеддинг — это то же самое, что токенизация?

Нет. Токенизация — это разбиение текста на подслова (токены) перед подачей в модель, промежуточный технический шаг. Эмбеддинг — итоговый вектор смысла для целого куска текста (или в других контекстах — вектор для одного токена), который получается уже после прохождения через модель.

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

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

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

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

Что делать, если поменяли модель эмбеддингов в уже работающем проекте?

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

Чем эмбеддинг отличается от классификации или тегов?

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

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →