はじめに
ベクトル検索は目的のドキュメントをキーワードマッチ以外の意味的な近さで見つけたり、テキスト以外のマルチモーダルな画像や音声などの情報の検索に使ったりといったことが可能で、特にここ1・2年は生成AIと組み合わせたRAGというユースケースの登場で非常に広く利用されるようになりました。ただElasticsearchでベクトル検索を利用する場合、最初のセットアップをするまでに必要な工程が煩雑であることは否めません。ベクトル検索の詳しい手順は他の記事で書いていますのでそちらを参照してください。
そこでElasticsearchではバージョン8.15からsemantic_textというフィールド型を導入しました。このsemantic_textを利用するとベクトル検索のセットアップが格段に容易になりますので、この記事ではその方法を紹介したいと思います。
バージョン8.16の時点で、semantic_text
フィールドはbetaです。また利用にはPratinum Licenseが必要です。参照
semantic_textフィールドの特徴
semantic_text
フィールドは、テキストデータに対するセマンティック(意味)検索実現のために特化したフィールドタイプです。これまでベクトル検索を利用してセマンティック検索を実現しようと思うと、インデックスに対してdense_vector
フィールドを定義し、利用するモデルが出力する次元数のベクトルを保存できるようにセットアップするなどの、比較的複雑な設定が必要でした。この定義にはある程度利用するMLモデルがどのようなものであるかの技術的な知識が求められます。しかしsemantic_text
フィールドを使うと、Inference Endpoint経由でどのMLモデル(エンドポイント)を利用するかを決めるだけで、適切な形でテキストをembeddingし、インデックスに保存したり検索したりすることができます。
また、それぞれのモデルは1回でembeddingできるテキストの大きさに上限があり、大きなテキストを扱うにはチャンキングという、テキストを小さなチャンクに分割するテクニックを独自に実装する必要がありました。しかしsemantic_text
フィールドを利用すると、このチャンキングもElasticsearchが自動で行ってくれるようになります。
実装の流れ
semantic_text
フィールドを利用した検索機能実装の流れは以下のようになります。
- Inference Endpointを作成
- Index定義
- データ投入
- 検索実行
このように書くとほとんど通常の検索と同じで実装できそうなことがわかりますね。それぞれ見ていきましょう。
Inference Endpointの作成
Elasticsearchの上でテキストデータをベクトルデータにembeddingするには、適切なMLモデルをElasticsearchのMLノードにデプロイする必要があります。具体的に利用したいモデルがHugging Faceで公開されている場合は、そのモデルをElandを使ってまずElasticsearchにアップロードする必要がありますが、ここではElasticがデフォルトでサポートしているE5モデルを利用することにします。E5モデルは日本語を含む多言語に対してembeddingをすることのできる大変便利なモデルです。Hugging Faceからのモデルアップロードについてはこちらの記事を参照してください。
モデルがElasticsearchにデプロイされたら、Create inference APIを利用してInference Endpointを作成します。
PUT _inference/text_embedding/my-e5-model
{
"service": "elasticsearch",
"service_settings": {
"num_allocations": 1,
"num_threads": 1,
"model_id": ".multilingual-e5-small"
}
}
これでmy-e5-model
という名前のエンドポイントが作成されました。試しにこのエンドポイントを使って日本語テキストをembeddingしてみましょう。
POST _inference/text_embedding/my-e5-model
{
"input": "試しにこのエンドポイントを使って日本語テキストをembeddingしてみましょう。"
}
すると以下のような結果が得られるはずです。
{
"text_embedding": [
{
"embedding": [
0.09948257,
-0.040724847,
-0.01904885,
(...)
]
}
]
}
Index定義とデータの投入
エンドポイントが作成できたら、これを使ってsemantic_text
フィールドを定義します。今回は単に"content"というフィールドを持つだけのドキュメントを扱うことにしましょう。
Index定義は以下のようになります。
PUT semantic_text_test
{
"mappings": {
"properties": {
"content": {
"type": "text",
"copy_to": "content_semantic"
},
"content_semantic": {
"type": "semantic_text",
"inference_id": "my-e5-model"
}
}
}
}
content_semantic
フィールドにsemantic_text
フィールドタイプを適用しています。したがってこのcontent_semantic
フィールドを利用してセマンティック検索が実現できます。このフィールドに対して直接データを投入しても良いのですが、そうすると元の本文の情報が失われてしまうため、まずはtext型のcontent
フィールドに対してデータをIngestして、そこからcopy_toを使ってテキストデータをcontent_semantic
フィールドにコピーしています。
実際にデータを投入してみましょう。
POST semantic_text_test/_doc/1
{
"content": """# はじめに
ベクトル検索は目的のドキュメントをキーワードマッチ以外の意味的な近さで見つけたり、テキスト以外のマルチモーダルな画像や音声などの情報の検索に使ったりといったことが可能で、特にここ1・2年は生成AIと組み合わせたRAGというユースケースの登場で非常に広く利用されるようになりました。
"""
}
内容を確認します。
GET semantic_text_test/_search
結果
{
"hits": {
"hits": [
{
"_index": "semantic_text_test",
"_id": "1",
"_score": 1,
"_source": {
"content_semantic": {
"inference": {
"inference_id": "my-e5-model",
"model_settings": {
"task_type": "text_embedding",
"dimensions": 384,
"similarity": "cosine",
"element_type": "float"
},
"chunks": [
{
"text": """# はじめに
ベクトル検索は目的のドキュメントをキーワードマッチ以外の意味的な近さで見つけたり、テキスト以外のマルチモーダルな画像や音声などの情報の検索に使ったりといったことが可能で、特にここ1・2年は生成AIと組み合わせたRAGというユースケースの登場で非常に広く利用されるようになりました。
""",
"embeddings": [
0.01843889,
-0.04999397,
-0.049595766,
-0.042462178,
0.08888733,
(...)
]
}
]
}
},
"content": """# はじめに
ベクトル検索は目的のドキュメントをキーワードマッチ以外の意味的な近さで見つけたり、テキスト以外のマルチモーダルな画像や音声などの情報の検索に使ったりといったことが可能で、特にここ1・2年は生成AIと組み合わせたRAGというユースケースの登場で非常に広く利用されるようになりました。
"""
}
}
]
}
}
embedding結果がchunks
という配列に含まれていることがわかります。適切にチャンキングされた上でembeddingされてインデックスされているようですね。
検索
semantic_text
フィールドに対する検索にはsemanticクエリーが利用できます。
GET semantic_text_test/_search
{
"query" : {
"semantic": {
"field": "content_semantic",
"query": "イメージを見つける方法"
}
}
}
とても自然な書き方なので、この記事で初めてElasticsearchのベクトル検索を実装する方には何の驚きもないかもしれないですが(それは良いことなのですが)、これまではベクトル検索が実現できるようになったときにはknnクエリーを使う必要があり、外部でembeddingした数値の配列を渡したり、あるいはここでクエリーをembeddingするためのモデル名を書いたりする必要があったことを考えると、かなりAPIも洗練されてきたように感じます。
ちなみに上記と同等のクエリーをknnで書くと以下のようになります。
GET semantic_text_test/_search
{
"query": {
"nested": {
"path": "content_semantic.inference.chunks",
"query": {
"knn": {
"field": "content_semantic.inference.chunks.embeddings",
"query_vector_builder": {
"text_embedding": {
"model_id": "my-e5-model",
"model_text": "イメージを見つける方法"
}
}
}
}
}
}
}
おわりに
この記事ではsemantic_text
フィールドを使った検索について紹介しました。これまで比較的設定の複雑だったベクトル検索が、かなり簡単に利用できるようになったことがわかると思います。Elasticsearch 8.15以降で利用できますので、ぜひ活用してください。
ただ、生成AIの影響でNLP周りの機能についてはElasitcsearchでも活発に開発が続いています。semantic_text
フィールドも8.16の時点でまだbetaです。新しいバージョンでは新機能が追加されたり、より効率的な実装・設定方法が提供されたりすることがありますので、ぜひElastic Search labsブログなどをウォッチしてキャッチアップしてください。