論文 -Neural Architectures for Named Entity Recognition-

以下の論文を解説した後、著者が公開したコードを使ってみます。

 

用語解説

Named Entity Recognition (NER): 固有表現抽出

人名・地名などの固有名詞や日付・容量などの数値表現を抽出するNLPタスク。

これまで精度の高いNERを実現するには大量のラベル付きデータが必要とされてきました。

しかし、言語や分野ごとにコーパスを作成するのはコストがかかります。

特に専門分野のコーパスの作成には専門知識が求められるため、よりコストが高くつくことが問題でした。

詳しい説明は以下の記事を参考にしてください。

 

Long Short-Term Memory (LSTM)

RNNの一種です。

RNNの課題だった長い系列データの利用が可能になりました。

詳しい経緯は以下の解説記事を参考にしてください。

 

論文概要

これまでのNERの手法では、高い精度を出すためには人間が手間をかけて作った言語資源(コーポラや辞書)が必要でした。

しかしそれでは言語ごと分野ごとに言語資源を作る必要があり、とてもコストがかかります。

注釈のついていないコーパス(unannotated corpus)あるいは少量の注釈付きコーパスから適切にタグ付けできる分類器を作れるのが望ましいです。

 

これらは言語資源としては以下の2つを用いています。

  • 注釈付きコーパスから教師あり学習した文字レベルの単語分散表現
  • 注釈なしコーパスから教師なし学習した単語分散表現

Gazetteer(地名辞書)のような言語資源も必要としておらず、これまでのモデルに比べてずっと少ないコストで学習できます。

 

実際に使ってみる

著者によりソースコードが公開されているので使ってみましょう。

 

READMEに注意があるので以下の環境で実行します。

  • MacBook Pro (Retina, 15-inch, Mid 2015)
  • macOS High Sierra 10.13.5
  • Python 2.7.11
  • Numpy 1.14.5
  • Theano 1.0.2 インストール方法*

*library dfftpack has Fortran sources but no Fortran compiler foundが出たらbrew install gcc

*-fPIC を付けて再コンパイルしてくださいとエラーが出たらCFLAGS="-fPIC" pyenv install 2.7.11

 

Bidirectional LSTM CRF -タグ付け-

tagger.pyを使えばすぐに学習済みのモデルでNERできます(英語のみ)。

このモデルで検出できる固有表現は、以下の4つのようです。

  • PER(人名)
  • LOC(地名)
  • ORG(組織名)
  • MISC(その他の固有表現)

フォーマットとしてはIOBESフォーマットを使っているみたいです。

 

まずはタグ付け対象の文書を作りましょう。

input.txt(ファイル名は実行時に指定するのでなんでもいい)に適当な文章を入力します。

1行に1文となるように注意します。

大文字や句読点、カッコなどが含まれていても大丈夫か気になります。

*後でわかりますが文末のカンマは抜いた方が良さげ。

Eddy Bonte is woordvoerder van diezelfde Hogeschool.
Sherlock Holmes is a fictional private detective created by British author Sir Arthur Conan Doyle.
We provide a brief description of LSTMs and CRFs, and present a hybrid tagging architecture. This architecture is similar to the ones presented by Collobert et al. (2011) and Huang et al. (2015).

 

実行してみます。

./tagger.py --model models/english/ --input input.txt --output output.txt

 

モデルのロードとコンパイルに10秒くらい時間がかかりますが、肝心のタグ付けは一瞬で終わりました。

Loading model...
Compiling...
Tagging...
---- 3 lines tagged in 0.7566s ----

 

output.txtを開いてみます。

 

第一文は「入門 自然言語処理」のNERのページにあったものを使いました。

Eddy__B-PER Bonte__I-PER is__O woordvoerder__O van__O diezelfde__O Hogeschool.__B-PER

「単語__タグ」という表示方法。これがスタンダードなんでしょうか?

CSVとかじゃないんですね。読み込みがめんどくさそう。おそらく文章中にアンダーバーが2回連続出現しないという前提なのでしょうが。

ちなみにこのデリミターは別のものに変えられます。「@」とか好きなものが使えるので文書に合わせて紛れないものにしたいですね。

 

答えはこちら。

