IPA 品詞体系の構文解析器の学習

(半)指導している学生が IPA 品詞体系に基づく構文解析器が遅過ぎて実験が進まないというので,手元の構文解析器(Juman 品詞体系を想定)を IPA 品詞体系に対応させてみた.構文解析器のコードは素性抽出周りを10行くらいいじるだけで簡単に対応させることができた(公開済).
次に,構文解析器の訓練に使う注釈付きデータが必要になるが,これには Juman 品詞体系に基づく注釈付きデータに IPA 品詞体系の品詞タグを付与して使うことにした.品詞タグを変換するには,

  • 元の Juman 品詞体系の品詞タグを IPA 品詞体系に変換する
  • 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 辞書を用いて推定した品詞を注釈付けすることもできるが,そのときは文節境界が一致しなかった文は(単純に解析誤りの可能性が高いので)捨てたほうが良いかもしれない.

*1:日本語複合辞用例データベースの作成と分析参照.

*2:元の MeCab のモデルの学習にこの注釈付けデータが使われている場合には,品詞タグ付けについてクローズドテストになるので,構文解析器の精度も(品詞タグ付けがオープンテストである場合に比べて)高く出ている可能性もある.実際どうなのか良く知らないが.[追記] NAIST-jdic+α では約91.3%.

*3:連語でカバーされた係り受けはほぼ正解していると期待できるので,見た目の精度はそれほど落ちていないのではないかと推測できる.