Разреженный вектор

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

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

В этом руководстве вы узнаете, как:

  • Готовить вкрапления разреженных векторов;
  • Создавать коллекцию с полем разреженного вектора;
  • Вставлять сущности с разреженными векторными вложениями;
  • Индексировать коллекцию и выполнять ANN-поиск по разреженным векторам.

Чтобы увидеть разреженные векторы в действии, обратитесь к hello_sparse.py.

примечания

В настоящее время поддержка разреженных векторов является бета-версией в версии 2.4.0, а в планах сделать ее общедоступной в версии 3.0.0.

Подготовка вкраплений разреженных векторов

Чтобы использовать разреженные векторы в Milvus, подготовьте векторные вкрапления в одном из поддерживаемых форматов:

  • Sparse Matrices: Используйте семейство классов scipy.sparse для представления разреженных вкраплений. Этот метод эффективен для работы с крупномасштабными, высокоразмерными данными.

  • Список словарей: Представьте каждое разреженное вкрапление в виде словаря, структурированного как {dimension_index: value, ...}, где каждая пара ключ-значение представляет собой индекс размерности и соответствующее ему значение.

    Пример:

    {2: 0.33, 98: 0.72, ...}
    
  • Список кортежей (Iterables of Tuples): Аналогично списку словарей, но для указания только ненулевых размерностей и их значений используется итерабельный список кортежей, [(dimension_index, value)].

    Пример:

    [(2, 0.33), (98, 0.72), ...]
    

Следующий пример готовит разреженные вкрапления, генерируя случайную разреженную матрицу для 10 000 сущностей, каждая из которых имеет 10 000 измерений и плотность разреженности 0,005.

# Prepare entities with sparse vector representation
import numpy as np
import random

rng = np.random.default_rng()

num_entities, dim = 10000, 10000

# Generate random sparse rows with an average of 25 non-zero elements per row
entities = [
    {
        "scalar_field": rng.random(),
        # To represent a single sparse vector row, you can use:
        # - Any of the scipy.sparse sparse matrices class family with shape[0] == 1
        # - Dict[int, float]
        # - Iterable[Tuple[int, float]]
        "sparse_vector": {
            d: rng.random() for d in random.sample(range(dim), random.randint(20, 30))
        },
    }
    for _ in range(num_entities)
]

# print the first entity to check the representation
print(entities[0])

# Output:
# {
#     'scalar_field': 0.520821523849214,
#     'sparse_vector': {
#         5263: 0.2639375518635271,
#         3573: 0.34701499565746674,
#         9637: 0.30856525997853057,
#         4399: 0.19771651149001523,
#         6959: 0.31025067641541815,
#         1729: 0.8265339135915016,
#         1220: 0.15303302147479103,
#         7335: 0.9436728846033107,
#         6167: 0.19929870545596562,
#         5891: 0.8214617920371853,
#         2245: 0.7852255053773395,
#         2886: 0.8787982039149889,
#         8966: 0.9000606703940665,
#         4910: 0.3001170013981104,
#         17: 0.00875671667413136,
#         3279: 0.7003425473001098,
#         2622: 0.7571360018373428,
#         4962: 0.3901879090102064,
#         4698: 0.22589525720196246,
#         3290: 0.5510228492587324,
#         6185: 0.4508413201390492
#     }
# }

примечания

Размеры вектора должны быть типа Python int или numpy.integer, а значения должны быть типа Python float или numpy.floating.

Для генерации вкраплений можно также использовать пакет model, встроенный в библиотеку PyMilvus, который предлагает ряд функций вкраплений. Подробнее см. в разделе Вкрапления.

Создание коллекции с разреженным векторным полем

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

from pymilvus import MilvusClient, DataType

# Create a MilvusClient instance
client = MilvusClient(uri="http://localhost:19530")

# Create a collection with a sparse vector field
schema = client.create_schema(
    auto_id=True,
    enable_dynamic_fields=True,
)

schema.add_field(field_name="pk", datatype=DataType.VARCHAR, is_primary=True, max_length=100)
schema.add_field(field_name="scalar_field", datatype=DataType.DOUBLE)
# For sparse vector, no need to specify dimension
schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR) # set `datatype` to `SPARSE_FLOAT_VECTOR`

client.create_collection(collection_name="test_sparse_vector", schema=schema)

Подробнее об общих параметрах коллекции см. в create_collection().

Вставка сущностей с разреженными векторными вкраплениями

Чтобы вставить сущности с разреженными векторными вложениями, просто передайте список сущностей в метод insert() методу.

