Help us understand the problem. What is going on with this article?

Elasticsearch によるサジェスト検索実装

More than 1 year has passed since last update.

はじめに

Solr によるサジェスト検索実装について書きましたが、Elasticserchで同じことをやってみました。

実験環境構築

https://github.com/ft28/practice/tree/master/es/suggestREADME.md に従って、実験環境を構築します。

実験環境説明

lucene-analyzers-kuromoji-7.2.1-SNAPSHOT.jar

前回作成した、各トークンの漢字・ひらがな・カタカナをローマ字に変換後、トークンを結合して1つのトークンを作成するモジュールを追加したjar ファイルです。

AnalysisKuromojiPlugin.java

Elasticsearch の analysis-kuromoji pluginに上記moduleを呼び出すための名前と対応するクラスを登録します。

61         extra.put("kuromoji_concat_japanese", KuromojiConcatenateJapaneseReadingFilterFactory::new);

KuromojiConcatenateJapaneseReadingFilterFactory.java

上で登録したクラスの実装ファイルです。このクラスの中で、luceneモジュールを生成し使えるようにします。

build.gradle

analysis-kuromoji pluginコンパイル時に新しく作成したlucene-analyzer-kuromoji を使うよう設定します。

build.gradle
 19 repositories {
 20   mavenLocal()
 21 }

 28 dependencies {
 29 // compile "org.apache.lucene:lucene-analyzers-kuromoji:${version.lucene}"
 30 // compile "test:lucene-analyzers-kuromoji:7.2.3"
 31    compile files("lucene-analyzers-kuromoji-7.2.1-SNAPSHOT.jar")
 32 }

実験

検索対象のフィールドword に対して、filterにローマ字読みサジェスト設定でよく使うkuromoji_readingformを使用したインデックスと、今回作成したkuromoji_concat_japaneseを使用したインデックスを作成するよう定義します。

mapping.json
        "word": {
          "type": "text",
          "analyzer": "default_index_analyzer",
          "search_analyzer": "default_search_analyzer",
          "fields": {
             "roman1": {
               "type": "text",
               "analyzer": "roman1_index_analyzer",
               "search_analyzer": "roman1_search_analyzer"
             },
             "roman2": {
               "type": "text",
               "analyzer": "roman2_index_analyzer",
               "search_analyzer": "roman2_search_analyzer"
             }
          }
        }
作成時 roman1_index_analyzer roman2_index_analyzer
tokenizer kuromoji_tokenizer kuromoji_tokenizer
filters kuromoji_readingform
edgeNGram
kuromoji_concate_japanese
edgeNGram
検索時 roman1_search_analyzer roman2_search_analyzer
tokenizer kuromoji_tokenizer kuromoji_tokenizer
filters kuromoji_readingform kuromoji_concate_japanese

インデックス作成時はedgeNGramを作り、検索時はedgeNGramを作りません

analyzer 比較

Elasiticsearchの場合、各analyzerで元データに対して、どのようなトークンが作成されるか以下手順で確認出来ます。

curl -XGET 'localhost:9200/suggest/_analyze?pretty' -d '{"analyzer": "<analyzer名>", "text": "<元データ>"}' -H 'Content-Type: application/json'

インデックス作成時に「品川」に対して各analyzerで作成されるトークンは以下のようになります。

analyzer名 roman1_index_analyzer roman2_index_analyzer
トークン1 s s
トークン2 sh sh
トークン3 shi shi
トークン4 shin shin
トークン5 shina shina
トークン6 shinaga shinaga
トークン7 shinagaw shinagaw
トークン8 shinagawa shinagawa
  • 「品川」はtokenizerから出力された時点で1つのトークンなので、romen1_index_analyzer、roman2_index_analyzerが作成するトークンは同じになります。

検索時の「しながわ」に対して各analyzer で作成されるトークンは以下のようになります。

analyzer名 roman1_search_analyzer roman2_search_analyzer
トークン1 shina shinagawa
トークン2 ga
トークン3 wa
  • 「しながわ」は、tokenizerで「しな」「が」「わ」に分解され、roman1_search_analyzerでは、ローマ字置換されたトークンがされていますが、roman2_search_analyzerでは結合して1つのトークンになっていることが分かります。

検索結果比較

  • roman1 で検索
# クエリ
curl -XGET http://localhost:9200/suggest/keyword/_search?pretty -H 'Content-Type: application/json' -d '
{
   "query": {
     "match": { 
       "word.roman1": {
          "query": "しながわ",
          "operator": "AND"
       }
     }
   }
 }'
# 結果
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}
  • roman2 で検索
# クエリ
curl -XGET http://localhost:9200/suggest/keyword/_search?pretty -H 'Content-Type: application/json' -d '
{
   "query": {
     "match": { 
       "word.roman2": {
          "query": "しながわ",
          "operator": "AND"
       }
     }
   }
 }'
# 結果
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 6.3382335,
    "hits" : [
      {
        "_index" : "suggest",
        "_type" : "keyword",
        "_id" : "279",
        "_score" : 6.3382335,
        "_source" : {
          "word" : "品川"
        }
      }
    ]
  }
}

まとめ

Elasticsearch でも Solrの時に作成したluceneモジュールを使って、サジェスト検索を改善できました。gradleによるビルドを今回はじめて触ったので、その点で少し苦労しましたが、それ以外は、Solr の時とほぼ同じ手順で組み込めました。

参考

Elasticsearch キーワードサジェスト日本語のための設計
* Elasticsearch のサジェスト設定について参考にさせて頂きました。
* 今回は比較対象モジュール部分に必要最小限の設定しか行っていないのですが、実用ではこちらのサイトにあるような設定を追加するのが良いと思われます。

Solr にジェスト検索実装
* luceneモジュールはこちらで作成したものを使っています。
* luceneモジュールの作成方法はこちらの記事をご参照ください。

ft28
プログラマーです。仕事探し始めました。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした