検索のためのデータモデル設計

検索エンジンとしても知られる情報検索システムは、RAG(Retrieval-augmented generation)、ビジュアル検索、商品推薦など、様々なAIアプリケーションに不可欠である。これらのシステムの核となるのは、情報を整理し、インデックスを付け、検索するために入念に設計されたデータモデルです。

Milvusでは、非構造化データ、密または疎なベクトル表現、構造化メタデータを整理し、コレクションスキーマを通して検索データモデルを指定することができます。テキスト、画像、その他のデータタイプのいずれを扱う場合でも、このハンズオンガイドは、検索データモデルを実際に設計するための主要なスキーマコンセプトを理解し、適用するのに役立ちます。

Data Model Anatomy データモデルの解剖

データモデル

検索システムのデータモデル設計には、ビジネスニーズを分析し、情報をスキーマで表現されたデータモデルに抽象化することが含まれる。十分に定義されたスキーマは、データモデルをビジネス上の目的に合致させ、データの一貫性とサービスの質を保証するために重要である。 さらに、適切なデータ型とインデックスを選択することは、ビジネス目標を経済的に達成するために重要です。

ビジネスニーズの分析

ビジネス・ニーズに効果的に対応するためには、ユーザーが実行するクエリのタイプを分析し、最適な検索方法を決定することから始まります。

  • ユーザーのクエリーユーザーが実行すると予想されるクエリのタイプを特定する。これは、スキーマが実際のユースケースをサポートし、検索パフォーマンスを最適化するのに役立ちます。これには以下が含まれる:

    • 自然言語のクエリに一致する文書の検索

    • 参照画像に類似した画像や、テキストの説明に一致する画像を検索する。

    • 名前、カテゴリー、ブランドなどの属性による商品の検索

    • 構造化されたメタデータ(発行日、タグ、評価など)に基づくアイテムのフィルタリング

    • 複数の条件を組み合わせたハイブリッドクエリ(ビジュアル検索では、画像とキャプションの両方の意味的類似性を考慮するなど)

  • 検索手法:ユーザーが実行するクエリのタイプに沿った適切な検索テクニックを選択する。異なる検索手法は異なる目的を持ち、組み合わせることでより強力な検索結果を得ることができます:

    • セマンティック検索:類似した意味を持つアイテムを見つけるために密なベクトル類似性を使用し、テキストや画像のような非構造化データに最適です。

    • 全文検索:セマンティック検索をキーワードマッチで補完。 全文検索では、語彙解析を利用して長い単語を断片的なトークンに分割することを避け、検索時に特殊な用語を把握することができる。

    • メタデータのフィルタリング:ベクトル検索の上に、日付範囲、カテゴリー、タグなどの制約を適用する。

ビジネス要件を検索データモデルに変換

次のステップは、ビジネス要件を具体的なデータモデルに変換することです。情報の中核となるコンポーネントとその検索方法を特定します:

  • 生のコンテンツ(テキスト、画像、音声)、関連するメタデータ(タイトル、タグ、著者名)、コンテキスト属性(タイムスタンプ、ユーザー行動など)など、保存する必要のあるデータを定義する。

  • 各要素に適切なデータタイプとフォーマットを決定します。例えば

    • テキスト記述 → 文字列

    • 画像またはドキュメントの埋め込み → 密なまたは疎なベクトル

    • カテゴリー、タグ、フラグ → 文字列、配列、bool

    • 価格や評価などの数値属性 → integer、float

    • 著者の詳細などの構造化情報 → json

これらの要素を明確に定義することで、データの一貫性、正確な検索結果、下流のアプリケーション・ロジックとの統合が容易になります。

スキーマ設計

Milvusでは、データモデルはコレクションスキーマで表現されます。コレクションスキーマ内で適切なフィールドを設計することは、効果的な検索を可能にする鍵です。各フィールドはコレクションに格納されたデータの特定のタイプを定義し、検索プロセスにおいて明確な役割を果たします。Milvusはベクトルフィールドと スカラーフィールドの2種類のフィールドをサポートしています。

ベクターと補助的なスカラーフィールドを含むフィールドのスキーマにデータモデルをマッピングすることができます。各フィールドがデータモデルの属性と相関していることを確認し、特にベクトルタイプ(denseまたはspase)とその次元に注意してください。

ベクトル・フィールド

ベクトル・フィールドは、テキスト、画像、音声などの非構造化データの埋め込みを格納します。これらの埋め込みは、データ型や利用する検索方法によって、密なもの、疎なもの、バイナリのものがあります。一般的に、密なベクトルは意味検索に使用され、疎なベクトルは全文検索や語彙照合に適している。バイナリ・ベクトルは、ストレージや計算リソースが限られている場合に有用である。コレクションは、マルチモーダルまたはハイブリッド検索ストラテジーを可能にするために、複数のベクトルフィールドを含むことができる。このトピックの詳細ガイドについては、マルチベクターハイブリッド検索を参照してください。

