ブログ

ユースケースにBetter Binary Quantization (BBQ)を実装する方法

ユースケースで Better Binary Quantization (BBQ) を実装する理由とその方法について説明します。

ベクトル検索は、テキストのセマンティック検索や、画像、ビデオ、オーディオの類似性検索を実装する際の基盤を提供します。ベクトル検索では、ベクトルはデータの数学的表現であり、そのサイズは膨大で、場合によっては遅くなることがあります。Better Binary Quantization (以下、BBQ と呼びます) は、ベクトルの圧縮方法として機能します。これにより、ベクトルを縮小して検索と処理を高速化しながら、適切な一致を見つけることができます。この記事では、BBQ と、ベクトルを自動的に再スコアリングする量子化インデックスにのみ使用可能なフィールドである rescore_vector について説明します。

この記事で説明したすべての完全なクエリと出力は、Elasticsearch Labs コード リポジトリで確認できます。

ユースケースで Better Binary Quantization (BBQ) を実装する理由は何ですか?

注: BBQ の背後にある数学の仕組みを詳しく理解するには、以下の「さらに学ぶ」セクションをご覧ください。このブログでは、実装に重点を置いています。

数学は興味深いものですが、ベクトル検索がなぜ正確であり続けるのかを完全に理解したい場合には重要です。結局のところ、現在のベクトル検索アルゴリズムではデータの読み取り速度によって制限されることが判明しているため、これはすべて圧縮に関することです。したがって、そのデータすべてをメモリに収めることができれば、ストレージから読み取る場合と比べて速度が大幅に向上します (メモリは SSD よりも約 200 倍高速です)。

いくつか留意すべき点があります:

  • HNSW (Hierarchical Navigable Small World) などのグラフベースのインデックスは、ベクター検索では最も高速です。

    • HNSW: 効率的な高次元類似性検索を可能にする多層グラフ構造を構築する近似最近傍検索アルゴリズム。

HNSW: 効率的な高次元類似性検索を可能にする多層グラフ構造を構築する近似最近傍検索アルゴリズム。
  • HNSW の速度は、基本的にメモリ、または最悪の場合、ストレージからのデータ読み取り速度によって制限されます。

    • 理想的には、保存されているすべてのベクトルをメモリにロードできるようにする必要があります。

  • 埋め込みモデルは通常、浮動小数点数ごとに 4 バイトの float32 精度のベクトルを生成します。

  • そして最後に、ベクトルや次元の数によっては、すべてのベクトルを保存するためのメモリがすぐに不足する可能性があります。

これを当然のこととして考えると、それぞれが数百、数千の次元を持つ可能性のある数百万、数十億のベクトルを取り込み始めると、すぐに問題が発生することがわかります。「圧縮率のおおよその数値」というセクションでは、おおよその数値が示されています。

始めるには何が必要ですか?

始めるには、次のものが必要です。

  • Elastic Cloud またはオンプレミスを使用している場合は、Elasticsearch のバージョン 8.18 以降が必要になります。BBQ は 8.16 で導入されましたが、この記事では 8.18 で導入されたvector_rescore使用します。

  • さらに、クラスター内に機械学習 (ML) ノードがあることも確認する必要があります。(注: モデルをロードするには最低 4 GB の ML ノードが必要ですが、完全な本番環境のワークロードには、はるかに大きなノードが必要になる可能性があります。)

  • Serverless を使用している場合は、ベクターに最適化されたインスタンスを選択する必要があります。

  • また、ベクター データベースに関する基本的な知識も必要になります。Elastic のベクトル検索の概念にまだ慣れていない場合は、まず次のリソースを確認することをお勧めします。

より良いバイナリ量子化(BBQ)実装

Elasticsearch バーベキュー実装。

このブログをシンプルに保つために、組み込み関数が利用可能な場合はそれを使用します。この場合、機械学習ノード上の Elasticsearch 内で直接実行される.multilingual-e5-smallベクトル埋め込みモデルがあります。text_embeddingモデルを、任意の埋め込みツール ( OpenAIGoogle AI StudioCohereなど) に置き換えることができることに注意してください。希望するモデルがまだ統合されていない場合は、独自の密なベクトル埋め込みも使用できます。

まず、特定のテキストのベクトルを生成するための推論エンドポイントを作成する必要があります。これらのコマンドはすべて、Kibana Dev Tools コンソールから実行します。このコマンドは.multilingual-e5-smallをダウンロードします。まだ存在しない場合はエンドポイントが設定されます。実行には 1 分ほどかかる場合があります。予想される出力は、Outputs フォルダー内のファイル01-create-an-inference-endpoint-output.jsonで確認できます。

PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}

