アイキャッチ画像: デスクの上でパソコンを使用している

Drupalの日本語検索、ちゃんと動いてる?N-gramの課題と自然言語処理での改善策

はじめに

こんにちは。スタジオ・ウミの大野です。12月にDrupalCon Singaporeへ参加してきました。会場に参加されていたコントリビューターの皆さんのすごい熱気に感化され、私も何か貢献活動しなければと思い立ち、Drupalの日本語の検索機能を向上させるモジュールを開発しました。そのモジュールを紹介する前段として、この記事では一般的な検索機能の説明とDrupalにおける日本語検索の問題点について解説します。

実際の検索エンジンはさらに高度な技術を用いています。この記事では解説用に簡潔に説明していますので、誤ったことをお伝えしている可能性がある点にはご容赦ください。

検索インデックスが必要な理由

現代人にとって、普段利用しているウェブサイトの検索機能が高速かつ的確に結果を返すのは当たり前のことだと感じているはずです。しかし、その裏側では膨大なデータを迅速かつ精度高く検索するための高度な技術が数多く使われています。メディア系のサイトであれば多くのコンテンツがあり、ユーザーが検索フォームにキーワードを入力すると、そのキーワードを含むコンテンツを検索して返します。安直に考えればデータベースに登録されているすべての文章から一致する文字列を検索すれば良いだけのように思えますが、実はそう簡単ではありません。大量の文章から全文検索をするには非常に多くの処理コストがかかり、複数の記事がある場合は期待するものを上位に表示する必要があります。

これらの課題を解決するために、検索インデックスと呼ばれる仕組みが用いられます。検索インデックスは、文章を単語ごとに分割してデータベースに登録することで、検索を高速化し、結果の精度を向上させることができる技術です。例えば「Easy come, easy go.」という文章が含まれた記事を単語ごとに分解して検索インデックスに登録すると、次のようにデータベースへレコードが登録されます。

記事ID キーワード スコア
1 easy 2000
1 come 1000
1 go 1000

一般的なデータベースは、すべての文章から一致する文字列を検索するよりも、完全に一致するキーワードを検索する方がはるかに高速であるため、単語ごとに分割してデータベースに登録します。

また、元の文章には「easy」という単語が2つありましたが、データベース上のレコードに「easy」の単語は1つしか登録されておらず、スコアが2倍になっていることに注目してください。同じ単語が1つの記事に複数含まれている場合は、スコアが加算され、その記事とキーワードの関連度を示し、検索結果の順位を決定するために用いられます。このように処理することで、ユーザーが期待するコンテンツを迅速かつ正確に検索することが可能になります。

日本語は単語ごとに検索インデックスに登録するのが難しい

さて、ここからが本題です。日本語や中国語・韓国語などの言語は英語などのアルファベット言語とは異なり、単語と単語の間にスペースがないため、単語の区切りを容易に判別できない言語です。極端な例ですが、「There are two chickens in the garden」という英文なら「There」や「are」などの単語はスペースで区切られているので簡単に分けることができます。一方で日本語の場合は、ひらがなで書くと「にわにはにわにわとりがいる」という文章になり、どこまでが1つの単語であるか単純な文字列の比較では判別できない事がわかります。このような形態を持つ日本語の文章から検索インデックスを作成するには、特別な工夫が必要です。

日本語の分割技術N-gramとは

日本語の文章を検索インデックス用に簡単に分割する方法の1つにN-gramと呼ばれる技術があります。DrupalコアのSearchモジュールやSearch APIモジュールでもこの技術が採用されています。

N-gramについて簡単に説明すると、文字列をN個単位の文字列で区切って文章を分割する方法となります。例えば「美味しい寿司」という文章を、Nを3とした3-gramで分割すると次のように文字が分割されます。

「美味しい寿司」を分割したイメージ

そして分割された文字は、それぞれ次のような形でデータベースのレコードに登録されます。

記事ID キーワード スコア
1 美味し 1000
1 味しい 1000
1 しい寿 1000
1 い寿司 1000

検索システムでキーワードを入力する際も、同様に3-gramで分割して検索されます。例えば、「美味しい」というキーワードが入力された場合は美味し味しいに分割されます。これらのキーワードをすべて含むレコードが検索され、それにマッチする記事IDの記事が検索結果として表示されます。

N-gramの課題

N-gramのロジックは単純なので簡単に実装できる反面、いくつかの課題があります。なお、ここではDrupalのコアのSearchモジュールやSearch APIモジュールに関連する課題のみを取り上げます。他のシステムでは異なる可能性があります。ご容赦ください。

課題1. 文字数(N)を下げるほど検索精度も下がる

N-gramは設定した文字数Nに基づいてインデックスを作成する仕組みであるため、決めた文字数以上の単語でないと検索できません。この問題はインデックスする文字数を小さくすることで解決できますが、文字数を小さくすればするほど検索精度は下がり、データベースの負荷が高まります。

1-gram(1文字ずつ)に設定した場合を例にしてみましょう。「スカイ」という単語はス | カ | イに分割してインデックスに登録されます。この状態で「イカ」と検索すると、キーワードはイ | カに分割され、文字の順番に関係なく、それらの文字を含む記事が検索対象になります。このため、「スカイ」と「イカ」は全く違う意味なのに、関係のない記事が検索結果に表示されてしまうという問題が発生します。

また、検索スコアの計算は1レコード(1文字)ずつカウントされるため、検索結果は出現する文字の数順となってしまいます。ユーザーが期待する記事は上位に表示されない可能性が高く、事実上1-gramでの検索インデックスは不可能だと言えます。

