LoginSignup
7
7

More than 1 year has passed since last update.

Elasticsearchを用いた日本語サジェスト ~公式で紹介されている方法から逆引きによる理解~

Posted at

はじめに

この記事では、少し興味のあった、かつ今後業務で利用する可能性のあるElasticsearchに関する理解をまとめたものです。

目的として、以下を理解しようと考えました。

  1. Analyzer と Mapping
  2. Elasticsearch の実装例・できること

方法としては、Elasticsearchの公式ページで紹介されている日本語サジェストの記事を参考に、逆引きで理解をしようと思います。なお日本語サジェストのAnalyzerの詳細については説明していません。

また個人的にElasticsearchを理解する上でのキーポイントは、Analyzerにおける 検索時とindex 時それぞれで定義 できる点とMappingにおける multi-field だと思いました。 (RDBと全文検索の違いかな)

Analyzer と Mapping の概要

それぞれについて簡単に解説

Analyzer

ドキュメントindexする前に走る処理 (正確には転地インデックスを張る前に走る処理)

以下の3つの手順に分解される

  1. Character Filter

    文章を分かち書きする前に行う処理。不要な文字列の削除(<li></li>など)や検索しやすいように文字列を置換する(色々→色色)などを行う

  2. Tokenizer

    文章を分かち書きする際に、分かち書きのルールを定義する。検索において重要な処理であり、様々なルールが提供されている。日本語は区切りが分かりづらいため、形態要素分析を行う必要があり、そのためにコンポーネントであるkuromojiが提供されている

  3. Token Filter

    分かち書きした後、それぞれの単語に対する個別の処理を行う 単語を小文字化するや品詞を削除するなど。

Analyzerの一連の処理は、ドキュメントをindexする際だけでなく、検索リクエストの際にも処理が走るようになっている (indexされたドキュメントに対して、同じルールで検索をかけよぜ、という話)

Mapping

マッピングはドキュメントのスキーマ定義であり、フィールド名や型定義、どのAnylizerを利用するかなどのメタ情報を宣言できます (multi-fieldも)。

// 具体例
PUT blogs
{
  "mappings": {
    "properties": {
      "content" : {
        "type": "text",
        "analyzer": "kuromoji"
      },
      "url" : {
        "type": "keyword"
      },
      "timestamp": {
        "type": "date"
      },
      "author" : {
        "type": "text",
        "fields": {
          "raw" : {
            "type" : "keyword"
          }
        }
      }
    }
  }
}

上記のマッピングからは以下のようにindexされます。

DOCUMENT/blogs マッピング メモ
content text kuromoji 日本語記事が入ることを想定し、アナライザに kuromojiを指定
url keyward
timestamp date
author text text型での検索→andやorで検索可
(auther.row) keyword [マルチフィールド定義]完全一致に対応すべくkeyward型検索も可能に

Elasticsearch 公式サジェストのAnalyzer と Mappingの解説

Elαsticsearchの公式ページで紹介されている、日本語サジェストから、逆引きでAnalyzerとMappingの定義方法を理解していきます

以下3つの節に分かれていますが、下に行くほど単純化しており、Elasticsearchの構文に近くなっています

なおAnylyzern役割の詳細は解説されておりませんので、ご了承ください。

