ベクトル検索の準備

BLOG

こんにちは。
サイオステクノロジーの田川です。

今回は、ベクトル検索のための準備を行います。

対象者

  • Elastic Cloud のアカウントを持っている人(トライアルライセンスを含む)
  • Elasticsearch の初心者~中級者

できるようになること

  • Elastic Cloud でベクトル検索を行うための準備が完了する。

    (今回は、まだ、ベクトル検索は行いません。)

前提条件

  • Elastic Cloud (version: 8.15.0)
  • Elastic Cloud 上のインデックスにデプロイメントを作成済

(2024年10月21日時点の情報を元に記載しています。)

1. ベクトル検索とは

前回、形態素解析を適用したことで、随分と日本語の検索がしやすくなりましたが、

“桃太郎の家来になった鳥は?”

のような検索を行っても望んでいるような検索結果にはなりません。
(検索しても関係が薄そうなドキュメントが多数ヒットしてしまいます。)

なぜなら、人間は ”きじ”が”鳥”の一種であること を知っていますが、
単なるキーワード検索では “きじ” != “鳥” となるため、
きじを含むドキュメントがヒットしないのです。

このような問題の解決策の一つがベクトル検索になります。

例)

  • “りんご”は、甘い果物。
  • “いちご”も甘い果物。
  • “犬”は(果物ではなく)動物

→ よって、”りんご” と “いちご” は近い関係にあり、”りんご” と “犬” は、それよりも遠い関係にある。

ベクトル検索を使うと、このように意味の近いものを検索しやすくなります。

また、

“桃から産まれたのは?”

をキーワード検索しても、関連が低そうなドキュメントが上位にヒットしてしまいます。

これは、”桃から産まれる” という文章の意味を Elasticsearch が理解していないため、
キーワード検索では、似たような意味のドキュメントを見つけることができないのです。

これもベクトル検索を行うことで、”桃から産まれる” に意味が近い(似ている)ドキュメントを
検索できるようになります。

(*脚注1)1

2. ベクトルの種類

ベクトル化を行う前に、ベクトルの種類について説明しておきます。

ベクトルの種類は大きく分けて次の2つになります。

  • 疎ベクトル
    • ほとんどの要素が0で、少数の0でない値を持つベクトル。
    • データ量が少なく、高速
    • 単純な単語の出現を示すことなど簡素な表現に向いている。
  • 密ベクトル
    • ほとんどの要素が0以外の値を持つベクトル。
    • データ量が多く、表現力が豊か。
    • 自然言語や画像などの表現に向いている。

Elasticsearch では、疎ベクトル(sparse_vector)も、密ベクトル(dense_vector)も、
どちらも利用可能です。

詳細な説明はここでは省略しますが、小説の文章を検索したい場合は
疎ベクトルよりも密ベクトルの方が適しているため、今回は密ベクトルを利用します。
(*脚注2)2

3. Elasticsearch でのベクトル検索の準備

Elasticsearch で日本語のベクトル検索を行いたい場合、準備作業として以下が必要となります。

  • 3.1. 日本語の形態素解析用のプラグインのインストール
  • 3.2. トレーニングされたモデルのインストール
  • 3.3. パイプラインの作成
  • 3.4. ベクトル値を格納するフィールドの作成

3.1 日本語の形態素解析用プラグインのインストール

これは前回 analysis-icu と analysis-kuromoji を追加インストール済なので、
ここでは説明は省略します。

3.2 トレーニングされたモデルのインストール

Elasticsearch では、日本語用にトレーニングされたモデルとして、あらかじめ、以下の2つが用意されています。

model次元数
.multilingual-e5-small_linux-x86_64384
cl-tohoku__bert-base-japanese-v3768

今回は、サンプルデータですので、軽量な .multilingual-e5-small_linux-x86_64 をインストールします。
(*脚注3)3

