Проектирование модели данных для поиска

Информационно-поисковые системы, также известные как поисковые системы, необходимы для различных приложений искусственного интеллекта, таких как поиск с расширением (RAG), визуальный поиск и рекомендация товаров. В основе этих систем лежит тщательно разработанная модель данных для организации, индексирования и получения информации.

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

Data Model Anatomy Анатомия модели данных

Модель данных

Проектирование модели данных поисковой системы включает в себя анализ потребностей бизнеса и абстрагирование информации в виде модели данных, выраженной в виде схемы. Хорошо определенная схема важна для согласования модели данных с бизнес-целями, обеспечения согласованности данных и качества обслуживания. Кроме того, выбор правильных типов данных и индексов важен для экономичного достижения бизнес-цели.

Анализ потребностей бизнеса

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

  • Запросы пользователей: Определите типы запросов, которые, как ожидается, будут выполнять пользователи. Это поможет убедиться, что ваша схема поддерживает реальные сценарии использования и оптимизирует производительность поиска. К ним могут относиться:

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

    • Поиск изображений, похожих на эталонное изображение или соответствующих текстовому описанию

    • Поиск товаров по таким атрибутам, как название, категория или бренд.

    • фильтрация элементов на основе структурированных метаданных (например, дата публикации, теги, рейтинги)

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

  • Методы поиска: Выберите подходящие методы поиска, соответствующие типам запросов, которые будут выполнять ваши пользователи. Различные методы служат разным целям и часто могут быть объединены для получения более мощных результатов:

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

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

    • Фильтрация метаданных: Поверх векторного поиска, применение ограничений, таких как диапазоны дат, категории или теги.

Перевод бизнес-требований в модель поисковых данных

Следующий шаг - преобразование бизнес-требований в конкретную модель данных путем определения основных компонентов вашей информации и методов их поиска:

  • Определите данные, которые необходимо хранить: необработанный контент (текст, изображения, аудио), связанные метаданные (заголовки, теги, авторство) и контекстные атрибуты (временные метки, поведение пользователей и т. д.).

  • Определите подходящие типы и форматы данных для каждого элемента. Например:

    • Текстовые описания → строка

    • Вложения изображений или документов → плотные или разреженные векторы

    • Категории, теги или флаги → строки, массивы и bool

    • Числовые атрибуты, такие как цена или рейтинг → integer или float

    • Структурированная информация, например, данные об авторе -> json

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

Разработка схемы

В Milvus модель данных выражается через схему коллекции. Разработка правильных полей в схеме коллекции - это ключ к эффективному поиску. Каждое поле определяет конкретный тип данных, хранящихся в коллекции, и играет определенную роль в процессе поиска. На высоком уровне Milvus поддерживает два основных типа полей: векторные и скалярные.

Теперь вы можете отобразить свою модель данных в схему полей, включая векторы и любые вспомогательные скалярные поля. Убедитесь, что каждое поле соотносится с атрибутами вашей модели данных, особенно обратите внимание на тип вектора (dense или spase) и его размерность.

Векторное поле

Векторные поля хранят вкрапления для неструктурированных типов данных, таких как текст, изображения и аудио. Эти вкрапления могут быть плотными, разреженными или двоичными, в зависимости от типа данных и используемого метода поиска. Как правило, плотные векторы используются для семантического поиска, в то время как разреженные векторы лучше подходят для полнотекстового или лексического поиска. Двоичные векторы полезны при ограниченном объеме памяти и вычислительных ресурсов. Коллекция может содержать несколько векторных полей, что позволяет использовать мультимодальные или гибридные стратегии поиска. Подробное руководство по этой теме см. в разделе Многовекторный гибридный поиск.

Milvus поддерживает типы векторных данных: FLOAT_VECTOR для плотного вектора, SPARSE_FLOAT_VECTOR для разреженного вектора и BINARY_VECTOR для двоичного вектора.

Скалярное поле

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

Milvus поддерживает скалярные типы, такие как BOOL, INT8/16/32/64, FLOAT, DOUBLE, VARCHAR, JSON и ARRAY для хранения и фильтрации невекторных данных. Эти типы повышают точность и настраиваемость операций поиска.

Использование расширенных возможностей при разработке схем