Milvusは以下のベクトルデータ型をサポートしています:Dense Vectorは FLOAT_VECTORSparse Vectorは SPARSE_FLOAT_VECTORBinary Vectorは BINARY_VECTOR

スカラーフィールド

スカラーフィールドは、数値、文字列、日付などのプリミティブで構造化された値-一般にメタデータと呼ばれる-を格納します。これらの値はベクターの検索結果と一緒に返すことができ、フィルタリングやソートに不可欠です。これらの値により、文書を特定のカテゴリや定義された時間範囲に限定するなど、特定の属性に基づいて検索結果を絞り込むことができます。

Milvusは、ベクトル以外のデータの保存やフィルタリングのために、BOOLINT8/16/32/64FLOATDOUBLEVARCHARJSONARRAY などのスカラー型をサポートしています。これらの型は検索操作の精度とカスタマイズ性を高めます。

スキーマ設計における高度な機能の活用

スキーマを設計する場合、サポートされているデータ型を使ってデータをフィールドにマッピングするだけでは十分ではありません。フィールド間の関係や、コンフィギュレーションに利用できるストラテジーを十分に理解することが不可欠です。設計段階で主要な機能を念頭に置いておくことで、スキーマが当面のデータ処理要件を満たすだけでなく、将来のニーズに対しても拡張性と適応性を持つようになります。これらの機能を注意深く統合することにより、Milvusの機能を最大限に活用し、より広範なデータ戦略と目的をサポートする強力なデータアーキテクチャを構築することができます。以下はコレクションスキーマを作成する主な機能の概要です:

主キー

主キーフィールドは、コレクション内の各エンティティを一意に識別するため、スキーマの基本要素です。主キーの定義は必須である。これは整数型または文字列型のスカラー・フィールドで、is_primary=True としてマークされなければならない。オプションで、プライマリ・キーにauto_id を有効にすることができます。プライマリ・キーには、データがコレクションに取り込まれるにつれてモノリシックに増加する整数番号が自動的に割り当てられます。

詳細については、プライマリフィールドとAutoIDを参照してください。

パーティショニング

検索を高速化するために、オプションでパーティショニングをオンにできます。特定のスカラー・フィールドをパーティショニングに指定し、検索時にこのフィールドに基づくフィルタリング基準を指定することで、検索範囲を関連するパーティションのみに効果的に制限できます。この方法は、検索領域を減らすことで検索操作の効率を大幅に向上させる。

詳細は「パーティション・キーの使用」を参照。

アナライザー

アナライザーは、テキストデータの処理と変換に不可欠なツールである。主な機能は、生のテキストをトークンに変換し、インデックス付けや検索のために構造化することである。文字列をトークン化し、ストップワードを削除し、個々の単語をトークンにステミングします。

詳細については、「Analyzer の概要」を参照してください。

機能

Milvusではスキーマの一部として組み込み関数を定義し、特定のフィールドを自動的に導出することができます。例えば、VARCHAR フィールドからスパースベクトルを生成する組み込み BM25 関数を追加して、全文検索をサポートすることができます。これらの関数派生フィールドは、前処理を合理化し、コレクションが自己完結的でクエリに対応できる状態を維持することを保証する。

詳細については、全文検索を参照してください。

実例

このセクションでは、上図に示すマルチメディア文書検索アプリケーションのスキーマ設計とコード例を概説する。このスキーマは、以下のフィールドにデータがマッピングされた記事を含むデータセットを管理するために設計されています:

フィールド

データソース

検索メソッドで使われる

主キー

パーティション・キー

アナライザー

関数の入出力

article_id (INT64)

有効化された状態で自動生成auto_id

Getを使ったクエリー

Y

N

N

N

タイトル (VARCHAR)

記事タイトル

テキストマッチ

N

N

Y

N

タイムスタンプ (INT32)

公開日

パーティションキーによるフィルタリング

N

Y

N

N

テキスト (VARCHAR)

記事の原文

マルチベクトルハイブリッドサーチ

N

N

Y

入力

テキスト密ベクトル (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 がパーティション・キーとして割り当てられ、パーティションによるフィルタリングが可能になる。これは次のようなものである。

  • テキストアナライザ:テキストアナライザが2つの文字列フィールドtitletext に適用され、それぞれテキストマッチと全文検索をサポートする。

(オプション)関数の追加

データ照会機能を強化するために、関数をスキーマに組み込むことができます。例えば、特定のフィールドに関連する処理を行う関数を作成することができる。

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 フィールドに格納しています。

次のステップ