辞書 追加 igo-ruby

以前はmecabを利用してプログラムを動かしていたけど、今回はruby形態素解析出来るライブラリがあったのでそれを利用することにしました。
ruby-igoでは辞書生成が出来ないのでjavaのigoを利用する

javaのIgoのサイト
http://igo.sourceforge.jp/
mecab...まぁ、コスト計算方法のサイト
http://www.mwsoft.jp/programming/munou/mecab_nitteretou.html
このサイトをメインに作業を行った。配布されてるプログラムをrubyに落としたら良いんだけど、そこまでの能力が無い。
http://blog.livedoor.jp/techblog/archives/65828235.html

他にも細々したサイト色々見たけど全部記録してなかったので上記3サイトだけ
作成したプログラムは継ぎ接ぎしただけだから修正したほうがいいかな〜っと思ってるけど。時間無いので後で?

以下作業-----
javaのIgoのサイトから
igo-0.4.4.jarを拾ってくるなりソースをビルドするなりする
naist-jdicは下からまたは別のところから
http://sourceforge.jp/projects/naist-jdic/downloads/48487/mecab-naist-jdic-0.6.3-20100801.tar.gz

tar xzvf mecab-naist-jdic-0.6.3-20100810.tar.gz
cd mecab-naist-jdic-0.6.3-20100810
// naist-jdic.csvはそのままだと使用できないので利用出来る形式にする
grep -v -E '^\"' naist-jdic.csv  > naist-jdic.tmp; mv naist-jdic.tmp naist-jdic.csv
cd ../
// -Xmx1000Mは環境によっては実行時にメモリ関係?でエラーがでるので必要に応じて入力する
// java [コマンド] -cp igoファイル net.reduis.igo.bin.BuilDic 生成するフォルダ名 naistのフォルダ名 エンコード文字列
java -Xmx1000M -cp igo-0.4.3.jar net.reduls.igo.bin.BuildDic jdic mecab-naist-jdic-0.6.3-20100801 UTF-8

これで辞書生成は終了したからigo-rubyは普通に使えるんだけど、そのままの辞書じゃ使い勝手が悪いのでユーザ辞書を追加する
この際だからそのあたりもigo-rubyでやってしまおう!
プログラムでやってること
Arrayクラス拡張→データ確認用
Igoファイルも拡張 parseToNodeで文脈idとか返すようにした
node_cost は辞書生成するときの文書コスト計算用

# Arrayクラス拡張 あんまりよくないけど、これだけに利用するからいいかな
class Array
  def dup_node
    srt = "WordId\tLeftId\tRightId\tCost\tSurface\tLength\tStart\tFeature\n"
    self.each do |s|
      srt << "#{s.word_id}\t#{s.left_id}\t#{s.right_id}\t#{s.cost}\t#{s.surface}\t#{s.length}\t#{s.start}\t#{s.feature}\n"
    end
    srt
  end
end
# 直接Igoを書きなおしてもいいけど、元ファイル弄りたくないからこれも拡張
module Igo
  class MorphemeToNode
    attr_accessor :surface, :feature, :cost, :is_space, :left_id, :length, :right_id, :word_id, :start
    def initialize(surface, feature, status)
      @surface = surface
      @feature = feature
      @cost = status.cost
      @is_space = status.is_space
      @left_id = status.left_id
      @right_id = status.right_id
      @length = status.length
      @word_id = status.word_id
      @start = status.start
    end
  end
  class Tagger
    attr_accessor :wdc, :unk, :mtx
    #コスト以外も返すようにする
    def parseToNode(text, result=[])
      vn = impl(text, result)
      txt = text.unpack("U*")
      while vn
        surface = txt.slice(vn.start, vn.length).pack("U*")
        s = @wdc.word_data(vn.word_id)
        feature = NKF.nkf('-W16Lo --utf8', s)
        result.push(MorphemeToNode.new(surface, feature, vn))
        vn = vn.prev
      end
      return result
    end
    #文章コスト計算用
    def node_cost node
      left_id = 0
      cost = 0
      node.each do |n|
        cost += n.cost
        cost += mtx.link_cost(left_id, n.right_id)
        left_id = n.right_id
      end
      cost += mtx.link_cost(left_id, 0)
      cost - 745
    end
  end
end

まず辞書生成にwikiはてなを利用します。
このあたりのことについてはgoogleで「mecab 辞書生成 wiki」などで調べればすぐに出てきます。
ファイルが無ければ下のコメントのチェックを外します。
file はnaist-jdic.csvのファイルパスを指定
file1,2 はwikiはてなのファイル名
file3 は顔文字を登録する場合利用
translatorはひらがなかたかな変換用 はてなキーワードで利用する
まずnaist-jdic.csvから固有名詞, 一般の文字を抜き出す
それを文字列の長さごとに文書生成コストを足していき 文書数で割る

#ファイルが無ければチェックを外す
#はてなとwikiのurl
#wiki = "http://dumps.wikimedia.org/jawiki/latest/jawiki-latest-all-titles-in-ns0.gz"
#hatena = "http://d.hatena.ne.jp/images/keyword/keywordlist_furigana.csv"
#open(file1, "w+"){ |f| f.puts open(wiki, 'rb'){|sio| Zlib::GzipReader.wrap(sio).read}}
#open(file2, "w+"){ |f| f.puts open(hatena).read.toutf8}
#保存するファイル名
file = "naist-jdic.csv" #ファイル先を記述
file1 = "wiki.csv"
file2 = "hatena.csv"
file3 = "kaomozi.txt"

def translator(from, to)
  lambda {|str| str.tr(from, to)}
end
upto = translator("a-z", "A-Z")
downto = translator("A-Z", "a-z")
hira2kata = translator("ぁ-ん", "ァ-ン")
kata2hira = translator("ァ-ン", "ぁ-ん")

