Elasticsearchでknnクエリーを利用して、ベクトル検索を試すことができるの試してみる。まず、Elasticsearchを起動する。
git clone https://github.com/codelibs/docker-elasticsearch.git
cd docker-elasticsearch
docker compose -f compose.yaml up -d
という感じで、Dockerがあれば、http://localhost:9200でアクセスできるElasticsearchが起動できる。
ML機能はライセンスが必要なので、以下でトライアルを有効にする。
curl -XPOST -H "Content-Type:application/json" "http://localhost:9200/_license/start_trial?acknowledge=true"
ElasticsearchのML機能を利用して、テキストデータを渡されたら、ベクトルに変換してくれるようにingestを設定して、ingestでテキストからベクトルを推論するモデルを登録していく。
まずは、モデルを登録していくのが、モデルをインポートするためには、Pythonのelandを用いて、登録することになる。そのために、Pythonモジュールをインストールする。今回は、日本語を利用するので、以下のようなモジュールを入れておく。
pip install 'eland[pytorch]'
pip install fugashi
pip install unidic-lite
次はモデルのインポート。今回はtohoku/bert-base-japanese-v2を指定する。(今回は、簡単に試せるように、httpsなどのsecurityを無効な状態にしてあるので、ユーザー名などの指定が不要である)
eland_import_hub_model --url http://localhost:9200 --hub-model-id cl-tohoku/bert-base-japanese-v2 --task-type text_embedding
インポートできたら、モデルをデプロイする。
curl -XPOST -H "Content-Type:application/json" "http://localhost:9200/_ml/trained_models/cl-tohoku__bert-base-japanese-v2/deployment/_start"
ingestを登録して、テキストがベクトルに変換できるようにする。
curl -XPUT -H "Content-Type:application/json" "http://localhost:9200/_ingest/pipeline/text_embedding" -d '
{
"description": "Text embedding pipeline",
"processors": [
{
"inference": {
"model_id": "cl-tohoku__bert-base-japanese-v2",
"target_field": "content_vector",
"field_map": {
"content": "text_field"
}
}
}
]
}
'
contentフィールドにテキスト情報を入れる。ベクトルは、content_vectorオブジェクトに入る。ベクトル自体はcontent_vector.predicted_valueに格納される。
ここまでくれば、下準備ができたので、次にデータを入れるインデックスを作る。
curl -XPUT -H "Content-Type:application/json" "http://localhost:9200/docs" -d '
{
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "standard"
},
"content_vector.model_id": {
"type": "keyword"
},
"content_vector.predicted_value": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "l2_norm"
}
}
}
}
'
検証なので、contentにデータを入れるシンプルなインデックスです。このインデックスにデータを入れrます。テスト用のデータは、ChatGPTに10件生成してもらったので、それを入れます。
curl -XPOST -H "Content-Type: application/x-ndjson" "http://localhost:9200/_bulk?pipeline=text_embedding" -d '
{"index": {"_index": "docs"}}
{"content": "明日の天気は晴れの予報です。"}
{"index": {"_index": "docs"}}
{"content": "彼女が好きな色はピンクです。"}
{"index": {"_index": "docs"}}
{"content": "日本語を勉強しています。"}
{"index": {"_index": "docs"}}
{"content": "新しいレストランがオープンしました。"}
{"index": {"_index": "docs"}}
{"content": "今日は疲れたので早く寝ます。"}
{"index": {"_index": "docs"}}
{"content": "おいしいコーヒーを飲みました。"}
{"index": {"_index": "docs"}}
{"content": "新しいスマートフォンが発売されます。"}
{"index": {"_index": "docs"}}
{"content": "友達と映画を見に行きました。"}
{"index": {"_index": "docs"}}
{"content": "最近、本を読む習慣を始めました。"}
{"index": {"_index": "docs"}}
{"content": "子供たちが公園で遊んでいます。"}
'
シンプルなデータなので、面白みにかけますが、検索は以下のリクエストを投げると、結果が帰ってきます。
curl -XPOST -H "Content-Type: application/json" "http://localhost:9200/docs/_search" -d '
{
"knn": {
"field": "content_vector.predicted_value",
"query_vector_builder": {
"text_embedding": {
"model_id": "cl-tohoku__bert-base-japanese-v2",
"model_text": "彼女が好きな色は?"
}
},
"k": 3,
"num_candidates": 10
},
"_source": [
"content"
]
}
'
「彼女が好きな色はピンクです。」が1件目で取得できると思いますが、2件目以降は類似度が高い順に返ってくるだけです。
という感じですが、詳しくはElasticsearchのドキュメントを参照するのが良いと思います。