関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
<< Lucene/Solr インデックスの中を覗いてジップの法則を確認する | main | モデルとなる京都コーパスの量を変えたときのCaboChaの性能を見る >>
CaboChaと京都コーパスで交差検定
ロンドンオリンピック真っ最中で仕事がはかどらない中、日本語係り受け解析器CaboCha京都コーパスで交差検定を行ったのでメモを残す。

この作業の目的は、本件交差検定というよりはどちらかというとこの作業を通じてCaboChaの学習や評価のツールを正しく使えるように私自身が練習し、作業記録を残すことである。また、必要に応じてテキスト処理のためのツールを整備することも行う。

以下の作業では京都コーパスの方は毎日新聞のCD-ROMを用意し、付属のauto_convを実行した後のデータがあることを前提とする。なおこの方法で作成したモデルファイルは(そしてCaboChaが現在デフォルトで内包しているモデルファイルも)学術研究用に利用が限定されることをここで念のため書き添えておく。

CaboChaのツールで共通して使えるmode(-eオプション)はchunk,dep,neのうち、chunkを用いることにした。depは学習(cabocha-learnの実行)に4時間かけても終わらなかったため、残念ながら時間的な制約から今回は作業対象からはずした。また、入手できたデータにneタグがついていなかったため、neも同様に対象外とした。

文字コードはコーパスに合わせてEUCを選択。京都コーパスのdat/syn/ ディレクトリにある拡張子が.KNPの全ファイルをファイル単位で5分割して交差検定を行う。ファイル数は28個なのでこれを上から6個、6個、6個、5個、5個ずつまとめて各グループとし(下表)、5グループのCaboCha形式の学習データを得る。なお、5分割の5の根拠は特にない。

CaboCha形式の学習データKNP元データ
G51.dat950101.KNP, 950103.KNP, 950104.KNP, 950105.KNP, 950106.KNP, 950107.KNP
G52.dat950108.KNP, 950109.KNP, 950110.KNP, 950111.KNP, 950112.KNP, 950113.KNP
G53.dat950114.KNP, 950115.KNP, 950116.KNP, 950117.KNP, 9501ED.KNP, 9502ED.KNP
G54.dat9503ED.KNP, 9504ED.KNP, 9505ED.KNP, 9506ED.KNP, 9507ED.KNP
G55.dat9508ED.KNP, 9509ED.KNP, 9510ED.KNP, 9511ED.KNP, 9512ED.KNP


またG5N.datのCaboCha形式の学習データから、1カラム目の単語情報を抜き出して原文テキストを逆再生し、それをS5N-x.txtというテキストファイルにする。ここで"-x"の部分は"-01"から"-20"程度の値をとるように分割する(G5N.datを対応するひとつの原文テキストファイルにするとcabochaコマンドで解析するには大きすぎるため、複数個のS5N-x.txtファイルに分割している)。評価時のことも考慮すると、G5N.datはS5N-x.txtと対応する正解データのファイルG5N-eval-x.datに分けておく(ただし、評価は後述するようにCaboCha付属の評価ツールが使えなかったので、結果としてG5N-eval-x.datは不要になった)。これらのファイルを出力するプログラムmake_source.rbは別途自作した。

上記のCaboCha形式の学習データを1つ残して4つずつまとめて学習させ、下表の5通りのモデルファイルと正解データのペアを得る。

モデルファイル名CaboCha形式の学習データ正解データ
chunk.juman.1G52.dat, G53.dat, G54.dat, G55.datG51.dat
chunk.juman.2G51.dat, G53.dat, G54.dat, G55.datG52.dat
chunk.juman.3G51.dat, G52.dat, G54.dat, G55.datG53.dat
chunk.juman.4G51.dat, G52.dat, G53.dat, G55.datG54.dat
chunk.juman.5G51.dat, G52.dat, G53.dat, G54.datG55.dat


