Elasticsearch 8.4 から利用可能な従来の検索機能と近似近傍探索を組み合わせたハイブリッド検索を試す
表題の通り、Elasticsearch 8.4 から待望の近似近傍探索と従来の検索を組み合わたハイブリッド検索が可能になったらしいので、試してみました。
Elascticsearch 8 で導入された近似近傍探索について
Elasticsearch 公式の記事1がわかりやすく近似近傍探索について語られています。 また、日本語では@pakio さんの紹介記事2も非常にわかりやすいので、そちらも御覧ください。
嬉しいけど物足りない点
公式の資料3や@pakio さんの資料でも触れられていますが、
- You can’t currently use the Query DSL to filter documents for an approximate kNN search. If you need to filter the documents, consider using exact kNN instead.
Elasticsearch の Query DSL との併用不可というのが物足りない点でした。
端的に説明すると Elasticsearch 8 で利用可能になった近似近傍探索は、あくまでベクトル間のみの近似近傍探索のみできるのであって、従来の Elasticsearch の検索機能(term や filter)と近似近傍探索を組み合わせて検索できないということです。
Vespa の開発者の Jo さんも同様の点4について触れていました。
The most surprising part of the announcement is that they won’t allow combining the nearest neighbor search with standard query terms and filters. I think that will be disappointing for many who have been waiting for this feature. That concludes this thread, thoughts 👇🧵 5/5
なぜならベクトルだけの素朴な近似近傍探索なら、正直ベクトル検索エンジンのQdrant、Milvus 、Valdでも事足ります。 検索エンジンの Elasticsearch に求められるのは、従来のフィルタリング機能と近似近傍探索を組み合わせたり、term による検索と近似近傍探索を組み合わせなど素朴な近似近傍探索では実現できない点を補える事ができれば近似近傍探索の実用可能性がグンと広がるので、ちょっと物足りないねというのが正直な感想でした。
それを実現するための関連するチケット5などはあったのでいつか実現してほしいなと思っていましたが、Linkedin の投稿で Elasticsearch のエンジニアの方が遂に実現できたよ!!と紹介しており6 、早速試してみたいと思います。
2022/10/30 追記: Elasticsearch version 8.2.0 | Elasticsearch Guide [8.2] | Elastic のリリースで
Integrate filtering support for ANN #84734 (issue: #81788)
と書かれており、ANN でのフィルタリングサポートは Elasticsearch 8.2 からサポートされていたみたいです。 なので正確には Elasticsearch 8.4 からは検索クエリが使えるようになったということですね。
ハイブリッド検索
Elasticsearch の公式ドキュメントはこちら7
このドキュメントを基に、
- 従来の検索機能
- 近似近傍探索のみによる検索結果
- 上記ふたつを組み合わせた検索結果
を一画面で表示できる Web アプリを作ったので結果を比較してみたいと思います。
データやインデキシング自体は前回作成した、Elasticsearch の近似近傍探索を使って、ドラえもんのひみつ道具検索エンジンを作ってみたを基に今回もコードは全て公開しています。release tag はv0.2
にしています。
https://github.com/hurutoriya/doraemon-himitsu-dogu-search/releases/tag/v0.2.0
前回からの変更点を主に書いていきます。
日本語検索の設定
Docker ファイルで日本語用の analyzer であるkuromoji
を追加し
FROM docker.elastic.co/elasticsearch/elasticsearch:8.4.3
# set the specific password for Elasticsearch
RUN echo "elastic" | bin/elasticsearch-keystore add "bootstrap.password" -xf
RUN bin/elasticsearch-plugin install analysis-kuromoji
RUN bin/elasticsearch-plugin install analysis-icu
Elasticsearch の mapping は zozo さんの記事8を参考にして以下のように書いてみました。
{
"settings": {
"analysis": {
"analyzer": {
"custome_ja_analyzer": {
"type": "custom",
"char_filter": ["icu_normalizer"],
"tokenizer": "kuromoji_tokenizer",
"filter": [
"kuromoji_baseform",
"kuromoji_part_of_speech",
"ja_stop",
"kuromoji_number",
"kuromoji_stemmer"
]
}
}
}
},
"mappings": {
"properties": {
"vector": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "l2_norm"
},
"name": {
"type": "text",
"analyzer": "custome_ja_analyzer"
},
"yomi": {
"type": "text",
"analyzer": "custome_ja_analyzer"
},
"description": {
"type": "text",
"analyzer": "custome_ja_analyzer"
}
}
}
}
kuromoji のおかげで前処理を行わなくても、日本語で解析ができるようになります。
実際に起動してインデックスを構築した、Elasticsearch が動く Docker にリクエストを投げてみると、analyzer によって処理がされます。 検索時にはこれらの tokens が転置インデックス上で検索されます。
curl --cacert http_ca.crt -u elastic:elastic -X POST "https://localhost:9200/himitsu_dogu/_analyze?pretty" -H 'Content-Type: application/json' -d'{"analyzer": "custome_ja_analyzer","text":"ドラえもんのひみつ道具はどこでもドア以外にもたくさん存在する"}'
{
"tokens" : [
{
"token" : "ドラえもん",
"start_offset" : 0,
"end_offset" : 5,
"type" : "word",
"position" : 0
},
{
"token" : "ひむ",
"start_offset" : 6,
"end_offset" : 8,
"type" : "word",
"position" : 2
},
{
"token" : "道具",
"start_offset" : 9,
"end_offset" : 11,
"type" : "word",
"position" : 4
},
{
"token" : "どこ",
"start_offset" : 12,
"end_offset" : 14,
"type" : "word",
"position" : 6
},
{
"token" : "ドア",
"start_offset" : 16,
"end_offset" : 18,
"type" : "word",
"position" : 8
},
{
"token" : "以外",
"start_offset" : 18,
"end_offset" : 20,
"type" : "word",
"position" : 9
},
{
"token" : "たくさん",
"start_offset" : 22,
"end_offset" : 26,
"type" : "word",
"position" : 12
},
{
"token" : "存在",
"start_offset" : 26,
"end_offset" : 28,
"type" : "word",
"position" : 13
}
]
}
実際に簡単な multi match query(複数フィールドでの一致検索)の結果はこちら
$ curl --cacert http_ca.crt -u elastic:elastic -X POST "https://localhost:9200/himitsu_dogu/_search?pretty" -H 'Content-Type: application/json' -d'{"size": 1, "query": {"multi_match" : {"query":"どこでもドア", "fields": [ "name", "description" ] }}}'
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 44,
"relation" : "eq"
},
"max_score" : 11.774845,
"hits" : [
{
"_index" : "himitsu_dogu",
"_id" : "816",
"_score" : 11.774845,
"_source" : {
"id" : 816,
"name" : "どこでもドア",
"yomi" : "どこでもどあ",
"description" : "このドアを開けるだけで、行きたいところへ、どこへでも行くことができる。",
"vector" : [
-0.23546676337718964,
...
-0.37538981437683105
]
}
}
]
}
}
これで、Elasticsearch で日本語検索が問題なく出来ていることが確認できたので、ハイブリッド検索についての説明に
結果比較
比較対象
multi match query
, ANN
, multi match
+ ANN
の三種類のクエリで検索結果を定性的に比較します。
Python 上ではクエリは以下のような形式で書いてみました。TOP_K
は 10 で、keyword
は入力クエリです。
multimatch_query: dict = {
"size": TOP_K,
"query": {"multi_match": {"query": keyword, "fields": ["name", "description"]}},
}
ann_query: dict = {
"knn": {
"field": "vector",
"query_vector": sentence_embeddings[0],
"k": TOP_K,
"num_candidates": 100,
}
}
# NOTE: score = match score + ANN score
hybrid_query: dict = {
"size": TOP_K,
"query": {"multi_match": {"query":keyword, "fields": ["name", "description"]}},
"knn": {
"field": "vector",
"query_vector": sentence_embeddings[0],
"k": TOP_K,
"num_candidates": 100,
},
}
multimatch_query
: ひみつ道具の名前と説明文の複数フィールドでマッチするドキュメントを検索するann_query
: ひみつ道具の名前と説明文を空白区切りで結合された文章をJapaneseSententsBERT
でベクトル化し、近似近傍探索を行うhybrid_query
: 上記2つのクエリのスコアを足し合わせた結果(match score
+ANN score
)を検索結果として返す。- 公式ドキュメントでは、
boost
を使うことで係数を書けて調整する方法も紹介されています。
- 公式ドキュメントでは、
実際にデモアプリの Streamlit 上では、上記3つのクエリの検索結果が side by side で比較できるようにしてみました。 これで、各検索手法で結果がどう異なっているか一目瞭然ですね。
Streamlit で得られた結果を表として以下にまとめました。
クエリ: AI
multi mach | スコア | ひみつ道具の名前 | 説明 | ANN | スコア | ひみつ道具の名前 | 説明 | hybrid(match score + ANN score | スコア | ひみつ道具の名前 | 説明 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 5.3606 | エーアイアイ | 高性能(せいのう)の AI(エーアイ/人工知能)が搭載(とうさい)されているサル型ロボット。一から教えてあげると、なんでも自分で考えて行動する。 | 1 | 0.0027 | なんでもデリバリーバックパック | デリバリー(配達)用のひみつ道具。中は四次元になっているため、いくらでも配達品を入れることができ、ゆれてもこわれることなく運べる。大きなポケットには、目的地まで自動運転してくれ、ひとこぎで10メートル進む自転車が入っている。運転しながら食べたり飲んだりできる装置(そうち)も付いており、雨がふれば自動でカサもさしてくれる。 | 1 | 5.3606 | エーアイアイ | 高性能(せいのう)の AI(エーアイ/人工知能)が搭載(とうさい)されているサル型ロボット。一から教えてあげると、なんでも自分で考えて行動する。 |
2 | 0.0027 | なりきりケイドロセット | 本格的なおにごっこが楽しめる未来の遊び道具。通信もできる警察(けいさつ)手帳型のバッジや『強力におい追跡鼻(ついせきばな)』、身につけると体力が三割増しになる『泥棒風呂敷(どろぼうふろしき)』、『牢屋(ろうや)シール』などが入っている。 | 2 | 0.0027 | なんでもデリバリーバックパック | デリバリー(配達)用のひみつ道具。中は四次元になっているため、いくらでも配達品を入れることができ、ゆれてもこわれることなく運べる。大きなポケットには、目的地まで自動運転してくれ、ひとこぎで10メートル進む自転車が入っている。運転しながら食べたり飲んだりできる装置(そうち)も付いており、雨がふれば自動でカサもさしてくれる。 | ||||
3 | 0.0026 | 地下工事マシン | この機械に設計図を描いた設計紙を入れ、コンクリートボンベを乗せてバルブを開くと、その設計図通りの空間を地下に作ってくれる。 | 3 | 0.0027 | なりきりケイドロセット | 本格的なおにごっこが楽しめる未来の遊び道具。通信もできる警察(けいさつ)手帳型のバッジや『強力におい追跡鼻(ついせきばな)』、身につけると体力が三割増しになる『泥棒風呂敷(どろぼうふろしき)』、『牢屋(ろうや)シール』などが入っている。 | ||||
4 | 0.0026 | ミチサキステッキ | どちらの道を進めばいいか迷ったときに使う道具。正しい道の方向に倒れて、道を教えてくれる。 | 4 | 0.0026 | 地下工事マシン | この機械に設計図を描いた設計紙を入れ、コンクリートボンベを乗せてバルブを開くと、その設計図通りの空間を地下に作ってくれる。 | ||||
5 | 0.0026 | ウラオモテックス | これを体に貼ると、裏でコソコソやっていたことや心の中で思っていることを、みんなの前で大っぴらにやってしまう。 | 5 | 0.0026 | ミチサキステッキ | どちらの道を進めばいいか迷ったときに使う道具。正しい道の方向に倒れて、道を教えてくれる。 | ||||
6 | 0.0026 | かべ景色きりかえ機 | かべを通して、どこの景色でも見ることができる。実際(じっさい)にうつし出した場所とつながるので、かべから出入りもできるようになる。 | 6 | 0.0026 | ウラオモテックス | これを体に貼ると、裏でコソコソやっていたことや心の中で思っていることを、みんなの前で大っぴらにやってしまう。 | ||||
7 | 0.0026 | クロマキーセット | グリーンバックやブルーバックを背景(はいけい)にして、セットのカメラで撮影(さつえい)すると、映(うつ)したものを実際(じっさい)の景色にはめこむことができる。 | 7 | 0.0026 | かべ景色きりかえ機 | かべを通して、どこの景色でも見ることができる。実際(じっさい)にうつし出した場所とつながるので、かべから出入りもできるようになる。 | ||||
8 | 0.0026 | 断層ビジョン | 地中や機械の内部、人間の体の中など、外から見えないところを画面に映し出し、調べることができる。映し出したところに付属の旗を立てると、実際のその場所にも旗が立ち、目印になる。 | 8 | 0.0026 | クロマキーセット | グリーンバックやブルーバックを背景(はいけい)にして、セットのカメラで撮影(さつえい)すると、映(うつ)したものを実際(じっさい)の景色にはめこむことができる。 | ||||
9 | 0.0026 | ゆっくり反射ぞうきん | このぞうきんで鏡やガラスを拭くと過去が映る。拭けば拭くほど、映る時間が過去に戻っていく。 | 9 | 0.0026 | 断層ビジョン | 地中や機械の内部、人間の体の中など、外から見えないところを画面に映し出し、調べることができる。映し出したところに付属の旗を立てると、実際のその場所にも旗が立ち、目印になる。 | ||||
10 | 0.0026 | とうしめがね | ムシめがねのようだが、これをつかってモノを見ると、中のようすがすけて見える。 | 10 | 0.0026 | ゆっくり反射ぞうきん | このぞうきんで鏡やガラスを拭くと過去が映る。拭けば拭くほど、映る時間が過去に戻っていく。 |
ANN
では検索対象になっていなかった秘密道具である「エーアイアイ」をmulti match
クエリでは検索できており、ハイブリッド検索によってmulti match
とANN
がお互いを補うあうことで、主観的な評価ですが、検索結果になっていることが分かります。
クエリ: ガリバー
multi match | スコア | ひみつ道具の名前 | 説明 | ANN | スコア | ひみつ道具の名前 | 説明 | hybrid(match score + ANN score | スコア | ひみつ道具の名前 | 説明 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 6.9183 | ガリバートンネル | 大きな入り口から入って、小さな出口から出ると、からだが小さくなるトンネル。逆にくぐりぬけると、もとに戻る。 | 1 | 0.0029 | ガリバーロープ | 巨大なものをかんたんにしばりつけることができるロープ。 | 1 | 6.9211 | ガリバーロープ | 巨大なものをかんたんにしばりつけることができるロープ。 |
2 | 6.9183 | ガリバーロープ | 巨大なものをかんたんにしばりつけることができるロープ。 | 2 | 0.0027 | なりきりケイドロセット | 本格的なおにごっこが楽しめる未来の遊び道具。通信もできる警察(けいさつ)手帳型のバッジや『強力におい追跡鼻(ついせきばな)』、身につけると体力が三割増しになる『泥棒風呂敷(どろぼうふろしき)』、『牢屋(ろうや)シール』などが入っている。 | 2 | 6.9183 | ガリバートンネル | 大きな入り口から入って、小さな出口から出ると、からだが小さくなるトンネル。逆にくぐりぬけると、もとに戻る。 |
3 | 5.8304 | 箱庭シリーズ 湖 | 大自然のミニチュアシリーズのひとつ。スモールライトやガリバートンネルで小さくなれば、この中で遊ぶことができる。 | 3 | 0.0027 | スーパーダンごっこふろしき | 未来の子どもたちがスーパーヒーローごっこで使うオモチャのふろしき。これを身につけると、低空ながら飛ぶことができ、空気銃の弾(たま)もはね返し、ものを透(す)かして見ることもできる。 | 3 | 5.8304 | 箱庭シリーズ 湖 | 大自然のミニチュアシリーズのひとつ。スモールライトやガリバートンネルで小さくなれば、この中で遊ぶことができる。 |
4 | 4.6887 | ミニハウス | エネルギーのムダをはぶくために作られた、ミニハウス。乾電池一本でクーラーや冷蔵庫などの家電製品をすべて動かすことができる。ガリバートンネルで小さくなって使う。 | 4 | 0.0026 | ゴキブリトレとれビッグ | ゴキブリをとる道具を、人間用に大きくしたもの。入ったら最後、ねんちゃくシートにつかまってしまう。 | 4 | 4.6887 | ミニハウス | エネルギーのムダをはぶくために作られた、ミニハウス。乾電池一本でクーラーや冷蔵庫などの家電製品をすべて動かすことができる。ガリバートンネルで小さくなって使う。 |
5 | 4.5612 | ムシムシ操縦ハンドル | このハンドルを自分が乗りたい虫に向けて、真ん中のボタンを押すと、その虫の背中にハンドルがくっつき、背中に乗って操縦することができる。ただし、ガリバートンネルなどで身体を小さくしてから使う。 | 5 | 0.0026 | 実景ひきよせ額縁 | 額縁の横にあるダイヤルを回すと、海でも雪山でもジャングルでも、実際にある景色をひきよせることができる。額縁のガラスを外してくぐれば、その景色の場所に行くこともできる。 | 5 | 4.5612 | ムシムシ操縦ハンドル | このハンドルを自分が乗りたい虫に向けて、真ん中のボタンを押すと、その虫の背中にハンドルがくっつき、背中に乗って操縦することができる。ただし、ガリバートンネルなどで身体を小さくしてから使う。 |
6 | 0.0026 | ぼんのうボウシ | これを頭にかぶると、その人の煩悩(ぼんのう)が雲のようなものに映像(えいぞう)となってうかび上がり、釣鐘(つりがね)のかたちにまとまる。それを撞木(しゅもく)型のハンマーでたたくと、それらの煩悩が追い払(はら)われる。 | 6 | 0.0027 | なりきりケイドロセット | 本格的なおにごっこが楽しめる未来の遊び道具。通信もできる警察(けいさつ)手帳型のバッジや『強力におい追跡鼻(ついせきばな)』、身につけると体力が三割増しになる『泥棒風呂敷(どろぼうふろしき)』、『牢屋(ろうや)シール』などが入っている。 | ||||
7 | 0.0025 | ねがい七夕ロケット | 専用(せんよう)の短冊(たんざく)に願いごとを書いて笹(ささ)につけ、笹ごとロケットで宇宙(うちゅう)に飛ばすと、一年間その願いごとをかなえてくれる。短冊に書いたことと反対のことがかなう「うら七夕ロケット」もある。 【うら七夕ロケット】 専用(せんよう)の短冊(たんざく)に願いごとを書いて笹(ささ)につけ、笹ごとロケットで宇宙(うちゅう)に飛ばすと、一年間、短冊に書いたことと反対のことがかなう。 | 7 | 0.0027 | スーパーダンごっこふろしき | 未来の子どもたちがスーパーヒーローごっこで使うオモチャのふろしき。これを身につけると、低空ながら飛ぶことができ、空気銃の弾(たま)もはね返し、ものを透(す)かして見ることもできる。 | ||||
8 | 0.0025 | フエルミラー | 増やしたいものをこのカガミにうつすと、本物になって出てくる。 | 8 | 0.0026 | ゴキブリトレとれビッグ | ゴキブリをとる道具を、人間用に大きくしたもの。入ったら最後、ねんちゃくシートにつかまってしまう。 | ||||
9 | 0.0025 | ふわふわぐすり | これを口に入れると、体の中にガスができて、空気より軽くなるため、空を歩いたり、飛んだりすることができる。 | 9 | 0.0026 | 実景ひきよせ額縁 | 額縁の横にあるダイヤルを回すと、海でも雪山でもジャングルでも、実際にある景色をひきよせることができる。額縁のガラスを外してくぐれば、その景色の場所に行くこともできる。 | ||||
10 | 0.0025 | 乗りものぐつ | これに両足を入れてボタンを押すと、車やジェット機、潜水艦(せんすいかん)など、いろいろな乗りものになる。 | 10 | 0.0026 | ぼんのうボウシ | これを頭にかぶると、その人の煩悩(ぼんのう)が雲のようなものに映像(えいぞう)となってうかび上がり、釣鐘(つりがね)のかたちにまとまる。それを撞木(しゅもく)型のハンマーでたたくと、それらの煩悩が追い払(はら)われる。 |
multi match
クエリでは、「ガリバー」とう単語が全てマッチし検索結果としては申し分ありません。
ANN
は「ガリバー」というクエリによって、「ガリバーロープ」はヒットしていますが「ガリバートンネル」はなぜか出てきていません。
ハイブリッド検索によって、上位 5 個はmulti match
クエリによる適合率(precision)が高いひみつ道具がランク付けされ、その後 ANN の結果が混ざってきています。正直「ガリバー」という単語から想起するようなひみつ道具とはあまり感じませんが、特徴ベクトルの閾値などで上手く枝刈りさえしておけば、質は担保しつつ多様性のある結果が確保できそうです。
実際はこんなに単純な multi match
計算ではなく、BM25 で計算されたスコアを導入したりなど工夫すべき点は多々ありますが、ハイブリッド検索の可能性を感じることができました。
特徴ベクトルのチューニングや、既存のクエリとの組み合わせるためのチューニングなどやることは増えそうですが、これは夢が広がりますね。 実際にはスコアを組み合わせたりするのもそうですが、ANN の結果にフィルタリングするほうが実務的には簡単に役立つことが多そうな気がしました。
たとえば、特定のカテゴリ内で ANN するだけでも、 「この商品に見た目が似ている商品はこちらです」を提示する際に画像としては似ているが商品としては全く違う商品(iPhone と iPhone ケースなど)を分けるのも簡単にできちゃいますしね。
まとめ
このハイブリッド検索によって近似近傍探索のスコアと従来の検索結果のスコアをかけあわせたり、フィルタリングしたりなど真の意味で近似近傍探索が Elasticsearch で活用可能になったのではないのでしょうか?
Introducing approximate nearest neighbor search in Elasticsearch 8.0 | Elastic Blog ↩︎
8.0 からの kNN はどう変わったのか / How kNN search changed in the Elasticsearch 8.0 - Speaker Deck ↩︎
k-nearest neighbor (kNN) search | Elasticsearch Guide [8.0] | Elastic ↩︎
https://twitter.com/jobergum/status/1491366596665016321 Jo さんの皮肉な検索エンジン批評は自分の中で名物になっている ↩︎
- [LUCENE-10318] Reuse HNSW graphs when merging segments? - ASF JIRA
- [LUCENE-10382] Allow KnnVectorQuery to operate over a subset of liveDocs - ASF JIRA
- [LUCENE-10592] Should we build HNSW graph on the fly during indexing - ASF JIRA
Elasticsearch 8.4 introduces a hybrid search by Mayya Sharipova Elasticsearch 8.4 introduces a hybrid search: ability to combine results from knn search with traditional search features (queries, aggs etc) and all this under a single familiar _search API. ↩︎
https://www.elastic.co/guide/en/elasticsearch/reference/8.4/knn-search.html#_combine_approximate_knn_with_other_features ↩︎
関連しているかもしれない記事
- Elasticsearchの近似近傍探索を使って、ドラえもんのひみつ道具検索エンジンを作ってみた
- デスクトップのGoogle 検索の検索フォームUIがかなり変化していた
- Amazon の製品検索で使われるロバストなキャッシュ手法の論文「ROSE: Robust Caches for Amazon Product Search」
- Web 検索とデータマイニングのトップカンファレンス WSDM2022 で気になった研究
- Search Engineering Newsletter vol.00
📮 📧 🐏: 記事への感想のおたよりをおまちしてます。 お気軽にお送りください。 メールアドレス入力があればメールで返信させていただきます。 もちろんお返事を希望せずに単なる感想だけでも大歓迎です。
このサイトの更新情報をRSSで配信しています。 お好きなフィードリーダーで購読してみてください。
このウェブサイトの運営や著者の活動を支援していただける方を募集しています。 もしよろしければ、Buy Me a Coffee からサポート(投げ銭)していただけると、著者の活動のモチベーションに繋がります✨
Amazonでほしいものリストも公開しているので、こちらからもサポートしていただけると励みになります。