はじめに
わーい、ホッテントリ、わーい!🙌1 nikkieです。
固有表現抽出(NER)タスクをCRF(Conditional Random Fields2)で解く実装の理解を深めたく、チュートリアルで素振りしました。
目次
Hironsanによるチュートリアル
素振りに選んだチュートリアルはこちら。
Hironsan(中山光樹さん)は機械学習やPython本の著者・訳者3であり、doccanoの非常に活動的なコントリビューターとも認識しています。
信頼できる方が過去に書いたチュートリアルであり、Qiita上でいいねやストックが多く(500超え)、CRFでNERを解くというドンピシャな内容だったので、最初に触るチュートリアルとして選びました。
素振り成果物
1つの巨大スクリプトにせずに、モジュール分割したのが工夫点です。
動作環境4
チュートリアルの概要
使うデータ
Hironsan作成のラベル付きデータを使います
README.mdより
hironsan.txtは、ウィキニュース日本語版をMeCabで形態素解析してIOB2タグでタグ付けしたコーパスです。
全部で500文にタグ付けしています。
素性(特徴量)抽出
CRFはディープラーニングより前の機械学習モデルであり、人手で特徴量を作る必要があり、それが性能を左右するという認識です。
素性抽出パートで特徴量を作っていきます。
チュートリアルの「概要」より
今回は、前後2文字の単語、品詞細分類、文字種、固有表現タグを使います。
具体的なデータで見ていきましょう。
hironsan.txtの1文目です。
2005 名詞 数 * * * * * B-DAT 年 名詞 接尾 助数詞 * * * 年 ネン ネン I-DAT 7 名詞 数 * * * * * I-DAT (省略) た 助動詞 * * * 特殊・タ 基本形 た タ タ O 。 記号 句点 * * * * 。 。 。 O
以下のように読み込まれています。
>>> train_sents[0][0] ['2005', '名詞', '数', '*', '*', '*', '*', '*', 'B-DAT'] >>> train_sents[0][1] ['年', '名詞', '接尾', '助数詞', '*', '*', '*', '年', 'ネン', 'ネン', 'I-DAT'] >>> train_sents[0][2] ['7', '名詞', '数', '*', '*', '*', '*', '*', 'I-DAT'] >>> train_sents[0][-1] ['。', '記号', '句点', '*', '*', '*', '*', '。', '。', '。', 'O']
作った素性はこちら。
1語目の「2005」は先行する文字がなく、後に続く「年」と「7」から素性が作られます。
>>> pprint(X_train[0][0]) ['bias', 'word=2005', 'type=ZDIGIT', 'postag=名詞-数', 'BOS', 'BOS', '+1:word=年', '+1:type=OTHER', '+1:postag=名詞-接尾-助数詞', '+2:word=7', '+2:type=ZDIGIT', '+2:postag=名詞-数']
3語目の「7」は、先行する「2005」「年」、後に続く「月」「14」から素性が作られます。
>>> pprint(X_train[0][2]) ['bias', 'word=7', 'type=ZDIGIT', 'postag=名詞-数', '-2:word=2005', '-2:type=ZDIGIT', '-2:postag=名詞-数', '-2:iobtag=B-DAT', '-1:word=年', '-1:type=OTHER', '-1:postag=名詞-接尾-助数詞', '-1:iobtag=I-DAT', '+1:word=月', '+1:type=OTHER', '+1:postag=名詞-一般', '+2:word=14', '+2:type=ZDIGIT', '+2:postag=名詞-数']
文の最後の語の「。」は、後に続く語がないので、先行する2語「れ」「た」から素性が作られます。
>>> pprint(X_train[0][-1]) ['bias', 'word=。', 'type=OTHER', 'postag=記号-句点', '-2:word=れ', '-2:type=HIRAG', '-2:postag=動詞-接尾', '-2:iobtag=O', '-1:word=た', '-1:type=HIRAG', '-1:postag=助動詞', '-1:iobtag=O', 'EOS', 'EOS']
写経した実装はfeature_engineering.pyにあります。
写経する中で私はほとんど同じコードをどうしても何度も書きたくなかったので、少し関数化するという工夫をしました(30分で動かすためにコピペするのも全然いいと思います)。
CRFsuiteを訓練
今回はpython-crfsuite
を使っています。
Trainer
やTagger
の扱いは(scikit-learnのインタフェースに慣れた身からすると)独特ですね
- Trainer5
append
メソッドでデータをもたせるset_params
メソッドでハイパーパラメタ指定train
で訓練(fit)。渡したパスに保存できる
- Tagger6
- 初期化してから
open
でファイルから読み込む tag
メソッドで推論(predict)
- 初期化してから
モデルの評価、結果の再現!
チュートリアルの結果は再現しました!
precision recall f1-score support B-ART 1.00 0.89 0.94 9 I-ART 0.92 1.00 0.96 12 B-DAT 1.00 1.00 1.00 12 I-DAT 1.00 1.00 1.00 22 B-LOC 1.00 0.95 0.97 55 I-LOC 0.94 0.94 0.94 17 B-ORG 0.75 0.86 0.80 14 I-ORG 1.00 0.90 0.95 10 B-PSN 0.00 0.00 0.00 3 B-TIM 1.00 0.71 0.83 7 I-TIM 1.00 0.81 0.90 16 micro avg 0.96 0.91 0.94 177 macro avg 0.87 0.82 0.84 177 weighted avg 0.95 0.91 0.93 177 samples avg 0.14 0.14 0.14 177
scikit-learn
のバージョンが新しいため末尾のavgの行が多いのだと思います。
チュートリアルとは「weighted avg」の行が一致しますね。
終わりに
CRFで固有表現抽出するチュートリアルで素振りしました。
重複コードが減るよう実装を工夫しつつ、チュートリアルの結果が再現したので満足です。
一通り動くようになったコードが手元にあるので、理解を深めるために改造を試していこうと思います(どうか次回がありますように)
- つ ↩
- 「条件付き確率場」と呼ばれますね。https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E4%BB%98%E3%81%8D%E7%A2%BA%E7%8E%87%E5%A0%B4↩
- 著書『機械学習・深層学習による自然言語処理入門』(固有表現抽出を扱った章あります)。訳書『機械学習エンジニアのためのTransformers』や直近では『ハイパフォーマンスPython 第2版』↩
- 詳細 https://github.com/ftnext/ml-playground/blob/89f3f277c4cd998dbbd58af756d2a0fbaaf072a2/crf/hironsan-tutorial/requirements.lock↩
- https://python-crfsuite.readthedocs.io/en/latest/pycrfsuite.html#pycrfsuite.Trainer↩
- https://python-crfsuite.readthedocs.io/en/latest/pycrfsuite.html#pycrfsuite.Tagger↩