関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
スポンサーサイト

一定期間更新がないため広告を表示しています

| スポンサードリンク | - | | - | - |
CaboChaとKNBコーパスで固有表現抽出を行う
さらにCaboChaの評価は続く。今回はKNBコーパスを使って固有表現抽出の交差検定(5分割)を行った。

今回はprecisionやrecallをカウントする評価ツールも自作した。実際にどのようにカウントするかをフィクションのデータを例にした下表を使って説明する:

説明番号正解CaboCha出力カウント
形態素NE形態素NE
1自民党B-ORGANIZATION自民党B-ORGANIZATIONtp++
OO
2谷垣B-PERSON谷垣B-PERSONtp++
総裁O総裁O
OO
3B-DATEB-MONEYfn++, fp++
4I-DATEOfn++
夕方O夕方O
遅いO遅いO
昼食O昼食O
OO
5民主党食堂O民主党B-ORGANIZATIONfp++
食堂O
OO
とったOとったO
OO


  1. 「自民党」のNEタグが一致
  2. 「谷垣」のNEタグが一致
  3. 「6」のNEタグが不一致
  4. 「日」のNEタグが出力されず
  5. 「民主党」のNEタグが出力されたが不正解


以上から、このフィクションのデータの例では:

precision = tp / (tp + fp) = 2 / (2 + 2) = 0.5

recall = tp / (tp + fn) = 2 / (2 + 2) = 0.5

F = 2PR / (P + R) = 2 * 0.5 * 0.5 / (0.5 + 0.5) = 0.5 (ここで、P:precision, R:recall)

結果

結果は予想していた通りF値で40から60ポイント程度と低い値となった。原因としては、KNBコーパスはデータ量が多くないがその中でも固有表現タグはさらに数が限定されるためと考えられる。また、モデルファイルのとりかたによって固有表現タグの出現に濃淡があり、そのため正解率にばらつきがある。固有表現タグの種類(ORGANIZATIONやLOCATIONなど)にも数の多い少ないがあり(詳細はKNBコーパスの論文を参照)、これらも影響している可能性があるだろう。さらに、学習データを作成する際に、複数のNEタグ候補があるOPTIONALタグをそのままOPTIONALタグとして出力した。複数候補をCaboChaに学習させる方法がわからなかったためだが、これも学習データの量を減少させ、正解率を落とした原因といえる(このため、評価ツールでは正解データがOPTIONALの場合、fnをカウントしないこととした)。いずれにしてもデータ量が少ないのが残念だ。

モデルファイル名precisionrecallF
ne.juman.10.4596770.3630570.405694
ne.juman.20.5468750.5844160.565022
ne.juman.30.6043580.6085450.606444
ne.juman.40.5444920.5444920.544492
ne.juman.50.5540900.4988120.525000


今回作成したプログラム

KNBコーパスをNEタグ付きのCaboCha形式に変更するプログラム knbc2cabocha_ne.rb と 評価プログラム eval_ne.rb を作成したので掲載する:

knbc2cabocha_ne.rb

このプログラムでは、OPTIONALタグはそのままOPTIONALタグとして出力した。しかし、(おそらくは)CaboChaではOPTIONALタグはスキップしており(?)、OPTIONALの実際の候補であるLOCATIONやORGANIZATIONなどを出力すべきであると考えられる。そのため、次の評価プログラムでは正解データがOPTIONALの場合は、fnはカウントしないようにした。

KNBC_DIR = ARGV[0]

CATEGORIES = ["Keitai", "Kyoto", "Gourmet", "Sports"]

def ne(terms)
  ne_m = //, 1)
  b = ne_m.post_match.slice(/(¥w+):(¥w+)>/, 2)
  b = b == "head" || b == "single" ? "B" : "I"
  return "#{b}-#{type}"
end