#平均値テーブルを作成するための固有名詞のものを抜きだす
list = []
open(file).each do |line|
  text = line.toutf8.strip.split(",")
  list << text if text[5] =~ /固有名詞/ && text[6] =~ /一般/
end

#文字列の長さごとにコストを計算
#ここの部分もっとうまい書き方したい(´・ω・`) なんかきもち悪い
cost = []
list.each do |l|
  s = l[0].size
  cost[s] ||= []
  cost[s][0] ||= 0
  cost[s][0] += l[3].to_i
  cost[s][1] ||= 0
  cost[s][1] += 1
end

#各コストの平均を計算
cost.map!{|c|c[0] /= c[1] if c != nil}

はてなキーワードから辞書生成用
平均値テーブルに無い単語は2850に設定している
17文字以上になると生成コストはこの値よりもあまり下がらないため

open(file2).each do |line|
    title_list = line.split("\t")
    next if title_list.length < 2
    title = title_list[1]
    title.strip!
  furigana = title_list[0]
    furigana.strip!
  katakana = hira2kata.call(furigana)
 
    # 登録したくないものをスキップ
    next if title =~ /[\+\-\.\$\(\)\?\*!"'_,]+/
    next if title =~ /^[0-9\-]+$/
    next if title =~ /^h?ttp/
  next if title =~ /[0-9]{4}(\/|\-)[0-9]{2}(\/|\-)[0-9]{2}/
  next if title =~ /[0-9]{4}/
  next if title =~ /[0-9]{1,2}[0-9]{1,2}/
 
  # 制御文字、HTML特殊文字が入ったものは外す
  next if title =~ /[[:cntrl:]]/
  next if title =~ /\&\#/
 
    # タイトルの長さ
    len = title.split(//u).length

    # スコア計算
  
  score = tagger.node_cost(tagger.parseToNode(title)) * 0.7

  if cost[len] == nil
    score2 = 2850
  else
    score2 = cost[len]
  end
  score = score2 if score > score2
    # 3文字より大きい場合だけ
    if len > 3
        mecab.puts "#{title},1360,1360,#{score.to_i},名詞,固有名詞,一般,*,*,*,#{title},#{katakana},#{furigana},はてなキーワード,"
    end
end

wikiの辞書生成用
はてなとあんまり大差ない

open(file1).each do |title|
	title.strip!
  # 登録したくないものをスキップ
  next if title =~ /[\+\-\.\$\(\)\?\*!"'_,]+/
  next if title =~ /^[0-9\-]+$/
  next if title =~ /^h?ttp/
  next if title =~ /[0-9]{4}(\/|\-)[0-9]{2}(\/|\-)[0-9]{2}/
  next if title =~ /[0-9]{4}/
  next if title =~ /[0-9]{1,2}[0-9]{1,2}/
  
  # 制御文字、HTML特殊文字が入ったものは外す
  next if title =~ /[[:cntrl:]]/
  next if title =~ /\&\#/
	# タイトルの長さ
	len = title.split(//u).length
  
  #スコア計算
  score = tagger.node_cost(tagger.parseToNode(title)) * 0.7
  if cost[len] == nil
    score2 = 2850
  else
    score2 = cost[len]
  end
  score = score2 if score > score2
	# 3文字より大きい場合だけ
	if len > 3
		mecab.puts "#{title},1360,1360,#{score.to_i},名詞,固有名詞,一般,*,*,*,#{title},*,*,wikipedia,"
	end
end

顔文字辞書生成用
記号とか登録するのでフィルタはかけないけど
コンマが含まれる単語はピリオドするなりスキップするなりする
顔文字の場合記号ばかり続いたりすると文章コストが-400000とかになってshort integerに変換出来ないので
マイナス32778以下の数値の場合は以下の計算式を入れる
score = [-32678.0 ,-400 *(title.size**1.5)].max.to_i
もう一つ計算式があったけどこれ↓
score = [-32768.0, (6000 - 200 *(title.size**1.3))].max.to_i
顔文字の場合はうまく行かなかったので上の方を選択した
後は他と一緒

open(file3).each do |line|
  title_list = line.split("\s")
  title_list[1].gsub!(",",".")
  title = title_list[1]
  title.strip!
  furigana = title_list[0]
  furigana.strip!
  katakana = hira2kata.call(furigana)

  #スコア計算
  score = tagger.node_cost(tagger.parseToNode(title)) * 0.7
  
  len = title.size
  if cost[len] == nil
    score2 = 2850
  else
    score2 = cost[len]
  end
  score = score2 if score > score2
  if score < -32678
    score = [-32678.0 ,-400 *(title.size**1.5)].max.to_i
  end
  # 3文字より大きい場合だけ
	if len > 3
    mecab.puts "#{title},5,5,#{score.to_i},名詞,記号,一般,*,*,*,#{title},#{katakana},#{furigana},顔文字,"
	end
end

すべてをつなぎ合わせたものがしたのが↓のファイルだけど、リファクタしてないから気持ちが悪い。


上のファイルを実行するとmecab.csv(ファイル名が悪いけど気にしない)

grep -v -E '^\"' mecab.csv  > mecab.tmp; mv mecab.tmp mecab.csv
mv mecab.csv mecab-naist-jdic-0.6.3-20100801
java -Xmx1000M -cp igo-0.4.3.jar net.reduls.igo.bin.BuildDic jdic mecab-naist-jdic-0.6.3-20100801 UTF-8
// 文字コード関係でエラーが出たら nkf -w --overwrite ファイル名 などのコマンドで変換する

はい、まとめ完了!
顔文字は適当なところから集めまくる。