Solr 6でneologdが組み込まれたkuromojiを使う方法


> この記事は<a href="http://tech.vasily.jp/entry/bigquery-standard-sql">VASILY DEVELOPERS BLOG</a>にも同じ内容で投稿しています。よろしければ他の記事もご覧ください。

こんにちは、VASILYバックエンドエンジニアの塩崎です。
VASILYでは商品情報の全文検索を行うためのバックエンドに、Apache Solr(以下、Solr)を利用しています。
先日、Solrのメジャーバージョンを最新の6にアップグレードしました。
それに伴ってSolrの形態素解析エンジンであるkuromojiに新語辞書であるmecab-ipadic-neologd(以下、neologd)を組み込みました。

この記事では、組み込むことのメリット及び、具体的な組み込み方を紹介します。

# kuromojiにneologdを組み込むことのメリット
では、まずkuromojiにneologdを組み込むことのメリットを、転置インデックスを利用した全文検索の仕組みに基づいて説明します。

## 転置インデックスを利用した全文検索の仕組み
Solrは全文検索を行うために、転置インデックスと呼ばれるインデックスを生成します。
これは、文章中に登場する単語をキーとし、その単語が出現した文章IDの配列をバリューとする連想配列です。

![スクリーンショット 2016-11-27 19.33.25.png](https://vasily.qiita.com/files/5344e698-6561-4e27-1a91-e90c1c9be53e.png "スクリーンショット 2016-11-27 19.33.25.png")

Solrはこの転置インデックスを用いることで、文章全体をスキャンすることなく、全文検索を行います。
その結果として、膨大な文章に対する全文検索を高速に行うことができます。

![スクリーンショット 2016-11-27 19.34.56.png](https://vasily.qiita.com/files/54d23e51-c7de-83c8-6866-3e89af31fa2e.png "スクリーンショット 2016-11-27 19.34.56.png")

## 全文検索のインデックスに形態素解析を用いるときの欠点
さて、先ほどの例のような英語(?)の文章では単語と単語の間が空白で区切られていました。
一方で日本語の文章は単語境界に空白がありません。
そのために、日本語の文章に転置インデックスを作るためには、空白文字に頼らずに文章を単語単位に分解する必要があります。

この単語分解の方法には大きく分けて2つの手法があります。
機械的にn文字ずつに単語に分解するngramと、文法的に意味のある単位で単語に分解する形態素解析です。
ngramは機械的に文章を分割するために、検索ノイズが多いという欠点があります。
(例: "京都"で検索をした時に、東京都がヒットしてしまう)
そのため、iQONでは形態素解析を用いた転置インデックスの生成を行い、それを使い全文検索を行っています。

![スクリーンショット 2016-11-27 19.51.48.png](https://vasily.qiita.com/files/d3ed0d27-258d-8e8a-b2dc-c33708cd5497.png "スクリーンショット 2016-11-27 19.51.48.png")

しかし、形態素解析を使った転置インデックスの作成にも欠点があります。
それは形態素解析エンジンが対応していない単語に対しては、正しくインデックスを作成できないということです。

![スクリーンショット 2016-11-30 22.48.37.png](https://vasily.qiita.com/files/a2ff0a49-7f4f-14f6-c6fe-de49fa390109.png "スクリーンショット 2016-11-30 22.48.37.png")

## kuromojiの標準辞書では、カバーされている単語が少ない
kuromojiに標準搭載されている辞書はIPA辞書です。
この辞書は2007年を最後に更新が止まっているため、それ以降に作られた単語に対しては正しく形態素解析を行うことができないケースが多いです。
また、辞書を作る時の元データがファッション用語に乏しいため、iQONに登場するファッション用語に対応できないケースがあります。

例えば、以下のような単語は辞書に登録されていません。

* ロクシタン
* スナイデル

## neologdを組み込むことで、辞書の語彙力を底上げ
この問題に対応するためには、全文検索を行うアプリケーション毎に、必要な語彙を辞書に追加する必要があります。
しかし、その作業はとても地道で時間の掛かるものです。

そこで、@overlastさんのmecab-ipadic-neologdをkuromojiに組み込むことを考えました。
この辞書は、以下の公式ドキュメントに書かれているように、Webの情報から新語を追加しています。

> mecab-ipadic-NEologd は、多数のWeb上の言語資源から得た新語を追加することでカスタマイズした MeCab 用のシステム辞書です。

https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

また、辞書の更新ペースも非常に早く、週に2回も辞書が更新されています。

もちろん、これを行えば全ての単語に対応できるわけではありません
ですがkuromojiの標準辞書だけを使うことに比べて、大幅な語彙力の向上が見込まれます。

# 組み込み方
## ソースコードの入手
今回はgithubに公開されているlucene-solrにパッチを当ててneologdを組み込みます。
https://github.com/apache/lucene-solr

なお、具体的なパッチの当て方は、moco(beta)さんや@kazuhira_rさんの以下の記事を大変参考にさせていただきました。ありがとうございます。
http://mocobeta-backup.tumblr.com/post/114318023832/neologd-kuromoji-lucene-4-10-4-branch
http://d.hatena.ne.jp/Kazuhira/20150316/1426520209

これらの記事を元に、Solr6用のパッチを作成しました。
パッチは以下のGistで公開してあります。

https://gist.github.com/shio-phys/02ece250ecbc291e1ec4923ed119d182

また、このパッチを適用させたソースコードをgithubに公開してあります。
https://github.com/vasilyjp/lucene-solr

また、ビルド済みのjarファイルもgithubに公開してありますので、とりあえず試したい方はご利用ください。
2016/12/02時点での最近のneologdを組み込んでいます。

<a href="https://github.com/vasilyjp/lucene-solr/releases/tag/releases%2Flucene-solr%2F6.2.0-neologd">Solr 6.2.0用</a>
<a href="https://github.com/vasilyjp/lucene-solr/releases/tag/releases%2Flucene-solr%2F6.2.1-neologd">Solr 6.2.1用</a>
<a href="https://github.com/vasilyjp/lucene-solr/releases/tag/releases%2Flucene-solr%2F6.3.0-neologd">Solr 6.3.0用</a>

これらのファイルはneologdのバージョンアップには追従していないので、バージョンアップが頻繁なneologdの特性を活かすためには、その時点での最新のneologdを利用することをお勧めします。

## ビルド
### 必要な環境
まずは、以下のドキュメントを参考に、neologdのビルドに必要なパッケージをインストールしましょう。
https://github.com/neologd/mecab-ipadic-neologd#getting-started

その後に、kuromojiをビルドするためのパッケージをインストールしましょう。
Ubuntu 16.04.01であれば、以下のパッケージが必要です。

```bash
$ sudo apt-get install default-jdk ant subversion
```

## jarの生成

パッチ適用済みのリポジトリを取得し、使用しているSolrのバージョンに合わせたブランチをチェックアウトします。

2016/12/02現在、以下のブランチを用意しています。
使用しているSolrのバージョンに合わせたブランチをチェックアウトしてください。

* kuromoji_neologd_6.2.0
* kuromoji_neologd_6.2.1
* kuromoji_neologd_6.3.0

```
$ git clone https://github.com/vasilyjp/lucene-solr.git
$ cd lucene-solr
$ git checkout <ブランチ名>
```

build.propertiesに書かれているneologdのバージョンを最新のものに合わせます。
以下のディレクトリに `mecab-user-dict-seed.*.csv.xz` というファイルがあるので、このファイル名のタイムスタンプ部分をneologd.versionに指定します。

https://github.com/neologd/mecab-ipadic-neologd/tree/master/seed

```
$ vim lucene/build.properties
```

あとは、以下のコマンドを順番に実行することで、kuromojiのjarが生成されます。

```
$ ant ivy-bootstrap
$ ant compile
$ cd lucene/analysis/kuromoji
$ ant clone-neologd
$ ant build-dict
$ ant jar-core
```

以下のディレクトリにjarが生成されます。

```
lucene/build/analysis/kuromoji/
```

jarのファイル名は以下のようなものになります。
実際にはSolrのバージョン名の部分とneologdのバージョン名の部分が変わり得ます。

```
lucene-analyzers-kuromoji-ipadic-neologd-6.3.0-SNAPSHOT-20161128.jar
```

## Solrに読み込ませる
上記の手順によってつくられたjarファイルを以下のパスに配置することで、Solrに読み込ませることができます。

`server/solr-webapp/webapp/WEB-INF/lib`

クラス名を変えていないため、もともとあったjarを上書きする必要があります。

## 読み込まれたことの確認
さて、このjarが正しく読み込まれたことをsolrのweb consoleで確認してみましょう。
solrのweb consoleにはtokenizeの結果をその場で確認する機能があるので、それを使って確認します。
Core Selectorから適当なcoreを選択した後に、Analysisを選択します。
テキストボックス内に文章を打ち込んで、Analysis Valuesボタンを押すと、その文章の解析結果が表示されます。
以下の例では「ロクシタン」という固有名詞が1つの単語として認識されていることが分かります。

![スクリーンショット 2016-12-01 15.12.02.png](https://vasily.qiita.com/files/f0b31afb-4181-f0a7-32b1-035d2927fd24.png "スクリーンショット 2016-12-01 15.12.02.png")

# neologdを組み込んだことによる問題と、その対処
## ブランド名の部分一致検索ができなくなった
neologdを組み込むことで辞書の語彙が大幅に増えましたが、増えすぎることによる問題も発生してしまいました。
複数語からなるブランド名を部分一致検索できなくなってしまいました。
例えば`JIMMY CHOO`というブランド名が1つの単語としてtokenizeされるために、`JIMMY`という検索クエリでヒットしなくなってしまいました。

この問題に対しては、ngramでtokenizeしたフィールドに対しても同時にクエリを投げることで解決しました。
ngramでtokenizeしたフィールドを併用することによって、このような検索漏れを無くすことができました。