CLOVER🍀

That was when it all began.

修正されたmecab-ipadic-neologdの辞書を、Lucene Kuromojiに適用してみる

先日、このようなエントリを書きました。

mecab-ipadic-neologdの辞書を、Lucene Kuromojiに適用してみる
http://d.hatena.ne.jp/Kazuhira/20150315/1426391366

mecab-ipadic-neologd自体については、こちら。

MeCab 用の新語辞書 mecab-ipadic-neologd を公開しました
http://diary.overlasting.net/2015-03-13-1.html

このエントリでは、LuceneのKuromojiにmecab-ipadic-neologdを適用してみたのですが、2つの問題が出ました。

ひとつは、Kuromojiがmecab-ipadic-neologdのシード辞書に含まれる原形が15文字を超える単語を取り込めないこと。もうひとつは、同じくmecab-ipadic-neologdのシード辞書に含まれる文脈IDと品詞の組み合わせが、元のIPA辞書とズレてしまっていてやっぱり取り込めないこと。

最初のものは、Kuromojiの事情だと思いますが(オリジナルのMeCabは原形が15文字を超えていようが動作するので)、後者はmecab-ipadic-neologdの問題かなぁとちょっと思っていました。

すると、作者の@overlastさんにこのエントリを見ていただけたようで、修正してもらえました!

ありがとうございます!

3/17追記)
その後、辞書に反映する原形の最大値を指定できるオプションまでつけてもらえました!

スミマセン…ありがとうございます!

というわけで、気を取り直して再度適用方法をまとめたいと思います。今度は、もう少しボリュームを抑えて…。
※前回のエントリの記述が混じっているものもありますが、ご了承ください

追記)
最終的に、bashスクリプトにしました。いきなり結果が欲しい方は、こちらへ。

Lucene Kuromojiに対して、mecab-ipadic-neologdの辞書を適用してビルドするbashスクリプトを書きました
http://d.hatena.ne.jp/Kazuhira/20150317/1426606053

MeCabとIPA辞書のインストール

READMEを読みつつ、必要なソフトウェアを揃えます。

mecab-ipadic-NEologd : Neologism dictionary for MeCab
https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

必要なものは、C++コンパイラ、iconv、MeCab、mecab-ipadic、xzだそうです。

前回までである程度揃えてしまったので、ここではMeCabとIPA辞書のインストールから始めます。

MeCabのインストール。今回もMeCabをシステムグローバルにインストールしないので、インストール先を指定します。ここでは、「$MECAB_HOME」と記載します。

$ wget https://mecab.googlecode.com/files/mecab-0.996.tar.gz
$ tar -zxvf mecab-0.996.tar.gz
$ cd mecab-0.996
$ ./configure --prefix=$MECAB_HOME
$ make
$ sudo make install

インストールしたMeCabにパスを通します。

$ export PATH=$MECAB_HOME/bin:$PATH

IPA辞書のインストール。

$ wget https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz
$ tar -zxvf mecab-ipadic-2.7.0-20070801.tar.gz
$ cd mecab-ipadic-2.7.0-20070801
$ ./configure --with-charset=utf-8
$ make
$ sudo make install

ここまでで、mecab-ipadic-neologdのインストール要件が揃います。

mecab-ipadic-neologdのインストール

以下に記載の手順に沿って、mecab-ipadic-neologdをインストールします。

mecab-ipadic-NEologd : Neologism dictionary for MeCab
https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

$ git clone https://github.com/neologd/mecab-ipadic-neologd.git
$ cd mecab-ipadic-neologd
$ git pull
$ ./bin/install-mecab-ipadic-neologd -n --max_baseform_length 15

今回、@overlastさんに追加していただいたオプション、「-n --max_baseform_length [原形の最大長]」を指定して、今回は原形の長さが15文字以内になるようにしています。

実行すると、途中でこんな表示が出て原形が15文字を超えたものを、消してくれてるっぽいです。

[make-mecab-ipadic-neologd] : Delete the entries whose length of base form is longer than 15 from seed file

そして、ビルドした時に、カレントディレクトリに生成される「build」ディレクトリの中身を、今後は使うことになります。