# Insert entities
client.insert(collection_name="test_sparse_vector", data=entities)

Индексирование коллекции

Перед выполнением поиска по сходству создайте индекс для коллекции. Дополнительную информацию о типах и параметрах индексов см. в разделах add_index() и create_index().

# Index the collection

# Prepare index params
index_params = client.prepare_index_params()

index_params.add_index(
    field_name="sparse_vector",
    index_name="sparse_inverted_index",
    index_type="SPARSE_INVERTED_INDEX", # the type of index to be created. set to `SPARSE_INVERTED_INDEX` or `SPARSE_WAND`.
    metric_type="IP", # the metric type to be used for the index. Currently, only `IP` (Inner Product) is supported.
    params={"drop_ratio_build": 0.2}, # the ratio of small vector values to be dropped during indexing.
)

# Create index
client.create_index(collection_name="test_sparse_vector", index_params=index_params)

Для создания индексов на разреженных векторах обратите внимание на следующее:

  • index_type: Тип индекса, который необходимо построить. Возможные варианты для разреженных векторов:

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

    • SPARSE_WAND: Использует алгоритм Weak-AND (WAND) для быстрого обхода маловероятных кандидатов, фокусируя оценку на тех, кто имеет более высокий потенциал ранжирования. Рассматривает размерности как термины, а векторы - как документы, что ускоряет поиск в больших разреженных наборах данных.

  • metric_type: Для разреженных векторов поддерживается только метрика расстояния IP (Inner Product).

  • params.drop_ratio_build: Параметр index используется специально для разреженных векторов. Он управляет долей малых значений вектора, которые исключаются в процессе индексирования. Этот параметр позволяет тонко настраивать компромисс между эффективностью и точностью, игнорируя малые значения при построении индекса. Например, если drop_ratio_build = 0.3, то при построении индекса все значения из всех разреженных векторов собираются и сортируются. Наименьшие 30 % этих значений не включаются в индекс, что снижает вычислительную нагрузку при поиске.

Дополнительную информацию см. в разделе Индекс в памяти.

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

# Load the collection into memory
client.load_collection(collection_name="test_sparse_vector")

# Perform ANN search on sparse vectors

# for demo purpose we search for the last inserted vector
query_vector = entities[-1]["sparse_vector"]

search_params = {
    "metric_type": "IP",
    "params": {"drop_ratio_search": 0.2}, # the ratio of small vector values to be dropped during search.
}

search_res = client.search(
    collection_name="test_sparse_vector",
    data=[query_vector],
    limit=3,
    output_fields=["pk", "scalar_field"],
    search_params=search_params,
)

for hits in search_res:
    for hit in hits:
        print(f"hit: {hit}")
        
# Output:
# hit: {'id': '448458373272710786', 'distance': 7.220192909240723, 'entity': {'pk': '448458373272710786', 'scalar_field': 0.46767865218233806}}
# hit: {'id': '448458373272708317', 'distance': 1.2287548780441284, 'entity': {'pk': '448458373272708317', 'scalar_field': 0.7315987515699472}}
# hit: {'id': '448458373272702005', 'distance': 0.9848432540893555, 'entity': {'pk': '448458373272702005', 'scalar_field': 0.9871869181562156}}

При настройке параметров поиска обратите внимание на следующее:

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

Выполнение скалярных запросов

Помимо ANN-поиска, Milvus также поддерживает скалярные запросы к разреженным векторам. Эти запросы позволяют получать документы на основе скалярного значения, связанного с разреженным вектором. Дополнительную информацию о параметрах см. в разделе query().

Фильтр сущностей со значением scalar_field больше 3:

# Perform a query by specifying filter expr
filter_query_res = client.query(
    collection_name="test_sparse_vector",
    filter="scalar_field > 0.999",
)

print(filter_query_res[:2])

