日本語でのサジェストの難しさとElasticsearchを用いた実装例

このエントリーをはてなブックマークに追加

このエントリは Elastic stack (Elasticsearch) Advent Calendar 2016 の17日目の投稿です。

こんにちは。Housmartの高松です。今回のテーマは Elasticsearchを使った日本語でのサジェスト です。

Elasticsearch便利ですよね。個人的には今から検索関連の機能を作るならElasticsearch一択だと思っているのですが、こんな便利なElasticsearchを使っても、実用レベルの日本語のサジェスト機能を実装するのはちょっと難易度が高いです。

今回は実務での経験をもとに、なぜ日本語でのサジェストが難しいのか、Elasticsearchを用いた実装はどうすれば良いのかについて書いていこうと思います。

今回取り組んだ問題

弊社HousmartはReTech(不動産テック)スタートアップで、中古マンション売買サービス「カウル」をメインのサービスとして運営しています。
また、メインのサービス以外にも、ユーザがマンションの情報を検索できるカウルライブラリー、マンション購入のノウハウを載せた自社メディアであるマンションジャーナルの運営をしています。

今回はマンションのデータベースサイトであるカウルライブラリーの検索にサジェスト機能を実装しました。

カウルライブラリーのサジェスト

マンション名の検索の場合、出て来る固有名詞の種類が多い(マンションブランド名、地名、駅名など)ので、その全てにマスタを作るのが難しい(手間がかかる)という難点があります。これは例えばECサイトなどでも同様の悩みがあるのではないでしょうか。

なぜ日本語のサジェストが難しいのか

まず、なぜ日本語のサジェストが難しいのかを整理しておきます。
もともと検索システム初心者だったため、このあたりの事がWeb上できちんと言語化されていなかったので随分悩みました。
こちらのブログにこの辺りがまとめられていたので適宜引用しながら、これから取り組む人にとって役立つように、なぜ日本語のサジェストが難しいのかをまとめておきます。

参考)Solrを用いて検索のサジェスターを作りました - VASILY Developers Blog

一般的なサジェストは、検索対象のドキュメント中の単語リストを保持し、ユーザの入力途中の単語を使って前方一致検索をかけ、出現頻度が高い順に単語のリストを返すという単純な仕組みで成り立っています。
しかし、日本語の場合はこの過程でいくつかのポイントを特別に考慮する必要があります。

単語の抽出

英語の文章は単語と単語との間がスペースや記号などで区切られています。 そのため、文章を単語に分解する処理を簡単に書くことができます。
しかし他方で日本語の文章にはそのような機械的に単語分割できるような目印はありません。 そのため、日本語の文章を単語分割するためには形態素解析という処理を行う必要があります。 - VASILY Developers Blog

このため、tokenizerに形態素解析用のプラグインを指定する必要があります。

形態素解析ではどのような辞書を用いて解析をするかが検索精度の観点で重要になります。今回は辞書として新語を定期的にアップデートしてくれている mecab-ipadic-NEologd を使いたいので、analysis-kuromoji-neologdのプラグインを利用しました。

Elasticsearch Analysis Kuromoji Neologd - GitHub

漢字・英語の読み

さらに、単語に分割した後には「漢字の読み」の問題もあります。 例えば、「とう」と入力した時に「東京」という単語を返すためには、「東京」という単語の読みが「とうきょう」であるという情報が必要です。 - VASILY Developers Blog

この問題に対しても一般語であればanalysis-kuromoji-neologdで読みを取得することが可能です。

また、マンション名では頻繁に英語とカタカナ語の表記ゆれが存在します。例えば、「ブリリア」というマンションブランド名が「Brillia」と表記されていたりします。「Brillia」という入力に対してブリリアシリーズのマンション名一覧を返すにはこれらの読み情報も必要です。この場合は一般語では無いので独自で辞書を追加する必要があります。

打ち途中の単語に対するサジェスト

未確定の子音やひらがなにも対応する必要があります。 「とうky」と入力した時には、「東京」や「東急」といった単語を候補として表示する必要があります。 - VASILY Developers Blog

こちらの問題に対しては、ローマ字読みのfieldを別で用意してそれに対して前方一致をかけることで解決していきます。