CATEGORIES.each{|category|
  File.open("#{category}.dat", "w"){|ofile|
    Dir.glob(KNBC_DIR+"/KN???_#{category}_*").sort!.each{|article_dir_path|
      File.open("#{article_dir_path}/fileinfos"){|info_file|
        while info_line = info_file.gets
          File.open("#{article_dir_path}/#{info_line.split[1].split(/:/)[1]}"){|ifile|
            chunk_num = 0
            while line = ifile.gets
              next if /¥A# S-ID:/ =~ line # skip the comment line
              terms = line.split
              if terms[0] == "*"
#                ofile.puts "* #{chunk_num} #{terms[1]}"
                ofile.puts "* #{chunk_num} -1D"
                chunk_num += 1
              elsif terms[0] == "+"
                next
              elsif terms[0] == "EOS"
                ofile.puts "EOS"
              else
                ne_info = ne(terms)
                ofile.puts "#{terms[0]}¥t#{terms[3]},#{terms[5]},*,*,#{terms[2]},#{terms[1]},*¥t#{ne_info}"
              end
            end
          }
        end
      }
    }
  }
}


eval_ne.rb

def next_term_ne(file)
  while line = file.gets
    next if /¥A¥*/ =~ line
    return "EOS", nil if /¥AEOS/ =~ line
    terms = line.split
    return terms[0], terms[-1]
  end
  return nil, nil
end

def no_ne(ne_a)
  return true if ne_a.length == 1 && ne_a[0] == nil
  ne_a.each{ |ne|
    return false unless ne == "O"
  }
  true
end

def print_terms(ans_str_a, ans_ne_a, out_str_a, out_ne_a)
  if ans_str_a != out_str_a || ans_ne_a != out_ne_a
    puts "#{ans_str_a.join("/")}(#{ans_ne_a.join(",")}) #{out_str_a.join("/")}(#{out_ne_a.join(",")})"
  end
end

def num_ne(ne_a)
  return 0 if ne_a.length == 1 && ne_a[0] == nil
  num = 0
  ne_a.each{ |ne|
    num += 1 unless ne == "O"
  }
  num
end

def check(ans_str_a, ans_ne_a, out_str_a, out_ne_a, tp, fp, fn)
  if !no_ne(ans_ne_a)
    print_terms(ans_str_a, ans_ne_a, out_str_a, out_ne_a)
    if !no_ne(out_ne_a)
      i = 0
      while true
        ans_ne_c = i < ans_ne_a.length ? ans_ne_a[i] : ""
        out_ne_c = i < out_ne_a.length ? out_ne_a[i] : ""
        break if ans_ne_c == "" && out_ne_c == ""
        if ans_ne_c != "O" && out_ne_c != "O"
          if ans_ne_c == out_ne_c
            tp += 1
          else
            fn += 1 if ans_ne_c != "O" && ans_ne_c != "" && ans_ne_c.index("OPTIONAL") != nil
            fp += 1 if out_ne_c != "O" && out_ne_c != ""
          end
        end
        i += 1
      end
    else
      fn += num_ne(ans_ne_a)
    end
  elsif !no_ne(out_ne_a)
    fp += num_ne(out_ne_a)
    print_terms(ans_str_a, ans_ne_a, out_str_a, out_ne_a)
  end
  return tp, fp, fn
end

ans_file = File.open(ARGV[0])
out_file = File.open(ARGV[1])

tp = fp = fn = 0

while true
  ans_str_a = []
  ans_ne_a = []
  out_str_a = []
  out_ne_a = []
  ans_term, ans_ne = next_term_ne(ans_file)
  ans_str_a << ans_term
  ans_ne_a << ans_ne
  out_term, out_ne = next_term_ne(out_file)
  out_str_a << out_term
  out_ne_a << out_ne

  break if ans_term == nil || out_term == nil

  while out_str_a.join("").length != ans_str_a.join("").length
    if out_str_a.join("").length > ans_str_a.join("").length
      ans_term, ans_ne = next_term_ne(ans_file)
      ans_str_a << ans_term
      ans_ne_a << ans_ne
    elsif out_str_a.join("").length < ans_str_a.join("").length
      out_term, out_ne = next_term_ne(out_file)
      out_str_a << out_term
      out_ne_a << out_ne
    end
  end
  tp, fp, fn = check(ans_str_a, ans_ne_a, out_str_a, out_ne_a, tp, fp, fn)