Hogeschoolというのを人名(B-PER)としてしまっています。

あと、文末のピリオドは自分で取り除いてあげたほうがよさそう。

 

2文目はシャーロックホームズの説明をWikipediaから引っ張ってきました(引用: Sherlock Holmes – Wikipedia)。

Sherlock__B-PER Holmes__I-PER is__O a__O fictional__O private__O detective__O created__O by__O British__B-MISC author__O Sir__O Arthur__B-PER Conan__I-PER Doyle.__I-PER

だいたい良さそう。

Sherlock Holmes: 人(PER)

British: その他(MISC)

Arthur Conan Doyle: 人(PER)

 

最後は本論文の適当な部分から引用しました。

We__O provide__O a__O brief__O description__O of__O LSTMs__B-ORG and__I-ORG CRFs,__I-ORG and__O present__O a__O hybrid__O tagging__O architecture.__O This__O architecture__O is__O similar__O to__O the__O ones__O presented__O by__O Collobert__B-MISC et__I-MISC al.__I-MISC (2011)__I-MISC and__O Huang__B-PER et__O al.__O (2015).__O

こんな感じ。

  • LSTMs and CRFs: 組織名(ORG)
  • Collobert et al. (2011): その他(MISC)
  • Huang et al. (2015): Huangのみ人(PER)と認識

まあネットワーク名は確かに組織名に見えなくもない。知らなければ人間でも判別できないでしょう。むしろ固有表現として抽出できたのがすごい。

論文の引用表現をまとめてその他に抜いてしまうのもまぁ仕方ないかも。本当はHuangの方みたいに名前だけ抜き出して欲しいですが。

 

とりあえず使い勝手はわかりました。

文末のカンマが単語の一部とされているのが気持ち悪いのであらかじめ抜いた方がよさそうです。

ただし今回の入力データではカンマのあるなしで結果が変わりませんでした。

文字レベルのLSTMが入っているので、カンマが入っていても大きな問題はないのでしょうか。

 

Bidirectional LSTM CRF -学習-

自分の言語資源で学習させたい場合はtrain.pyを使います。

コマンドはこんな形で訓練データ、開発データ、評価データを指定します。


./train.py --train train.txt --dev dev.txt --test test.txt

 

他にも細かく設定を変えられます。詳しくはヘルプをみてください。


./train.py --help

 

学習に使うテキストデータのフォーマットはCoNLL2003 sharing taskのものを参考にしてくださいとのこと。

リポジトリ内のdataset/の中にサンプルが入っているのでチェックします。

1行に「単語」、「POSタグ」、「partial parse (chunk) tag」、「NEタグ」がタブ区切りで収められています。

LEICESTERSHIRE NNP I-NP I-ORG

文章の区切りは空行が1つ、文書の区切りは空行が2つです。

 

ちなみに詳しいことは以下のページや公式を参考にしてください。

This data is in the CoNLL2003 format. Each line has four columns containing the original word, it’s part of speech (POS) tag, a partial parse (chunk) tag, and a NE tag. For example, the first full line says that the original word was “EU”, the POS tag (as predicted by a tagger) is NNP, the word is inside a noun pharse (I-NP) as predicted by a partial parser and the word should be tagged as an organization name (I-ORG). For more details on the CoNLL2003 data format see the official CoNLL2003 shared task website.

引用: Named Entity (NE) tagging (CoNLL2003 data) – ERMA

 

ただIOBタグのつけ方がいまいちわからない。

今回のリポジトリの訓練データは20万単語くらいあるのですが、

B-XXXがほとんど使われてないです。

  • B-PER: 0回
  • B-LOC: 11回
  • B-ORG: 24回
  • B-MISC: 35回

これに対してI-XXXは33973回使われていたので、僕の理解通りのIOBフォーマットの使い方をされていたら(NEの始まりはB-XXX、続きはI-XXX)、

平均して一つの固有表現が340単語の長さということになります。

なので、ここに関しては一旦無視します。

 

さて、自分で学習データを用意するわけですが、完全に上記のCoNLL2003のフォーマットでなくても良いようです。

どうやら行の最初の要素を単語、最後の要素をNEタグとして認識しているようなので間のPOSタグはあってもなくてもどっちでもいいとのことです。

