新型の形態素解析エンジン JUMAN++をRubyから使う


今一番熱い形態素解析エンジンといえば、やっぱりJUMAN++ですよね！

[(参考1)JUMAN++ - 黒橋・河原研究室 - 京都大学](http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN++)

[(参考2)新形態素解析器JUMAN++を触ってみたけど思ったより高精度でMeCabから乗り換えようかと思った話](http://qiita.com/riverwell/items/438e88427363511e9f28)

少しだけ使ってみましたが、状況次第ではmecab + NEologdを凌駕する精度が出そうだと感じました。

JUMAN++にはpythonラッパーが提供されていますが、今回はrubyから利用する方法を試してみました。

[17年1月24日追記]Gemとして公開してみました。

[GitHub:EastResident/jumanpp_ruby](https://github.com/EastResident/jumanpp_ruby)

[RubyGem:jumanpp_ruby](https://rubygems.org/gems/jumanpp_ruby)

# 環境

OS: Mac OS X EI Capitan
Ruby: 2.3.1

# 前提

前提として、当然ではありますがJUMAN++が入っている環境である必要があります。Macならhomebrewで一発なので、それが一番簡単かと思います。

```
brew install jumanpp
```

`jumanpp`コマンドが実行できる環境なら問題ありません。

# ラッパーモジュールの定義

方法は色々あるかと思いますが、今回はごくシンプルな方法で実装しました。

```rb
# coding: utf-8
require 'open3'

module Jumanpp

  def parse(sentents)
    o, = Open3.capture2("echo \"#{sentents}\" \| jumanpp --force-single-path")
    sep_words = o.split("\n").map { |r| r.delete("\\").delete("\"")}
    if block_given?
      sep_words.each do |word|
        yield word.split
      end
    else
      sep_words.map { |word| word.split.first }
    end
  end

  module_function :parse
end
```

# 使用例

`Jumanpp.parse`メソッドは、引数に与えた文字列を形態素に分解し、表層形のみを一次元配列の形で返します。ざっくり言うと、分かち書きして配列で返すだけです。

```rb
p Jumanpp.parse('モビルスーツの性能の違いが、戦力の決定的差でないということを教えてやる')
```

出力結果

```rb
["モビルスーツ", "の", "性能", "の", "違い", "が", "、", "戦力", "の", "決定", "的", "差", "で", "ない", "と", "いう", "こと", "を", "教えて", "やる", "EOS"]
```

さらに、`Jumanpp.parse`メソッドにブロックを与えることで、構文解析結果を形態素ごとにイテレートして処理することも可能です。

```rb
Jumanpp.parse('ララァ…私を導いてくれ！') do |word|
  p word
end
```

出力結果

```rb
["ララァ", "ララァ", "ララァ", "名詞", "6", "普通名詞", "1", "*", "0", "*", "0", "自動獲得:Wikipedia", "Wikipediaリダイレクト:ララァ・スン"]
["…", "…", "…", "特殊", "1", "記号", "5", "*", "0", "*", "0", "NIL"]
["私", "わたし", "私", "名詞", "6", "普通名詞", "1", "*", "0", "*", "0", "代表表記:私/わたし", "漢字読み:訓", "カテゴリ:人"]
["を", "を", "を", "助詞", "9", "格助詞", "1", "*", "0", "*", "0", "NIL"]
["導いて", "みちびいて", "導く", "動詞", "2", "*", "0", "子音動詞カ行", "2", "タ系連用テ形", "14", "代表表記:導く/みちびく"]
["くれ", "くれ", "くれる", "接尾辞", "14", "動詞性接尾辞", "7", "母音動詞", "1", "基本連用形", "8", "代表表記:くれる/くれる"]
["！", "！", "！", "特殊", "1", "記号", "5", "*", "0", "*", "0", "NIL"]
["EOS"]
```

ひらがなだけの文章でも試してみました。

```rb
# 認めたくないものだな、自分自身の若さ故の過ちというものを
Jumanpp.parse('みとめたくないものだな、じぶんじしんのわかさゆえのあやまちというものを') do |word|
  p word
end
```

出力結果

```rb
["みとめ", "みとめ", "みとめる", "動詞", "2", "*", "0", "母音動詞", "1", "基本連用形", "8", "代表表記:認める/みとめる", "補文ト"]
["たく", "たく", "たい", "接尾辞", "14", "形容詞性述語接尾辞", "5", "イ形容詞アウオ段", "18", "基本連用形", "7", "代表表記:たい/たい"]
["ない", "ない", "ない", "接尾辞", "14", "形容詞性述語接尾辞", "5", "イ形容詞アウオ段", "18", "基本形", "2", "代表表記:ない/ない"]
["もの", "もの", "もの", "名詞", "6", "形式名詞", "8", "*", "0", "*", "0", "NIL"]
["だ", "だ", "だ", "判定詞", "4", "*", "0", "判定詞", "25", "基本形", "2", "NIL"]
["な", "な", "な", "助詞", "9", "終助詞", "4", "*", "0", "*", "0", "NIL"]
["、", "、", "、", "特殊", "1", "読点", "2", "*", "0", "*", "0", "NIL"]
["じぶん", "じぶん", "じぶん", "名詞", "6", "時相名詞", "10", "*", "0", "*", "0", "代表表記:時分/じぶん", "カテゴリ:時間"]
["じしん", "じしん", "じしん", "名詞", "6", "普通名詞", "1", "*", "0", "*", "0", "代表表記:自身/じしん", "カテゴリ:人"]
["の", "の", "の", "助詞", "9", "接続助詞", "3", "*", "0", "*", "0", "NIL"]
["わか", "わか", "わかい", "形容詞", "3", "*", "0", "イ形容詞アウオ段", "18", "語幹", "1", "代表表記:若い/わかい"]
["さ", "さ", "さ", "接尾辞", "14", "名詞性述語接尾辞", "1", "*", "0", "*", "0", "代表表記:さ/さ", "準内容語", "カテゴリ:抽象物;数量"]
["ゆえ", "ゆえ", "ゆえ", "名詞", "6", "副詞的名詞", "9", "*", "0", "*", "0", "代表表記:故/ゆえ"]
["の", "の", "の", "助詞", "9", "接続助詞", "3", "*", "0", "*", "0", "NIL"]
["あやまち", "あやまち", "あやまち", "名詞", "6", "普通名詞", "1", "*", "0", "*", "0", "代表表記:過ち/あやまちv", "連用形名詞化:形態素解析"]
["と", "と", "と", "助詞", "9", "格助詞", "1", "*", "0", "*", "0", "NIL"]
["いう", "いう", "いう", "動詞", "2", "*", "0", "子音動詞ワ行", "12", "基本形", "2", "代表表記:言う/いう", "補文ト"]
["もの", "もの", "もの", "名詞", "6", "形式名詞", "8", "*", "0", "*", "0", "NIL"]
["を", "を", "を", "助詞", "9", "格助詞", "1", "*", "0", "*", "0", "NIL"]
["EOS"]
```

# 最後に

とりあえずRubyから処理できるようにしてみました。mecabと性能比較して、自分の目的に合うようなら本格的に使ってみたいですねー。