end

ans_file.close
out_file.close

printf("tp = %d, fp = %d, fn = %d¥n", tp, fp, fn)

precision = tp.to_f / (tp.to_f + fp.to_f)
recall = tp.to_f / (tp.to_f + fn.to_f)
f = 2.0 * precision * recall / (precision + recall)

printf("precision = %f, recall = %f, F = %f¥n", precision, recall, f)


オペレーションメモ

# KNBコーパスをNEタグ付きCaboCha形式に変更。
$ ruby knbc2cabocha_ne.rb ../KNBC_v1.0_090925/corpus1

# NEタグ付きCaboCha形式のデータをマージしながら同程度の5つに分割。
$ ruby rearrange.rb 4 837 Keitai.dat Kyoto.dat Gourmet.dat Sports.dat

# モデルのソースを生成。
$  cat G0402.dat G0403.dat G0404.dat G0405.dat > model.source.ne.1
$  cat G0401.dat G0403.dat G0404.dat G0405.dat > model.source.ne.2
$  cat G0401.dat G0402.dat G0404.dat G0405.dat > model.source.ne.3
$  cat G0401.dat G0402.dat G0403.dat G0405.dat > model.source.ne.4
$  cat G0401.dat G0402.dat G0403.dat G0404.dat > model.source.ne.5

# NEを学習。
$ /usr/local/libexec/cabocha/cabocha-learn -e ne -P JUMAN model.source.ne.1 ne.juman.1
$ /usr/local/libexec/cabocha/cabocha-learn -e ne -P JUMAN model.source.ne.2 ne.juman.2
$ /usr/local/libexec/cabocha/cabocha-learn -e ne -P JUMAN model.source.ne.3 ne.juman.3
$ /usr/local/libexec/cabocha/cabocha-learn -e ne -P JUMAN model.source.ne.4 ne.juman.4
$ /usr/local/libexec/cabocha/cabocha-learn -e ne -P JUMAN model.source.ne.5 ne.juman.5

# CaboCha形式のデータから原文テキストを逆再生。
$ ruby make_source.rb 0401
$ ruby make_source.rb 0402
$ ruby make_source.rb 0403
$ ruby make_source.rb 0404
$ ruby make_source.rb 0405

# cabocha -e ne で固有表現抽出。
$ cat S0401-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.1 -O1 -f1 -n1 > O0401.txt
$ cat S0401-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.1 -O1 -f1 -n1 >> O0401.txt
$ cat S0401-03.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.1 -O1 -f1 -n1 >> O0401.txt
$ cat S0402-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.2 -O1 -f1 -n1 > O0402.txt
$ cat S0402-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.2 -O1 -f1 -n1 >> O0402.txt
$ cat S0402-03.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.2 -O1 -f1 -n1 >> O0402.txt
$ cat S0403-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.3 -O1 -f1 -n1 > O0403.txt
$ cat S0403-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.3 -O1 -f1 -n1 >> O0403.txt
$ cat S0403-03.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.3 -O1 -f1 -n1 >> O0403.txt
$ cat S0404-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.4 -O1 -f1 -n1 > O0404.txt
$ cat S0404-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.4 -O1 -f1 -n1 >> O0404.txt
$ cat S0404-03.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.4 -O1 -f1 -n1 >> O0404.txt
$ cat S0405-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.5 -O1 -f1 -n1 > O0405.txt
$ cat S0405-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.5 -O1 -f1 -n1 >> O0405.txt
$ cat S0405-03.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./ne.juman.5 -O1 -f1 -n1 >> O0405.txt

