BimyouSegmenter: Rubyだけで書かれた微妙なサイズの分かち書きソフトウェア

BimyouSegmenterはRubyだけで書かれた微妙なサイズの日本語分かち書きソフトウェアです。
約150Kバイトの微妙なサイズのソースコードで、青空文庫にある太宰治宮沢賢治夏目漱石夢野久作作品であれば、MeCab+ipadicによるの分かち書きの結果と94%くらいは同じになります。

というTinySegmenterのパロディです。
TinySegmenterは新聞記事で学習されているのと、空白文字が単語にくっついたりして扱いが難しかったので、TinySegmenterのようなひとつのソースコードに全部つっこんだ感じの分かち書きのライブラリを自分の用意したデータで学習して好きに調節できたらいいなと思ったので作ってみました。
nekoneko_genでもこれを使っています。

TinySegmenterとは特徴ベクトルも学習アルゴリズムも学習データも違いますが、基本的な考え方はTinySegmenterのソースコードから得られる情報を参考にしています。
(ある文字の間で切るか切らないかを、そこを中心とした前後N文字の文字と文字種のN-gram、それまでの分割状態を特徴ベクトルにして線形分類器で判定する)

BimyouSegmenterは、(青空文庫の)太宰治宮沢賢治夏目漱石夢野久作 作品、高田力 ベーシック英語、(Project Gutenbergの) Alice's Adventures in Wonderland, by Lewis Carroll に対するMeCab+ipadicによる分かち書き結果(約800万件)を LIBLINEAR の L1-regularized logistic regression で学習して、学習されたModelファイルからRubyのコードを生成しています。

インストール

gem install bimyou_segmenter

でインストールされます。

コマンドラインツールとライブラリがあります。

コマンドラインツール

% bimyou_segmenter
今年もよろしくお願いします。Happy New Year 2013!
今年
も
よろしく
お願い
し
ます
。
Happy
New
Year
2013
!
EOS
いろいろあったけど、ぼくはげんきです
いろいろ
あっ
た
けど
、
ぼく
は
げんき
です
EOS

オプションは、
-dでデリミタを指定するとその文字列で単語間を区切ります。デフォルトは\nです。
-eでEOSの文字列を指定できます。デフォルトはEOSです。
-sを付けると空白文字や改行コードも単語として返します。-sを付け場合、分割後の単語を繋げると元の文字列と一致します。

% bimyou_segmenter -d "|"
今年もよろしくお願いします。Happy New Year 2013!
今年||よろしく|お願い||ます||Happy|New|Year|2013|!|EOS

% bimyou_segmenter -s -d "<>" -e ""
今年もよろしくお願いします。Happy New Year 2013!
今年<><>よろしく<>お願い<><>ます<><>Happy<> <>New<> <>Year<> <>2013<>!

% bimyou_segmenter --help

ライブラリ

# coding: utf-8
require 'bimyou_segmenter'

puts BimyouSegmenter.segment("今年もよろしくお願いします。Happy New Year 2013!").join("|")
puts BimyouSegmenter.segment("今年もよろしくお願いします。Happy New Year 2013!",
                             :white_space => true).join("|")
今年|も|よろしく|お願い|し|ます|。|Happy|New|Year|2013|!
今年|も|よろしく|お願い|し|ます|。|Happy| |New| |Year| |2013|!

:white_spaceをtrueにすると(default: false)、空白文字や改行も単語して返します。このオプションを指定した場合は、結果の文字列配列を順に結合すると元の文字列と一致します。
また:symbol => falseにすると(default: true)、記号のみからなる単語(トークン)を返しません。

Ruby 1.8.7の場合は、require 'rubygems'と$KCODEをuにすると使えます。

TinySegmenterとの違い(勘)

TinySegmenterは連続する漢字を繋げやすいですが、BimyouSegmenterは連続する漢字を2個ずつで分けることが多いです。たぶん学習データの違いによるものだと思います。
TinySegmenterは空白文字(スペースタブ改行)も返しますが、BimyouSegmenterはデフォルトでは返しません。またBimyouSegmenterは空白文字をルールベースで分割しているので、空白文字と空白文字以外がひとつの単語として結合されることはありません。

MeCab+ipadicとの違い