また、Juman辞書のバージョン(?)の違いのせいか、はたまた形態素解析器の違いのせいかどうかはわからないが、京都コーパスの形態素の単位と、今回cabochaで原文テキストを解析したときの形態素の単位が異なってしまい、結果としてCaboChaの評価ツールcabocha-system-evalが動かなかった(tree size is differentやToken size is differentというエラーになる)。形態素の単位が異なる部分としては、具体的には次のような部分である:

$ diff c.txt d.txt 
89,90c89
< 至ら	動詞,*,子音動詞ラ行,未然形,至る,いたら,*
< ない	接尾辞,形容詞性述語接尾辞,イ形容詞アウオ段,基本形,ない,ない,*
---
> 至らない	形容詞,*,イ形容詞アウオ段,基本形,至らない,いたらない,代表表記:至らない


この問題に対応するため、形態素を文節ごとにまとめて文節番号とともに出力し、1文で1行に出力するプログラムnorm_chunk.rbを自作した。このプログラムを使うと、CaboChaの出力を次のように正規化できる:

$ ruby norm_chunk.rb G51-eval-1.dat
(0)村山富市首相は(1)年頭に(2)あたり(3)首相官邸で(4)内閣記者会と(5)二十八日(6)会見し、(7)社会党の(8)新民主連合所属議員の(9)離党問題に(10)ついて(11)「政権に(12)影響を(13)及ぼす(14)ことには(15)ならない。(16)離党者が(17)いても、(18)その(19)範囲に(20)とどまると(21)思う」と(22)述べ、(23)大量離党には(24)至らないとの(25)見通しを(26)示した。
:


そして評価は、CaboChaの出力と正解データの出力をともにnorm_chunk.rbにかけ、それらをdiffで比較することで異なる文の数(異なる行数)をカウントすることで評価することにした。異なる部分の例として、diff出力の一部を以下に記す:

$ diff a-diff-source.txt G51-diff-source.txt
46c46
< (0)世界が(1)アッと(2)驚く(3)若い(4)首相が(5)誕生し、(6)がんじがらめの(7)規制が(8)たった(9)一本の(10)法律で(11)撤廃された(12)――新春の(13)初夢であり、(14)期待です。
---
> (0)世界が(1)アッと(2)驚く(3)若い(4)首相が(5)誕生し、(6)がんじがらめの(7)規制が(8)たった(9)一本の(10)法律で(11)撤廃された――(12)新春の(13)初夢であり、(14)期待です。
:


ただ、正解データとcabochaの出力の異なる文を表示するにはdiffコマンドは便利だが、両方のテキストファイルを比べて正解率(文節がすべて一致する文の割合)を見るには不便なので、別途この目的だけに使える簡易diffプログラムdiff_chunk.rbを作成した。

今回作成したプログラム

前述の通り、今回の作業のため以下のmake_source.rb, norm_chunk.rbおよびdiff_chunk.rbの3つのプログラムを自作したのでそれを記す:

make_source.rb

SPS = 400 # number of Sentences Per Source

ifname = "G#{ARGV[0]}.dat"
$osfprefix = "S#{ARGV[0]}"
$oefprefix = "G#{ARGV[0]}-eval"
$onum = 0

def next_out_files
  $onum = $onum + 1
  ofname1 = sprintf("%s-%02d.txt", $osfprefix, $onum)
  ofname2 = sprintf("%s-%02d.dat", $oefprefix, $onum)
  return File.open(ofname1,"w"), File.open(ofname2,"w")
end

# osfile : output source file
# oefile : output eval file
osfile, oefile = next_out_files
count = 0 # EOS(sentence) counter

File.open(ifname){ |ifile|
  while line = ifile.gets
    oefile.puts line
    next if /¥A¥*/ =~ line
    if /¥AEOS/ =~ line
      osfile.print "¥n" # print CRLF for EOS
      count = count + 1
      if count > SPS
        osfile.close
        oefile.close
        osfile, oefile = next_out_files
        count = 0
      end
      next
    end
    osfile.print line.split(nil)[0]
  end
}

osfile.close
oefile.close


norm_chunk.rb