# 正解データと比較。
$ ruby eval_ne.rb G0401.dat O0401.txt
$ ruby eval_ne.rb G0402.dat O0402.txt
$ ruby eval_ne.rb G0403.dat O0403.txt
$ ruby eval_ne.rb G0404.dat O0404.txt
$ ruby eval_ne.rb G0405.dat O0405.txt


| 関口宏司 | NLP | 15:50 | comments(0) | trackbacks(0) |
CaboChaでKNBコーパスを使う
またまたCaboChaの続きで、今回は京都コーパスではなくKNBコーパス(KNBC)を使う。KNBコーパスは京都コーパスと異なり、タグ情報だけでなく本文情報も含まれているので、この記事の通りのオペレーションを行えば、(途中までは)毎日新聞データを持たない読者も実際にCaboChaの学習や評価を試すことができる。

KNBコーパスにはさまざまな特徴があるが、ここで注目するのは京都コーパスと比較しての下記の点である:

  • データ量が少ない。
  • (大学生が書いた)ブログ記事である。


KNBコーパスは4つのカテゴリ「携帯電話」「京都観光」「グルメ」「スポーツ」のどれかについて書かれたブログ記事に文境界、形態素、係り受け、格・省略・照応、固有表現、評価表現の各種アノテーションを付加した解析済みコーパスである。毎日新聞の記事データにアノテーションを付加した京都コーパスに比べデータ量が約一桁少ない(京都コーパス38400文に対し、KNBコーパスは4184文)。また、ブログ記事ということで新聞記事と比べて「文境界があいまい」「構文構造の解析を困難にする括弧表現」「誤字、方言、顔文字などの多様な形態素」というCGM的な特徴を有する。

このような特徴を踏まえ、今回は次のような評価を行った:

  1. [KNBC-KNBC] KNBコーパスの一部を学習して他の部分のKNBコーパスで評価を行う。
  2. [KNBC-KC] KNBコーパスの全部を学習して京都コーパス(毎日新聞記事)を解析してみる。
  3. [KC-KNBC] 京都コーパスを学習してKNBコーパス(ブログ記事)を解析してみる。


1.はKNBコーパス内部の学習・評価を行うものである。ここでは簡単に522文からなるスポーツカテゴリを評価用に残し、それ以外のカテゴリである携帯電話、京都観光、グルメの全合計3662文を学習することにした。量的には前の記事で作成した chunk.juman.10.1 モデルファイルでのテスト(3200文;エラー率0.2116)に近い量である。

2.と3.はブログ記事と新聞記事のクロス的・相互的な学習・評価のテストであり、ブログ記事を学習してモデルファイルを作成し新聞記事を解析した場合やその逆を行ってみて、解析性能がどの程度になるか見るものである。

結果

前の記事と比較のため、また、自作したツール類が再利用できることなどから、同様にCaboChaのchunkを対象に評価を行った。結果は下表の通り:

テスト名学習データ評価データエラー率備考
KNBC-KNBCブログ記事(携帯電話、京都観光、グルメ)
chunk.juman.knbc-knbc, model.source.knbc-knbc
ブログ記事(スポーツ)
G44.txt, S44-x.txt, O44.txt
0.2989京都コーパス3200文ではエラー率0.2116
KNBC-KC全ブログ記事
chunk.juman.knbc-kc, model.source.knbc-kc
毎日新聞記事
G1011.dat, S1011-x.dat, O1011.txt
0.3214
KC-KNBC京都コーパスchunk.juman.10.10ブログ記事(スポーツ)
G44.txt
0.2969同じモデルを新聞記事に適用した場合のエラー率は 0.1259


KNBC-KNBCテストでは、ブログ記事の3662文を学習したモデルが、量的にはそれよりも少ない京都コーパス3200文を学習したモデルよりもエラー率が高くなった。