Input files for the training script have to follow the same format than the CoNLL2003 sharing task: each word has to be on a separate line, and there must be an empty line after each sentence. A line must contain at least 2 columns, the first one being the word itself, the last one being the named entity. It does not matter if there are extra columns that contain tags or chunks in between. Tags have to be given in the IOB format (it can be IOB1 or IOB2).

引用: glample/tagger | Github

 

試しに学習させてみます。


./train.py --train dataset/eng.train --dev dataset/eng.testa --test dataset/eng.testb


 

なんか0ばっかりで不穏。。。


Model location: ./models/tag_scheme=iobes,lower=False,zeros=False,char_dim=25,char_lstm_dim=25,char_bidirect=True,word_dim=100,word_lstm_dim=100,word_bidirect=True,pre_emb=,all_emb=False,cap_dim=0,crf=True,dropout=0.5,lr_method=sgd-lr_.005
Found 23624 unique words (203621 in total)
Found 84 unique characters
Found 17 unique named entity tags
14041 / 3250 / 3453 sentences in train / dev / test.
Saving the mappings to disk...
Compiling...
Starting epoch 0...
50, cost average: 16.017062
100, cost average: 12.043882
150, cost average: 10.004235
200, cost average: 11.057611
250, cost average: 13.818798
300, cost average: 10.895356
350, cost average: 11.452390
400, cost average: 12.121825
450, cost average: 11.837930
500, cost average: 12.739769
550, cost average: 11.871366
600, cost average: 9.914921
650, cost average: 9.181969
700, cost average: 10.206943
750, cost average: 12.798749
800, cost average: 12.083279
850, cost average: 9.848755
900, cost average: 10.180503
950, cost average: 10.889115
processed 51362 tokens with 5942 phrases; found: 0 phrases; correct: 0.
accuracy: 83.25%; precision: 0.00%; recall: 0.00%; FB1: 0.00
LOC: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
MISC: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
ORG: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
PER: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
ID NE Total O S-LOC B-PER E-PER S-ORG S-MISC B-ORG E-ORG S-PER I-ORG B-LOC E-LOC B-MISC E-MISC I-MISC I-PER I-LOC Percent
0 O 42759 42759 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100.000
1 S-LOC 1603 1603 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
2 B-PER 1234 1234 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
3 E-PER 1234 1234 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
4 S-ORG 891 891 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
5 S-MISC 665 665 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
6 B-ORG 450 450 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
7 E-ORG 450 450 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
8 S-PER 608 608 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
9 I-ORG 301 301 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
10 B-LOC 234 234 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
11 E-LOC 234 234 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
12 B-MISC 257 257 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
13 E-MISC 257 257 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
14 I-MISC 89 89 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
15 I-PER 73 73 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
16 I-LOC 23 23 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
42759/51362 (83.25026%)
processed 46435 tokens with 5648 phrases; found: 0 phrases; correct: 0.
accuracy: 82.53%; precision: 0.00%; recall: 0.00%; FB1: 0.00
LOC: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
MISC: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
ORG: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
PER: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
ID NE Total O S-LOC B-PER E-PER S-ORG S-MISC B-ORG E-ORG S-PER I-ORG B-LOC E-LOC B-MISC E-MISC I-MISC I-PER I-LOC Percent
0 O 38323 38323 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100.000
1 S-LOC 1436 1436 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
2 B-PER 1086 1086 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
3 E-PER 1086 1086 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
4 S-ORG 1082 1082 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
5 S-MISC 525 525 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
6 B-ORG 579 579 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
7 E-ORG 579 579 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
8 S-PER 531 531 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
9 I-ORG 256 256 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
10 B-LOC 232 232 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
11 E-LOC 232 232 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
12 B-MISC 177 177 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
13 E-MISC 177 177 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
14 I-MISC 39 39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
15 I-PER 70 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
16 I-LOC 25 25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000
38323/46435 (82.53042%)
Score on dev: 0.00000
Score on test: 0.00000
New best score on dev.
Saving model to disk...
New best score on test.

 

特に以下の部分が不安!

おそらく全ての単語をO(固有表現じゃない)と判断しているようです。


