LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

【読書会】入門自然言語処理_第5章

Last updated at Posted at 2016-12-08

単語の分類とタグ付け

タグ付けという処理は、NLPの一般的な流れにおいてトークン化に続く第二のステップである。

「品詞」は、様々な言語処理のタスクをこなす上で便利な分類法として利用することができる
品詞はテキスト中の単語の分布の簡単な分析から調べることができる

そのため単語の品詞を分類してタグ付していく

今回の目的は
1.タガーを利用してみる
2.単語と単語の分類を格納するのにてきしたpythonのデータ構造を知る
3.テキスト中の単語に自動的に品詞をタグ付けする

本章では、系列ラベリング、Nフラムモデル、バックオフなどを含むNLPの基本的な技術を学んでいく

1.タガーを利用してみる

品詞タガーは単語列のそれぞれの単語に品詞タグをつける処理を行う

#最初にインポートしておく
from __future__ import division
import nltk, re, pprint
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
nltk.download('maxent_treebank_pos_tagger')
>>> text = nltk.word_tokenize("And now for something >>> completely different")
nltk.pos_tag(text)
[('And', 'CC'), ('now', 'RB'), ('for', 'IN'), ('something', 'NN'),
('completely', 'RB'), ('different', 'JJ')]

タグ付けされた!

CC:等位接続詞
RB:副詞
IN:前置詞
NN:名詞

>>> text = word_tokenize("They refuse to permit us to obtain the refuse permit")
>>> nltk.pos_tag(text)
[('They', 'PRP'), ('refuse', 'VBP'), ('to', 'TO'), ('permit', 'VB'), ('us', 'PRP'),
('to', 'TO'), ('obtain', 'VB'), ('the', 'DT'), ('refuse', 'NN'), ('permit', 'NN')]

同音異義語もちゃんとわかれている!

だがどういった理由があってこうなっているのかわからないと思いますが
これらの分類の多くはテキスト中の単語の分布を解析して得られています。

2.単語と単語の分類を格納するのに適したpythonのデータ構造を知る

先ほど見たように、タグ付きの単語とは、単語と品詞の組み合わせによってできている
品詞タグ付を行っていく中で、単語に特定の文脈中でのタグ付けを行う必要がある
この処理は単語をタグにマップする作業だとみなすことができる
pythonにおいてデータを保持するもっとも自然な方法はディクショナリ型である

ディクショナリ型とは、図5_3の様に、名前に対して数字がふられた形になっている

3.テキスト中の単語に自動的に品詞をタグ付けする

テキストに自動的に品詞タグをタグ付けする様々な方法を試みていく

まずデータをダウンロードする
python
from nltk.corpus import brown
nltk.download('punkt')
nltk.download('brown')
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')

デフォルトタガー

まず全てのトークンに同じタグをつけてみる
これは非常に乱暴な処理に見えるが、タガーの重要な基本性能を規定できる

tags = [tag for (word, tag) in brown.tagged_words(categories='news')]
nltk.FreqDist(tags).max()
raw = 'I do not like green eggs and ham, I do not like them Sam I am!'
tokens = nltk.word_tokenize(raw)
default_tagger = nltk.DefaultTagger('NN')
default_tagger.tag(tokens)

これで全てにNNのタグをつけることができた
つぎに精度をみてみる

default_tagger.evaluate(brown_tagged_sents)

数字をみると精度0.13と低い

正規表現タガー

次はパターンマッチングに基づいて、トークンにタグを割り当てる
「ed」で終わる単語は動詞の過去形、「's」で終わる単語は名詞の所有格であることを推測できる

パターンマッチは正規表現によって表される

patterns = [
(r'.*ing$', 'VBG'),               # gerunds
(r'.*ed$', 'VBD'),                # simple past
(r'.*es$', 'VBZ'),                # 3rd singular present
(r'.*ould$', 'MD'),               # modals
(r'.*\'s$', 'NN$'),               # possessive nouns
(r'.*s$', 'NNS'),                 # plural nouns
(r'^-?[0-9]+(.[0-9]+)?$', 'CD'),  # cardinal numbers
(r'.*', 'NN')                     # nouns (default)
]