ifname = ARGV[0]

chunk = ""
chunk_num = 0

File.open(ifname){ |ifile|
  while line = ifile.gets
    if /¥A¥*/ =~ line
      print "(#{chunk_num})#{chunk}" unless chunk.empty?
      chunk = ""
      chunk_num = line.split(nil)[1]
    elsif /¥AEOS/ =~ line
      print "(#{chunk_num})#{chunk}¥n" unless chunk.empty?
      chunk = ""
    else
      chunk = chunk + line.split(nil)[0]
    end
  end
}


diff_chunk.rb

ifname1 = ARGV[0]
ifname2 = ARGV[1]
out_diff = ARGV[2] ? true : false

sentences = 0
diffs = 0

File.open(ifname1){ |ifile1|
  File.open(ifname2){ |ifile2|
    while line1 = ifile1.gets
      line2 = ifile2.gets
      sentences = sentences + 1
      if line1 != line2
        if out_diff
          puts line1
          puts line2
          puts "---"
        end
        diffs = diffs + 1
      end
    end
  }
}

error_rate = diffs.to_f / sentences.to_f
printf("error = %1.4f (%d/%d)¥n", error_rate, diffs, sentences)


交差検定の結果

前述のdiff_chunk.rbで正解データとcabochaの出力を各文ごとに比較し、すべての文節が一致するものを正解、それ以外を不正解(エラー)とし、エラーの文の数が全体の文の数に占める割合をエラー率とすると以下のようになった:

モデルファイルエラー率
chunk.juman.10.1821
chunk.juman.20.1688
chunk.juman.30.1556
chunk.juman.40.1297
chunk.juman.50.1254


エラー率は12%から18%となった。28個のKNPファイルから5グループのモデルファイルを作成したわけだが、学習データを「多く」使えたchunk.juman.4とchunk.juman.5(6x3+5=23個のKNPファイル)はエラー率が12%程度と低く、学習データが「少ない」それ以外のモデル(6x2+5x2=22個のKNPファイル)ではエラー率が15%から18%程度となった(ただし、学習データ量の「多い」「少ない」は、KNPファイルの個数なので正確ではない)。

オペレーションメモ

以下にまっさらなUbuntu Server 10.04.3にCaboChaのインストールから本件交差検定を行ったときのオペレーションを示す。

# 必要ツール類のインストール
$ sudo apt-get openssh-server install build-essential nkf emacs23 unzip ruby

# CRF++ のインストール
$ wget http://crfpp.googlecode.com/files/CRF%2B%2B-0.57.tar.gz
$ tar xvzf CRF++-0.57.tar.gz
$ cd CRF++-0.57
$ ./configure
$ make
$ sudo make install

# MeCab のインストール
$ wget http://mecab.googlecode.com/files/mecab-0.994.tar.gz
$ tar xvzf mecab-0.994.tar.gz
$ cd mecab-0.994
$ ./configure --with-charset=euc
$ make
$ make check
$ sudo make install
$ sudo ldconfig

# MeCab Juman辞書のインストール
$ wget http://mecab.googlecode.com/files/mecab-jumandic-5.1-20070304.tar.gz
$ tar xvzf mecab-jumandic-5.1-20070304.tar.gz
$ cd mecab-jumandic-5.1-20070304
$ ./configure --with-charset=euc-jp
$ make
$ sudo make install

# MeCab Perlバインディングのインストール
$ wget http://mecab.googlecode.com/files/mecab-perl-0.994.tar.gz
$ tar xvzf mecab-perl-0.994.tar.gz
$ cd mecab-perl-0.994
$ perl Makefile.PL
$ make
$ sudo make install

# MeCab+Juman 辞書の実行(ここまでの動作確認)
# 「すもももももももものうち」を解析するも、Jumanだと「すもも」が「す股」と解釈される(笑)
$ mecab -d /usr/local/lib/mecab/dic/jumandic
すもももももももものうち
す	接頭辞,名詞接頭辞,*,*,す,す,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
の	助詞,接続助詞,*,*,の,の,*
うち	名詞,副詞的名詞,*,*,うち,うち,*
EOS
^D