# Output:
# [{'pk': '448458373272701862', 'scalar_field': 0.9994093623822689, 'sparse_vector': {173: 0.35266244411468506, 400: 0.49995484948158264, 480: 0.8757831454277039, 661: 0.9931875467300415, 1040: 0.0965644046664238, 1728: 0.7478245496749878, 2365: 0.4351981580257416, 2923: 0.5505295395851135, 3181: 0.7396837472915649, 3848: 0.4428485333919525, 4701: 0.39119353890419006, 5199: 0.790219783782959, 5798: 0.9623121619224548, 6213: 0.453134149312973, 6341: 0.745091438293457, 6775: 0.27766478061676025, 6875: 0.017947908490896225, 8093: 0.11834774166345596, 8617: 0.2289179265499115, 8991: 0.36600416898727417, 9346: 0.5502803921699524}}, {'pk': '448458373272702421', 'scalar_field': 0.9990218525410719, 'sparse_vector': {448: 0.587817907333374, 1866: 0.0994109958410263, 2438: 0.8672442436218262, 2533: 0.8063794374465942, 2595: 0.02122959867119789, 2828: 0.33827054500579834, 2871: 0.1984412521123886, 2938: 0.09674275666475296, 3154: 0.21552987396717072, 3662: 0.5236313343048096, 3711: 0.6463911533355713, 4029: 0.4041993021965027, 7143: 0.7370485663414001, 7589: 0.37588241696357727, 7776: 0.436136394739151, 7962: 0.06377989053726196, 8385: 0.5808192491531372, 8592: 0.8865005970001221, 8648: 0.05727503448724747, 9071: 0.9450633525848389, 9161: 0.146037295460701, 9358: 0.1903032660484314, 9679: 0.3146636486053467, 9974: 0.8561339378356934, 9991: 0.15841573476791382}}]

Фильтр сущностей по первичному ключу:

# primary keys of entities that satisfy the filter
pks = [ret["pk"] for ret in filter_query_res]

# Perform a query by primary key
pk_query_res = client.query(
    collection_name="test_sparse_vector", filter=f"pk == '{pks[0]}'"
)

print(pk_query_res)

# Output:
# [{'scalar_field': 0.9994093623822689, 'sparse_vector': {173: 0.35266244411468506, 400: 0.49995484948158264, 480: 0.8757831454277039, 661: 0.9931875467300415, 1040: 0.0965644046664238, 1728: 0.7478245496749878, 2365: 0.4351981580257416, 2923: 0.5505295395851135, 3181: 0.7396837472915649, 3848: 0.4428485333919525, 4701: 0.39119353890419006, 5199: 0.790219783782959, 5798: 0.9623121619224548, 6213: 0.453134149312973, 6341: 0.745091438293457, 6775: 0.27766478061676025, 6875: 0.017947908490896225, 8093: 0.11834774166345596, 8617: 0.2289179265499115, 8991: 0.36600416898727417, 9346: 0.5502803921699524}, 'pk': '448458373272701862'}]

Ограничения

При использовании разреженных векторов в Milvus учитывайте следующие ограничения:

  • В настоящее время для разреженных векторов поддерживается только метрика расстояния IP.

  • Для полей разреженных векторов поддерживаются только типы индексов SPARSE_INVERTED_INDEX и SPARSE_WAND.

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

ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ

  • Какая метрика расстояния поддерживается для разреженных векторов?

    Для разреженных векторов поддерживается только метрика расстояния Inner Product (IP) из-за высокой размерности разреженных векторов, что делает нецелесообразным использование расстояния L2 и косинусного расстояния.

  • Можете ли вы объяснить разницу между SPARSE_INVERTED_INDEX и SPARSE_WAND, и как мне выбрать между ними?

    SPARSE_INVERTED_INDEX - это традиционный инвертированный индекс, а SPARSE_WAND использует алгоритм Weak-AND для уменьшения количества полных оценок IP-расстояния во время поиска. SPARSE_WAND обычно быстрее, но его производительность может снижаться с увеличением плотности векторов. Чтобы выбрать один из них, проведите эксперименты и бенчмарки, основанные на конкретном наборе данных и сценарии использования.

  • Как выбрать параметры drop_ratio_build и drop_ratio_search?

    Выбор параметров drop_ratio_build и drop_ratio_search зависит от характеристик ваших данных и ваших требований к задержке/пропускной способности и точности поиска.

  • Какие типы данных поддерживаются для разреженных вкраплений?

    Размерная часть должна быть беззнаковым 32-битным целым числом, а часть значения может быть неотрицательным 32-битным числом с плавающей точкой.

  • Может ли размерность разреженного вкрапления быть любым дискретным значением в пространстве uint32?

    Да, за одним исключением. Размерность разреженного вкрапления может быть любой величиной в диапазоне [0, maximum of uint32). Это означает, что вы не можете использовать максимальное значение uint32.

  • Поиск в растущих сегментах осуществляется через индекс или методом грубой силы?

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

  • Можно ли в одной коллекции иметь как разреженные, так и плотные векторы?

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

  • Каковы требования к вставке или поиску разреженных вкраплений?

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