mosowave

sinamon129による(主に)技術ブログ。Ruby,Ruby on Rails,Elasticsearchやその他について書きます。

FRILの商品検索をnGramから形態素解析にした話

この記事はElasticsearch Advent Calendar 2015の7日目のエントリです。

こんにちは、ファッションフリマアプリFRILを運営しているFablicでエンジニアをしている@sinamon129です。

FRILの商品検索はElasticsearchを使っていて、最近nGramベースだったものを形態素解析ベースに変更しました。
その経緯やどういう手順で行ったかを書こうと思います。
主にユーザー辞書とsynonym辞書の構築の話がメインです。

どうしてnGramベースから形態素解析ベースに変更することになったか

関係ないものがなるべくひっかからないようにしたい

nGramだとファーで検索したときに、ローファーやローリーズファームが引っかかり、本当に検索したかったものが出てこないという問題がありました。
(実際は出ているのだけども、埋もれてしまっている状態)

同じ意味の単語の検索結果はなるべく同じにしたい

バッグ・バックやカシミア・カシミヤなど、表記が揺れる語があり、それらの検索結果を同じにしたいけどnGramだと上手くできないという問題がありました。

どういう手順を踏んだか

最初は、単語ごとに適合率と再現率を出すために、既存の商品データから正解データを作り、その適合率と再現率から改善を行おうとしていました。

しかし、正解データを作る作業はほぼ人力で行うしかないのでコストが高く、また作業者の主観が入るという問題があったのと、nGramの結果を前提として引っかかって欲しくないものが引っかからないようにする方が効率が良いことに気づき、nGramでの検索hit数をみながら辞書の構築を行うことにしました。

analyzer設定

index用のanalyzerとsearch用のanalyzerを用意して、index用のanalyzerのみにsynonymの設定を追加しました。
またchar_filterにicu_nomalizerを追加したことで、文字の正規化をやってくれるようになったので、とても辞書追加が楽になりました。
(半角カタカナや全角英数字や大文字を辞書に登録する必要がなくなったので)

index_analyzer

  • kuromoji_tokenizer
  • search mode
  • char_filter
    • icu_normalizer
  • filter
    • cjk_width
    • pos_filter
    • kuromoji_baseform

search_analyzer

index_analzerにsynoymを追加

ユーザー辞書とsynonym辞書の構築

FRILの商品情報や検索ワードには、ファッションブランドや洋服に関する単語、モデルの名前などデフォルトのkuromoji辞書には載ってないような単語が多く、デフォルトの辞書では引っかからないものが増えてしまいます。

検索ログからの辞書追加

実際に検索されたワードのログを貯蓄しているので、まずはそこから辞書データを作っていきます。
ですが、生の検索ワードログには以下の問題点があります。

  • フリガナがない
  • 間違っているワードが入っている
    • ミスタイプ・勘違い等

そのため、そのまま辞書データにするのは良くないと判断し、mecabと[mecab-ipadic-neologd](https://github.com/neologd/mecab-ipadic-neologd)を用いて、形態素解析した結果、フリガナが存在する名詞のデータだけを辞書に登録しました。

本番データでindexを構築して漏れを見つける

mecabで分解できた範囲では足りない&不安なので、本番で動いているクラスタ形態素解析版のindexを載せて、チェックしていきます。

nGramモードのキーワードのhit数と、形態素解析モードのhit数とで、あまりにもかけ離れていているものをベースにチェックしてきます。
(この時、影響範囲が大きいものから潰したかったので、検索数の多いものから順にチェックしていきました)

hit数が減ってる場合は以下のような場合があります。

  1. そもそも引っかかってはダメだったものがたくさんひっかかっていた場合
  2. ワンピ(ワンピースの省略形)など、nGramから形態素解析になったことによってひっかかる数が減ってしまった場合
  3. 形態素解析で分解されたが、その分解され方がおかしい場合

1の場合はそのままで大丈夫ですが、その他の場合は、実際にワードを検索したり、inquisitorプラグインで単語を分解しながら、適宜ユーザー辞書やsynonym辞書に単語を追加していきました。

社内テストで抜け漏れを見つける

上位数千ワードぐらいが大体大丈夫だろうというところで、社内ユーザーが検索する時は先ほどつくったindexを参照するようにapiの実装を変更しました。
実際に使ってもらって、ひっかからなくなった単語や、この単語で検索した時とこの単語で検索した時は同じ結果になってほしいなどの要望をもらい、ひたすら辞書を更新していきました。

FRILでは、Ruby on Railsからsearchkichというgemを使っているので、商品検索indexは基本的に商品データの更新時にリアルタイム更新されているのですが、テスト用に作ったindexは手動更新だったため、社内ユーザーがほぼいつも通り使えるように15分に一回更新するようなバッチを書いて対応しました。

本番適応後

本番適応してからは、検索結果からの商品遷移率を見ながら辞書を修正してきました。
関係ないものが引っかかると検索結果から商品への遷移率が落ちる傾向にあるので、nGramの時の遷移率よりも下がっているものを探しながら修正を行いました。
またhit数が0になっているワード等を確認し、必要に応じて辞書の修正を行いました。

副産物

検索レポート

GoogleBigQueryに溜まっている検索ログを形態素解析モードの変更した時の影響等を観測したいので、毎日集計するようにしました。

f:id:sinamon129:20151206221603p:plain
検索ワードごとのdauや検索回数、検索結果からの商品ページへの遷移率を観測できる画面や、デイリーランキングをslackに通知したり等、社内で今何が検索されてるかを確認できる状態になりました。
f:id:sinamon129:20151206215623p:plain:w300

辞書登録システム

ユーザー辞書ファイル・synonym辞書ファイルはメンテナンスしていかないといけないものなので、自分以外も更新できるように辞書登録システムを作りました。カナが同じワードをだしたり、部分一致したワードを出すことで、類義語設定がとてもスピーディーにできるようになりました。

f:id:sinamon129:20151206213857p:plain

特に苦労したこと

データが綺麗じゃない

商品名や商品情報をユーザーさんは自由に書くので、顔文字や絵文字はもちろん、商品に関係ないことがたくさん書かれます(`・д´・ ;)

辞書追加作業が大変だった

ファッションブランドや流行りのファッションワードは、どこかにまとまっていることが少ないことが多く、ニコニコ大百科wikipediaからデータを作る、みたいなことがしづらくて苦労しました。。。

また、FRILユーザーでそこそこ洋服が好きな自分がみても、ぱっと見何かわからないワードも多く(知らないブランド・アーティスト等)これはどの表記が正しいのかなどを必死にググりました。

まとめ

nGramモードから形態素解析モードに変更するために辞書を必死に作った話でした。
日本語全文検索のための辞書構築の話はなかなか知見として公開されなくて、自分としてもどうやってやっていくか手探り状態だったので、参考になれば幸いです。