Elasticsearch公式ページで紹介されている、日本語サジェストのAnalyzerとMapping例 (コピペ)
PUT my_suggest
{
	// カスタムAnalzer の定義
  "settings": {
    "analysis": {
      "char_filter": {
        "normalize": { /* 省略*/ },
        "kana_to_romaji": { /* 省略*/ }
      },
      "tokenizer": {
        "kuromoji_normal": { /* 省略*/ }
      },
      "filter": {
        "readingform": { /* 省略*/ },
        "edge_ngram": { /* 省略*/ },
        "synonym": { /* 省略*/ }
      },
      "analyzer": {
        "suggest_index_analyzer": {
          "type": "custom",
          "char_filter": ["normalize"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "edge_ngram"]
        },
        "suggest_search_analyzer": {
          "type": "custom",
          "char_filter": ["normalize"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase"]
        },
        "readingform_index_analyzer": {
          "type": "custom",
          "char_filter": ["normalize", "kana_to_romaji"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "readingform", "asciifolding", "synonym", "edge_ngram"]
        },
        "readingform_search_analyzer": {
          "type": "custom",
          "char_filter": ["normalize", "kana_to_romaji"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "readingform", "asciifolding", "synonym"]
        }
      }
    }
  },
  // カスタムAnalzerを利用したMappingの定義
  "mappings": {
    "properties": {
      "my_field": {
        "type": "keyword",
        "fields": {
          "suggest": {
            "type": "text",
            "search_analyzer": "suggest_search_analyzer",
            "analyzer": "suggest_index_analyzer"
          },
          "readingform": {
            "type": "text",
            "search_analyzer": "readingform_search_analyzer",
            "analyzer": "readingform_index_analyzer"
          }
        }
      }
    }
  }
}

少し単純化: AnalyzerとMapping方法理解

PUT my_suggest
{
	// カスタムAnalzer の定義
  "settings": {
    "analysis": {
      "char_filter": {
        "normalize": { /* 省略*/ },
        "kana_to_romaji": { /* 省略*/ }
      },
      "tokenizer": {
        "kuromoji_normal": { /* 省略*/ }
      },
      "filter": {
        "readingform": { /* 省略*/ },
        "edge_ngram": { /* 省略*/ },
        "synonym": { /* 省略*/ }
      },
      "analyzer": {
        "suggest_index_analyzer": {
          "type": "custom",
          "char_filter": ["normalize"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "edge_ngram"]
        },
        "suggest_search_analyzer": {
          "type": "custom",
          "char_filter": ["normalize"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase"]
        },
        "readingform_index_analyzer": {
          "type": "custom",
          "char_filter": ["normalize", "kana_to_romaji"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "readingform", "asciifolding", "synonym", "edge_ngram"]
        },
        "readingform_search_analyzer": {
          "type": "custom",
          "char_filter": ["normalize", "kana_to_romaji"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "readingform", "asciifolding", "synonym"]
        }
      }
    }
  },
	// カスタムAnalzerを利用したMappingの定義
  "mappings": {
    "properties": {
      "my_field": {
        "type": "keyword",
        "fields": {
          "suggest": {
            "type": "text",
            "search_analyzer": "suggest_search_analyzer",
            "analyzer": "suggest_index_analyzer"
          },
          "readingform": {
            "type": "text",
            "search_analyzer": "readingform_search_analyzer",
            "analyzer": "readingform_index_analyzer"
          }
        }
      }
    }
  }
}
DOCUMENT/my_suggest index時Analyzer 検索時Analyzer メモ
my_field keyward
my_field.suggest text suggest_index_analyzer suggest_search_analyzer my_fieldのマルチフィールド定義
my_field.reading_form text readingform_search_analyzer readingform_index_analyzer my_fieldのマルチフィールド定義

さらに単純化: Elasticsearch のカスタムAnalyzer, Mappingの構文理解

PUT my_suggest
{
	// カスタムAnalzer の定義
  "settings": {
    "analysis": {
      "char_filter": {
        // カスタムCharacter Filter
      },
      "tokenizer": {
        // カスタムTokenizer を定義
      },
      "filter": {
	      // カスタムToken Filter を定義
      },
			// 上記で定義した3つのコンポーネントを利用して、カスタムAnalyzerを定義する
      "analyzer": {
				// カスタムAnalyzer 'suggest_index_analyzer' を定義
        "suggest_index_analyzer": {
          "type": "custom",
          "char_filter": ["normalize"],
          "tokenizer": "kuromoji_normal",
          "filter": ["lowercase", "edge_ngram"]
        },
        "suggest_search_analyzer": {
          /* 同上 */
        },
        "readingform_index_analyzer": {
          /* 同上 */
        },
        "readingform_search_analyzer": {
          /* 同上 */
        }
      }
    }
  },
	// カスタムAnalzer を利用し、my_suggestというドキュメントのMappingの定義
  "mappings": {
    "properties": {
      "my_field": {
        "type": "keyword",
				// 以下はマルチフィールド定義
        "fields": {
          "suggest": {
            "type": "text",
            "search_analyzer": "suggest_search_analyzer", // ここで検索時のAnalyzerとして、上で定義したものを利用
            "analyzer": "suggest_index_analyzer"          // ここでindex時のAnalyzerとして、上で定義したものを利用
          },
          "readingform": {
	          /* 同上 */
          }
        }
      }
    }
  }
}

日本語サジェストのAnalyzer理解

これ以前の章で、カスタムAnaylzerとカスタムMappingの方法が理解できたと思います。

だいぶ抽象化すると、最終的に検索サジェストの為に以下のようなドキュメントの定義になりました

最後のこの章では、それぞれのIndexと対応するAnalzerの役割について、ざっくりと理解していきます

DOCUMENT/my_suggest index時Analyzer 検索時Analyzer メモ
my_field keyward
my_field.suggest text suggest_index_analyzer suggest_search_analyzer my_fieldのマルチフィールド定義
my_field.reading_form text readingform_search_analyzer readingform_index_analyzer my_fieldのマルチフィールド定義

マッピングの構成の理解

今回のサジェスト用indexでは、Elasticsearchのmulti-fieldsを利用して、my_fieldというkeywardフィールドに対して、sugesstとreading_formという2つのmulti-fieldを定義しています

suggestフィールドでは、漢字の読み方を吸収するsuggestというアナライザーを定義。

reading_formフィールドでは、未確定の仮名文字 (”にほn”など) を吸収するsuggestというアナライザーを定義。

それぞれのAnalzerに関する、詳細の説明 (Character Filter, Tokenizer, Token Filter)についてはElasticsearch公式のドキュメントの説明をご覧ください、以下簡易的な説明です

  • index時は半角小文字に変換し、形態素解析で細かく文字を分割しすぎないようAnalyze
  • reading_formではそれに加え、正規化や異なる読み方の違いを吸収
  • 検索時は上記に加え、接頭辞検索を利用し、形態素解析の欠点をフォロー(恐らく)

おわりに

個人的にはElasticsearchの使い方・できることがなんとなく理解できた気がしました。

ただスコアのチューニングとかは、やはり実際に手を動かさないことには理解が進まないな、と思います。

参考

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7