$ ls -l build
合計 11928
drwxrwxr-x 2 xxxxx xxxxx     4096  3月 17 21:53 mecab-ipadic-2.7.0-20070801-neologd-20150317
-rw-rw-r-- 1 xxxxx xxxxx 12208105  3月 17 21:52 mecab-ipadic-2.7.0-20070801.tar.gz

「neologd」の次の日付の部分(今回は「20150317」)は、辞書が更新されるにしたがって上がっていくようなので、適宜読み替えてください。

ちなみに、今回のような使い方だと、最後に実行するスクリプトは、「bin/install-mecab-ipadic-neologd」ではなくて「libexec/make-mecab-ipadic-neologd.sh」でもいいかもしれません。

以降、MeCabは使わなくなります。

Luceneのビルド

続いて、Luceneをビルドします。

Subversionからエクスポートして、Lucene本体をビルド。今回のLuceneのバージョンは5.0.0を使うので、タグは「lucene_solr_5_0_0」です。

$ svn export http://svn.apache.org/repos/asf/lucene/dev/tags/lucene_solr_5_0_0
$ cd lucene_solr_5_0_0/lucene
$ ant ivy-bootstrap
$ ant compile

「ant ivy-bootstrap」は、すでにAntにIvyが導入済みであれば不要です。

なお、ここでLuceneをエクスポートしたディレクトリ(/path/to/lucene_solr_5_0_0)を、$LUCENE_SRC_HOMEと記載します。

Kuromojiの配置されているディレクトリへ移動。

$ cd analysis/kuromoji

先ほど、mecab-ipadic-neologdをビルドした時の中間生成物(build/mecab-ipadic-2.7.0-20070801-neologd-20150317)を、Kuromojiのビルド成果物の出力先にコピーします。

$ cp -Rp [mecab-ipadic-neologdをビルドしたディレクトリ]/build/mecab-ipadic-2.7.0-20070801-neologd-20150317 $LUCENE_SRC_HOME/lucene/build/analysis/kuromoji

デフォルトのIPA辞書ではなく、コピーしたmecab-ipadic-neologdの中間生成物を使うように、build.xmlのipadic.versionを修正します(ここが、ディレクトリ名も指すようになっているので)。

  <!-- <property name="ipadic.version" value="mecab-ipadic-2.7.0-20070801" /> -->
  <property name="ipadic.version" value="mecab-ipadic-2.7.0-20070801-neologd-20150317" />

先にも書きましたが、neologdの後ろに続く日付は、適宜インストールしたバージョンに合わせて修正してください。

今回使う辞書(というかCSVファイル)はUTF-8で書かれているので、デフォルトのEUC-JPから変更します。

  <!-- <property name="dict.encoding" value="euc-jp"/> -->
  <property name="dict.encoding" value="utf-8"/>

build-dictタスクでは、辞書のダウンロードは不要になるので、dependsからdownload-dictタスクを切り離します。

  <!-- <target name="build-dict" depends="compile-tools, download-dict"> -->
  <target name="build-dict" depends="compile-tools">

辞書作成ツールは、今回のCSVファイルを読ませるとデフォルトのヒープサイズ(1G)では足りなくなるので、拡張します。2Gにしましたが、今回はこれで十分に余裕がありました。

      <!-- TODO: optimize the dictionary construction a bit so that you don't need 1G -->
      <!-- <java fork="true" failonerror="true" maxmemory="1g" classname="org.apache.lucene.analysis.ja.util.DictionaryBuilder"> -->
      <java fork="true" failonerror="true" maxmemory="2g" classname="org.apache.lucene.analysis.ja.util.DictionaryBuilder">

※Antの設定はシステムプロパティでもできることを後で思い出しましたが、maxmemoryなどは変えられなさそうなのでこのままbuild.xmlを修正する方向のままにしました

では、辞書をビルドします。

$ ant regenerate

