Ahogrammer

Deep Dive Into NLP, ML and Cloud

実践!固有表現認識 ~Flairを使って最先端の固有表現認識を体験しよう~

自然言語処理の分野で昔から研究され、実際に使われている技術として固有表現認識があります。固有表現認識は、テキスト中で固有表現が出現する位置を特定し、人名や地名などのラベルを付与するタスクです。情報抽出や質問応答、対話システムなどへの幅広い応用が可能なため、今でも盛んに研究され、使われている技術です。本記事では、日本語の固有表現認識をFlairと呼ばれるPythonパッケージを使って実現する方法について紹介します。

準備

本記事では Flair を使って固有表現認識のモデルを学習させます。Flairは最先端の自然言語処理のモデルを簡単に使い始められる形で提供してくれているパッケージです。その中で提供されている機能として、固有表現認識や品詞タグ付け、文書分類のモデルを学習するための機能があります。使い始めるために、以下のようにしてFlairをインストールしておく必要があります。

$ pip install flair

モデルを学習するためのデータも用意しておきましょう。本記事を読んでいる方はおそらく日本人なので、日本語の固有表現認識を行うことにします。以下のリポジトリからコーパスをダウンロードしてください。

github.com

wgetでダウンロードすると簡単なのでおすすめです。

$ wget https://raw.githubusercontent.com/Hironsan/IOB2Corpus/master/ja.wikipedia.conll

実装

今回は、2018年にAkbikらがContextual String Embeddings for Sequence Labelingという論文の中で提案したモデルを実装します。以下にモデルのアーキテクチャを示しました。このモデルでは、文字ベースの言語モデルを使って単語分散表現を構築し、それを使ってラベルを予測します。固有表現認識に携わる者であれば、まず知っているモデルであり、とても良い性能を出すことで知られています。

f:id:Hironsan:20190913151023p:plain
モデルのアーキテクチャ

ちなみに、以下の記事で今回実装するモデルのパワーアップ版を紹介しているので、本記事と合わせて読むと知識の幅が広がります。

hironsan.hatenablog.com

では実装していきましょう。まずは必要なクラスをインポートします。

from flair.datasets import ColumnCorpus
from flair.embeddings import StackedEmbeddings, FlairEmbeddings
from flair.data import Sentence
from flair.models import SequenceTagger
from flair.trainers import ModelTrainer

次に、ダウンロードしたコーパスを読み込みます。data_folderにはダウンロードしたコーパスが存在するディレクトリを指定してください。

columns = {0: 'text', 1: 'ner'}
data_folder = '.'
corpus = ColumnCorpus(data_folder, columns,
                      train_file='ja.wikipedia.conll')

コーパスの読み込みが終わったら、タグを用意します。今回は固有表現認識を行うので、tag_type='ner'を指定します。

tag_type = 'ner'
tag_dictionary = corpus.make_tag_dictionary(tag_type=tag_type)

タグの一覧を表示してみましょう。コーパス中に存在するタグに加えて、<unk><START><STOP>という3つのタグが用意されていることを確認できます。

>>> print(tag_dictionary.idx2item)
[b'<unk>', b'O', b'B-PERSON', b'I-PERSON', ..., b'<START>', b'<STOP>']

次に使用する分散表現を用意します。今回は論文中で使われている文字ベースの言語モデルから得られる分散表現を使いたいのでFlairEmbeddingsを指定します。事前学習済みモデルとしてja-forwardja-backwardを指定することで、日本語の事前学習済み言語モデルを使うことができます。

embedding_types = [
    FlairEmbeddings('ja-forward'),
    FlairEmbeddings('ja-backward'),
]
embeddings = StackedEmbeddings(embeddings=embedding_types)

分散表現を用意したら、学習させるモデルを定義します。ラベル間の依存関係を考慮するため、use_crf=TrueでCRFを使うようにします。

tagger = SequenceTagger(hidden_size=256,
                        embeddings=embeddings,
                        tag_dictionary=tag_dictionary,
                        tag_type=tag_type,
                        use_crf=True)

モデルを定義できたので、学習させていきます。今回は小さなコーパスを使っているので、CPUで計算しても数時間で学習は完了します。そんなに待てないという方はGPUやTPUを使いましょう。

trainer = ModelTrainer(tagger, corpus)
trainer.train('resources/taggers/example-ner',
              learning_rate=0.1,
              mini_batch_size=32,
              max_epochs=150)

学習が終わると以下のようにスコアを表示してくれます。今回はF1のマイクロ平均で0.7886という結果でした。以前に同じデータセットに対して文字情報なしのBiLSTM-CRFで実験した際には0.55程度だったので、それに比べると遥かに良い性能となりました。

0.8156    0.7633 0.7886
MICRO_AVG: acc 0.6509 - f1-score 0.7886
MACRO_AVG: acc 0.6436 - f1-score 0.7638666666666667
ARTIFACT   tp: 40 - fp: 19 - fn: 47 - tn: 40 - precision: 0.6780 - recall: 0.4598 - accuracy: 0.3774 - f1-score: 0.5480
DATE       tp: 123 - fp: 11 - fn: 15 - tn: 123 - precision: 0.9179 - recall: 0.8913 - accuracy: 0.8255 - f1-score: 0.9044
EVENT      tp: 21 - fp: 7 - fn: 11 - tn: 21 - precision: 0.7500 - recall: 0.6562 - accuracy: 0.5385 - f1-score: 0.7000
LOCATION   tp: 242 - fp: 45 - fn: 26 - tn: 242 - precision: 0.8432 - recall: 0.9030 - accuracy: 0.7732 - f1-score: 0.8721
NUMBER     tp: 76 - fp: 13 - fn: 13 - tn: 76 - precision: 0.8539 - recall: 0.8539 - accuracy: 0.7451 - f1-score: 0.8539
ORGANIZATION tp: 86 - fp: 41 - fn: 60 - tn: 86 - precision: 0.6772 - recall: 0.5890 - accuracy: 0.4599 - f1-score: 0.6300
OTHER      tp: 19 - fp: 7 - fn: 25 - tn: 19 - precision: 0.7308 - recall: 0.4318 - accuracy: 0.3725 - f1-score: 0.5429
PERCENT    tp: 13 - fp: 0 - fn: 0 - tn: 13 - precision: 1.0000 - recall: 1.0000 - accuracy: 1.0000 - f1-score: 1.0000
PERSON     tp: 70 - fp: 13 - fn: 17 - tn: 70 - precision: 0.8434 - recall: 0.8046 - accuracy: 0.7000 - f1-score: 0.8235

学習が完了したので、最も良いモデルを読み込みます。

model = SequenceTagger.load('resources/taggers/example-ner/final-model.pt')

読み込んだモデルを使って予測を行います。

sentence = Sentence('私 は 田中 と 東京 駅 へ 行っ た')
model.predict(sentence)

結果を表示すると以下のようになりました。正しく認識できていますね。

>>> print(sentence.to_tagged_string())
私 は 田中 <B-PERSON> と 東京 <B-LOCATION> 駅 <I-LOCATION> へ 行っ た

See also

Colaboratoryにノートブックを用意したので、試してみてください。

colab.research.google.com

参考文献