これが返されると、モデルが設定され、次のコマンドを使用してモデルが期待どおりに動作するかどうかをテストできます。期待される出力は、Outputs フォルダー内のファイル02-embed-text-output.jsonで確認できます。

POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}

トレーニング済みのモデルがどのノードにも割り当てられないという問題が発生した場合は、モデルを手動で起動する必要がある場合があります。

POST _ml/trained_models/.multilingual-e5-small/deployment/_start

ここで、埋め込みモデルからの出力と一致するように、標準テキスト フィールド ( my_field ) と 384 次元の密なベクトル フィールド ( my_vector ) の 2 つのプロパティを持つ新しいマッピングを作成しましょう。index_options.type to bbq_hnswもオーバーライドします。予想される出力は、Outputs フォルダー内のファイル03-create-byte-qauntized-index-output.jsonで確認できます。

PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}

Elasticsearch がベクトルを生成するようにするには、 Ingest Pipelineを利用できます。このパイプラインには、エンドポイント ( model_id )、ベクトルを作成するinput_field 、およびそれらのベクトルを格納するoutput_fieldの 3 つが必要です。以下の最初のコマンドは、内部で推論サービスを使用する推論取り込みパイプラインを作成し、2 番目のコマンドはパイプラインが正しく動作していることをテストします。予想される出力は、Outputs フォルダー内のファイル04-create-and-simulate-ingest-pipeline-output.jsonで確認できます。

PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

POST _ingest/pipeline/my_inference_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "my_field": "my awesome text field"
      }
    }
  ]
}

これで、以下の最初の 2 つのコマンドを使用してドキュメントを追加し、3 番目のコマンドで検索が機能することをテストする準備が整いました。予想される出力は、Outputs フォルダー内のファイル05-bbq-index-output.jsonで確認できます。

PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}

この投稿で推奨されているように、圧縮の利点を活用しながら高い再現精度を維持するのに役立つため、大量のデータに拡張する場合は、再スコアリングとオーバーサンプリングが推奨されます。Elasticsearch バージョン 8.18 以降では、 rescore_vectorを使用してこの方法で実行できます。予想される出力は、Outputs フォルダー内のファイル06-bbq-search-8-18-output.jsonにあります。

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "rescore_vector": {
              "oversample": 3
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}

これらのスコアは、生データで得られるスコアと比べてどうでしょうか?上記のすべてをindex_options.type: hnswを使ってもう一度実行すると、スコアが非常に似ていることがわかります。予想される出力は、Outputs フォルダーのファイル07-raw-vector-output.jsonで確認できます。

PUT my-raw-vector-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "hnsw"
        }
      }
    }
  }
}

PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}

圧縮比のおおよその数値

ベクトル検索を使用する場合、ストレージとメモリの要件がすぐに大きな課題になる可能性があります。次の内訳は、さまざまな量子化手法によってベクター データのメモリ フットプリントがいかに劇的に削減されるかを示しています。

ベクトル(V)

寸法(D)

生(V x D x 4)

int8 (V x (D x 1 + 4))

int4 (V x (D x 0.5 + 4))

バーベキュー(V×(D×0.125+4))

10,000,000

384

14.31GB

3.61GB

1.83GB

0.58GB

50,000,000

384

71.53GB

18.07GB

9.13GB

2.89GB

1億

384

143.05GB

36.14GB

18.25GB

5.77GB

まとめ

BBQ は、精度を犠牲にすることなくベクター データを圧縮するために適用できる最適化です。これはベクトルをビットに変換することで機能し、データを効果的に検索できるようにし、AI ワークフローを拡張して検索を高速化し、データ ストレージを最適化できるようにします。

さらなる学習

バーベキューについてさらに詳しく知りたい場合は、次のリソースをぜひチェックしてください。

関連記事

LINQ to Elasticsearch ES|QL:C#を記述してElasticsearchをクエリ

Florian Bernd

Elasticsearch の NLP とベクトル検索によるチャットボット機能の強化

Priscilla Parodi

OpenSearchとElasticsearchの違い:ベクトル検索のパフォーマンス比較

Ugo Sangiorgi

AI盗作:Elasticsearchによる盗作検出

Priscilla Parodi

高度なRAGテクニックパート2:クエリとテスト

Han Xiang Choong

最先端の検索体験を構築する準備はできましたか?

十分に高度な検索は 1 人の努力だけでは実現できません。Elasticsearch は、データ サイエンティスト、ML オペレーター、エンジニアなど、あなたと同じように検索に情熱を傾ける多くの人々によって支えられています。ぜひつながり、協力して、希望する結果が得られる魔法の検索エクスペリエンスを構築しましょう。

はじめましょう