このようなパターンマッチで行い
改めて精度を測定してみる

regexp_tagger = nltk.RegexpTagger(patterns)
regexp_tagger.tag(brown_sents[3])
regexp_tagger.evaluate(brown_tagged_sents)

すると精度0.2と少し上がっている事がわかる

ルックアップタガー

高い頻度でしゅつげんする単語にも、NNタグを持たないものもおおくある
最頻出単語100個とそれらにもっとも多くつけられているタグを調べてみる

fd = nltk.FreqDist(brown.words(categories='news'))
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))
most_freq_words = fd.most_common(100)
likely_tags = dict((word, cfd[word].max()) for (word, _) in most_freq_words)
baseline_tagger = nltk.UnigramTagger(model=likely_tags)
baseline_tagger.evaluate(brown_tagged_sents)

その情報をもとにタグ付けされていないテキストを処理してみる

sent = brown.sents(categories='news')[3]
baseline_tagger.tag(sent)

多くの単語がnoneをつけられていることがわかる
それは先ほどの最頻出単語100に含まれていないため
そこで今回はそれらの全てにNNをつける事にする

baseline_tagger = nltk.UnigramTagger(model=likely_tags,backoff=nltk.DefaultTagger('NN'))

評価

作成したツールの性能を評価する事はNLPの重要なテーマの1つである
タガーの性能は、人間の専門家がつけたタグとの比較によって評価される
しかし、通常は専門家に都度聞く事はできないのでゴールドスタンダードと呼ばれるテストデータを使って評価する

タガーが与えられたタグに対して推定したタグがゴールドスタンダードのタグと同じものであった場合、そのタガーは正しいと見なされる

Nグラムタグ付け

文脈中から1つの要素を取りだして使うモデルにおいては、それぞれの単語にもっともあり得る可能性が高いタグをつけることしかできない。
Nグラムタガーは、図5,5のように現在の単語とn-1個のその単語の前にあるトークンの品詞タグを文脈として利用する
n=3の場合には、現在の単語に加えてその前にある2つの単語、タグを考慮して推測を行う

バイグラムタガー

まずn=2のバイグラムタガーをみてみる

#訓練データ
bigram_tagger = nltk.BigramTagger(train_sents)
bigram_tagger.tag(brown_sents[2007])
#テストデータ
unseen_sent = brown_sents[4203]
bigram_tagger.tag(unseen_sent)

バイグラムタガーは初めて扱う文にたいしてはひどい結果になる
未知の単語にであうとタグ付けを行う事ができず、さらにそれに続く単語もタグ付けが出来なくなる
よって精度も低くなる

bigram_tagger.evaluate(test_sents)

nの値がおおくなるにつれ、文脈が特殊になり、訓練データに含まれていない事が多くなる
よって、得られる結果におけるカバー率と精度はトレードオフの関係となる

タガーを組み合わせる

精度とカバー率の間のトレードオフに対処する方法の1つは、より精度の高いアルゴリズムを使えるときは使い、使えないときは適用範囲の広いアルゴリズムをつかう事である

1.バイグラムタガーを使ってトークンのタグ付けを試みる
2.もしバイグラムタガーがタグを発見できなかった場合、ユニグラムタガーを使う
3.もしユニグラムタガーもタグを発見できなかった場合、デフォルトタガーを使う

t0 = nltk.DefaultTagger('NN')
t1 = nltk.UnigramTagger(train_sents, backoff=t0)
t2 = nltk.BigramTagger(train_sents, backoff=t1)
t2.evaluate(test_sents)

タガーの保存

タガーを訓練するのには時間が掛かる
そのため、一度訓練したタガーを保存し、あとで読み込めるようにする事ができる

まず、t2というタガーをt2.pklというファイルで保存する

from pickle import dump
output = open('t2.pkl', 'wb')
dump(t2, output, -1)
output.close()

続いて、異なるpythonのプロセスにおいて保存したタガーを読み込んでみる

from pickle import load
input = open('t2.pkl', 'rb')
tagger = load(input)
input.close()

保存したタガーでタグ付けできるか確かめてみる

text = """The board's action shows what free enterprise
...     is up against in our complex maze of regulatory laws ."""
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up