Flairで文章中のバイオメディカル関連語を抽出する

## 1. はじめに
機械学習初心者の化学屋が自分用のメモとして書いた記事です。
自然言語処理のためのライブラリFlairで提供されている学習済みモデル<b>”HunFlair”</b>を利用して、文章中の<b>化合物</b>や<b>病気</b>、<b>微生物</b>、<b>遺伝子</b>、<b>タンパク質</b>、<b>生物種</b>といったバイオメディカル関連の単語を抽出します。

>
実行環境
・python 3.7.1
・flair 0.8.1
・pytorch 1.5.0（flairの動作に torch<1.8.0, >=1.5.0 が必要）
・torchvision 0.6.0

## 2. FlairによるNER（固有表現抽出）
Flairとは自然言語処理用のライブラリで、最新の言語モデルなどを簡単に試すことができます。
Flairは[公式チュートリアル](https://github.com/flairNLP/flair/blob/master/resources/docs/TUTORIAL_1_BASICS.md)が充実しています。文章中の単語へのタグ付け(Tagging)、単語へのベクトル埋め込み(Word Embedding)、オリジナルモデルのトレーニングまでカバーされており、読めば一通りのことが試せるようになっています。

Flairでは様々なNERのためのtaggerが提供されています。
以下のチュートリアルを見ればそれぞれのtaggerでどのようなタグ付けができるのかわかります。
各モデルのIDの部分をクリックすると各taggerで付与できるタグの一覧とサンプルコードが載っているページに飛びます。
・[Tutorial 2: Tagging your Text --- List of Pre-Trained Sequence Tagger Models](https://github.com/flairNLP/flair/blob/master/resources/docs/TUTORIAL_2_TAGGING.md#list-of-pre-trained-sequence-tagger-models, "List of Pre-Trained Sequence Tagger Models")

["ner"のページ](https://huggingface.co/flair/ner-english)を見てみると、以下のタグ付けができることが分かります。

|tag|meaning|
|:--|:--|
|PER|person name|
|LOC|location name|
|ORG|organization name|
|MISC|other name|

サンプルコードを参考に動かしてみると以下のような感じです。

```py
from flair.data import Sentence
from flair.models import SequenceTagger

# load "ner" tagger
tagger = SequenceTagger.load("ner")

# make example sentence
sentence = Sentence("George Washington went to Washington .")

# predict NER tags
tagger.predict(sentence)

# print sentence
print(sentence)

# print predicted NER spans
print('The following NER tags are found:')
# iterate over entities and print
for entity in sentence.get_spans('ner'):
    print(entity)
```
出力結果

```
George <B-PER> Washington <E-PER> went to Washington <S-LOC> . 
The following NER tags are found:
Span [1,2]: "George Washington"   [− Labels: PER (0.9968)]
Span [5]: "Washington"   [− Labels: LOC (0.9994)]
```
この例では人名のワシントンと地名のワシントンを区別できており、"George Washington"を１つの意味のまとまり(chunk)として抽出できていることが分かります。
また、各タグの接頭語（B-, E-, S-）は[BIOESフォーマット](https://en.wikipedia.org/wiki/Inside%E2%80%93outside%E2%80%93beginning_(tagging))に従って付与されているようです。
B, I, O, E, SはそれぞれchunkのBeggining, Inside, Outside, Ending, Singleを意味しています。

上の例では"George Washington"の２語で人名\<PER>に対応するchunkを構成しており、Georgeは人名のBegginingなので\<B-PER>、Washingtonは人名のEndingなので\<E-PER>となっています。
地名\<LOC>の"Wahington"は単独Singleでchunkを構成しているので、\<S-LOC>となっています。
chunkが3単語以上からなる場合は、内部の単語のタグに"I-"が付与されます。

Span[*] は文章中での位置を表しています。

このような感じで簡潔なコードで固有表現抽出が可能となっています。

## 3. HunFlairによるバイオメディカル関連語の抽出
Flairではバイオメディカル分野におけるNER用にHunFlairというtaggerが提供されており、今回はその中の`hunflair-chemical`を使用して化合物の抽出を行ってみます。
hunflairは以下の5種類のtaggerから構成されており、[HunFlair-DataSets](https://github.com/flairNLP/flair/blob/master/resources/docs/HUNFLAIR_CORPORA.md#huner-data-sets) に各taggerが学習に用いたデータセットなどが示されています。
・hunflair-celline
・hunflair-chemical
・hunflair-disease
・hunflair-gene
・hunflair-species

### 3.1 hunflair-chemical を使った化合物の抽出
`SequenceTagger.load()`で`hunflair-chemical`を指定することで化合物の抽出ができます。

```py
from flair.data import Sentence
from flair.models import SequenceTagger

# load "hunflair-chemical" tagger
tagger = SequenceTagger.load("hunflair-chemical")

# make example sentence
sentence = Sentence("[Co(NH3)6]Cl3 is prepared by treating CoCl2 with ammonia and ammonium chloride followed by oxidation.")

# predict hunflair-chemical tags
tagger.predict(sentence)

# print sentence
print(sentence.to_tagged_string())

# print predicted NER spans
print('The following hunflair-chemical tags are found:')
# iterate over entities and print
for entity in sentence.get_spans():
    print(entity)
```
出力結果

```
[ <B-Chemical> Co <I-Chemical> ( <I-Chemical> NH3 <I-Chemical> ) <I-Chemical> 6 <I-Chemical> ] <I-Chemical> Cl3 <E-Chemical> is prepared by treating CoCl2 <S-Chemical> with ammonia <S-Chemical> and ammonium <B-Chemical> chloride <E-Chemical> followed by oxidation .
The following hunflair-chemical tags are found:
Span [1,2,3,4,5,6,7,8]: "[ Co ( NH3 ) 6 ] Cl3"   [− Labels: Chemical (0.8793)]
Span [13]: "CoCl2"   [− Labels: Chemical (0.9527)]
Span [15]: "ammonia"   [− Labels: Chemical (0.9331)]
Span [17,18]: "ammonium chloride"   [− Labels: Chemical (0.9692)]
```

このようにテキスト中の化合物を抽出できます。
`[Co(NH3)6]Cl3` のような単語中に `( ) [ ] ・` を含むものについては、

|[Co(NH3)6]Cl3|→|[|Co|(|NH3|)|6|]|Cl3|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|

のように分けられていますが、1つのchunkとして認識されています。
（Flairの内部処理で、`( ) [ ] ・`などによって単語が区切られるようです。）

### 3.2 MultiTaggerを使ったバイオメディカル関連語の抽出
先程は、`SequenceTagger.load("hunflair-chemical")`として化合物の検出のみを行いました。
病名、化合物、遺伝子など複数のタイプのタグ付けを行う際には、公式チュートリアルのように`MultiTagger.load("hunflair")`とすれば5種類全てのtaggerが読み込まれて、5つのタイプのタグを付与することができます。

例えば以下のコードでは、病名、タンパク質、化合物、生物種の抽出ができます。

```py
from flair.data import Sentence
from flair.models import MultiTagger # SequenceTaggerではなくMultiTaggerをimportする

# load tagger
tagger = MultiTagger.load("hunflair")

# make example sentence
sentence = Sentence("Amyloid beta (Aβ or Abeta) denotes peptides of 36–43 amino acids that are the main component of the amyloid plaques found in the brains of people with Alzheimer's disease.")

# predict hunflair-chemical tags
tagger.predict(sentence)

# print sentence
print(sentence.to_tagged_string())

# print predicted NER spans
print('The following hunflair tags are found:')
# iterate over entities and print
for entity in sentence.get_spans():
    print(entity)
```
出力結果

```
Amyloid <B-Gene> beta <E-Gene> ( Aβ <S-Gene> or Abeta <S-Gene> ) denotes peptides of 36 – 43 amino <B-Chemical> acids <E-Chemical> that are the main component of the amyloid plaques found in the brains of people <S-Species> with Alzheimer <B-Disease> 's <I-Disease> disease <E-Disease> .
The following hunflair tags are found:
Span [14,15]: "amino acids"   [− Labels: Chemical (0.8314)]
Span [32,33,34]: "Alzheimer 's disease"   [− Labels: Disease (0.9604)]
Span [1,2]: "Amyloid beta"   [− Labels: Gene (0.5752)]
Span [4]: "Aβ"   [− Labels: Gene (0.8071)]
Span [6]: "Abeta"   [− Labels: Gene (0.9552)]
Span [30]: "people"   [− Labels: Species (0.7343)]
```

5種類のtaggerを読み込むので少し時間がかかります。
"hunflair-chemical"タイプの"Chemical"タグ
"hunflair-disease"タイプの"Disease"タグ
"hunflair-gene"タイプの"Gene"タグ（GeneとProteinの両方にGeneタグが付与される）
"hunflair-species"タイプの"Species"タグ
が付与されています。
"hunflair-cellline"タイプの"CellLine"タグに該当するものは無いようです。

ここから、"hunflair-gene"タイプのタグだけを取り出したいときは、以下のように記述します。

```py
for disease in sentence.get_spans("hunflair-gene"):
    print(disease)
```
出力結果

```
Span [1,2]: "Amyloid beta"   [− Labels: Gene (0.5752)]
Span [4]: "Aβ"   [− Labels: Gene (0.8071)]
Span [6]: "Abeta"   [− Labels: Gene (0.9552)]
```

<br>
任意のタイプのタグから、"Disease"タグだけを取り出したいときは、以下のように記述します。

```py
for entity in sentence.get_spans():
    if entity.tag=="Disease" :
        print(entity)
```
出力結果

```
Span [27,28,29]: "Alzheimer 's disease"   [− Labels: Disease (0.9609)]
```

## 参考サイト
・ [FLAIR TUTORIAL](https://github.com/flairNLP/flair/tree/master/resources/docs)
・ [NLP state of the artをお手軽に使えるflair（fukuの犬小屋）](https://roy29fuku.com/natural-language-processing/nlp-state-of-the-art-flair/)