手順:

  1. Elastic Cloud へログインします。
  2. デプロイメント一覧からデプロイメント(このブログでは “SIOS-BLOG-SAMPLE-1” )の Manage をクリックします。
    deployment manage
  3. Edit
    左のメニューから Edit をクリックします。
    deployment edit
  4. Edit内の各種設定を行います。
    – Enable Autoscaling for:
    “Machine Learning only”
    となっていることを確認します。
    なっていない場合は、”Machine Learning only” に変更します。

    – Hot data and Content tier
    厳密なサイジングに関する説明は、ここでは割愛しますが、 Machine Learning を利用する場合、Hot data and Content tier に割り当てる zone は、 最低でも 2 にしておいた方がいいです。 ここでは、2 zones としておきます。(あくまでもサンプル用の設定です。本番運用時には、きちんとしたサイジング設計が必要です)
    deployment edit 1
    – Machine Learning instances
    Machine Learning instances の Minimum Size per zone は、4GB または、それ以上に変更します(モデルの展開に4GB程度必要になります)。
    deployment edit 2
    変更後、一番下の [Save]をクリックします。
    deployment edit save
    反映にしばらくかかるので、待ちます。
  1. 左のメニューから Kibana をクリックします。
    kibana
  2. Kibana を Open し、Home 画面へ戻ります。
    kibana open
  3. Home画面の左のメニューから Analytics の Machine Learning をクリックします。
    analytics machine learning
  4. Machine Learning の画面が表示されます。
    machine learning
  5. 左のメニューの中から、 Model Management の Trained Models をクリックします。
    model management trained models
  6. Elasticsearch であらかじめ用意されているモデルの一覧が表示されます。
    この右上の [Add trained model] をクリックします。
    model list
  7. 次に、下図のようなダイアログが表示されるので、
    Intel and Linux Optimized
    .multinlingual-e5-small_linux-x86_64
    を選択してください。(※ディフォルトでは、上部にある .elser_model_2_linux-x86_64 が選択されています。)
    .multinlingual-e5-small_linux-x86_64 を選択後、下の [Download] をクリックします。
    model select
  8. .multinlingual-e5-small_linux-x86_64 のダウンロードが開始されるので、しばらく待ちます。
  1. .multinlingual-e5-small_linux-x86_64 のダウンロードが終了すると、
    元のモデル一覧中の .multilingual-e5-small_linux-x86_64 のステータスが
    Ready to deploy に変わるので、右の3点メニューから ▷ Deploy をクリックします。
    model list
  1. 下図のようなダイアログが表示されるので、ディフォルトのまま [Start] をクリックします。
    start confirm

上図の設定値を転記すると、以下のようになります。

項目設定値
Deployment ID.multilingual-e5-small_linux-x86_64
Prioritynormal
Number of allocations1
Threads per allocation1
  1. .multilingual-e5-small_linux-x86_64 モデルのデプロイが始まり、
    モデル一覧中の .multilingual-e5-small_linux-x86_64 モデルのステータスが
    Starting deployment…
    に変わります。デプロイが終了するまで、しばらく待ちます。

    しばらく待っても変わらない場合は、右上の [Refresh] を押してみてください。
    (それでも変わらない場合は、再びしばらく待ってから[Refresh]を押してみてください。)
  1. デプロイが完了すると、
    モデル一覧中の .multilingual-e5-small_linux-x86_64 モデルのステータスが
    Deployed に変わります。
    model deployed

なお、機械学習モデルがインストールされることで、Elasticsearchの1時間あたりの利用料金が若干上昇します。

3.3 パイプラインの作成

前述でインストールしたモデルを使ってベクトル化を行えるよう、パイプラインを作成します。
Elasticsearch で言うところのパイプラインというのは、ざっくり説明すると、ドキュメント投入時に行われる処理になります。
pipeline
パイプラインで、何らかの処理が行われます。
(今回の記事の中では、文字列 → ベクトル化の処理)
参考URL
https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html

下記のリクエストを Elastic Cloud の Console に入力し、実行します。
パイプライン作成リクエスト:

PUT /_ingest/pipeline/japanese-text-embeddings
{
  "description" : "Text embedding pipeline",
  "processors" : [
    {
      "inference": {
        "model_id": ".multilingual-e5-small_linux-x86_64",
        "target_field": "text_embedding",
        "field_map": {
          "content": "text_field"
        }
      }
    }
  ]
}

上記で作成した japanese-text-embeddings パイプラインの意味は、次のとおりです。

content フィールドに格納された文字列を
.multilingual-e5-small_linux-x86_64 を使ってベクトル化し、
算出されたベクトル値を text_embedding フィールドに格納します。

作成したパイプラインをテストすることも可能です。

パイプラインのテスト用のリクエストを Console から発行してみます。

GET _ingest/pipeline/japanese-text-embeddings/_simulate
{
  "docs": [
    {
      "_source": {
        "content": "むかし、むかし、あるところに"
      }
    }
  ]  
}

レスポンス:

{
  "docs": [
    {
      "doc": {
        ...
        "_source": {
          "text_embedding": {
            "predicted_value": [
              ...
              384次元のベクトル値
              ...
            ],
            "model_id": ".multilingual-e5-small_linux-x86_64"
          },
          "content": "むかし、むかし、あるところに"
        },
        ...
      }
    }
  ]
}

うまく動いていそうです。

3.4 ベクトル値を格納するフィールドの作成

前述のパイプラインを利用してベクトル化された値を格納できるよう、新たなインデックスを作成します。

用途項目
インデックスインデックス名momotaro_v3
チャンク番号用フィールド名chunk_no
フィールドタイプinteger
本文用フィールド名content
フィールドタイプtext
登録用アナライザーja_kuromoji_index_analyzer
検索用アナライザーja_kuromoji_search_analyzer
ベクトル値(の親フィールド)用フィールド名text_embedding
フィールドタイプ(マルチフィールド)(*1)

(*1) text_embedding フィールドは、内部に別フィールド(model_id, predicted_value)を持つマルチフィールドとします。

マルチフィールドの詳細については、下記を参照してください。
(一般的なリレーショナルデータベースのカラムにはない考え方です)
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html#types-multi-fields
https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html

text_embedding 内の子フィールド

用途項目
ベクトルのモデルid用フィールド名model_id (*2)
フィールドタイプkeyword
最大長256
ベクトル値用フィールド名predicted_value
フィールドタイプdense_vector
次元数384