ここで、mecab-ipadic-neologdインストール時に「-n --max_baseform_length 15」オプションを付けていなかった場合、しばらく待っていると辞書のビルドに失敗します。

     [java] building tokeninfo dict...
     [java]   parse...
     [java]   sort...
     [java]   encode...
     [java] Exception in thread "main" java.lang.AssertionError
     [java] 	at org.apache.lucene.analysis.ja.util.BinaryDictionaryWriter.put(BinaryDictionaryWriter.java:129)
     [java] 	at org.apache.lucene.analysis.ja.util.TokenInfoDictionaryBuilder.buildDictionary(TokenInfoDictionaryBuilder.java:143)
     [java] 	at org.apache.lucene.analysis.ja.util.TokenInfoDictionaryBuilder.build(TokenInfoDictionaryBuilder.java:78)
     [java] 	at org.apache.lucene.analysis.ja.util.DictionaryBuilder.build(DictionaryBuilder.java:37)
     [java] 	at org.apache.lucene.analysis.ja.util.DictionaryBuilder.main(DictionaryBuilder.java:82)

冒頭にも書きましたが、Kuromojiが原形が15文字を超えることを許さないみたいなのです。

      assert baseForm.length() < 16;

https://github.com/apache/lucene-solr/blob/lucene_solr_5_0_0/lucene/analysis/kuromoji/src/tools/java/org/apache/lucene/analysis/ja/util/BinaryDictionaryWriter.java#L129

参考:MeCabの辞書のフォーマットについて)
単語の追加方法
http://mecab.googlecode.com/svn/trunk/mecab/doc/dic.html

うまくいかなかった場合は、このあたりを見直してみてください。

成功すると、以下の用に表示されます。

regenerate:

BUILD SUCCESSFUL
Total time: 1 minute 31 seconds

また、前回はここで文脈IDと品詞の組み合わせがズレていてコケていたのでした。バッチリ修正されていますね!

そして、Kuromoji本体をビルド。

$ ant jar-core
jar-core:
      [jar] Building jar: $LUCENE_SRC_HOME/lucene/build/analysis/kuromoji/lucene-analyzers-kuromoji-5.0.0-SNAPSHOT.jar

BUILD SUCCESSFUL
Total time: 5 seconds

Lucene Kuromojiを使って動作確認

では、Kuromojiを使って動作確認します。プログラムは、Scalaで記述。

build.sbt

build.sbt 
name := "lucene-kuromoji-mecab-neologd"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.5"

organization := "org.littlewings"

updateOptions := updateOptions.value.withCachedResolution(true)

scalacOptions ++= Seq("-Xlint", "-unchecked", "-deprecation", "-feature")

libraryDependencies ++= Seq(
  "org.apache.lucene" % "lucene-core" % "5.0.0",
  "org.apache.lucene" % "lucene-analyzers-common" % "5.0.0",
  "org.apache.lucene" % "lucene-analyzers-kuromoji" % "5.0.0"
)

確認用のコード。
src/main/scala/org/littlewings/lucene/kuromoji/KuromojiWithNeologd.scala

package org.littlewings.lucene.kuromoji

import org.apache.lucene.analysis.ja.JapaneseAnalyzer
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute

object KuromojiWithNeologd {
  def main(args: Array[String]): Unit = {
    val texts = Array(
      "すもももももももものうち",
      "きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー!",
      "日本経済新聞でモバゲーの記事を読んだ",
      "くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ",
      "艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム"
    )

    val analyzer = new JapaneseAnalyzer

    for (text <- texts) {
      val tokenStream = analyzer.tokenStream("", text)

      val charTermAttr = tokenStream.addAttribute(classOf[CharTermAttribute])

      tokenStream.reset()

      val tokens =
        Iterator
          .continually(tokenStream.incrementToken())
          .takeWhile(identity)
          .map(_ => charTermAttr.toString)

      println(s"InputText = $text")
      println(s"  Tokenized = ${tokens.mkString("[", ", ", "]")}")

      tokenStream.close()
    }
  }
}

形態素解析する文章は、適当にWikipediaなどから貼っています。また、KuromojiはデフォルトのSEARCHモードです。

このプログラムを実行してみます。

> run
[info] Running org.littlewings.lucene.kuromoji.KuromojiWithNeologd 
InputText = すもももももももものうち
  Tokenized = [すもも, もも, もも]
InputText = きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー!
  Tokenized = [く, ー, ぱみゅぱみゅは, 2012, 年, つけ, つける, デビュ]
InputText = 日本経済新聞でモバゲーの記事を読んだ
  Tokenized = [日本, 日本経済新聞, 経済, 新聞, モバゲ, 記事, 読む]