# CaboChaのインストール(Juman品詞セット)
$ wget http://cabocha.googlecode.com/files/cabocha-0.64.tar.gz
$ tar xvzf cabocha-0.64.tar.gz
$ cd cabocha-0.64
$ ./configure --with-charset=EUC-JP --with-posset=JUMAN
# make時のエラーを避けるため、Makefileを編集して
# CABOCHA_MODEL_LIST および CABOCHA_TXTMODEL_LISTから*.ipa.*のメンバーを除く
$ emacs Makefile model/Makefile
$ sudo ldconfig
$ make
$ make check
$ sudo make install
$ sudo ldconfig

# CaboChaの動作確認
$ cabocha -d /usr/local/lib/mecab/dic/jumandic
オリンピックがいよいよロンドンで開幕した。
                 オリンピックが-----D
                         いよいよ---D
    ロンドンで-D
                           開幕した。
EOS
^D

# dat/syn/*.KNP のファイルをCaboCha形式に変換するとともに、前述の5グループにまとめる
$ ../cabocha-0.64/tools/kc2juman.pl $(ls -1 *.KNP|head -n 6) > G51.dat
$ ../cabocha-0.64/tools/kc2juman.pl $(ls -1 *.KNP|head -n 12|tail -n 6) > G52.dat
$ ../cabocha-0.64/tools/kc2juman.pl $(ls -1 *.KNP|head -n 18|tail -n 6) > G53.dat
$ ../cabocha-0.64/tools/kc2juman.pl $(ls -1 *.KNP|head -n 23|tail -n 5) > G54.dat
$ ../cabocha-0.64/tools/kc2juman.pl $(ls -1 *.KNP|head -n 28|tail -n 5) > G55.dat

# 正しくCaboCha形式のファイルになっているか、評価ツールを使って確認
$ /usr/local/libexec/cabocha/cabocha-system-eval -e chunk G51.dat G51.dat 
             precision              recall             F
Chunk: 100.0000 (63596/63596) 100.0000 (63596/63596) 100.0000
$ /usr/local/libexec/cabocha/cabocha-system-eval -e dep G51.dat G51.dat 
dependency level0: 100.0000 (63596/63596)
dependency level1: 100.0000 (57133/57133)
dependency level2: 100.0000 (50706/50706)
sentence         : 100.0000 (6463/6463)

# 5つのCaboCha形式の学習データを、4つと1つの組に分ける、次フェーズのモデルのソースを5通り作成。
$ cat G52.dat G53.dat G54.dat G55.dat > model.source.1
$ cat G51.dat G53.dat G54.dat G55.dat > model.source.2
$ cat G51.dat G52.dat G54.dat G55.dat > model.source.3
$ cat G51.dat G52.dat G53.dat G55.dat > model.source.4
$ cat G51.dat G52.dat G53.dat G54.dat > model.source.5

# 学習(chunk モデルファイルの作成)x5回(それぞれ150秒ほどかかる)
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.1 chunk.juman.1
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.2 chunk.juman.2
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.3 chunk.juman.3
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.4 chunk.juman.4
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.5 chunk.juman.5

# CaboCha形式の学習データG5N.datから対応する原文テキストファイルS5N-x.txtを逆再生し、
# 同時に対応する正解データG5N-eval-x.datを作成する。
$ ruby make_source.rb 51
$ ruby make_source.rb 52
$ ruby make_source.rb 53
$ ruby make_source.rb 54
$ ruby make_source.rb 55

# 原文テキストを試しにCaboChaにかけてみる(デフォルトのモデルファイルを使用)
$ cat S51-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -O2 -f1 -n0 > a.txt
# 同じことを、前フェーズで作成したモデルファイルに切り替えてやってみる。
$ cat S51-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.1 -O2 -f1 -n0 > b.txt

