Python for NLPの連載の4回目です。前回は、spaCyライブラリを使って、語彙やフレーズマッチングなどのタスクを実行する方法を説明しました。
今回は、品詞タグ付けと名前付き固有表現認識について詳しく勉強していきます。この2つのタスクを実行するためにspaCyライブラリをどのように使うことができるかを見ていきます。
品詞タグ付け
品詞タグ付けとは、文中の個々の単語に品詞を付けることです。つまり、文や複数単語のレベルで行われるフレーズマッチングとは異なり、品詞タグ付けはトークンレベルで行われます。
品詞タグ付けの非常に簡単な例を挙げてみよう。
import spacy
sp = spacy.load('en_core_web_sm')
例によって、上のスクリプトでは、コアとなるspaCy英語モデルをインポートしています。次に、品詞タグ付けを行うために使用するspaCyドキュメントを作成する必要があります。
sen = sp(u"I like to play football. I hated it in my childhood though")
spaCyドキュメントオブジェクトはいくつかの属性を持っていて、それを使って様々なタスクを実行することができます。例えば、ドキュメントのテキストを印刷するには、text
属性を使用します。同様に、pos_
属性は粗視化された品詞タグを返します。細かい品詞タグを得るには、 tag_
属性を利用することができる。最後に、タグの説明を得るには、 spacy.explain()
メソッドを使用して、タグの名前を渡します。
では、実際に見てみましょう。
print(sen.text)
上のスクリプトは、単に文のテキストを表示します。出力はこのようになります。
I like to play football. I hated it in my childhood though
次に、pos_
属性を見てみましょう。この文の7番目のトークンである “hated “という単語の品詞タグを表示します。
print(sen[7].pos_)
出力してみましょう。
VERB
hated “は動詞なので、”hated “のPOSタグは “VERB “であることがわかります。
次に、”hated “という単語に対する詳細な品詞タグを出力してみましょう。
print(sen[7].tag_)
出力してみましょう。
VBD
VBDの意味を知るために、以下のように spacy.explain()
メソッドを使うことができます。
print(spacy.explain(sen[7].tag_))
出力
verb, past tense
出力は、VBDが過去形の動詞であることを示しています。
文中の全ての単語について、本文、粗視化品詞タグ、細視化品詞タグ、そしてタグの説明を出力してみましょう。
for word in sen:
print(f'{word.text:{12}} {word.pos_:{10}} {word.tag_:{8}} {spacy.explain(word.tag_)}')
上のスクリプトでは、テキストと粗粒度品詞タグの間に12個のスペースを入れ、さらに粗粒度品詞タグと細粒度品詞タグの間に10個のスペースを入れて、読みやすさと書式を改善しました。
出力します。
I PRON PRP pronoun, personal
like VERB VBP verb, non-3rd person singular present
to PART TO infinitival to
play VERB VB verb, base form
football NOUN NN noun, singular or mass
. PUNCT . punctuation mark, sentence closer
I PRON PRP pronoun, personal
hated VERB VBD verb, past tense
it PRON PRP pronoun, personal
in ADP IN conjunction, subordinating or preposition
my ADJ PRP$ pronoun, possessive
childhood NOUN NN noun, singular or mass
though ADP IN conjunction, subordinating or preposition
品詞タグと細目タグの完全なリストとその説明は、spaCy公式ドキュメントに掲載されています。
品詞タグ付けはなぜ有効か?
品詞タグ付けは、特に複数の品詞タグを持つ単語やトークンがある場合に非常に便利です。たとえば、「google」という単語は、文脈に応じて、名詞としても動詞としても使用できます。自然言語を処理する際には、この違いを識別することが重要です。幸い、spaCyライブラリには機械学習アルゴリズムがあらかじめ組み込まれており、文脈(周囲の単語)に応じて、その単語に対する正しい品詞タグを返すことが可能です。
実際に使ってみましょう。以下のスクリプトを実行してみてください。
sen = sp(u'Can you google it?')
word = sen[2]
print(f'{word.text:{12}} {word.pos_:{10}} {word.tag_:{8}} {spacy.explain(word.tag_)}')
上のスクリプトでは、”Can you google it?” というテキストでspaCy文書を作成しています。ここでは、”google “という単語が動詞として使われています。次に、”google “という単語のPOSタグと、そのタグの説明を表示します。出力はこのようになります。
google VERB VB verb, base form
この出力から、”google “という単語が動詞として正しく認識されていることがわかります。
別の例を見てみましょう。
sen = sp(u'Can you search it on google?')
word = sen[5]
print(f'{word.text:{12}} {word.pos_:{10}} {word.tag_:{8}} {spacy.explain(word.tag_)}')
上のスクリプトでは、”google “という単語が名詞として使われていることが出力からわかります。
google PROPN NNP noun, proper singular
POSタグの枚数を調べる
各POSタグの出現回数は、spaCyドキュメントオブジェクトの count_by
メソッドを呼び出すことで知ることができます。このメソッドは spacy.attrs.POS
をパラメータ値として受け取ります。
sen = sp(u"I like to play football. I hated it in my childhood though")
num_pos = sen.count_by(spacy.attrs.POS)
num_pos
出力されます。
出力:“`
出力では、POSタグのIDが出現頻度とともに表示されます。実際のspaCy文書のボキャブラリーにタグのIDを渡すことで、POSタグのテキストを表示することができます。
for k,v in sorted(num_pos.items()):
print(f'{k}. {sen.vocab[k].text:{8}}: {v}’)
さて、出力では以下のように、各タグのID、テキスト、出現頻度が表示されます。
- ADJ : 1
- ADP : 2
- NOUN : 2
- PART : 1
- PRON : 3
- PUNCT : 1
- VERB : 3
#### 品詞タグの可視化
品詞タグをグラフィカルに視覚化することは、非常に簡単です。この目的のためには、`spacy`ライブラリの `displacy` モジュールを使用します。Jupyterノートブック内で品詞タグを視覚化するには、以下のように `displacy` モジュールから `render` メソッドを呼び出し、spacyドキュメントと視覚化のスタイルを渡し、`jupyter` 属性を `True` に設定する必要がある。
from spacy import displacy
sen = sp(u”I like to play football. I hated it in my childhood though”)
displacy.render(sen, style=’dep’, jupyter=True, options={‘distance’: 85})
出力では、POSタグの依存関係ツリーが以下のように表示されるはずです。
<div class="lazyload-wrapper"<div class="lazyload-placeholder" style="height:24rem"</div</div
各トークンが品詞タグに依存していることがよくわかります。
もしJupyterノートブックの外でPOSタグを可視化したい場合は、`serve`メソッドを呼び出す必要があります。POSタグのプロットはデフォルトのブラウザでHTML形式で表示される。以下のスクリプトを実行してください。
displacy.serve(sen, style=’dep’, options={‘distance’: 120})
上記のスクリプトを実行すると、以下のようなメッセージが表示されます。
Serving on port 5000…
Using the ‘dep’ visualizer
依存関係ツリーを表示するには、ブラウザで次のアドレスを入力します: http://127.0.0.1:5000/. すると、次のような依存関係ツリーが表示されます。
<div class="lazyload-wrapper"<div class="lazyload-placeholder" style="height:24rem"</div</div
### 名前付き固有表現(Nemed Entity Recognition)。
名前付き実体認識とは、文中の単語を人名、地名、組織名などの実体として識別することです。ここでは、spaCyライブラリがどのように名前付き実体の認識を行うかを見てみましょう。次のスクリプトを見てください。
import spacy
sp = spacy.load(‘en_core_web_sm’)
sen = sp(u’Manchester United is looking to sign Harry Kane for $90 million’)
上のスクリプトでは、いくつかのテキストで簡単なspaCyドキュメントを作成しました。名前付き実体を見つけるには、`ents`属性を使います。これは、ドキュメントにあるすべての名前付き実体のリストを返します。
print(sen.ents)
出力されます。
(Manchester United, Harry Kane, $90 million)
3つの名前付き実体が特定されたことがわかります。それぞれのエンティティの詳細を見るには、 `text`、`label`、そしてエンティティオブジェクトをパラメータとして受け取る `spacy.explain` メソッドを使用します。
for entity in sen.ents:
print(entity.text + ‘ – ‘ + entity.label_ + ‘ – ‘ + str(spacy.explain(entity.label_)))
出力では、以下のようにエンティティの名前、エンティティタイプ、およびエンティティの小さな説明が表示されます。
Manchester United – ORG – Companies, agencies, institutions, etc.
Harry Kane – PERSON – People, including fictional
$90 million – MONEY – Monetary values, including unit
Manchester United "が組織、会社などとして正しく識別されていることがわかります。同様に、「Harry Kane」は個人として識別され、最後に「$90 million」は Money タイプのエンティティとして正しく識別されています。
#### 新しいエンティティの追加
既存の文書に新しいエンティティを追加することもできます。例えば、次の例では、"Nesfruita "はspaCyライブラリによって会社として識別されていません。
sen = sp(u’Nesfruita is setting up a new company in India’)
for entity in sen.ents:
print(entity.text + ‘ – ‘ + entity.label_ + ‘ – ‘ + str(spacy.explain(entity.label_)))
出力されます。
India – GPE – Countries, cities, states
出力から、Indiaだけが実体として識別されていることがわかります。
さて、「Nesfruita」をタイプ「ORG」のエンティティとして文書に追加するには、以下のステップを実行する必要があります。
from spacy.tokens import Span
ORG = sen.vocab.strings[u’ORG’]
new_entity = Span(sen, 0, 1, label=ORG)
sen.ents = list(sen.ents) + [new_entity]
まず、`spacy.tokens`モジュールから `Span` クラスをインポートする必要があります。次に、ドキュメントから `ORG` というエンティティタイプのハッシュ値を取得する。その後、`ORG` のハッシュ値をスパンに代入する必要があります。Nesfruita "はドキュメントの最初の単語なので、スパンは0-1となります。最後に、新しいエンティティスパンをエンティティのリストに追加する必要があります。ここで、以下のスクリプトを実行すると、エンティティのリストに「Nesfruita」が表示されます。
for entity in sen.ents:
print(entity.text + ‘ – ‘ + entity.label_ + ‘ – ‘ + str(spacy.explain(entity.label_)))
上記のスクリプトの出力は、次のようになります。
Nesfruita – ORG – Companies, agencies, institutions, etc.
India – GPE – Countries, cities, states
#### エンティティのカウント
品詞タグの場合、特別なメソッド `sen.count_by` を使って、文書中の各品詞タグの出現頻度を数えることができる。しかし、名前付きエンティティの場合、そのようなメソッドは存在しない。各エンティティタイプの頻度を手動で数えることができます。例えば、以下のような文書とその実体があるとする。
sen = sp(u’Manchester United is looking to sign Harry Kane for $90 million. David demand 100 Million Dollars’)
for entity in sen.ents:
print(entity.text + ‘ – ‘ + entity.label_ + ‘ – ‘ + str(spacy.explain(entity.label_)))
出力。
Manchester United – ORG – Companies, agencies, institutions, etc.
Harry Kane – PERSON – People, including fictional
$90 million – MONEY – Monetary values, including unit
David – PERSON – People, including fictional
100 Million Dollars – MONEY – Monetary values, including unit
上記の文書に含まれる人物タイプのエンティティを数えるには、次のスクリプトを使用できます。
len([ent for ent in sen.ents if ent.label_==’PERSON’])
出力では、ドキュメントにPERSONタイプのエンティティが2つあるため、2と表示されます。
#### 名前付きエンティティの可視化
POSタグと同様に、名前付きエンティティもJupyterノートブックの内部やブラウザで表示することができます。
そのために、今回も `displacy` オブジェクトを使用します。次の例を見てください。
from spacy import displacy
sen = sp(u’Manchester United is looking to sign Harry Kane for $90 million. David demand 100 Million Dollars’)
displacy.render(sen, style=’ent’, jupyter=True)
名前付きエンティティと品詞タグの可視化の違いは、名前付きエンティティの場合は `style` パラメータの値として `ent` を渡していることだけであることがわかると思います。上記のスクリプトの出力は以下のようになります。
<div class="lazyload-wrapper"<div class="lazyload-placeholder" style="height:24rem"</div</div
出力から、名前付きエンティティがそのエンティティタイプとともに異なる色でハイライトされていることがわかります。
また、表示するエンティティタイプをフィルタリングすることもできます。そのためには、表示するエンティティの種類をリストにして、辞書の `ents` キーに値として渡す必要があります。この辞書は、以下のように `displacy` モジュールの `render` メソッドの `options` パラメータに渡されます。
filter = {‘ents’: [‘ORG’]}
displacy.render(sen, style=’ent’, jupyter=True, options=filter)
上のスクリプトでは、ORGタイプのエンティティのみを出力に表示するように指定しています。上のスクリプトの出力は以下のようになります。
<div class="lazyload-wrapper"<div class="lazyload-placeholder" style="height:24rem"</div</div
最後に、Jupyterノートブックの外側で名前付きエンティティを表示することもできます。以下のスクリプトは、名前付き実体をデフォルトのブラウザに表示します。以下のスクリプトを実行してください。
displacy.serve(sen, style=’ent’)
“`
これで、ブラウザでアドレス http://127.0.0.1:5000/ にアクセスすると、名前付きエンティティが表示されるはずです。
さらに上を目指す – 手作りのEnd to Endプロジェクト
好奇心旺盛なあなたは、もっと先を目指したいと思っていませんか?ガイド付きプロジェクトのチェックをお勧めします。「CNNとKerasのTransformerを使った画像キャプション作成」をご覧ください。
をご覧ください。
このガイド付きプロジェクトでは、画像を入力として受け取り、出力としてテキストキャプションを生成する画像キャプションモデルの構築方法を学びます。
このプロジェクトでは、次のことを学びます。
- テキストを前処理する
- 入力されたテキストを簡単にベクトル化する
-
tf.data
APIを使用し、パフォーマンスの高いデータセットを構築する。 - TensorFlow/Keras と KerasNLP – 最先端の NLP モデルを構築するための Keras への公式な水平方向の追加機能 – を使用してゼロから Transformers を構築する。
- あるネットワークの出力が別のネットワークにエンコードされるハイブリッドアーキテクチャの構築
画像キャプションをどのようにとらえるか?私たちはネットワークに説明を生成するように教えているので、ほとんどの人が生成的な深層学習の例だと考えています。しかし、私はこれをニューラル機械翻訳の一例と見なしたいと思います。つまり、画像の視覚的特徴を言葉に翻訳しているのです。翻訳することで、単に新しい意味を生成するだけでなく、その画像の新しい表現を生成しているのです。翻訳、ひいては生成と捉えることで、このタスクは別の観点から見ることができ、もう少し直感的に理解できるようになります。
問題を翻訳の問題としてとらえることで、どのアーキテクチャを使いたいかを考えることが容易になる。エンコーダのみのトランスフォーマーは、エンコーダが意味のある表現をエンコードするため、テキストの理解(感情分析、分類など)に適している。デコーダのみのモデルは、デコーダが意味のある表現を同じ意味を持つ別のシーケンスに推論することができるため、生成(GPT-3など)に最適である。翻訳とは通常、エンコーダーとデコーダーのアーキテクチャによって行われ、エンコーダーは文(我々の場合は画像)の意味のある表現をエンコードし、デコーダーはこのシーケンスを我々にとってより解釈しやすい別の意味のある表現(文など)に変換することを学習するものである。
結論
品詞タグ付けと固有表現認識は、自然言語処理タスクの成功に欠かせないものです。今回は、PythonのspaCyライブラリを使って品詞タグ付けと名前付き実体の認識を行う方法を、さまざまな例を使って紹介しました。