CLOVER🍀

That was when it all began.

Lucene Kuromojiのトークナイズを、Graphvizを使ってビジュアル化する

ちょっと前に見ていたこちらのエントリ。

Solr + kuromoji で単語の切れ方がおかしかったのでガッツリ調べてみた、理由と調べ方その方法を公開します!
http://blog.yoslab.com/entry/2014/09/12/005207

kuromoji のサイトに行くと、トークナイズの処理を分析することができる。
http://blog.yoslab.com/entry/2014/09/12/005207

Atilika Kuromojiのサイトやkuromoji-serverで、Kuromojiのトークナイズの様子がビジュアル化できるようで、へぇ〜と思っていたのですが、最近Lucene Kuromojiで同じことができそうなことに気付き、ちょっとやってみました。

kuromoji-server
http://atilika.org/kuromoji/
※このページで、「Viterbi」を選んで形態素解析する

オリジナルのkuromoji-serverのソースコードを見る限り、

org.atilika.kuromoji.server.KuromojiServer.java
https://github.com/atilika/kuromoji-server/blob/master/src/main/java/org/atilika/kuromoji/server/KuromojiServer.java

Luceneではこのクラス、

GraphvizFormatter
http://lucene.apache.org/core/5_2_1/analyzers-kuromoji/org/apache/lucene/analysis/ja/GraphvizFormatter.html

そしてグラフ化にはこちらを使えばよいみたいです。

Graphviz
http://www.graphviz.org/

使い方は簡単なようなので、さっくりと。

先に、Graphvizをインストールします。自分の環境はUbuntu Linux 14.04 LTSですが、最新版でなくても別にいいので普通にapt-getでGraphvizをインストール。

$ sudo apt-get install graphviz

確認。

$ dot -V
dot - graphviz version 2.36.0 (20140111.2315)

はい。

続いて、プログラム側。まずはビルド定義。
build.sbt

name := "lucene-kuromoji-viterbi"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.7"

organization := "org.littlewings"

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

updateOptions := updateOptions.value.withCachedResolution(true)

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

※kuromojiとcommonで分けているのは、あとでちょっと遊ぶからです

ソースコードは短めです。
src/main/scala/org/littlewings/lucene/kuromoji/KuromojiViterbi.scala

package org.littlewings.lucene.kuromoji

import scala.language.postfixOps
import scala.sys.process._

import java.io.{File, ByteArrayInputStream, StringReader}
import java.nio.charset.StandardCharsets

import org.apache.lucene.analysis.ja.dict.ConnectionCosts
import org.apache.lucene.analysis.ja.{GraphvizFormatter, JapaneseTokenizer}

object KuromojiViterbi {
  def main(args: Array[String]): Unit = {
    val word = args.toList.headOption.getOrElse("すもももももももものうち")

    val graphvizFormatter = new GraphvizFormatter(ConnectionCosts.getInstance)

    val tokenizer = new JapaneseTokenizer(null, true, JapaneseTokenizer.Mode.NORMAL)
    tokenizer.setReader(new StringReader(word))
    tokenizer.setGraphvizFormatter(graphvizFormatter)

    tokenizer.reset()

    Iterator.continually(tokenizer.incrementToken()).takeWhile(identity).foreach(_ => ())

    tokenizer.end()
    tokenizer.close()

    val dotOutput = graphvizFormatter.finish()

    "dot -Tgif" #< new ByteArrayInputStream(dotOutput.getBytes(StandardCharsets.UTF_8)) #> new File("out.gif") !
  }
}

ちょっと普段と違うのは、GraphvizFormatterのインスタンスを作成するところと、

    val graphvizFormatter = new GraphvizFormatter(ConnectionCosts.getInstance)

JapaneseTokenizerにGraphvizFormatterのインスタンスを設定するところ、

    tokenizer.setGraphvizFormatter(graphvizFormatter)

そしてトークナイズが終わったら、GraphvizFormatter#finishを呼び出して結果の文字列を取得するところです。

あとは、インストールしたdotコマンドに渡して画像に変換します。ここは、外部プロセス起動です。

    "dot -Tgif" #< new ByteArrayInputStream(dotOutput.getBytes(StandardCharsets.UTF_8)) #> new File("out.gif") !

今回はGIF画像で、ファイル名は「out.gif」としました。

kuromoji-serverの場合は、SVGで作成してブラウザで表示しているようです。

で、今回のプログラムは形態素解析対象の言葉を起動引数で受け取れるのですが、デフォルトは「すもももももももものうち」としているので、

    val word = args.toList.headOption.getOrElse("すもももももももものうち")

これをビジュアル化すると、このようになります。

候補に挙がった単語や、コストなどがわかって良いですね。

ちょっと言葉を変えてみましょう。

> run 関西国際空港に行こう

今度は、こんな結果に。

なかなか面白いですね。こちらもサーバー化したみたいですけど…どうかな…。

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

オマケ:mecab-ipadic-NEologdを組み込んだLucene Kuromojiを使う

ちょっとしたオマケということで、mecab-ipadic-NEologdを組み込んだLucene Kuromojiでビジュアル化してみます。
※使用したneologdの辞書は、20150716日付のものです

> run きゃりーぱみゅぱみゅ

こちらもOKですね。

絵文字でもできたんですけど、画像内に絵文字が表現できなかったのでやめました…。