Score on dev: 0.00000
Score on test: 0.00000

 

とりあえず様子を見ようと思って待っていたら、10epoch回る頃にはスコアが上がってきていました。


Score on dev: 49.79000
Score on test: 48.76000

 

 

さてそれでは本命、薬剤名の抽出を目標に学習させ直してみましょう。

その前にデータの準備を行います。

BioCreativeでは論文のabstructに薬剤名のタグをつけたNER用のコーパスを公開しています。

 

training、development、evaluationがそれぞれ訓練、開発、評価用のデータになります。

*.annotations.txtがタグデータです。

開いてみると、タブ区切りのtsvであることがわかります。

1行に書かれているのは以下の6項目です。

  • 論文ID(PubMed ID)
  • テキストタイプ(T: title or A: abstruct)
  • 開始オフセット
  • 終了オフセット
  • 薬剤名
  • 薬剤の種類

21826085 A 946 957 haloperidol TRIVIAL
22080034 A 190 199 aflatoxin FAMILY
22080034 A 594 603 aflatoxin FAMILY

 

*.abstructs.txtが論文のabstructのデータです。同じくタブ区切りで以下の3つの項目を含みます。

  • 論文ID
  • タイトル
  • abstruct

 

困ったことにタグのオフセットが文字レベルなので扱いにくいです。

tagger.pyが扱えるフォーマットに変換してあげる必要があります。

 

方針としては、abstructs.txtをスペース区切りで単語ごとにバラバラにする。

論文IDとオフセットを頼りに適切なタグを設定する。

 

なんとかしてtrain.pyに渡せる形のtraining.chem.txt、development.chem.txt、evaluation.chem.txtを用意しました。

ドキドキしながら走らせてみます。


./train.py --train dataset/training.chem.txt --dev dataset/development.chem.txt --test dataset/evaluation.chem.txt

 

なんとか走り出したみたいです。


Model location: ./models/tag_scheme=iobes,lower=False,zeros=False,char_dim=25,char_lstm_dim=25,char_bidirect=True,word_dim=100,word_lstm_dim=100,word_bidirect=True,pre_emb=,all_emb=False,cap_dim=0,crf=True,dropout=0.5,lr_method=sgd-lr_.005
Found 59792 unique words (576977 in total)
Found 210 unique characters
Found 5 unique named entity tags
24952 / 25121 / 21424 sentences in train / dev / test.
Saving the mappings to disk...
Compiling...
Starting epoch 0...
50, cost average: 9.211995
100, cost average: 5.212244
150, cost average: 4.647317
200, cost average: 4.435499
250, cost average: 5.619099
300, cost average: 4.114544
350, cost average: 8.296568
400, cost average: 4.187215
450, cost average: 4.999699
500, cost average: 4.403419
550, cost average: 5.171318
600, cost average: 5.182671
650, cost average: 6.831722
700, cost average: 6.022907
750, cost average: 5.740782
800, cost average: 6.885817
850, cost average: 4.299852
900, cost average: 5.268873
950, cost average: 5.870510
processed 572827 tokens with 26397 phrases; found: 0 phrases; correct: 0.
accuracy: 94.65%; precision: 0.00%; recall: 0.00%; FB1: 0.00
MED: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
ID NE Total O S-MED B-MED E-MED I-MED Percent
0 O 542198 542198 0 0 0 0 100.000
1 S-MED 22923 22923 0 0 0 0 0.000
2 B-MED 3474 3474 0 0 0 0 0.000
3 E-MED 3474 3474 0 0 0 0 0.000
4 I-MED 758 758 0 0 0 0 0.000
542198/572827 (94.65301%)

 

手元のMac book Proで1epoch 12分ぐらいでした。1000epoch学習するみたいなので2000分=200時間かかる計算です。Wow.

とりあえず手元のPCでの計算を止めて、本格的な計算はGPUで行います。

 

AWSにsshログインして、nohupコマンドで実行します。

これによってsshが切れても計算を続けてくれます。

 


nohup python -u train.py --train dataset/training.chem.txt --dev dataset/development.chem.txt --test dataset/evaluation.chem.txt > out.log &

 

現在学習中なので学習が終了したら評価を行います。

 

参考

コメントを残す