Gomokuの形態素解析部をScalaで実装してみた
ここ数日はScalaのコップ本を読んでいて、何かまとまったプログラムをScalaで書いてみたくなったのでGomoku(Java形態素解析器。ver0.0.6)をScalaで実装してみた*1。
・github: scala-gomoku(ver0.0.1)
使用例
$ scala -cp scala-gomoku-0.0.1.jar // インタプリタ起動 & パッケージインポート scala> import net.reduls.scala.gomoku._ // 分かち書き scala> Tagger.wakati("Scalaはオブジェクト指向言語と関数型言語の特徴を統合したマルチパラダイムのプログラミング言語である。") res0: List[String] = List(Scala, は, オブジェクト, 指向, 言語, と, 関数, 型, 言語, の, 特徴, を, 統合, し, た, マルチパラダイム, の, プログラミング, 言語, で, ある, 。) // 形態素解析 scala> Tagger.parse("Scalaはオブジェクト指向言語と関数型言語の特徴を統合したマルチパラダイムのプログラミング言語である。") res1: List[net.reduls.scala.gomoku.Morpheme] = List(Morpheme(Scala,名詞,固有名詞,組織,*,*,*,0), Morpheme(は,助詞,係助詞,*,*,*,*,5), Morpheme(オブジェクト,名詞,一般,*,*,*,*,6), Morpheme(指向,名詞,サ変接続,*,*,*,*,12), Morpheme(言語,名詞,一般,*,*,*,*,14), Morpheme(と,助詞,並立助詞,*,*,*,*,16), Morpheme(関数,名詞,一般,*,*,*,*,17), Morpheme(型,名詞,接尾,一般,*,*,*,19), Morpheme(言語,名詞,一般,*,*,*,*,20), Morpheme(の,助詞,連体化,*,*,*,*,22), Morpheme(特徴,名詞,一般,*,*,*,*,23), Morpheme(を,助詞,格助詞,一般,*,*,*,25), Morpheme(統合,名詞,サ変接続,*,*,*,*,26), Morpheme(し,動詞,自立,*,*,サ変・スル,連用形,28), Morpheme(た,助動詞,*,*,*,特殊・タ,基本形,29), Morpheme(マルチパラダイム,名詞,一般,*,*,*,*,30), Morpheme(の,助詞,連体化,*,*,*,*,38), Morpheme(プログラミング,名詞,サ変接続,*,*,*,*,39), Morpheme(言語,名詞,一般,*,*,*,*,46), Morpheme(で,助動詞,*,*,*,特殊・ダ,連用形,48), Morpheme(ある,助動詞,*,*,*,五段・ラ行アル,基本形,49), Morpheme(。,記号,句点,*,*,*,*,51)) // 名詞のみ取り出し scala> for(m <- res1 if m.feature.startsWith("名詞")) yield m.surface res2: List[String] = List(Scala, オブジェクト, 指向, 言語, 関数, 型, 言語, 特徴, 統合, マルチパラダイム, プログラミング, 言語)
ソースコード行数
Java:
$ cd gomoku-0.0.6-src $ wc -l `find . -name '*.java'` 117 ./analyzer/src/net/reduls/gomoku/Tagger.java 12 ./analyzer/src/net/reduls/gomoku/Morpheme.java 23 ./analyzer/src/net/reduls/gomoku/util/ReadLine.java 83 ./analyzer/src/net/reduls/gomoku/util/Misc.java 38 ./analyzer/src/net/reduls/gomoku/bin/Gomoku.java 32 ./analyzer/src/net/reduls/gomoku/dic/Unknown.java 72 ./analyzer/src/net/reduls/gomoku/dic/Char.java 23 ./analyzer/src/net/reduls/gomoku/dic/WordDic.java 61 ./analyzer/src/net/reduls/gomoku/dic/SurfaceId.java 43 ./analyzer/src/net/reduls/gomoku/dic/Morpheme.java 26 ./analyzer/src/net/reduls/gomoku/dic/PartsOfSpeech.java 23 ./analyzer/src/net/reduls/gomoku/dic/ViterbiNode.java 26 ./analyzer/src/net/reduls/gomoku/dic/Matrix.java 579 合計
$ cd scala-gomoku-0.0.1-src $ wc -l `find . -name '*.scala'` 3 ./src/net/reduls/scala/gomoku/Morpheme.scala 27 ./src/net/reduls/scala/gomoku/bin/Gomoku.scala 15 ./src/net/reduls/scala/gomoku/dic/Matrix.scala 13 ./src/net/reduls/scala/gomoku/dic/PartsOfSpeech.scala 18 ./src/net/reduls/scala/gomoku/dic/Morpheme.scala 22 ./src/net/reduls/scala/gomoku/dic/Char.scala 32 ./src/net/reduls/scala/gomoku/dic/Util.scala 9 ./src/net/reduls/scala/gomoku/dic/ViterbiNode.scala 39 ./src/net/reduls/scala/gomoku/dic/SurfaceId.scala 30 ./src/net/reduls/scala/gomoku/dic/Unknown.scala 15 ./src/net/reduls/scala/gomoku/dic/WordDic.scala 56 ./src/net/reduls/scala/gomoku/Tagger.scala 279 合計
処理速度
以下のようなベンチマークスクリプトを書いて、両者の処理速度を比較してみた。
// ファイル名: Benchmark.scala import scala.testing.Benchmark import net.reduls.scala.gomoku.{Tagger=>ScalaTagger} import net.reduls.gomoku.{Tagger=>JavaTagger} import scala.io.Source // ベンチマーク用データ: 使用したのは約17MBの日本語テキストデータ object BenchmarkData { val lines = Source.fromFile("/path/to/testdata").getLines.toArray } // Scala用のベンチマークオブジェクト object ScalaGomokuBenchmark extends Benchmark { // BenchmarkData.linesの各行を分かち書き override def run() { BenchmarkData.lines.foreach(ScalaTagger.wakati _) } } // Scala用のベンチマークオブジェクト object JavaGomokuBenchmark extends Benchmark { override def run() { BenchmarkData.lines.foreach(JavaTagger.wakati _) } } // ベンチマーク実行 println("[Data]") println(" lines: " + BenchmarkData.lines.length) println("") val scalaRlt = ScalaGomokuBenchmark.runBenchmark(11).tail println("[Scala]") println(" result : " + scalaRlt.mkString(", ")) println(" average: " + (scalaRlt.sum / scalaRlt.length)) println("") val javaRlt = JavaGomokuBenchmark.runBenchmark(11).tail println("[Java]") println(" result : " + javaRlt.mkString(", ")) println(" average: " + (javaRlt.sum / javaRlt.length)) println("")
実行結果:
# Scala: version 2.9.0.1 (OpenJDK 64-Bit Server VM, Java 1.6.0_23). $ scala -cp scala-gomoku-0.0.1.jar:gomoku-0.0.6.jar Benchmark.scala [Data] lines: 172088 # データの行数(約17万行) [Scala] result : 4529, 4574, 4568, 4540, 4503, 4510, 4523, 4515, 4551, 4531 average: 4534 # 平均: 4.534秒 [Java] result : 3153, 3111, 3118, 3112, 3102, 3098, 3118, 3130, 3117, 3133 average: 3119 # 平均: 3.119秒
自分の環境では、Scala版はJava版よりも1.5倍程度遅かった。
※ まだScalaでの効率の良い書き方とかが全然分かっていないので、その辺りを踏まえてちゃんと最適化を行えばもっと差は縮まるかもしれない