При разработке схемы недостаточно просто сопоставить данные с полями, используя поддерживаемые типы данных. Необходимо хорошо понимать взаимосвязи между полями и стратегии, доступные для настройки. Учет ключевых особенностей на этапе проектирования гарантирует, что схема будет не только соответствовать текущим требованиям к обработке данных, но и будет масштабируемой и адаптируемой к будущим потребностям. Тщательно интегрировав эти функции, вы сможете построить надежную архитектуру данных, которая максимально использует возможности Milvus и поддерживает вашу более широкую стратегию и цели в области данных. Ниже приведен обзор ключевых особенностей создания схемы коллекции:

Первичный ключ

Поле первичного ключа является основополагающим компонентом схемы, поскольку оно однозначно идентифицирует каждую сущность в коллекции. Определение первичного ключа является обязательным. Это должно быть скалярное поле целого или строкового типа, помеченное как is_primary=True. В качестве опции можно включить auto_id для первичного ключа, которому автоматически присваиваются целые числа, монолитно увеличивающиеся по мере поступления данных в коллекцию.

Дополнительные сведения см. в разделе Первичное поле и автоидентификация.

Разбиение на разделы

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

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

Анализатор

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

Более подробную информацию см. в разделе Обзор анализатора.

Функция

Milvus позволяет определять встроенные функции как часть схемы для автоматического получения определенных полей. Например, вы можете добавить встроенную функцию BM25, которая генерирует разреженный вектор из поля VARCHAR для поддержки полнотекстового поиска. Эти поля, созданные с помощью функций, упрощают предварительную обработку и обеспечивают самодостаточность коллекции и ее готовность к запросу.

Более подробную информацию можно найти в разделе "Полнотекстовый поиск.

Пример из реального мира

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

Поле

Источник данных

Используется методами поиска

Первичный ключ

Ключ раздела

Анализатор

Функция ввода/вывода

article_id (INT64)

автоматически генерируется при включении auto_id

Запрос с помощью Get

Y

N

N

N

название (VARCHAR)

заголовок статьи

Текстовое соответствие

N

N

Y

N

временная метка (INT32)

дата публикации

Фильтр по ключу раздела

N

Y

N

N

текст (VARCHAR)

необработанный текст статьи

Многовекторный гибридный поиск

N

N

Y

вход

text_dense_vector (FLOAT_VECTOR)

плотный вектор, сгенерированный моделью встраивания текста

Базовый векторный поиск

N

N

N

N

text_sparse_vector (SPARSE_FLOAT_VECTOR)

разреженный вектор, автоматически генерируемый встроенной функцией BM25

Полный текстовый поиск

N

N

N

выход

Более подробную информацию о схемах и подробное руководство по добавлению различных типов полей вы найдете в разделе "Объяснение схем".

Инициализация схемы

Для начала нам нужно создать пустую схему. Этот шаг устанавливает основополагающую структуру для определения модели данных.

from pymilvus import MilvusClient

schema = MilvusClient.create_schema()
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

// 1. Connect to Milvus server
ConnectConfig connectConfig = ConnectConfig.builder()
        .uri("http://localhost:19530")
        .build();

MilvusClientV2 client = new MilvusClientV2(connectConfig);

// 2. Create an empty schema
CreateCollectionReq.CollectionSchema schema = client.createSchema();
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

//Skip this step using JavaScript
import "github.com/milvus-io/milvus/client/v2/entity"

schema := entity.NewSchema()
# Skip this step using cURL

Добавить поля

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

from pymilvus import DataType

schema.add_field(field_name="article_id", datatype=DataType.INT64, is_primary=True, auto_id=True, description="article id")
schema.add_field(field_name="title", datatype=DataType.VARCHAR, enable_analyzer=True, enable_match=True, max_length=200, description="article title")
schema.add_field(field_name="timestamp", datatype=DataType.INT32, description="publish date")
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000, enable_analyzer=True, description="article text content")
schema.add_field(field_name="text_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense vector")
schema.add_field(field_name="text_sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse vector")
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;