Elasticsearch標準のサジェスト機能について

ここで、Elasticsearch標準のサジェスト機能について言及しておきます。

ElasticsearchではCompletionSuggesterというサジェスト用の機能が提供されているのですが、この機能は英語のドキュメントに対するサジェストを提供するためには非常に優れた機能です。一方で、 この機能を使って実用レベルの日本語のサジェストを実現することは難しい です。

日本語でのサジェストは以上で挙げたように工夫すべきポイントがいくつか存在します。Elasticsearchの標準のサジェスト機能ではここがすべて考慮されているわけではないので、現状では実用的な日本語サジェストには使いにくいという結果になっています。

更に言うと、私はこの機能でどこまで出来るかを調べだしてしまったため結構ハマりました。 要件によってはこの機能を使わない という選択をした方が良い事を知っておいてください。

どうやって実装していくか

今回は要件を満たすためにCompletionSuggesterは使うのを諦め、サジェスト用に新しいインデックスを作成していく方針にしました。

サジェスト用インデックスのデータ

サジェスト用のインデックスに流し込むデータは

  • なるべく多いマンションが引っかかる検索ワードを優先的に出したい
  • マンション名そのものもサジェストしたい

の2点を満たすべく、マンション名をそれぞれ形態素解析し、その結果の組み合わせを下図の様に作成したものを用います。(マンション名そのものは2レコード入れる)

サジェスト用インデックスのレコード作成

このデータに対して検索をかけ、group byでカウントが多い順にサジェストの結果とします。また、group byの結果が2件以上のものに限定することで、無意味なワードが引っかかからず、マンション名そのものはサジェストの結果に入れることができます。(マンション名そのものは2レコード入れているため)

本来ならばブランド名や地名のマスタデータを作るのがベストですが、その手間を省くためにこのような実装にしています。データの性質によりますが、この方法でそれなりの精度を実現できています。

Analyzerの設定

続いてインデックスに対してAnalyzerを設定していきます。
ここの設定に関しては下記のブログが大変参考になりました。

参考)Elasticsearch キーワードサジェスト日本語のための設計

確定したキーワード用と入力途中のキーワード用に別々のfieldを定義していきます。

確定したキーワード用のAnalyzer

「六本木」の様に確定したキーワードのための設定です。

データインポート時は、一般的なノーマライズを掛けてEdge-ngramでインデックスを作成しておきます。検索時は、文字列に対してノーマライズを掛けるだけです。

確定したキーワード用のAnalyzer

# インポート時
char_filter: [
  'normalize'
],
tokenizer: 'keyword',
filter: [
  'lowercase',
  'trim',
  'engram'
]
---
# 検索時
char_filter: [
  'normalize'
],
tokenizer: 'keyword',
filter: [
  'lowercase',
  'trim'
]

入力途中のキーワード用のAnalyzer

「roppo」の様に入力途中のキーワードのための設定です。

データインポート時は、一般的なノーマライズを掛けた後、すべてをローマ字に変換してEdge-ngramでインデックスを作成しておきます。検索時も同様に、文字列をノーマライズした後にすべてローマ字に変換します。

入力途中のキーワード用のAnalyzer

# インポート時
char_filter: [
  'normalize'
],
tokenizer: 'keyword',
filter: [
  'lowercase',
  'trim',
  'readingform_neologd_romaji',
  'asciifolding',
  'engram'
]
---
# 検索時
char_filter: [
  'normalize',
  'katakana',
  'romaji'
],
tokenizer: 'keyword',
filter: [
  'lowercase',
  'trim',
  'readingform_neologd_romaji',
  'asciifolding'
]

Analyzerに関するより詳しい挙動や流れは先程挙げたブログを参照してください。

終わりに

Elasticsearchの設計は見通しが立ってしまえばそこまで難しくはないのですが、一つ一つの事例ごとに特性が違い、データの設計とAnalyzerの設定をそれぞれの特性に合わせて同時に考えなければならない点が難易度が高いと思っています。
今回はひとつの実装例をご紹介しましたが、これから設計をする人の参考になれば幸いです。


Housmartではエンジニアを募集しています。
今話題のReTech!創業期を支える4人目のエンジニアをWanted!

このエントリーをはてなブックマークに追加