ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

deep-crfという固有表現抽出ツールを使ってみる

anagoという固有表現抽出ツールを日本語に対して適用してみたことがありますが、F値が60台ぐらいしか行かなかったため、他のツールを探していました。ここでは、deep-crfというもっと汎用的なツールがあったのでそれを試してみます。

インストール

インストール方法は以下のgithubページから見てください。 https://github.com/aonotas/deep-crf

データの準備

kwdlcのデータをanagoの形式に変換するスクリプトを以前の記事で書きました。以下がそのリンクです。 https://qiita.com/sugiyamath/items/a6bed3078de971667621

deep-crfも似た形式なので、juman形式に直さずに、kwdlc.txtファイルを生成しておきます。

次に、正しい形式に変換し直します。以下がそのコードです。

import re

with open('kwdlc.txt') as f:
    out = []
    lines = f.readlines()
    for i, line in enumerate(lines):
        if line == '\n':
            out.append(line)
            continue
        current_line = line.split('\t')
        next_line = lines[i+1].split('\t')
        if (current_line[1].startswith('I') and next_line[1].startswith('O')) or (current_line[1].startswith('I') and next_line[1].startswith('B')):
            current_line[1] = re.sub('I', 'E', current_line[1])
            out.append(' '.join(current_line))
        else:
            out.append(' '.join(current_line))

with open('kwdlc_fixed.txt', 'w') as f:
    f.write(''.join(out))

汚いコードですが、勘弁してください。

そして、このデータをtrain, dev, testにsplitします。tst.awkというコードを作成します。

BEGIN {
    split(pcts,p)
    nrs[1]
    for (i=1; i in p; i++) {
        pct += p[i]
        nrs[int(size * pct / 100) + 1]
    }
}
NR in nrs{ close(out); out = "part" ++fileNr ".txt" }
{ print $0  >  out }

以下のコマンドを実行します。

$ awk -v size=$(wc -l < kwdlc_fixed.txt) -v pcts="60 10 30" -f tst.awk kwdlc_fixed.txt

これで、part1.txt, part2.txt, part3.txtというファイルが、それぞれ60%, 10%, 30%の割合で生成されます。(ラベルが不均衡になっている可能性がありますが、とりあえずそのことは無視。)

testデータは、ラベルと予測に使うもので分ける必要があるので、以下のコードを実行します。

out = []
out2 = []
with open("part3.txt") as f:
    lines = f.readlines()
    sentence = []
    for line in lines:
        if line == "\n":
            out.append(' '.join(sentence))
            out2.append(line)
            sentence = []
        else:
            x = line.split(' ')
            sentence.append(x[0])
            out2.append(x[1])

with open("part3_raw.txt", "w") as f:
    f.write('\n'.join(out))

with open("part3_gold.txt", "w") as f:
    f.write(''.join(out2))

訓練

訓練するために、以下コマンドを実行します。

$ mkdir model
$ deep-crf train part1.txt --delimiter=' ' --dev_file part2.txt --save_dir model --save_name bilstm-cnn-crf_adam --optimizer adam

modelの中にモデルが格納されます。

予測と評価

テストデータに対して予測します。

$ deep-crf predict part3_raw.txt --delimiter=' ' --model_filename ./model/bilstm-cnn-crf_adam_epoch10.model --save_dir model --save_name bilstm-cnn-crf_adam --predicted_output predicted.txt

predicted.txtに予測結果が出力されます。この結果と正解ラベルを比較します。

$ deep-crf eval part3_gold.txt predicted.txt 
+------------+--------------------+--------------------+--------------------+
| Tag Name   | Precision          | Recall             | F_measure          |
+------------+--------------------+--------------------+--------------------+
| All_Result | 48.392778511668865 | 42.563903950426024 | 45.291572223366984 |
| LOC        | 43.7992125984252   | 54.93827160493827  | 48.74041621029573  |
| ART        | 31.046931407942242 | 26.959247648902824 | 28.859060402684566 |
| DAT        | 71.15749525616698  | 83.14855875831486  | 76.68711656441718  |
| ORG        | 44.871794871794876 | 9.776536312849162  | 16.05504587155963  |
| PER        | 43.2               | 22.736842105263158 | 29.793103448275858 |
| TIM        | 45.83333333333333  | 55.00000000000001  | 50.0               |
| TEM        | 35.714285714285715 | 41.66666666666667  | 38.46153846153846  |
| MON        | 85.71428571428571  | 78.94736842105263  | 82.19178082191782  |
| OPT        | 8.0                | 4.040404040404041  | 5.369127516778524  |
+------------+--------------------+--------------------+--------------------+

考察

データをtrain, dev, testに分離する段階で不均衡が生じている可能性もありますが、単に過学習しているだけのような気もします。embeddingを用いればもっと精度が上がりそうですが、embedding作成に時間がかかるので省略しています。

crfsuiteよりも精度が高いという旨が書かれていますし、複数の方法で訓練が可能なようなので、他の方法を試せばもっと精度があがるかもしれません。