論文 -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.
ただ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).
試しに学習させてみます。
./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 &
現在学習中なので学習が終了したら評価を行います。
参考
- Language-Independent Named Entity Recognition (II)
- Named Entity (NE) tagging (CoNLL2003 data) – ERMA
- glample/tagger | Github
- Neural Architectures for Named Entity Recognition
- BioCreative – CHEMDNER Corpus
- サーバの接続切れてもコマンドを実行する ~ nohup – 忘れないようにメモっとく
- 【 nohup 】コマンド――端末を閉じてもログアウトしても処理を続ける:Linux基本コマンドTips(137) – @IT
- Linuxコマンドを連続して使うには – Qiita
- pyenvでのPythonで-fPICを付けて再コンパイル | CHAZINE.COM
- python – Error importing Theano – Stack Overflow
- python > print > 処理終了時にしかリダイレクトされない? > -u 付きで実行する – Qiita
- 分野特有の教師なし固有表現認識