# 上記の結果をCaboCha付属の評価ツールにかけようとすると、前述のエラーが出てしまう。
# (a.txt を b.txt にしてももちろん同じ)
$ /usr/local/libexec/cabocha/cabocha-system-eval -e chunk a.txt G51-eval-01.dat 
eval.cpp(133) [tree1.token_size() == tree2.token_size()] Token size is different

# 実際にcabochaを実行
# 原文テキスト S5N-x.txt に対し、モデルファイル chunk.juman.N の切り替えと
# 結果ファイル O5N.txt にAPPENDすることに注意しながら手作業で(!)行う
# (もちろん、プログラムを書ければ書いた方が実行は楽)
$ cat S51-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.1 -O2 -f1 -n0 > O51.txt
$ cat S51-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.1 -O2 -f1 -n0 >> O51.txt
:
$ cat S52-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.2 -O2 -f1 -n0 > O52.txt
$ cat S52-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.2 -O2 -f1 -n0 >> O52.txt
:

# 簡易評価ツール diff_chunk.rb で正解データとcabochaの出力を比較するため、
# 両者のファイルを正規化
$ ruby norm_chunk.rb G51.dat > G51-norm.dat
$ ruby norm_chunk.rb O51.txt > O51-norm.txt
$ ruby norm_chunk.rb G52.dat > G52-norm.dat
$ ruby norm_chunk.rb O52.txt > O52-norm.txt
$ ruby norm_chunk.rb G53.dat > G53-norm.dat
$ ruby norm_chunk.rb O53.txt > O53-norm.txt
$ ruby norm_chunk.rb G54.dat > G54-norm.dat
$ ruby norm_chunk.rb O54.txt > O54-norm.txt
$ ruby norm_chunk.rb G55.dat > G55-norm.dat
$ ruby norm_chunk.rb O55.txt > O55-norm.txt

# 評価
$ ruby diff_chunk.rb G51-norm.dat O51-norm.txt
error = 0.1821 (1177/6463)
$ ruby diff_chunk.rb G52-norm.dat O52-norm.txt 
error = 0.1688 (1446/8567)
$ ruby diff_chunk.rb G53-norm.dat O53-norm.txt 
error = 0.1556 (1219/7833)
$ ruby diff_chunk.rb G54-norm.dat O54-norm.txt 
error = 0.1297 (1034/7975)
$ ruby diff_chunk.rb G55-norm.dat O55-norm.txt 
error = 0.1254 (948/7562)

# なお、diff_chunk.rb の第3引数に"on"(なんでもよい)を指定すると、以下のように異なる文を表示する。
$ ruby diff_chunk.rb G51-norm.dat O51-norm.txt on
:
(0)旧満州・奉天生まれ。
(0)旧満州・奉(1)天(2)生まれ。
---
error = 0.1821 (1177/6463)
| 関口宏司 | NLP | 17:38 | comments(0) | trackbacks(0) |









http://lucene.jugem.jp/trackback/468
+ Solrによるブログ内検索
+ PROFILE
   1234
567891011
12131415161718
19202122232425
262728293031 
<< March 2017 >>
+ LINKS
検索エンジン製品 - 比較のポイント
商用検索エンジンを購入した企業担当者は読まないでください。ショックを受けますから・・・
>>製品比較 10のポイント
+ Lucene&Solrデモ
+ ThinkIT記事
+ RECOMMEND
Apache Solr入門 ―オープンソース全文検索エンジン
Apache Solr入門 ―オープンソース全文検索エンジン (JUGEMレビュー »)
関口 宏司,三部 靖夫,武田 光平,中野 猛,大谷 純
+ RECOMMEND
Lucene in Action
Lucene in Action (JUGEMレビュー »)
Erik Hatcher,Otis Gospodnetic,Mike McCandless
FastVectorHighlighterについて解説記事を寄稿しました。
+ RECOMMEND
+ SELECTED ENTRIES
+ RECENT COMMENTS
+ RECENT TRACKBACK
+ CATEGORIES
+ ARCHIVES
+ MOBILE
qrcode
+ SPONSORED LINKS