MeCab+ipadicは半角スペースやタブ、改行コードを返しませんが全角スペースは返します。BimyouSegmenterは:white_spaceオプションを指定した場合を除いて全角スペースも返しません。
(char.defによりそうですが)MeCabは全角数字を文字ごとに分割しますが、BimyouSegmenterは結合しやすいです。
BimyouSegmenterは辞書を持っていないので、3文字以上連続する漢字の多い文章やひらがなの多い文章は超苦手です(大体うまくいかない)。MeCabが使えるならMeCabを使いましょう。

BimyouSegmenter、MeCab、TinySegmenter(Ruby Version)の結果を目視で比較するコード

TinySegmenterはTinySegmenterをRubyに移植 - llameradaの日記Ruby移植版を1.9.2対応してカレントディレクトリに置いて使っています。

# coding:utf-8
if (RUBY_VERSION <= "1.9.0")
  $KCODE='u'
  require 'rubygems'
end
require './tiny_segmenter'
require 'bimyou_segmenter'
require 'MeCab'
require 'kconv'

@@mecab = MeCab::Tagger.new
def mecab_segment(s)
  node = @@mecab.parseToNode(s)
  wakachi = []
  while node
    wakachi << node.surface.toutf8
    node = node.next
  end
  wakachi[1 ... -1]
end

data = <<DATA
昼飯のスパゲティナポリタンを眺めながら、積年の疑問を考えていた。
それは「なぜナポリタンは赤いのだろうか」という問いである。
簡単に見えて、奥の深い問題だ。
「赤いから赤いのだ」などとトートロジーを並べて悦に入る浅薄な人間もいるが、
それは思考停止に他ならず、知性の敗北以外なにものでもない。
「赤方偏移」という現象がある。
宇宙空間において、地球から高速に遠ざかる天体ほどドップラー効果により、
そのスペクトル線が赤色の方に遷移するという現象である。
つまり、本来のナポリタンが何色であろうとも、ナポリタンが我々から
高速で遠ざかっているとすれば、毒々しく赤く見えるはずなのだ。
目の前のナポリタンは高速で動いているか否か?
それはナポリタンの反対側に回ってみることでわかる。
運動の逆方向から観察することで、スペクトルは青方遷移し、
青く見えるはずなのだ。
逆に回ってみたところ、ナポリタンは赤かった。
よってこのナポリタンは高速移動をしていないと言える。
DATA
data = data.gsub(/\s/, '')

puts "\n---- BimyouSegmenter"
puts BimyouSegmenter.segment(data).join("|")
puts "\n---- MeCab"
puts mecab_segment(data).join("|")
puts "\n---- TinySegmenter(Ruby Version)"
puts TinySegmenter.segment(data).join("|")
puts "\n"

t = Time.now
1000.times do
   BimyouSegmenter.segment(data)
end
puts "BimyouSegmenter              1000 loop #{Time.now - t} sec"

t = Time.now
1000.times do
  mecab_segment(data)
end
puts "MeCab                        1000 loop #{Time.now - t} sec"

t = Time.now
1000.times do
  TinySegmenter.segment(data)
end
puts "TinySegmenter(Ruby Version)  1000 loop #{Time.now - t} sec "
% ruby hikaku.rb

---- BimyouSegmenter
昼飯|の|スパゲティナポリタン|を|眺め|ながら|、|積年|の|疑問|を|考え|て|い|た|。|それ|は|「|なぜ|ナポリタン|は|赤い|の|だろ|う|か|」|という|問い|で|ある|。|簡単|に|見え|て|、|奥|の|深い|問題|だ|。|「|赤い|から|赤い|の|だ|」|など|と|トートロジー|を|並べ|て|悦|に|入る|浅薄|な|人間|も|いる|が|、|それ|は|思考|停止|に|他|なら|ず|、|知性|の|敗北|以外|なに|もの|で|も|ない|。|「|赤方|偏移|」|という|現象|が|ある|。|宇宙|空間|において|、|地球|から|高速|に|遠ざかる|天体|ほど|ドップラー|効果|に|より|、|その|スペクトル|線|が|赤色|の|方|に|遷移|する|という|現象|で|ある|。|つまり|、|本来|の|ナポリタン|が|何色|で|あろ|う|と|も|、|ナポリタン|が|我々|から|高速|で|遠ざかっ|て|いる|と|すれ|ば|、|毒々しく|赤く|見える|はず|な|の|だ|。|目|の|前|の|ナポリタン|は|高速|で|動い|て|いる|か|否|か|?|それ|は|ナポリタン|の|反対|側|に|回っ|て|みる|こと|で|わかる|。|運動|の|逆方|向|から|観察|する|こと|で|、|スペクトル|は|青方|遷移|し|、|青く|見える|はず|な|の|だ|。|逆|に|回っ|て|み|た|ところ|、|ナポリタン|は|赤かっ|た|。|よっ|て|この|ナポリタン|は|高速|移動|を|し|て|い|ない|と|言える|。

