こんにちは。サイオステクノロジー OSS サポート担当 山本 です。
今回は solr の仕組みの時にお話しした辞書を拡張するお話です。
■おさらい:solr と辞書のお話
今回のお話を進めていく前に、軽くおさらいをしておきましょう。
solr は予め登録しておいた一連のデータであるドキュメントから高速な文字列検索を行うことに特化した、全文検索 OSS です。
この高速検索を実現するため、solr はドキュメントを登録する時に文字列の解析を行い、解析の結果出てきた検索ワードとして有効なワードを元にしてインデックスと呼ばれるデータベースを作成します。
検索を行う際にも検索ワードを解析して、出てきたワードをインデックスから検索することで、検索の際の負荷や所要時間を大幅に抑えて無駄の少ない全文検索ができる、というわけです。
ところで、「文字列の解析」と軽く言いはしましたが、これには様々なメカニズムが使われています。
特に日本語は「単語ごとに空白スペースで区切る」という言語的なルールがない故に “単語に分割する” 処理が機械的にできないため、内部の辞書を活用して単語に切り分けるメカニズムが使用されています。
■解析と辞書とインデックス
解析の例を見ながら、もう少し細かくおさらいしておきます。
例えば、solr の demo 用コアで “これはあかきえんぴつです。” という文章を解析すると、以下のように分割されます。
これ / は / あかき / えんぴつ / です / 。
このように分割ができるのは、「これ」「は」「あかき」「えんぴつ」「です」というそれぞれの単語が solr の内部辞書に存在しているから、ということになります。
分割された後は、以前紹介したフィルタによって基本形に戻したり検索に不適なワードを除外したりして、最終的に残ったものがインデックスに登録されます。
この文章を持つドキュメントを登録するならば、「あかい (“あかき” の基本形)」と「えんぴつ」がインデックスに追加され、ドキュメントと関連付けられます。
検索の際にはこのインデックスと一致するかをチェックしているため、例えばですが
・「グレー」で検索して「グレープ」が引っかかる
・「レンジ」で検索して「オレンジ」が引っかかる
などのような、一般的な文字列検索で起こってしまうような「そうなんだけど、そうじゃない…!」というタイプの検索ノイズを低減できるというのも、solr の強みの一つと言えます。
ともあれ、日本語で solr を使う場合には、辞書に単語がちゃんと登録されているかがかなり重要であると言えます。
ちなみに、辞書を使って上手く分割できなかった部分は、文字種 (かな・カナ・漢字など) が変わる部分で区切ってそれぞれを固有名詞と想定して処理してくれます。
■辞書を拡張する
さて、では今回の本題です。
solr の採用している日本語形態素解析器 “Kuromoji” の内部辞書は非常に強力で、一般的な単語であれば殆どカバーしてくれるはずです。
しかし、それでも解析結果が常に思い描いたとおりになるわけではありません。例えば……
・単語を分割したい
一例として、デフォルトの日本語辞書では「ショルダーバッグ」「トートバッグ」は1単語として扱われます。
そのため、「バッグ」で検索しても「ショルダーバッグ」や「トートバッグ」を含むドキュメントはヒットしません。
・人名や社名、製品名などの固有名
一般的な単語ではない固有名は当然ながら辞書には登録されておらず、うまく解析できないケースがあります。
一例としては、「サイオステクノロジー」「OSSよろず相談室」は、demo コアのデフォルト設定では「サイ / オス / テクノロジー」「OSS / よろず / 相談 / 室」と分割され、例えば「サイ」で検索して「サイオステクノロジー」がヒットするなど意図しない形でのヒットを誘発してしまう可能性があります。
このような問題を解決できるアプローチの一つが、辞書の拡張です。
■ユーザ辞書の内容確認と作成
solr ではユーザ辞書という機能を使って、任意のフレーズが出てきた際の単語の区切り方を形態素解析の辞書に追加することができます。
実際に前回作成したコアにユーザ辞書を追加してみます。
(コアの作り方や前回お話ししたファイルなどについては省略します。前回記事も一読の上、読み進めてください。)
ユーザ辞書もデモ用環境のコア “demo” にサンプルが入っているので、今回はこちらを参考にして、前回作成した実験用コア “test-new-core” を使って確認していきます。
まずは前回使ったデモ用環境のコンテナを起動し、”demo” のユーザ辞書のサンプル “/var/solr/data/demo/conf/lang/userdict_ja.txt” を手元の環境に持ってきます。
## (デモ用環境起動) $ podman start test-solr ## (ユーザ辞書サンプルのコピー) $ podman cp test-solr:/var/solr/data/demo/conf/lang/userdict_ja.txt ./
手元の環境にコピーした userdict_ja.txt を適当なテキストエディタで開き、まずは内容を確認してみます。
行頭が “#” の行はコメント行扱いとなるので、(ファイルの先頭のユーザ辞書の説明コメント部分を除くと) このファイルはこのような記述がされています。
# Custom segmentation for kanji compounds 日本経済新聞,日本 経済 新聞,ニホン ケイザイ シンブン,カスタム名詞 関西国際空港,関西 国際 空港,カンサイ コクサイ クウコウ,カスタム名詞 # Custom segmentation for compound katakana トートバッグ,トート バッグ,トート バッグ,かずカナ名詞 ショルダーバッグ,ショルダー バッグ,ショルダー バッグ,かずカナ名詞 # Custom reading for former sumo wrestler 朝青龍,朝青龍,アサショウリュウ,カスタム人名
これを見れば (あるいはファイル先頭の説明コメントを読めば) なんとなくわかるかと思いますが、ユーザ辞書は以下のような書式で登録したいワードを列挙したテキストファイルになります。
[登録したい文字列],[登録したい文字列での単語の切り分け方],[各単語の読み方 (カナ)],[品詞]
第2要素の単語の切り分けでは、半角スペースで単語を切り分けます。
第3要素の読み方は、第2要素で切り分けた単語数と同じ数になるよう、半角スペース区切りで各単語の読み方を記述します。
第4要素の品詞は、主にフィルタ “japanesePartOfSpeechStop” で使用される要素で、このフィルタで参照しているファイルに記述されている品詞と一致していると、その単語はインデックスの登録対象から除外されます。
デモ用環境では、japanesePartOfSpeechStop フィルタは “/var/solr/data/demo/conf/lang/stoptags_ja.txt” を参照しています。気になる方はこちらのファイルも確認してみてください。
以下の行を例にしてみると…
関西国際空港,関西 国際 空港,カンサイ コクサイ クウコウ,カスタム名詞
・対象となるフレーズ (文字列) :「関西国際空港」
・単語の切り分け方 :「関西」「国際」「空港」
・切り分けた単語の読み方 (カナ):「カンサイ」「コクサイ」「クウコウ」
・このフレーズの品詞 :「カスタム名詞」
となるわけですね。
さて、構造がなんとなくわかったところで、このユーザ辞書 “userdict_ja.txt” をベースにユーザ辞書を試してみましょう。
先ほど挙げた例 (「トートバッグ」「ショルダーバッグ」「サイオステクノロジー」) を使おうかと思いますが、「トートバッグ」「ショルダーバッグ」はサンプルとして既に登録されているので、「サイオステクノロジー」が1単語として扱われるように登録してみます。
以下のような行を “userdict_ja.txt” の末尾に追加しましょう。
サイオステクノロジー,サイオステクノロジー,サイオステクノロジー,カスタム名詞
■ユーザ辞書の適用
続いて変更したユーザ辞書を、前回作成した実験用コア “test-new-core” に適用してみましょう。
まずは、作成したユーザ辞書を “test-new-core” に配置します。
“test-new-core” のスキーマファイルから参照できればどこにおいても大丈夫なはずですが、サンプルに従ってコアのディレクトリ内の “conf/lang/” 配下に配置するのが無難でしょう。
$ podman cp ./userdict_ja.txt test-solr:/var/solr/data/test-new-core/conf/lang/userdict_ja.txt
続いて、このユーザ辞書を参照するように、スキーマファイルを変更します。
“test-new-core” のスキーマファイル “managed-schema.xml” を一旦手元に持ってきて…(デモ用コンテナにはテキストエディタがないため)
$ podman cp test-solr:/var/solr/data/test-new-core/conf/managed-schema.xml ./managed-schema.xml
このスキーマの日本語解析の設定を行なっている部分の “tokenizer” 要素を以下のように変更します。
## 変更前
<fieldType name="text_ja" class="solr.TextField" autoGeneratePhraseQueries="false" positionIncrementGap="100">
<analyzer>
<tokenizer mode="search" name="japanese"/>
<filter name="japaneseBaseForm"/>
:
:
## 変更後
<fieldType name="text_ja" class="solr.TextField" autoGeneratePhraseQueries="false" positionIncrementGap="100">
<analyzer>
<tokenizer mode="search" name="japanese" userDictionary="lang/userdict_ja.txt" />
<filter name="japaneseBaseForm"/>
:
:
変更したら “test-new-core” のスキーマファイルを置き換えれば準備 OK です。
$ podman cp ./managed-schema.xml test-solr:/var/solr/data/test-new-core/conf/managed-schema.xml
変更したスキーマの設定を読み込むため、”test-new-core” をリロードします。
リロードはコマンドと管理画面のどちらからでも可能で、
コマンドでリロードするなら以下のようなコマンドを、
$ curl -X POST http://(IP or ホスト名):8984/api/cores/test-new-core/reload
管理画面でリロードするなら “Core Admin” の画面から対象のコアを選択して、”Reload” ボタンを押してください。
これで “test-new-core” にカスタムしたユーザ辞書を適用できました。
早速、管理画面で試してみましょう。
“Core Selector” タブで test-new-core を選んで、”Analysis” を開いて、ユーザ辞書を適用した FieldType “text_ja” で「ショルダーバッグ」「サイオステクノロジー」の単語分けの状況を確認してみましょう。
上手くいっていそうです。
そういえば、前回このコアに登録したドキュメントに「サイオステクノロジー」を含むものがありましたね。
折角なのでちゃんと検索もできるかを確認してみましょう。
(コマンドだと URL エンコードで見辛いので管理画面で確認してみます。)
……引っかかっていませんね?何か間違えたのでしょうか?
試しに元々の解析で出ていたワードの一つ「サイ」で検索をかけてみましょう…
こちらだと引っかかってしまいますね。(こういう引っかかり方を避けるためにユーザ辞書を変更したはずなのに…)
これは一体どういうことでしょう?
■ユーザ辞書変更とインデックス
ユーザ辞書変更直後に検索が上手くいかない現象には、アレが深く関係しています。
そう、アレです。インデックスです。
何度かお話ししましたが、solr は高速な文字列検索を実現するために
・ドキュメント (データ) の登録時に、登録データを解析した結果をインデックスに登録する
・検索時は検索ワードの解析結果を基にインデックスから検索する
というアプローチをとっています。
これは特に変なことをしなければ上手くいくのですが、今回起こったことを (簡略化して) 順に見ていきましょう…
まず、「サイオステクノロジー」をドキュメントに登録しました。
この時点では「サイオステクノロジー」は「サイ / オス / テクノロジー」と切り分けられていたため、インデックスには「サイ」「オス」「テクノロジー」の 3ワードがこのドキュメントと関連するワードとして登録されます。
この時点では「サイオステクノロジー」で検索すると、解析された「サイ」「オス」「テクノロジー」の3ワードでの検索となります。
インデックスの「サイ」「オス」「テクノロジー」は、いずれもドキュメントの「サイオステクノロジー」に紐づいているので、この検索は成功します。
さて、ここで今回の変更です。
解析結果がおかしいことに気が付いて、「サイオステクノロジー」を1ワードとして扱うようにユーザ辞書を作りました。
しかし、この作業をする前に登録済みのインデックスは変更されていません。
この状態で「サイオステクノロジー」で検索すると、解析結果の「サイオステクノロジー」をインデックスから検索しようとします。
ところが、ドキュメント「サイオステクノロジー」に紐づいているインデックスは登録した時点の「サイ」「オス」「テクノロジー」の3ワードのみであり、「サイオステクノロジー」でインデックスから検索しても一致するものがありません。
他方、「サイ」だけで検索すればこの解析結果は「サイ」です。
これは最初に「サイオステクノロジー」をドキュメント登録した時の解析結果「サイ」「オス」「テクノロジー」の 3ワードのうちの「サイ」と合致して、結果としてドキュメント「サイオステクノロジー」がヒットしてしまいます。
この問題を解決するには、ユーザ辞書を変更した後にドキュメントを登録しなおして、インデックスを作り直す必要があります。
また、そもそも元々登録していたドキュメント・インデックスが検索の邪魔になってしまう可能性があるので、元々のドキュメント・インデックスは一度消したほうがよいです。
つまり、ドキュメント登録を一からやり直しする必要があります。
ドキュメントとインデックスの削除については、今回の “test-new-core” であれば以下のようなコマンドで行うことができます。
$ curl -X POST -H 'Content-Type: application/json' --data-binary '{"delete":{"query":"*:*" }}' http://(IP or ホスト名):8984/solr/test-new-core/update
上記コマンドの実行後、コアのホームディレクトリの data/index 配下 (今回の “test-new-core” ならコンテナ内の ) にファイルがない状態なのを確認できたら、再度新しくドキュメントを登録し直せば OK です。
再度ドキュメントの登録が完了した後に改めて「サイオステクノロジー」で検索すれば、今度こそ上手くいってくれるはずです。
■最後に
今回はユーザ辞書についてと、インデックスの補足についてお話ししてみました。
最後にお話ししたインデックスの再作成は、ユーザ辞書の登録以外にもスキーマ設定の変更 (使用するフィルタの変更など) をする場合には大体必要になってくるので、solr を使う上では忘れてはならない要素です。
以前にお話ししたとおり、解析とインデックス作成をする都合上 solr のドキュメント登録は重めの処理となっているため、何度もインデックス作成をするのはできれば避けたいところです。
少量のテストデータで試してみたり、デモ画面の “Analysis” を使って主要なワードが思った通りに解析されているかをチェックし、できる限りドキュメントの本登録を行う前にスキーマ設定やユーザ辞書をいい感じにしておくとよいでしょう。
なおユーザ辞書に登録したフレーズは、常にユーザ辞書に登録した切り分け方が採用されるので、「デフォルト辞書との競合で意図しないことが起こる」ということは基本的にはないはずです。
安心して必要なフレーズだけ登録するようにしましょう。