KNBC-KCテストは全ブログ記事のコーパスを学習したモデルで新聞記事を解析するテストだが、やはりエラー率はさらに上がった。

KC-KNBCテストはKNBC-KCテストとは逆に、新聞記事である京都コーパスを学習したモデルでブログ記事を解析するテストであるが、同様にエラー率は高かった。

今回作成したプログラム

KNBコーパスのcorpus1ディレクトリを第一引数に指定するとコーパスデータをCaboCha形式のKeitai.dat, Kyoto.dat, Gourmet.dat, Sports.datのデータに出力する knbc2cabocha_chunk.rb というプログラムを作成した:

KNBC_DIR = ARGV[0]

CATEGORIES = ["Keitai", "Kyoto", "Gourmet", "Sports"]

CATEGORIES.each{|category|
  File.open("#{category}.dat", "w"){|ofile|
    Dir.glob(KNBC_DIR+"/KN???_#{category}_*").sort!.each{|article_dir_path|
      File.open("#{article_dir_path}/fileinfos"){|info_file|
        while info_line = info_file.gets
          File.open("#{article_dir_path}/#{info_line.split[1].split(/:/)[1]}"){|ifile|
            chunk_num = 0
            while line = ifile.gets
              next if /¥A# S-ID:/ =~ line # skip the comment line
              terms = line.split
              if terms[0] == "*"
#                ofile.puts "* #{chunk_num} #{terms[1]}"
                ofile.puts "* #{chunk_num} -1D"
                chunk_num += 1
              elsif terms[0] == "+"
                next
              elsif terms[0] == "EOS"
                ofile.puts "EOS"
              else
                ofile.puts "#{terms[0]}¥t#{terms[3]},#{terms[5]},*,*,#{terms[2]},#{terms[1]},*"
              end
            end
          }
        end
      }
    }
  }
}


オペレーションメモ

# KNBコーパスをCaboCha形式に変形しつつ、4つのカテゴリごとのファイルに分ける。
$ ruby knbc2cabocha_chunk.rb ../KNBC_v1.0_090925/corpus1

# 前の記事の習慣に合わせリネーム。
$  mv Keitai.dat G41.dat
$  mv Kyoto.dat G42.dat
$  mv Gourmet.dat G43.dat
$  mv Sports.dat G44.dat

# [KNBC-KNBC] モデルのソースを作成。
$ cat G4[1-3].dat > model.source.knbc-knbc

# [KNBC-KNBC] 学習。
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.knbc-knbc chunk.juman.knbc-knbc

# [KNBC-KNBC] CaboCha形式の学習データG44.datから原文テキストファイルS44-x.txtを逆再生
$ ruby make_source.rb 44

# [KNBC-KNBC] cabochaの実行。
$ cat S44-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.knbc-knbc -O2 -f1 -n0 > O44.txt
$ cat S44-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.knbc-knbc -O2 -f1 -n0 >> O44.txt

# [KNBC-KNBC] 正解データとcabochaの出力を比較用に正規化。
$ ruby norm_chunk.rb G44.dat > G44-norm.dat
$ ruby norm_chunk.rb O44.txt > O44-norm.txt

# [KNBC-KNBC] エラー率の表示。
$ ruby diff_chunk.rb G44-norm.dat O44-norm.txt 
error = 0.2989 (156/522)

# -----------

# [KNBC-KC] モデルのソースを作成。
$ cat G4[1-4].dat > model.source.knbc-kc

# [KNBC-KC] 学習。
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.knbc-kc chunk.juman.knbc-kc

# [KNBC-KC] cabochaの実行。
$ cat S1011-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.knbc-kc -O2 -f1 -n0 > O1011.txt
$ cat S1011-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.knbc-kc -O2 -f1 -n0 >> O1011.txt
:
$ cat S1011-16.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.knbc-kc -O2 -f1 -n0 >> O1011.txt

