次に、データ集計基盤で集計したログデータを活用したキーワードサジェストの事例を紹介していきます。
前回の最後に紹介したQassのキーワードサジェストは、主に下記の二つで構築されています。
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つのリクエストを発行して問い合わせを行っています。
同時に二つのリクエストを行い、そのレスポンスをマージして1つの結果として返却しています。
では、なぜ二つのリクエストを発行しているのでしょうか。
例えば、「傘」などで検索した場合、図3のようになります。「傘」「傘立て」「傘 レディース」「傘 メンズ」などの検索候補は、前方一致なため、ヒットしますが、「日傘」「雨晴兼用 傘」「折りたたみ傘」などのキーワードはマッチしません。
カスタマーが入力するキーワードは、記憶が曖昧で不完全な部分もあるため、前方一致のみにしてしまった場合、本当に必要な候補が出ない可能性があります。一方で、最初から部分一致にしてしまうとノイズが多くなるため、再現率は上がりますが適合率は低くなってしまい、これもまた適切な検索候補が出ない可能性があります。
これらの二つのハイブリッドなサジェストの検索結果を組み合わせることで、よりカスタマーが求めているような検索キーワード候補に近づくようにしているのです。
他にも、リクエストごとにスコア値のレンジを変えることで、あるキーワードから新たなキーワードの発見につながるような“気付き”が生まれるように調整しています。
例えば「傘」など1ワード目を入力してスペースを押した場合は、「入力したワードが確定された」と判断して1ワード目は完全一致で検索し、2ワード目を前方一致で検索することで、1ワード目で確定されたキーワードに関連が強いキーワード候補が表示されるような仕組みになっています。
最初に入力する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"
}
このようにすることで、より最適な検索結果候補が表示されるようにチューニングを行っています。
Hadoopは「難しい・遅い・使えない」? 越えられない壁がある理由と打開策を整理する
いまさら聞けないHadoopとテキストマイニング入門
検索エンジンの常識をApache Solrで身につける
全文検索エンジン「Lucene.Net」を使うCopyright © ITmedia, Inc. All Rights Reserved.