IPA 品詞体系の構文解析器の学習
(半)指導している学生が IPA 品詞体系に基づく構文解析器が遅過ぎて実験が進まないというので,手元の構文解析器(Juman 品詞体系を想定)を IPA 品詞体系に対応させてみた.構文解析器のコードは素性抽出周りを10行くらいいじるだけで簡単に対応させることができた(公開済).
次に,構文解析器の訓練に使う注釈付きデータが必要になるが,これには Juman 品詞体系に基づく注釈付きデータに IPA 品詞体系の品詞タグを付与して使うことにした.品詞タグを変換するには,
という二つの方法が考えられる.今回は運用時の状況を考慮して,構文解析器とパイプライン的に組み合わせる予定の形態素解析器/辞書を利用して品詞タグを再付与することにした.以下がそのスクリプト.
#!/usr/bin/env python # Usage: replace_pos.py [mecab_options] < in.KNP > out.KNP # you may want to set mecab_options to '-d your_target_dict' import re, sys, MeCab class Binfo: """ bunsetsu infomation """ def __init__ (self, offset, head_id, ptype): self.offset, self.head_id, self.ptype = offset, head_id, ptype; self.morphemes = [] def header (self): return "%d%s" % (self.head_id, self.ptype) def morph (self): return '\n'.join (' '.join (m) for m in self.morphemes) t = MeCab.Tagger (' '.join (sys.argv)) header = '' sent = '' binfo = [] stat = { 'success': 0, 'failed': 0 } for line in sys.stdin: if line[0] == '#': header = line sent = '' binfo[:] = [] elif line[0] == 'E': # EOS footer = line binfo.append (Binfo (len (sent), -1, 'D')) # dummy offset = 0 i = 0 n = t.parseToNode (sent).next while n: # unknown words lacks fields related to surface form field = n.feature.split (',') # modify here to be consistent with the MeCab output pos1, pos2, pos3, pos4, pred, infl, fin, yomi = \ field[:8] if len (field) >= 9 else field[:6] + [n.surface] * 2 fin = "*" if n.surface == fin else fin if binfo[i].offset == offset: # correct bunsetsu chunk i += 1 elif i + 1 < len (binfo): if binfo[i+1].offset == offset and binfo[i-1].head_id == i: # assuming compounds; you may want to add more conditions binfo[i-1].head_id = binfo[i].head_id del (binfo[i]) for bi in filter (lambda bj: bj.head_id >= i, binfo): bi.head_id -= 1 i += 1 elif binfo[i+1].offset < offset: # mismatched stat['failed'] += 1 break if n.surface: m = (n.surface, yomi, fin, pos1, pos2, pred, infl, pos3, pos4) binfo[i-1].morphemes.append (m) offset += len (n.surface) n = n.next else: stat['success'] += 1 print header, for j in range (len (binfo) - 1): print "* %d %s" % (j, binfo[j].header ()) print binfo[j].morph () print footer, elif line[0] == '*': head_type = line[:-1].split(' ')[2] binfo.append (Binfo (len (sent), int (head_type[:-1]), head_type[-1])) else: sent += line[:-1].split(' ')[0] sys.stderr.write ("(success, failed) = (%d, %d)\n" % (stat['success'], stat['failed']))
IPAdic には複合助詞*1(という,として,について,による,など)が多数含まれており,これらが文節境界を含む関係で,約20%の文はそのままでは変換できない.そこで,文節境界をまたぐ形態素があり,かつその文節境界の前後の文節間に依存関係がある場合は二つの文節を一つにまとめるようにして注釈付きデータ自体を変更するようにした.この処理を入れることで,全注釈付きデータの約99%を IPA 品詞体系の構文解析済みデータに変換できた.
得られた注釈付きデータを使って,標準的な分割で学習させてみたところ,解析精度は約91.4%*2 ぐらいだった.文数も文節数も少し減っているので単純には比較できないが,人手でタグ付けした Juman 品詞体系のタグ付けを用いた場合の解析精度が約91.8%であることを考えると,まあ使えると思って良さそうだ*3.解析速度は,元の注釈付きデータから学習した構文解析器とほぼ同じ.ちなみに第三階層以下の品詞タグ(pos3, pos4)は(素性として使ってみたが)あまり解析精度には貢献しなかった.
さらに手を抜くには上記のスクリプトを C++ で実装して,Juman 品詞体系の構文解析器の解析結果の変換に使うという手もある.真面目に IPA 品詞体系の構文解析済みデータを得ることが目的なら Juman 品詞体系の結果を素性に使った形態素解析器とか構築するのが良いかも知れない(融通が利かない感じだが).
[追記] Juman 辞書を用いて推定した品詞を注釈付けすることもできるが,そのときは文節境界が一致しなかった文は(単純に解析誤りの可能性が高いので)捨てたほうが良いかもしれない.