検索
連載

Hadoop+Embulk+Kibanaのデータ集計基盤によるデータ可視化と集計データを活用したキーワードサジェストの仕組みElasticsearch+Hadoopベースの大規模検索基盤大解剖(3)(2/3 ページ)

リクルートの事例を基に、大規模BtoCサービスに求められる検索基盤はどう構築されるものなのか、どんな技術が採用されているのか、運用はどうなっているのかなどについて解説する連載。今回は、ログデータの分析および可視化の基盤を構成する5つの主なOSSや集計データを活用したキーワードサジェストの事例を紹介します。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

キーワードサジェストのシステムとIndexの構成

 次に、データ集計基盤で集計したログデータを活用したキーワードサジェストの事例を紹介していきます。

 前回の最後に紹介したQassのキーワードサジェストは、主に下記の二つで構築されています。

  • Go言語(バージョン1.4)
  • Elasticsearch(バージョン1.5系)

 Go言語、Elasticsearchのバージョンは、なるべく最新のものを利用するようにしています。

 「カスタマーが求めている結果」と「システムが提供する結果」が、情報検索の分野では「検索品質」と呼ばれているように、キーワードサジェストにも「品質」があります。サジェストの場合は、システムが提供する結果の中から、カスタマーが考えているキーワードとして、より最適なものをアシストできるようにしなければなりません。

 カスタマーが「ローマ字で入力しているのか」「ひらがな/カタカナで入力しているのか」「スペースなどを入れて複合語検索で探しているのか」「現在、入力中なのか」など、さまざまな状態に対応するため、独自で作っているプラグインもありますが、たいていはIndexを以下のような構成で構築しています。

{
template: "suggest*",
  settings: {
    analysis: {
      analyzer: {
        autocomplete: {
          type: "custom",
          tokenizer: "keyword",
      filter: [
        "icu_normalizer",
        "katakanaHiraganaTransform",
        "Qass_romaji"
      ]
    },
    suggestNgramAnalyzer: {
      tokenizer: "suggestNgramTokenizer",
      filter: [
        "icu_normalizer",
        "katakanaHiraganaTransform"
      ]
    },
    icuNormalizationAnalyzer: {
      tokenizer: "keyword",
      filter: [
        "icu_normalizer"
      ]
    }
  },
  tokenizer: {
  suggestNgramTokenizer: {
    type: "nGram",
    min_gram: "1",
    max_gram: "2",
    token_chars: [
      "letter",
      "digit",
      "punctuation",
      "symbol"
    ]
  }
},
  "mappings" : {
    "_default_" : {
      "dynamic_templates" : [
        {
          "rank" : {
            "match" : "rank",
            "mapping" : {
              "type" : "float",
              "store" : "true"
            }
          }
        },
        {
          "word_ngram" : {
            "match" : "word_ngram",
            "mapping" : {
              "type" : "string",
              "analyzer" : "suggestNgramAnalyzer"
            }
          }
        },
        {
          "headchar" : {
            "match" : "headchar",
            "mapping" : {
              "type" : "string",
              "analyzer" : "icuNormalizationAnalyzer"
            }
          }
        },
        {
          "token1" : {
            "match" : "token1",
            "mapping" : {
              "type" : "string",
              "analyzer" : "autocomplete"
            }
          }
        },
        {
          "token2" : {
            "match" : "token2",
            "mapping" : {
              "type" : "string",
              "analyzer" : "autocomplete"
            }
          }
        },
        {
          "word" : {
            "match" : "word",
            "mapping" : {
              "type" : "string",
              "store" : "true",
              "index" : "not_analyzed"
            }
          }
        },
      ]
    }
  }
}
キーワードサジェストのマッピング例

 Qassのサジェストでは、全体的には、N-Gramを利用していますが、1回のリクエストごとに2つのリクエストを発行して問い合わせを行っています。

  • 前方一致にマッチしたN-Gram検索
  • 部分一致にマッチしたN-Gram検索

 同時に二つのリクエストを行い、そのレスポンスをマージして1つの結果として返却しています。

 では、なぜ二つのリクエストを発行しているのでしょうか。


図3 前方一致だけでは、「日傘」などにマッチしない

 例えば、「傘」などで検索した場合、図3のようになります。「傘」「傘立て」「傘 レディース」「傘 メンズ」などの検索候補は、前方一致なため、ヒットしますが、「日傘」「雨晴兼用 傘」「折りたたみ傘」などのキーワードはマッチしません。

 カスタマーが入力するキーワードは、記憶が曖昧で不完全な部分もあるため、前方一致のみにしてしまった場合、本当に必要な候補が出ない可能性があります。一方で、最初から部分一致にしてしまうとノイズが多くなるため、再現率は上がりますが適合率は低くなってしまい、これもまた適切な検索候補が出ない可能性があります。

 これらの二つのハイブリッドなサジェストの検索結果を組み合わせることで、よりカスタマーが求めているような検索キーワード候補に近づくようにしているのです。

 他にも、リクエストごとにスコア値のレンジを変えることで、あるキーワードから新たなキーワードの発見につながるような“気付き”が生まれるように調整しています。

 例えば「傘」など1ワード目を入力してスペースを押した場合は、「入力したワードが確定された」と判断して1ワード目は完全一致で検索し、2ワード目を前方一致で検索することで、1ワード目で確定されたキーワードに関連が強いキーワード候補が表示されるような仕組みになっています。


図4 ワードが確定された場合は、確定されたワードに関連が強いワードを表示するようにしている

 最初に入力する1文字目のキーワードで検索を行う場合、前方一致でも取得するデータが多くなるため、パフォーマンスが劣化してしまうことがあります。これを改善するために、例えば1文字のみ入力する場合はchar専用のフィールドを明示的に指定することで、使用メモリを少なくし、パフォーマンスが劣化しないように工夫しています。

{
    "_source": [
        "word",
        "word_id",
    ],
    "filter": {
        "and": [
            {
                "and": [
                    {
                        "range": {
                            "token_num": {
                                "gte": 2
                            }
                        }
                    },
                    {
                        "range": {
                            "rank": {
                                "lte": 10
                            }
                        }
                    }
                ]
            }
        ]
    },
    "query": {
        "query_string": {
            "analyze_wildcard": "true",
            "default_operator": "AND",
            "query": "target0:\"iphone\" AND word_ngram:\"あ\""
        }
    },
    "size": 10,
    "sort": {
        "total_rate": {
            "order": "desc"
        }
    },
    "timeout": "200"
}
Elasticsearchへのリクエストサンプル

 このようにすることで、より最適な検索結果候補が表示されるようにチューニングを行っています。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る