---- MeCab
昼飯|の|スパゲティナポリタン|を|眺め|ながら|、|積年|の|疑問|を|考え|て|い|た|。|それ|は|「|なぜ|ナポリ|タン|は|赤い|の|だろ|う|か|」|という|問い|で|ある|。|簡単|に|見え|て|、|奥|の|深い|問題|だ|。|「|赤い|から|赤い|の|だ|」|など|と|トートロジー|を|並べて|悦に入る|浅薄|な|人間|も|いる|が|、|それ|は|思考|停止|に|他|なら|ず|、|知性|の|敗北|以外|なに|もの|で|も|ない|。|「|赤|方偏|移|」|という|現象|が|ある|。|宇宙|空間|において|、|地球|から|高速|に|遠ざかる|天体|ほど|ドップラー|効果|により|、|その|スペクトル|線|が|赤色|の|方|に|遷移|する|という|現象|で|ある|。|つまり|、|本来|の|ナポリ|タン|が|何|色|で|あろ|う|とも|、|ナポリ|タン|が|我々|から|高速|で|遠ざかっ|て|いる|と|すれ|ば|、|毒々しく|赤く|見える|はず|な|の|だ|。|目|の|前|の|ナポリ|タン|は|高速|で|動い|て|いる|か|否|か|?|それ|は|ナポリ|タン|の|反対|側|に|回っ|て|みる|こと|で|わかる|。|運動|の|逆|方向|から|観察|する|こと|で|、|スペクトル|は|青|方|遷移|し|、|青く|見える|はず|な|の|だ|。|逆|に|回っ|て|み|た|ところ|、|ナポリ|タン|は|赤かっ|た|。|よって|この|ナポリ|タン|は|高速|移動|を|し|て|い|ない|と|言える|。

---- TinySegmenter(Ruby Version)
昼飯|の|スパゲティナポリタン|を|眺め|ながら|、|積年|の|疑問|を|考え|て|い|た|。|それは|「なぜ|ナポリタン|は|赤い|の|だろ|う|か|」|という|問い|で|ある|。|簡単|に|見えて|、|奥|の|深い|問題|だ|。|「|赤い|から|赤い|の|だ|」|など|と|トートロジー|を|並べ|て|悦|に|入る|浅薄|な|人間|も|いる|が|、|それ|は|思考停止|に|他|ならず|、|知性|の|敗|北以外|な|に|ものでも|ない|。「|赤方|偏移|」|と|いう|現象|が|ある|。|宇宙空間|に|おいて|、|地球|から|高速|に|遠ざかる|天体|ほど|ドップラー|効果|により|、|その|スペクトル|線|が|赤色|の|方|に|遷移|する|という|現象|で|ある|。つまり|、|本来|の|ナポリタン|が|何|色|で|あろ|うと|も|、|ナポリタン|が|我々|から|高速|で|遠ざかっ|て|いる|とすれ|ば|、|毒々しく|赤く|見える|はず|な|の|だ|。|目|の|前|の|ナポリタン|は|高速|で|動い|て|いる|か|否か|?|それは|ナポリタン|の|反|対側|に|回っ|て|みる|こと|でわかる|。運動|の|逆|方向|から|観察|する|こと|で|、|スペクトル|は|青方遷移|し|、|青く|見える|はず|な|の|だ|。|逆|に|回っ|て|み|た|ところ|、|ナポリタン|は|赤かっ|た|。よって|この|ナポリタン|は|高速移動|を|し|て|い|ない|と|言える|。

BimyouSegmenter              1000 loop 5.426601826 sec
MeCab                        1000 loop 1.153953701 sec
TinySegmenter(Ruby Version)  1000 loop 6.062332638 sec

なかなか良い感じだと思います。
分割の速度は、MeCabより5倍以上遅いです(Ruby 1.9.3の場合。1.8.7の場合は16倍以上遅い)。これはRubyで書いているせいだと思うので、Cの拡張ライブラリを書けばクソ速くなると思いますが、今はないです。