Эмбеддинг, или векторное представление, — числовой вектор (z\in\mathbb R^d), поставленный в соответствие объекту так, чтобы его координаты или геометрические отношения с другими векторами были пригодны для некоторой вычислительной задачи. Объектом может быть токен, запрос, документ, пользователь, товар, изображение или последовательность событий.
Вектор может быть строкой обучаемой матрицы, результатом факторизации либо выходом энкодера. В PyTorch nn.Embedding, например, представляет собой обучаемую таблицу векторов, индексируемую целочисленными идентификаторами. В LSI документы и запросы получаются через низкоранговое разложение матрицы терминов и документов.
Вектор может использоваться как самостоятельный признак модели и вообще не участвовать в поиске ближайших соседей.
Место в поисковой системе
В двухбашенном поиске типичный контур выглядит так:
- Документ разбирается и при необходимости делится на фрагменты.
- Энкодер документа получает вектор (d_i).
- Вектор сохраняется вместе с идентификатором документа и добавляется в векторный индекс.
- При запросе энкодер запроса получает (q).
- По (q) извлекаются ближайшие документы.
- Векторные кандидаты могут объединяться с BM25 или другим лексическим поиском.
- Кандидаты передаются следующему ранжировщику.
DPR использует два энкодера и скалярное произведение между представлениями запроса и фрагмента документа. Представления корпуса вычисляются заранее, а запрос кодируется во время поиска.
Вход
Для документа:
- текст или другой объект;
- схема нарезки;
- токенизация;
- версия энкодера;
- дополнительные входные поля, если их использует модель.
Для запроса:
- исходный запрос;
- при необходимости язык, тип задачи или инструкция модели;
- та же версия пространства, с которой построен индекс.
Выход
[
z=(z_1,\ldots,z_d),\qquad z\in\mathbb R^d.
]Дальше этот вектор используется для вычисления сходства, извлечения кандидатов либо как признак ранжирующей модели.
Критический контракт здесь состоит не из одного имени модели. Совместимы должны быть:
[
\text{предобработка}
+
\text{энкодер}
+
\text{размерность}
+
\text{нормализация}
+
\text{функция сходства}
+
\text{версия индекса}.
]Смена одного элемента может изменить порядок соседей даже при корректных типах данных.
Место в рекомендательной системе
В рекомендациях векторные представления часто применяются на первом этапе отбора кандидатов.
Упрощенный контур:
- Система собирает просмотры, клики, покупки, пропуски и другие события.
- Из истории и контекста вычисляется представление пользователя или текущей сессии (u).
- Для объектов каталога заранее вычисляются представления (v_i).
- По функции (s(u,v_i)) выбирается небольшой набор кандидатов из большого каталога.
- Следующая модель получает больше признаков и пересчитывает порядок.
- После ранжирования применяются ограничения, диверсификация и другие правила выдачи.
Опубликованная Google архитектура рекомендаций YouTube уже в 2016 году разделяла отбор сотен кандидатов из большого корпуса и последующее более дорогое ранжирование.
Здесь эмбеддинг опять отвечает только за часть задачи. Он не определяет сам по себе, какие объекты система покажет пользователю.
История
LSI: низкоразмерное пространство до нейронных эмбеддингов
В 1990 году Deerwester и соавторы описали Latent Semantic Indexing. Матрица «термин × документ» разлагалась с помощью SVD, после чего запросы и документы представлялись в общем низкоразмерном пространстве и сравнивались по косинусу. Это не нейронный эмбеддинг в нынешнем смысле, но прямой пример низкоразмерного векторного представления для поиска.
Нейронные распределенные представления слов
Bengio и соавторы в 2003 году обучали векторное представление каждого слова одновременно с нейронной языковой моделью. Цель состояла в том, чтобы обобщать статистику между ранее не встречавшимися последовательностями через близкие представления слов.
Word2vec
В 2013 году Mikolov и соавторы предложили вычислительно более дешевые архитектуры для обучения непрерывных представлений слов на больших корпусах. Значение этой работы для истории эмбеддингов связано прежде всего с масштабируемостью и распространением обучаемых векторов слов, а не с изобретением самой идеи распределенного представления.
Векторы предложений и документов
В 2019 году Reimers и Gurevych предложили Sentence-BERT. Вместо совместной обработки каждой пары текстов модель кодирует предложения отдельно. После этого их можно сравнивать, например, по косинусному сходству. Авторы показывают практический выигрыш на задаче поиска похожих предложений: для 10 000 предложений обычному BERT требовалось около 50 млн попарных прогонов, а SBERT сокращал расчет с примерно 65 часов до нескольких секунд.
В 2020 году Karpukhin и соавторы применили двухэнкодерную схему к поиску фрагментов документов. Один энкодер получает запрос, второй — фрагмент текста. Оба преобразуются в отдельные векторы, а релевантность считается через скалярное произведение. Такой подход позволяет заранее посчитать векторы документов и искать по большому корпусу без совместного прогона каждой пары через BERT.
ColBERT использует другой компромисс. Модель не сводит весь запрос и весь документ к одному вектору. Она отдельно кодирует их токены и сохраняет набор контекстных векторов. Затем для каждого токена запроса ищется максимальное сходство с токенами документа, а результаты складываются. Авторы называют эту операцию MaxSim и относят ее к late interaction.
Такой подход требует больше памяти и вычислений, чем поиск по одному вектору на документ, зато позволяет сравнивать запрос и документ на уровне отдельных токенов.
Математическая основа
Для энкодерного варианта удобно записать:
[
f_\theta:\mathcal X\rightarrow\mathbb R^d,
][
z=f_\theta(x).
]Здесь:
- (x) — исходный объект;
- (\theta) — параметры модели;
- (d) — размерность представления;
- (z) — полученный вектор.
Это удобная запись, но не универсальный способ получения эмбеддинга. В матричной факторизации представление может непосредственно храниться как строка обучаемой матрицы.
Скалярное произведение
[
s(q,d)=q^\top d.
]Оценка зависит от направления и длины обоих векторов:
|q|_2|d|_2\cos\theta.
]Поэтому два вектора одного направления, но разной нормы могут иметь одинаковый косинус и разное скалярное произведение.
Косинусное сходство
[
\cos(q,d)=
\frac{q^\top d}
{|q|_2|d|_2}.
]Косинус убирает влияние масштаба и оставляет угол между векторами.
Евклидово расстояние
[
L_2(q,d)=|q-d|_2.
]Если оба вектора нормированы:
[
|q|_2=|d|_2=1,
]то:
[
q^\top d=\cos(q,d)
]и
[
|q-d|_2^2=2-2q^\top d.
]Следовательно, для нормированных векторов максимальный косинус, максимальное скалярное произведение и минимальное квадратное (L_2)-расстояние дают один порядок соседей.
Без нормализации это неверно. Faiss прямо требует нормировать запросы и документы при реализации косинусного поиска через METRIC_INNER_PRODUCT.
Как обучается пространство
Для поисковой двухбашенной модели есть запрос (q_i), положительный документ (d_i^+) и отрицательные документы (d_{ij}^-).
Одна из стандартных функций потерь:
[
L_i=
-\log
\frac{
\exp(s(q_i,d_i^+)/\tau)
}{
\exp(s(q_i,d_i^+)/\tau)
+
\sum_j
\exp(s(q_i,d_{ij}^-)/\tau)
}.
]Модель уменьшает потери, повышая оценку положительной пары относительно отрицательных.
(\tau) управляет масштабом логитов. Чем она меньше, тем сильнее softmax концентрируется на небольших различиях оценок.
Сама функция потерь еще не определяет хорошее пространство. Качество сильно зависит от того, какие отрицательные примеры попадают в обучение.
Слишком легкие отрицательные примеры
Если для запроса про возврат товара в отрицательные примеры постоянно попадут документы про погоду, модель научится отличать слишком простые случаи. Для реального поиска этого мало: трудность возникает там, где нерелевантные документы похожи на релевантные.
Авторы ANCE как раз разбирают эту проблему. Они показывают, что случайные отрицательные примеры из мини-батча плохо соответствуют тем нерелевантным документам, которые модель реально встречает при поиске по корпусу. Поэтому ANCE берет сложные отрицательные примеры из результатов поиска по ANN-индексу всего корпуса и периодически обновляет этот индекс по мере обучения модели.
Ложные отрицательные примеры
Сложный отрицательный пример может на деле быть релевантным документом, который просто не попал в разметку. Если использовать его как отрицательный, функция потерь будет специально отдалять запрос от полезного документа.
Авторы RocketQA отдельно разбирают эту проблему. Они называют такие случаи false negatives и перед обучением фильтруют сложные отрицательные примеры, чтобы не наказывать модель за документы, которые могут быть релевантными.
Отрицательные примеры внутри пакета
Пусть пакет содержит (B) положительных пар:
[
Q\in\mathbb R^{B\times d},
\qquad
D\in\mathbb R^{B\times d}.
]Тогда:
[
S=QD^\top.
]Диагональ соответствует положительным парам, остальные элементы можно использовать как отрицательные.
Это вычислительно выгодно, но предполагает, что чужой положительный документ действительно отрицателен для текущего запроса. На данных с несколькими релевантными документами это предположение регулярно нарушается.
Обучение по журналам поведения
Если положительные пары берутся из кликов, модель учит не чистую релевантность, а распределение наблюдавшегося поведения.
Документы выше в выдаче чаще видят и чаще кликают независимо от их истинной релевантности. Поэтому непосредственное использование клика как оценки релевантности переносит в модель позиционное смещение предыдущего ранжировщика. Эта проблема подробно изучена в работах по unbiased learning-to-rank.
Для эмбеддингов это означает, что обучающая геометрия зависит не только от пользователя, запроса и документа, но и от политики, которая решила, какие документы пользователь вообще увидел.
Минимальный пример
Пусть запрос:
[
q=[1,0].
]Документы:
[
d_1=[1,0],
][
d_2=[10,0],
][
d_3=[0.8,0.6].
]Для (d_1) и (d_2):
[
\cos(q,d_1)=\cos(q,d_2)=1.
]По направлению они одинаковы.
Но скалярные произведения:
[
q^\top d_1=1,
][
q^\top d_2=10.
]Поэтому поиск по скалярному произведению поставит (d_2) выше.
По евклидову расстоянию:
[
|q-d_1|_2=0,
][
|q-d_2|_2=9.
]Здесь порядок противоположный.
После нормализации (d_2) превращается в ([1,0]), и три функции становятся согласованными.
Пример на Python
Что проверяем кодом
- как одна и та же группа векторов ранжируется по скалярному произведению, косинусу и (L_2);
- почему нормализация делает cosine и dot product эквивалентными;
- как считать полноту приближенного индекса относительно точного поиска, не смешивая ее с оценками релевантности.
Установка
pip install numpyКод
import numpy as np
def normalize(x: np.ndarray) -> np.ndarray:
norm = np.linalg.norm(x, axis=-1, keepdims=True)
return x / np.clip(norm, 1e-12, None)
def cosine_scores(query: np.ndarray, docs: np.ndarray) -> np.ndarray:
q = normalize(query[None, :])[0]
d = normalize(docs)
return d @ q
def dot_scores(query: np.ndarray, docs: np.ndarray) -> np.ndarray:
return docs @ query
def l2_distances(query: np.ndarray, docs: np.ndarray) -> np.ndarray:
return np.linalg.norm(docs - query, axis=1)
def rank_desc(values: np.ndarray) -> np.ndarray:
return np.argsort(-values)
def rank_asc(values: np.ndarray) -> np.ndarray:
return np.argsort(values)
def ann_recall_at_k(
exact_ids: list[int],
approximate_ids: list[int],
) -> float:
if len(exact_ids) != len(approximate_ids):
raise ValueError("Списки должны иметь одинаковый K")
return len(set(exact_ids) & set(approximate_ids)) / len(exact_ids)
names = np.array(["d1", "d2", "d3", "d4"])
query = np.array([1.0, 0.0], dtype=np.float32)
docs = np.array([
[1.0, 0.0],
[10.0, 0.0],
[0.8, 0.6],
[-1.0, 0.0],
], dtype=np.float32)
dot = dot_scores(query, docs)
cos = cosine_scores(query, docs)
l2 = l2_distances(query, docs)
print("dot ranking:", names[rank_desc(dot)].tolist())
print("cosine ranking:", names[rank_desc(cos)].tolist())
print("L2 ranking:", names[rank_asc(l2)].tolist())
docs_norm = normalize(docs)
query_norm = normalize(query[None, :])[0]
dot_norm = dot_scores(query_norm, docs_norm)
cos_norm = cosine_scores(query_norm, docs_norm)
l2_norm_sq = np.sum((docs_norm - query_norm) ** 2, axis=1)
print()
print("После L2-нормализации:")
for name, dot_value, cos_value, l2_value in zip(
names,
dot_norm,
cos_norm,
l2_norm_sq,
):
print(
f"{name}: "
f"dot={dot_value:.3f}, "
f"cos={cos_value:.3f}, "
f"L2^2={l2_value:.3f}"
)
# Пример проверки приближенного индекса.
exact_top5 = [12, 4, 9, 7, 3]
ann_top5 = [12, 4, 7, 18, 21]
print()
print(
"ANN Recall@5:",
f"{ann_recall_at_k(exact_top5, ann_top5):.2f}",
)Ожидаемый вывод
dot ranking: ['d2', 'd1', 'd3', 'd4']
cosine ranking: ['d1', 'd2', 'd3', 'd4']
L2 ranking: ['d1', 'd3', 'd4', 'd2']
После L2-нормализации:
d1: dot=1.000, cos=1.000, L2^2=0.000
d2: dot=1.000, cos=1.000, L2^2=0.000
d3: dot=0.800, cos=0.800, L2^2=0.400
d4: dot=-1.000, cos=-1.000, L2^2=4.000
ANN Recall@5: 0.60Ограничения примера
Код не обучает текстовую модель и не имитирует качество реального поиска. Его задача — проверить геометрию и разделить два разных источника ошибок:
[
\text{ошибка представления}
\neq
\text{ошибка ANN}.
]В рабочей системе сначала сравнивают точный поиск по тем же векторам с оценками релевантности, затем приближенный индекс — с точными ближайшими соседями.
Эмбеддинги в поиске
Один вектор на документ
DPR относится к классу моделей, где запрос и документ независимо сжимаются в один вектор. Это делает возможным предварительное вычисление всех документов и быстрый поиск по индексу.
Цена такой архитектуры — информационное бутылочное горлышко. Весь документ должен быть представлен фиксированным числом координат.
Совместная обработка запроса и документа
Модель типа cross-encoder получает запрос и документ совместно. Она может моделировать взаимодействие токенов между сторонами, но не позволяет заранее получить один независимый вектор документа для поиска по всему корпусу.
Поэтому такие модели чаще применяют после извлечения кандидатов.
SBERT появился именно как способ вынести вычисление независимых представлений из попарного BERT-сравнения.
Позднее взаимодействие
ColBERT занимает промежуточное положение. Запрос и документ кодируются независимо, но вместо одного вектора система хранит несколько токенных представлений и считает MaxSim между ними.
Поэтому выражение «векторный поиск» скрывает несколько существенно разных архитектур:
[
\text{single-vector}
\neq
\text{multi-vector}
\neq
\text{cross-encoder}.
]Плотный и разреженный поиск
BM25 и другие лексические методы используют явные совпадения терминов. Плотные представления могут извлекать документы без буквального совпадения слов, если обученная геометрия ставит их рядом.
Из этого не следует, что плотный поиск должен заменить лексический.
BEIR сравнил лексические, sparse neural, dense, late-interaction и reranking-подходы на 18 наборах. BM25 оказался устойчивой базовой линией, а плотные методы не были универсально лучшими вне обучающего распределения.
Vespa в актуальной документации прямо поддерживает совместное извлечение и ранжирование по BM25 и плотным представлениям. В их же учебном примере результат разных способов смешивания отличается, поэтому формулу объединения нужно оценивать на собственном наборе релевантности.
Семантический поиск не является синонимом поиска по эмбеддингам. Семантические признаки могут участвовать в переупорядочивании, классификации запроса, расширении запроса, entity matching и других этапах без ANN-поиска.
Эмбеддинги в рекомендациях
Матричная факторизация
В классической матричной факторизации пользователь и объект получают низкоразмерные векторы:
[
p_u\in\mathbb R^d,
\qquad
q_i\in\mathbb R^d.
]Для оценки рейтинга можно использовать:
\mu+b_u+b_i+p_u^\top q_i.
]Здесь (p_u^\top q_i) моделирует взаимодействие латентных факторов пользователя и объекта.
Современная двухбашенная модель отличается способом получения этих представлений: пользовательский вектор может зависеть от последовательности событий, времени, устройства и других признаков, а объектный — от идентификатора, текста, изображения и метаданных. Но требование дешево сравнить пользователя с большим каталогом остается тем же.
Холодный старт
Если представление объекта хранится только как обучаемая строка по ID, новый объект не имеет обученной истории.
Контентный энкодер частично решает проблему: вектор можно получить из текста, изображения или других доступных признаков до накопления взаимодействий.
Устаревание
Представление пользователя, рассчитанное вчера, может уже плохо описывать текущую сессию. Для быстро меняющегося каталога та же проблема возникает у объектов.
Поэтому freshness lag в рекомендациях нужно измерять так же, как задержку обновления поискового индекса.
Смещение данных
История показов зависит от предыдущей рекомендательной системы. Объект, который почти не показывался, почти не мог получить клик.
Следовательно, отсутствие взаимодействия нельзя автоматически трактовать как отрицательную оценку.
Метрики
У эмбеддинга нет одной универсальной метрики качества.
MTEB был создан именно из-за того, что хорошие результаты на semantic textual similarity не гарантируют хорошие результаты в поиске, кластеризации или переупорядочивании. В исходной работе оценивались 33 метода на восьми типах задач, и ни один метод не доминировал на всех.
Для извлечения кандидатов
Если для запроса (q) множество релевантных документов равно (R_q), то:
\frac{|R_q\cap TopK(q)|}{|R_q|}.
]Для первого этапа эта метрика особенно полезна: документ, который не попал в набор кандидатов, следующий ранжировщик уже не увидит.
Для порядка результатов
При градуированных оценках релевантности используют NDCG:
\sum_{i=1}^{K}
\frac{2^{rel_i}-1}{\log_2(i+1)},
]\frac{DCG@K}{IDCG@K}.
]MRR полезен в задачах, где особенно важна позиция первого подходящего результата:
[
MRR=
\frac{1}{|Q|}
\sum_{q\in Q}
\frac{1}{rank_q}.
]Для ANN
Пусть точный поиск по тем же векторам возвращает (E_K(q)), а приближенный индекс — (A_K(q)).
Тогда:
\frac{|A_K(q)\cap E_K(q)|}{K}.
]Это не метрика релевантности. Она измеряет, насколько ANN воспроизводит соседей, определенных текущими векторами и функцией сходства.
Разделение дает простую диагностику:
- exact Recall@K низкий → проблема в представлении, данных или постановке задачи;
- exact Recall@K высокий, ANN Recall низкий → проблема в индексе, его параметрах или квантовании.
Стоимость точного поиска
Для (N) документов размерности (d) прямое сравнение запроса со всем корпусом требует:
[
O(Nd)
]арифметических операций на запрос.
Хранение исходных векторов требует:
[
M=Ndb,
]где (b) — число байт на координату.
Например, для (100,000,000) векторов размерности 768:
- float32: 307,2 ГБ;
- float16: 153,6 ГБ;
- int8: 76,8 ГБ.
Это только данные векторов, без графа HNSW, идентификаторов, метаданных, реплик и служебных структур.
Размерность поэтому является эксплуатационным параметром, а не декоративной характеристикой модели.
ANN и HNSW
При большом (N) полный перебор часто заменяют приближенным поиском.
HNSW строит многоуровневый граф соседства и выполняет навигацию по нему вместо сравнения запроса со всеми объектами. Исходная работа сообщает логарифмическое масштабирование предложенной структуры, но это не стоит превращать в универсальную гарантию времени ответа: фактическое соотношение recall, задержки и памяти зависит от данных и параметров графа.
Для HNSW обычно приходится настраивать как минимум:
- число связей графа;
- стоимость построения;
- ширину поиска во время запроса.
Чем больше работы разрешено поиску, тем выше обычно ANN Recall и задержка.
Квантование
Сжатие векторов уменьшает память и стоимость вычислений, но меняет расстояния.
Поэтому после int8, int4, binary quantization или PQ нужно повторно измерять:
- совпадение соседей с точным float-поиском;
- Recall@K по разметке;
- NDCG/MRR после ранжирования;
- задержку;
- объем памяти.
Elasticsearch поддерживает HNSW с int8, int4 и бинарным квантованием; документация отдельно указывает потерю точности и возможность oversampling с последующим пересчетом результатов по исходным векторам.
Ошибки внедрения
1. Несогласованная функция сходства
Модель рассчитана на cosine, а индекс использует ненормированный inner product.
Проявление: offline-проверка модели нормальная, выдача индекса резко отличается.
Проверка: сравнить порядок для exact cosine, exact dot product и реального индекса.
Исправление: зафиксировать нормализацию и функцию сходства как часть версии модели.
2. Разные версии пространства
Документы рассчитаны encoder_v17, а запросы уже обслуживает encoder_v18.
Размерность может совпадать. Типы данных тоже. Ошибки выполнения не будет. Но координатные системы двух моделей не обязаны быть совместимы.
Проверка: логировать версию энкодера отдельно для запроса и индекса.
Исправление: атомарное переключение модели и индекса либо специально обученная совместимость между версиями.
3. Частичная переиндексация
Часть документов рассчитана старой моделью, часть новой.
Получается смесь нескольких пространств внутри одного индекса.
Проверка: хранить embedding_model_version на уровне каждого объекта и считать распределение версий.
4. Устаревшие векторы
Исходный документ изменился, а его вектор остался старым.
Проверка: сравнивать версию документа, время его изменения и время расчета вектора.
Метрика: lag между обновлением объекта и попаданием нового представления в обслуживающий индекс.
5. Обрезка текста
Модель видит только первые (L) токенов, а нужная информация находится после границы.
Проверка: строить качество отдельно по длине документов и положению релевантного фрагмента.
6. Неправильная нарезка
Слишком большой фрагмент смешивает несколько независимых тем. Слишком маленький теряет контекст и увеличивает число индексируемых объектов.
Размер фрагмента нужно выбирать по Recall@K/NDCG и стоимости индекса, а не копировать из чужого примера.
7. Ложные отрицательные примеры
Неотмеченный релевантный документ попадает в отрицательные и получает градиент в неправильную сторону.
Проверка: вручную или более сильной моделью проверять верхние сложные отрицательные примеры.
RocketQA показывает этот отказ непосредственно.
8. Слишком легкие отрицательные примеры
Функция потерь падает, но модель почти не учится различать соседние по задаче документы.
Проверка: измерять качество на полном корпусе, а не только training loss.
ANCE был построен именно вокруг разрыва между распределением отрицательных примеров при обучении и реальными конкурентами во время поиска.
9. Низкий ANN Recall
Точный поиск по векторам работает хорошо, HNSW — плохо.
Проверка: ANN Recall@K относительно exact top-K.
Исправление: параметры поиска, параметры графа, квантование, фильтрация, число извлекаемых кандидатов.
10. Фильтры уничтожают полноту
Поиск сначала получает ближайших соседей, после чего большая часть удаляется фильтром.
В зависимости от системы это может резко уменьшить число пригодных кандидатов.
Vespa поддерживает объединение nearestNeighbor с фильтрующими условиями непосредственно в запросе.
11. Одна плотная модель на все типы запросов
Запросы с точными идентификаторами, навигационные запросы, редкие сущности и естественно-языковые вопросы предъявляют разные требования к извлечению.
Проверять нужно отдельные срезы запросов. Средний NDCG может скрывать провал одного критического класса.
12. Анизотропия
Векторные представления могут оказаться слишком похожими друг на друга. Тогда модель хуже различает разные объекты.
Авторы SimCSE проверили один такой случай экспериментально. Они убрали dropout из unsupervised contrastive learning. После этого два прохода одного и того же предложения через модель давали почти одинаковые представления, а обучение теряло полезный шум, который помогал различать положительные и отрицательные пары. Авторы называют этот эффект collapse.
Они также отдельно измеряли анизотропию — насколько неравномерно представления распределены по векторному пространству — и проверяли, как contrastive objective меняет это распределение.
Но этот результат относится к конкретной схеме обучения SimCSE. Если поиск работает плохо, этого недостаточно, чтобы утверждать, что представления схлопнулись.
Мониторинг
Представления
Следить за:
- долей NaN/Inf;
- распределением норм;
- распределением cosine/dot scores;
- версией модели;
- долей объектов без актуального вектора;
- задержкой обновления;
- срезами по языкам, категориям и источникам.
Порог нормы или среднего cosine нельзя переносить между моделями: масштаб зависит от конкретного пространства.
Индекс
Следить за:
- числом векторов;
- размером индекса;
- временем построения и перестроения;
- задержкой обновлений;
- p50/p95/p99 поиска;
- ANN Recall@K относительно точной контрольной выборки;
- потреблением памяти.
Поиск
Следить за:
- Recall@K;
- NDCG@K;
- MRR/MAP там, где они соответствуют задаче;
- качеством по типам запросов;
- результатами dense, lexical и hybrid вариантов отдельно.
Эксперимент
Улучшение среднего cosine на тестовом наборе не является основанием для выпуска поисковой модели.
Нужна последовательность:
[
\text{публичный тест}
\rightarrow
\text{собственная offline-разметка}
\rightarrow
\text{точный поиск}
\rightarrow
\text{ANN}
\rightarrow
\text{полный ранжирующий контур}
\rightarrow
\text{A/B-тест}.
]Связанные термины
Вектор признаков — любой числовой набор признаков объекта. Он может быть вручную построен и не быть эмбеддингом.
Разреженный вектор — представление, где большинство координат равно нулю. TF-IDF — классический пример.
Плотный вектор — представление, где большинство координат имеет ненулевые значения. Большинство нейронных эмбеддингов плотные, но плотность сама по себе не делает вектор эмбеддингом.
Hidden state — внутреннее состояние слоя модели. Его иногда используют как основу эмбеддинга, но любой hidden state не становится автоматически пригодным представлением для поиска.
Энкодер — модель или функция, преобразующая объект в представление.
Двухбашенная модель — архитектура, где две стороны пары кодируются независимо.
Cross-encoder — модель, которая обрабатывает пару совместно.
Late interaction — архитектура с независимым кодированием и отложенным взаимодействием нескольких векторов.
Векторный индекс — структура данных для поиска по векторам.
ANN — приближенный поиск ближайших соседей.
Векторная база — инфраструктура хранения и поиска векторов. Для использования эмбеддингов она не обязательна: векторный поиск поддерживают обычные поисковые движки и библиотеки, включая Lucene, Elasticsearch, Vespa и Faiss.
Распространенные неверные утверждения
«Эмбеддинг хранит смысл текста»
Во время обучения модель учится располагать тексты так, чтобы решать конкретную задачу. Что именно попадет в векторное представление, зависит от данных, функции потерь и того, какие положительные и отрицательные примеры использовали при обучении.
Изменим задачу или данные — получим другое векторное пространство и другие отношения между теми же текстами.
«Чем выше cosine, тем документ релевантнее»
Cosine определяет близость в текущем пространстве. Релевантность определяется внешней задачей и проверяется разметкой или экспериментом.
«Dense retrieval заменяет BM25»
DPR показал сильное преимущество на части QA-наборов, но не на всех. BEIR позже подтвердил, что универсального победителя нет.
«Cosine и dot product одинаковы»
Только после соответствующей нормализации.
«ANN возвращает настоящих ближайших соседей»
ANN по определению допускает потерю точных соседей ради стоимости поиска.
«Чем больше размерность, тем лучше»
Большая размерность увеличивает память и стоимость сравнений. Прирост качества нужно измерять на целевой задаче.
«Нужна специальная векторная база»
Нет. Нужна система, способная хранить представления и выполнять требуемый поиск. Faiss — библиотека поиска по векторам, а Lucene, Vespa и Elasticsearch совмещают векторные операции с другими механизмами поиска.
«Лучшая модель в MTEB будет лучшей в моем поиске»
Исходный MTEB как раз показывает зависимость результата от класса задачи и отсутствие одного метода, доминирующего везде.
Практический чек-лист внедрения
- Определить, что именно должно означать сходство в целевой задаче.
- Собрать релевантные положительные и отрицательные пары.
- Зафиксировать предобработку, токенизацию и схему нарезки.
- Выбрать модель и функцию сходства.
- Зафиксировать нормализацию и размерность.
- Проверить качество точным поиском.
- Сравнить с сильной лексической базовой линией.
- Только после этого выбрать ANN-индекс.
- Измерить ANN Recall относительно точного поиска.
- Проверить фильтры и квантование.
- Измерить Recall@K/NDCG/MRR на собственных данных.
- Зафиксировать версии энкодера и индекса.
- Измерять задержку обновления векторов.
- Провести online-эксперимент.
- Подготовить откат модели и индекса как одной версии.