schema.addField(AddFieldReq.builder()
        .fieldName("article_id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("title")
        .dataType(DataType.VarChar)
        .maxLength(200)
        .enableAnalyzer(true)
        .enableMatch(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("timestamp")
        .dataType(DataType.Int32)
        .build())
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(2000)
        .enableAnalyzer(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text_dense_vector")
        .dataType(DataType.FloatVector)
        .dimension(768)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text_sparse_vector")
        .dataType(DataType.SparseFloatVector)
        .build());
const fields = [
    {
        name: "article_id",
        data_type: DataType.Int64,
        is_primary_key: true,
        auto_id: true
    },
    {
        name: "title",
        data_type: DataType.VarChar,
        max_length: 200,
        enable_analyzer: true,
        enable_match: true
    },
    {
        name: "timestamp",
        data_type: DataType.Int32
    },
    {
        name: "text",
        data_type: DataType.VarChar,
        max_length: 2000,
        enable_analyzer: true
    },
    {
        name: "text_dense_vector",
        data_type: DataType.FloatVector,
        dim: 768
    },
    {
        name: "text_sparse_vector",
        data_type: DataType.SparseFloatVector
    }
]
schema.WithField(entity.NewField().
    WithName("article_id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true).
    WithIsAutoID(true).
    WithDescription("article id"),
).WithField(entity.NewField().
    WithName("title").
    WithDataType(entity.FieldTypeVarChar).
    WithMaxLength(200).
    WithEnableAnalyzer(true).
    WithEnableMatch(true).
    WithDescription("article title"),
).WithField(entity.NewField().
    WithName("timestamp").
    WithDataType(entity.FieldTypeInt32).
    WithDescription("publish date"),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithMaxLength(2000).
    WithEnableAnalyzer(true).
    WithDescription("article text content"),
).WithField(entity.NewField().
    WithName("text_dense_vector").
    WithDataType(entity.FieldTypeFloatVector).
    WithDim(768).
    WithDescription("text dense vector"),
).WithField(entity.NewField().
    WithName("text_sparse_vector").
    WithDataType(entity.FieldTypeSparseVector).
    WithDescription("text sparse vector"),
)
export fields='[
    {
        "fieldName": "article_id",
        "dataType": "Int64",
        "isPrimary": true
    },
    {
        "fieldName": "title",
        "dataType": "VarChar",
        "elementTypeParams": {
            "max_length": 200,
            "enable_analyzer": true,
            "enable_match": true
        }
    },
    {
        "fieldName": "timestamp",
        "dataType": "Int32"
    },
    {
       "fieldName": "text",
       "dataType": "VarChar",
       "elementTypeParams": {
            "max_length": 2000,
            "enable_analyzer": true
        }
    },
    {
       "fieldName": "text_dense_vector",
       "dataType": "FloatVector",
       "elementTypeParams": {
            "dim": 768
        }
    },
    {
       "fieldName": "text_sparse_vector",
       "dataType": "SparseFloatVector",
    }
]'

export schema="{
    \"autoID\": true,
    \"fields\": $fields
}"

В этом примере для полей заданы следующие атрибуты:

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

  • Ключ раздела: timestamp назначается в качестве ключа раздела, что позволяет осуществлять фильтрацию по разделам. Это может быть

  • Текстовый анализатор: текстовый анализатор применяется к двум строковым полям title и text для поддержки текстового соответствия и полнотекстового поиска соответственно.

(Необязательно) Добавление функций

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

from pymilvus import Function, FunctionType

bm25_function = Function(
    name="text_bm25",
    input_field_names=["text"],
    output_field_names=["text_sparse_vector"],
    function_type=FunctionType.BM25,
)

schema.add_function(bm25_function)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;

import java.util.*;

schema.addFunction(Function.builder()
        .functionType(FunctionType.BM25)
        .name("text_bm25")
        .inputFieldNames(Collections.singletonList("text"))
        .outputFieldNames(Collections.singletonList("text_sparse_vector"))
        .build());
import FunctionType from "@zilliz/milvus2-sdk-node";

const functions = [
    {
      name: 'text_bm25',
      description: 'bm25 function',
      type: FunctionType.BM25,
      input_field_names: ['text'],
      output_field_names: ['text_sparse_vector'],
      params: {},
    },
];
function := entity.NewFunction().
    WithName("text_bm25").
    WithInputFields("text").
    WithOutputFields("text_sparse_vector").
    WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
export myFunctions='[
    {
        "name": "text_bm25",
        "type": "BM25",
        "inputFieldNames": ["text"],
        "outputFieldNames": ["text_sparse_vector"],
        "params": {}
    }
]'

export schema="{
    \"autoID\": true,
    \"fields\": $fields
    \"functions\": $myFunctions
}"

В этом примере в схему добавлена встроенная функция BM25, использующая в качестве входных данных поле text и сохраняющая полученные разреженные векторы в поле text_sparse_vector.

Следующие шаги