こんにちは、検索チームのよこやまです。
最近単語の位置を考慮した検索について調べる機会があり、ElasticsearchのIntervalsクエリを確認していたため、調べた一部をご紹介できればと思います。
Intervalsクエリは意外と使用している例が見つかりづらく、パッと見挙動も分かりづらいため、ここでご紹介した内容がお役に立てれば幸いです。
※ Intervalsクエリは「単語の位置」ではなく「トークンの位置」を考慮しますが、この記事では以下の前提で述べるように英文に対してstandard analyzerを適用するため、単語とトークンを同じものとして表記します
前提
- Elasticsearch 7.14
- 以下のmappingを使用します
{
"mappings": {
"properties": {
"contents": {
"type": "text",
"analyzer": "standard"
}
}
}
}
- 以下のドキュメントに対して検索することを想定します
{
"contents": "VQ (VisasQ) is a leading global expert network service headquartered in Japan."
}
Query stringクエリで近接検索(Proximity searches)を行う
Intervalsクエリを取り上げる前に、 Query stringクエリでも近接検索を行えることを確認します。
公式ドキュメント Query string query: Proximity searches
以下の query_string
を利用したクエリが対象のドキュメントにマッチします。
{
"query": {
"query_string": {
"query": "\"global network\"~1",
"fields": [
"contents"
]
}
}
}
対象のドキュメントでは global
と network
は1単語分離れていますが、クエリ内でフレーズクエリに対して ~1
と指定しているためヒットします。
また、 Query stringクエリの近接検索は単語の編集距離を見ているため、単語の順番が逆でもその分指定する最大編集距離を大きく指定してあげるとヒットさせることができます。
Intervalsクエリによる絞り込み
Intervalsクエリを使用すると、 Query stringクエリよりも複雑な条件で近接検索を行うことができます。
まずは単純な例から見ていきます。
単語同士が近い場合にヒットさせる
{
"query": {
"intervals": {
"contents": {
"match": {
"query": "global network",
"max_gaps": 1
}
}
}
}
}
こちらは先述したQuery stringクエリと似た挙動をするクエリです。
max_gaps
で単語間の最大距離※を指定することができます。
また、この query
を network global
のように順序を逆にしてもヒットします。
上記クエリでは指定していない ordered
パラメータを true
にすると単語の順序を固定して検索を行うことができます。
※ ここで言う距離は「単語の位置の差」のことであり、編集距離やベクトル間の距離ではありません
単語間の距離に対して別の条件を加える
filter
パラメータを使用することで単語間の距離とはまた違った条件を加えることができます。
公式ドキュメント Intervals query: filter rule parameters
filter
パラメータで指定できる条件の一つ、 containing
条件を例に取ります。
{
"query": {
"intervals": {
"contents": {
"match": {
"query": "global network",
"max_gaps": 1,
"filter": {
"containing": {
"match": {
"query": "expert"
}
}
}
}
}
}
}
}
このクエリは「 global
と network
の間に expert
がある」ドキュメントにヒットします。
もし対象のドキュメントに含まれる文字列が global expert network
ではなく global professional network
などであった場合は上記クエリはヒットしません。
他にも before
条件では「filterの条件よりも前に intervals.[フィールド名].match.query
で指定した内容がある」場合、 overlap
条件では「filter条件でヒットした箇所と、intervals.[フィールド名].match.query
の条件でヒットした箇所が少しでもかぶっている」場合、などの指定ができます。
他の条件については公式ドキュメントを御覧ください。
Intervalsクエリを組み合わせる
all_of
any_of
パラメータを使用することで、複雑な条件を指定することができます。
all_of
まずは all_of
を指定したクエリから見ていきましょう。
{
"query": {
"intervals": {
"contents": {
"all_of": {
"max_gaps": 10,
"ordered": true,
"intervals": [
{
"match": {
"query": "global network",
"max_gaps": 1
}
},
{
"match": {
"query": "headquartered Japan",
"max_gaps": 1
}
}
]
}
}
}
}
}
徐々にクエリが複雑になってきました。
このクエリは「最初に global network
(1単語離れていることは許容) という文字列があり、そこから10単語以内の箇所に headquartered Japan
(1単語離れていることは許容)という文字列がある」というドキュメントにヒットします。
all_of
を使用することで複数のIntervalsクエリを組み合わせつつ、そのクエリの順序や距離を指定することできます。
any_of
all_of
は内部で指定した全てのIntervalsクエリの条件にヒットすることを求めますが、 any_of
はこれをORにすることができます。
{
"query": {
"intervals": {
"contents": {
"all_of": {
"max_gaps": 10,
"ordered": true,
"intervals": [
{
"match": {
"query": "global network",
"max_gaps": 1
}
},
{
"any_of": {
"intervals": [
{
"match": {
"query": "headquartered Japan",
"max_gaps": 1
}
},
{
"match": {
"query": "headquartered Singapore",
"max_gaps": 1
}
}
]
}
}
]
}
}
}
}
}
ネストがかなり深くなってきました。このクエリは all_of
で例に挙げたクエリの拡張です。
all_of
で例に挙げたクエリは 「(略)headquartered Japan
(1単語離れていることは許容)という文字列がある」ドキュメントにヒットしましたが、このクエリの場合は headquartered Japan
だけでなくheadquartered Singapore
(1単語離れ)にもヒットさせることができます。
all_of
と異なり any_of
は max_gaps
や ordered
が指定できません。
その他
ここまで Intervalsクエリを使った絞り込みについて一部の機能を取り上げました。
他にも match
の代わりに prefix
wildcard
fuzzy
を使用することができたり、 filter
条件にnotを使用できたりなど複数の機能があります。
また、Intervalsクエリではクエリ実行時間を短くできるよう(線形時間で伸びるよう)、常に最小間隔の文字列群に対してマッチします。これは初見だと想定外な挙動にも見えるため、使用する前に公式ドキュメントの Minimization の項目を見ておくことを推奨します。
Intervalsクエリのスコアリング
最後にIntervalsクエリでドキュメントがヒットした場合のスコアリングについて確認します。
下記の単純なIntervalsクエリを想定します。
{
"query": {
"intervals": {
"contents": {
"match": {
"query": "global network",
"max_gaps": 1
}
}
}
}
}
このクエリを冒頭に記載したドキュメントに対してexplainをかけると、以下のようなスコアを確認できます。
// 一部略
"explanation": {
"value": 0.3333333,
"description": "Saturation function on interval frequency, computed as w * S / (S + k) from:",
"details": [
{
"value": 1,
"description": "w, weight of this function",
"details": []
},
{
"value": 1,
"description": "k, pivot feature value that would give a score contribution equal to w/2",
"details": []
},
{
"value": 0.5,
"description": "S, the sloppy frequency of the interval query contents:MAXGAPS/1(UNORDERED(global,network))",
"details": []
}
]
}
S
の値はヒット時の単語間の距離に影響を受け、Intervalsクエリはヒット時に単語間の距離が短いほどスコアが高くなります。
よって上記のクエリでは、 global expert network
という文字列を含むドキュメントよりも、 global network
という文字列を含むドキュメントのほうがスコアが高くなります。
おわりに
今回はElasticsearchのIntervalsクエリについて取り上げました。
フレーズクエリにある程度単語の曖昧性をもたせて検索をさせたいときなど、Intervalsクエリを使うことを考えてみるのも良いかもしれません。