(*2) 今回の設定では、model_id に格納される値は、”.multilingual-e5-small_linux-x86_64″固定となります。

これらを踏まえて、インデックス “momotaro_v3” を作成します。

下記のリクエストを Elastic Cloud の Console に入力し、実行します。

momotaro_v3 インデックスの作成リクエスト:

PUT /momotaro_v3
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "refresh_interval": "3600s"
    },
    "analysis": {
      "char_filter": {
        "ja_normalizer": {
          "type": "icu_normalizer",
          "name": "nfkc_cf",
          "mode": "compose"
        }
      },
      "tokenizer": {
        "ja_kuromoji_tokenizer": {
          "mode": "search",
          "type": "kuromoji_tokenizer",
          "discard_compound_token": true,
          "user_dictionary_rules": [
            "きびだんご,きびだんご,キビダンゴ,カスタム名詞",
            "鬼が島,鬼が島,オニガシマ,カスタム名詞"
          ]
        }
      },
      "analyzer": {
        "ja_kuromoji_index_analyzer": {
          "type": "custom",
          "char_filter": [
            "ja_normalizer",
            "kuromoji_iteration_mark"
          ],
          "tokenizer": "ja_kuromoji_tokenizer",
          "filter": [
            "kuromoji_baseform",
            "kuromoji_part_of_speech",
            "cjk_width",
            "ja_stop",
            "kuromoji_number",
            "kuromoji_stemmer"
          ]
        },
        "ja_kuromoji_search_analyzer": {
          "type": "custom",
          "char_filter": [
            "ja_normalizer",
            "kuromoji_iteration_mark"
          ],
          "tokenizer": "ja_kuromoji_tokenizer",
          "filter": [
            "kuromoji_baseform",
            "kuromoji_part_of_speech",
            "cjk_width",
            "ja_stop",
            "kuromoji_number",
            "kuromoji_stemmer"
          ]
        }
      }
    }
  }
}

momotaro_v3 インデックスのマッピング作成のリクエスト:

PUT /momotaro_v3/_mappings
{
  "dynamic": false,
  "properties": {
    "chunk_no": {
      "type": "integer"     
    },
    "content": {
      "type": "text",
      "analyzer": "ja_kuromoji_index_analyzer",
      "search_analyzer": "ja_kuromoji_search_analyzer"
    },
    "text_embedding": {
      "properties": {
        "model_id": {
          "type": "keyword",
          "ignore_above": 256
        },
        "predicted_value": {
          "type": "dense_vector",
          "dims": 384
        }
      }
    }
  }
}

4. ドキュメントのコピー(Reindex)

Reindex API を使って、momotaro_v2 から momotaro_v3 へドキュメントをコピーします。
その際、pipelineに “japanese-text-embeddings” を指定します。
(これにより、momotaro_v3 の “content” フィールドへ値を格納する際にベクトル化の処理が行われ、算出されたベクトル値が “text_embedding” フィールドに格納されます。)

下記のリクエストを Elastic Cloud の Console に入力し、実行します。

Reindex リクエスト:

POST /_reindex?refresh=true
{
  "source": {
    "index": "momotaro_v2"
  },
  "dest": {
    "index": "momotaro_v3",
    "pipeline": "japanese-text-embeddings"
  }
}

これで、momotaro_v3 インデックスに桃太郎の本文と、本文から算出されたベクトル値が格納されます。

5. 登録されたドキュメントの確認

下記の検索リクエストを Console から発行してみます。

GET /momotaro_v3/_search

レスポンス:

{
  ...
  "hits": {
    ...
    "hits": [
      {
        "_index": "momotaro_v3",
        ...
        "_source": {
          "chunk_no": 1,
          "text_embedding": {
            "predicted_value": [
              0.09554096311330795,
              ...
              (384次元のベクトル)
              ...
            ],
            "model_id": ".multilingual-e5-small_linux-x86_64"
          },
         "content": "  むかし、むかし、あるところに、..."
        },
        ...
      }
    ]
  },
    ...
}

text_embedding.predicted_value フィールドにベクトル値が格納されていることがわかります。

お気づきかもしれませんが、Elasticsearch では、ベクトル化の処理を独自に実装しなくても
ベクトル化の処理が行われ、算出されたベクトル値が登録されます。

次回は、実際にベクトル検索を行ってみたいと思います。


  1. 同義語を定義することでキーワード検索でも似たような意味を持つドキュメントを検索することは可能ですが、
    この記事では、同義語に関しては考慮していません。
    ↩︎
  2. Elasticsearch で疎ベクトルを扱うことは可能ですが、v8.15.0 の時点では、
    ディフォルトで用意されている疎ベクトル用のモデル(ELSER)は英語の場合に推奨されるようです。日本語を利用する場合には、v8.15.0の時点では密ベクトルを利用するのがいいように思われます。

    https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html
    ↩︎
  3. 今回は、.multilingual-e5-small_linux-x86_64 のモデルをインストールしましたが、
    eland を使って、他のモデルをインストールすることも可能です。
    https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-models.html
    ↩︎