課題2. N-gramによる単語の分割で意味が変わってしまう

検索精度を向上させるために、N-gramの文字数を2(2-gram)に設定するとしましょう。しかし、この方法でも新たな問題が発生します。

たとえば、「東京都」という単語があった場合、2-gramでは「東京」と「京都」に分割されてしまいます。すると、検索時に「京都」と入力しても「東京都」がヒットしてしまい、本来意図しない検索結果が表示されることになります。このように、単語の一部が他の単語と重なっていると、意味の異なる単語が検索結果に含まれてしまうという問題が生じます。

また、N-gramは単純に文字の並びを区切るだけのため、意味を持たない単語までインデックスされてしまいます。たとえば、「美味しい寿司」という文章を3-gramで処理すると、「美味し」「味しい」「しい寿」「い寿司」といった単語が登録されます。しかし、「味しい」や「しい寿」は本来単独では意味を持たない単語です。それにもかかわらず、こうした単語で検索した場合でも結果が表示されてしまい、検索の精度が低下する原因になります。

課題3. 完全に一致するキーワードしか検索できない

N-gram方式では一致するキーワードのみが対象となります。例えば、「コーヒー」について書かれた記事がある場合、検索システムから同じ意味である「珈琲」と検索しても当該の記事はヒットしません。

N-gramの課題を解決することができる自然言語処理

N-gramの課題を解決するために自然言語処理と呼ばれる技術が用いられます。自然言語処理というとちょっと仰々しい感じがしますが、簡単に言うと人間が使う言語を解析してコンピューターが処理できるようにする技術のことです。

自然言語処理をすれば、文章内を意味のある単位で分割できるので、それを検索インデックスに登録することでN-gramの課題を解決します。

自然言語処理にはいくつかの手法がありますが、ここでは代表的な2つの手法を紹介します。

形態素解析

形態素解析とは、文章を意味のある単語ごとに分割して品詞を判別する技術です。ここでも極端な例を出しますが「すもももももももものうち」の様な日本人でも読みにくい文章であても、これらの形態素解析器を通せば次のように分割できます。

すもももももももものうち

日本語の形態素解析としてはMeCabKuromojiSudachiというアプリケーションが有名です。試しにSudachiを使って「美味しい珈琲が飲みたい。」という文章を形態素解析すると、次のように分割・分析されます。(記事用に見やすくしているため実際の出力結果とは異なります)

単語 品詞 正規化
美味しい 形容詞 美味しい
珈琲 名詞 コーヒー
助詞
飲み 動詞 飲む
たい 助動詞 たい
補助記号

このように単語が品詞単位で分割されます。分割されたこれらの単語を検索インデックスに登録することで、N-gramで起きる文字数制限や意味のない単語で検索できてしまう問題を解決できます。また、Sudachiには単語の正規化機能が備わっており、検索インデックスへの登録時と検索時の両方でキーワードを正規化することで、表記揺れの問題もある程度解決できます。ただし、正規化のルールは辞書に依存するため、すべてのケースをカバーできるわけではありません。

「が」や「たい」などの助動詞や記号は、ほとんどの文章で頻繁に出現する単語です。検索インデックスに入れてしまうとノイズになりやすい性質がありますが、形態素解析することによって品詞を特定でき、このような単語のみをインデックスから取り除くことも可能です。

形態素解析は辞書を使って解析するため、辞書に登録されていない新しい単語や固有名詞などは正しく判別できないという問題があります。形態素解析の精度を向上させるには、辞書のメンテナンスやカスタマイズが必要になるため、注意が必要です。

機械学習を用いたトークナイズ

次に紹介するのは機械学習を用いたトークナイズです。これは機械学習したデータを元にコンピューターが入力された文章を単語ごとに分割する技術です。機械学習を用いたトークナイズは形態素解析と比べて辞書が不要で、新しい単語や固有名詞にも対応できる可能性があるというメリットがあります。一方で、精度が辞書を使った形態素解析よりも劣ることがほとんどです。

ここで少し余談ですがトークナイズすることを日本語では「分かち書き」とも言います。ファミコン世代の方だったらご存知だと思いますが、ドラクエなどの昔のゲームは漢字が使えなかったため、文章を分かち書きして読みやすくしていました。

よくぞきた! ゆうしゃをめざすものよ!
そなたも また せかいをすくうため
たびを しているのであろう!

TinySegmenterと言う分かち書きライブラリで「桃から生まれた桃太郎」という文章をトークナイズすると次のように分割されます。

桃 | から | 生まれ | た | 桃太郎

完璧に分割されますね。次に「すもももももももものうち」と言う文章をトークナイズしてみましょう。

すも | も | も | も | も | も | も | もの | うち

ご覧のように、漢字の無い推測がしにくいひらがなだけの文章では少し精度が落ちてしまうことがわかります。

まとめ

日本語の文章は自然言語処理を行わなければユーザーが期待するコンテンツを返すのが難しい、ということがおわかりいただけたでしょうか。次の記事では自然言語処理を行うアプリケーションとDrupalのSearch APIモジュールを連携させることができる、私が開発したモジュールについて解説する予定です。


共に働く新しい仲間を
募集しています

スタジオ・ウミは「Drupal」に特化したサービスを提供する Drupal のエキスパートチーム。
フルリモート&フレックス制だから、働く場所を選ばず時間の使い方も自由です。
そんなワークライフバランスの整った環境で、当ブログに書かれているような
様々な技術を共に学びながら、Drupalサイト開発に携わってみたい方を募集しています。
まずはお話だけでも大歓迎!ぜひお気軽にご連絡ください。