# [KNBC-KC] cabochaの出力を比較用に正規化。
$ ruby norm_chunk.rb O1011.txt > O1011-norm.txt

# [KNBC-KC] エラー率の表示。
$ ruby diff_chunk.rb G1011-norm.dat O1011-norm.txt 
error = 0.3214 (2057/6400)

# -----------

# [KC-KNBC] cabochaの実行。
$ cat S44-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.10.10 -O2 -f1 -n0 > O1044.txt
$ cat S44-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.10.10 -O2 -f1 -n0 >> O1044.txt

# [KC-KNBC] cabochaの出力を比較用に正規化。
$ ruby norm_chunk.rb O1044.txt > O1044-norm.txt

# [KC-KNBC] エラー率の表示。
$ ruby diff_chunk.rb G44-norm.dat O1044-norm.txt 
error = 0.2969 (155/522)
| 関口宏司 | NLP | 11:05 | comments(0) | trackbacks(0) |
モデルとなる京都コーパスの量を変えたときのCaboChaの性能を見る
CaboCha+京都コーパスの続きである。

前の記事ではdat/syn/ ディレクトリにある拡張子が.KNPのファイル28個を上から6個、6個、6個、5個、5個ずつまとめて交差検定を行った。すると、学習データの量に偏りが出たせいか、エラー率は12%から18%とばらつきがあった。

そこで今度は学習データのファイルを文の個数で正確に分割し、学習データの量とchunkの正解率の変化を見てみることにした。具体的には、データが38400文あったのでこれを12分割(すると38400/12=3200となる)し、最後の2つ分、6400文を正解データと固定して、残りの10個分をモデルファイルのソースとし、これを1つ分、2つ分、... と増やしていったときのエラー率に注目する。

結果は期待通り、データ量が増えるにしたがいエラー率が下がった:

モデルファイル名エラー率
chunk.juman.10.10.2116
chunk.juman.10.20.1861
chunk.juman.10.30.1708
chunk.juman.10.40.1598
chunk.juman.10.50.1509
chunk.juman.10.60.1448
chunk.juman.10.70.1383
chunk.juman.10.80.1341
chunk.juman.10.90.1272
chunk.juman.10.100.1259


ここで、chunk.juman.10.1は3200文、chunk.juman.10.2は6400文と学習データ量が増えていき、chunk.juman.10.10は32000文である。グラフ化すると次の通り:

京都コーパスの学習データ量を変えたときのCaboChaのchunkのエラー率

今回作成したプログラム

G5[1-5].datという5つのファイルから同じ数の文に再整理して出力する rearrange.rb というプログラムを作成した。

rearrange.rb
TARGET_NUM = ARGV[0].to_i
SENTENCE_NUM = ARGV[1].to_i

def target_file_name(num)
  sprintf("G%02d%02d.dat", TARGET_NUM, num)
end

$ai = 1
$ifile = nil
$end_of_sources = false

def next_source
  $ifile.close if $ifile
  $ai = $ai + 1
  return nil if $ai >= ARGV.length
  puts "reading #{ARGV[$ai]} ..."
  $ifile = File.open(ARGV[$ai])
end

def read_sentence
  sentence = Array.new
  while true do
    while line = $ifile.gets do
      sentence << line
      return sentence if /¥AEOS/ =~ line
    end
    unless next_source
      $end_of_sources = true
      return []
    end
  end
end

  return nil if $ai >= ARGV.length
  puts "reading #{ARGV[$ai]} ..."
  $ifile = File.open(ARGV[$ai])
end

def read_sentence
  sentence = Array.new
  while true do
    while line = $ifile.gets do
      sentence << line
      return sentence if /¥AEOS/ =~ line
    end
    unless next_source
      $end_of_sources = true
      return []
    end
  end
end