InputText = くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ
  Tokenized = [くり, ぃむしちゅ, ー, 上田, 晋, 也, 有田, 哲, 平, 2, 人, 日本, お笑い, コンビ]
InputText = 艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム
  Tokenized = [艦隊, くし, ょんは, 角川, ゲームス, 開発, dmm, com, 配信, ブラウザゲーム]
[success] Total time: 0 s, completed 2015/03/17 0:14:21

Tokenized = の部分が形態素解析した結果ですが、けっこうすごいことになりました。わからない単語が多いということですね。

では、ここで先ほど辞書を使ってビルドした、KuromojiのJARファイルを使ってみます。
1度sbtを終了。

> exit

libディレクトリを作成します。

$ mkdir lib

この中に、ビルドしたJARファイルを放り込みます。

$ cp $LUCENE_SRC_HOME/lucene/build/analysis/kuromoji/lucene-analyzers-kuromoji-5.0.0-SNAPSHOT.jar lib/

sbtの依存関係定義から、Kuromojiを外します。

libraryDependencies ++= Seq(
  "org.apache.lucene" % "lucene-core" % "5.0.0",
  "org.apache.lucene" % "lucene-analyzers-common" % "5.0.0"
  // "org.apache.lucene" % "lucene-analyzers-kuromoji" % "5.0.0"
)

では、再度sbtを起動して、実行。

> run
[info] Running org.littlewings.lucene.kuromoji.KuromojiWithNeologd 
InputText = すもももももももものうち
  Tokenized = [すもももももも, すもももももももものうち, もも]
InputText = きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー!
  Tokenized = [きゃりーぱみゅぱみゅ, 2012年, つけまつける, デビュ]
InputText = 日本経済新聞でモバゲーの記事を読んだ
  Tokenized = [日本, 日本経済新聞, 経済, 新聞, mobage, 記事, 読む]
InputText = くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ
  Tokenized = [くりぃむしちゅー, 上田, 晋也, 有田, 有田哲平, 哲平, 2, 人, 日本, お笑いコンビ]
InputText = 艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム
  Tokenized = [艦隊これくしょん, 角川ゲームス, 開発, dmm.com, 配信, ブラウザゲーム]
[success] Total time: 1 s, completed 2015/03/17 0:15:09

結果がだいぶ変わりましたね。「きゃりーぱみゅぱみゅ」といった人名などがかなり認識できています。

ところで、「すもももももももものうち」の結果が、妙なことになりました。

InputText = すもももももももものうち
  Tokenized = [すもももももも, すもももももももものうち, もも]

一応、シードのCSVを確認…。

$ view [mecab-ipadic-neologdをビルドしたディレクトリ]/build/mecab-ipadic-2.7.0-20070801-neologd-20150317/mecab-user-dict-seed.20150317.csv

このあたりに引っかかりましたね…。

すももももも,1288,1288,5072,名詞,固有名詞,一般,*,*,*,すももももも,スモモモモモ,スモモモモモ
すもももももも,1288,1288,4587,名詞,固有名詞,一般,*,*,*,すもももももも,スモモモモモモ,スモモモモモモ

そして、「すもももももももものうち」は単体で名詞として存在していたり…。

すもももももももものうち,1288,1288,4143,名詞,固有名詞,一般,*,*,*,すもももももももものうち,スモモモモモモモモノウチ,スモモモモモモモモノウチ

とりあえず、mecab-ipadic-neologdの辞書をKuromojiで取り込んで動かすという目標は達成したので、よしとしましょう!

終わりに

mecab-ipadic-neologdについてですが、公開されてから注目を集めているようで、いろんなところで名前を見たり、◯◯で使ってみたみたいなエントリが出ているようです。

その中で、先日自分はLucene Kuromojiで踏んだわけですが…。とはいえ、@overlastさんにコメントおよび修正いただけたりして、とても驚きました。前回はかなり無理矢理ビルドを通しましたが、修正してもらえたことでLucene Kuromojiでもかなり取り込みやすくなったと思います。

ありがとうございました!

今回作成したソースコードとスクリプトは、こちらに置いています。
https://github.com/kazuhira-r/lucene-examples/tree/master/lucene-kuromoji-mecab-neologd

前回より、形態素解析する文章をちょっといじったりしています。