# create target files - for model files
next_source
TARGET_NUM.times {|tnum|
  snum = 0
  File.open(target_file_name(tnum+1), "w"){|ofile|
    while snum < SENTENCE_NUM
      read_sentence.each{|line|
        ofile.puts line
      }
      snum = snum + 1
    end
  }
}

# also create a target file - for evaluation
File.open(target_file_name(TARGET_NUM+1), "w"){|ofile|
  until $end_of_sources do
    read_sentence.each{|line|
      ofile.puts line
    }
  end
}


オペレーションメモ

# 学習データと評価用データの再作成
# 第1引数: ターゲット学習データの個数
# 第2引数: 1個あたりのターゲット学習データ内の文の個数
# 第3引数以降: CaboCha形式ソースデータファイル
$ ruby rearrange.rb 10 3200 G5?.dat

# モデルのソースを生成
$ cat G100[1].dat > model.source.10.1
$ cat G100[1-2].dat > model.source.10.2
$ cat G100[1-3].dat > model.source.10.3
$ cat G100[1-4].dat > model.source.10.4
$ cat G100[1-5].dat > model.source.10.5
$ cat G100[1-6].dat > model.source.10.6
$ cat G100[1-7].dat > model.source.10.7
$ cat G100[1-8].dat > model.source.10.8
$ cat G100[1-9].dat > model.source.10.9
$ cat G100[1-9].dat G1010.dat > model.source.10.10

# 学習。規模に応じてだんだん時間がかかる(20秒〜150秒程度)
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.1 chunk.juman.10.1
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.2 chunk.juman.10.2
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.3 chunk.juman.10.3
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.4 chunk.juman.10.4
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.5 chunk.juman.10.5
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.6 chunk.juman.10.6
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.7 chunk.juman.10.7
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.8 chunk.juman.10.8
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.9 chunk.juman.10.9
$ /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN model.source.10.10 chunk.juman.10.10

# CaboCha形式の学習データG1011.datから原文テキストファイルS1011-x.txtを逆再生
$ ruby make_source.rb 1011

# 各モデルファイルを使い原文テキストS1011-x.datに対してcabochaを実行
$ cat S1011-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.10.1 -O2 -f1 -n0 > O10A01.txt
$ cat S1011-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.10.1 -O2 -f1 -n0 >> O10A01.txt
:
$ cat S1011-01.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.10.2 -O2 -f1 -n0 > O10A02.txt
$ cat S1011-02.txt | cabocha -d /usr/local/lib/mecab/dic/jumandic -M ./chunk.juman.10.2 -O2 -f1 -n0 >> O10A02.txt
:

# 正解データの正規化
$ ruby norm_chunk.rb G1011.dat > G1011-norm.dat

# cabochaの出力を正規化
$ ruby norm_chunk.rb O10A01.txt > O10A01-norm.txt
$ ruby norm_chunk.rb O10A02.txt > O10A02-norm.txt
:
$ ruby norm_chunk.rb O10A10.txt > O10A10-norm.txt

# モデルファイル量ごとの評価(エラー率の表示)
$ ruby diff_chunk.rb G1011-norm.dat O10A01-norm.txt 
error = 0.2116 (1354/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A02-norm.txt 
error = 0.1861 (1191/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A03-norm.txt 
error = 0.1708 (1093/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A04-norm.txt 
error = 0.1598 (1023/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A05-norm.txt 
error = 0.1509 (966/6400)
$ ruby .diff_chunk.rb G1011-norm.dat O10A06-norm.txt 
error = 0.1448 (927/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A07-norm.txt 
error = 0.1383 (885/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A08-norm.txt 
error = 0.1341 (858/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A09-norm.txt 
error = 0.1272 (814/6400)
$ ruby diff_chunk.rb G1011-norm.dat O10A10-norm.txt 
error = 0.1259 (806/6400)
| 関口宏司 | NLP | 12:31 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
   1234
567891011
12131415161718
19202122232425
262728293031 
<< August 2012 >>
+ 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