見出し画像

StemmingとLemmatization

テキストマイニングする前の処理として、これまで正規化、トークン化を行ってきました。この2つで文章をデータ化はできているのですが、このまま分析するにはまだ問題があります。例えば、

organize, organizes, organizing

のような単語はほぼ同じような意味合いで使われていますが、このままでは別々のものとして扱われてしまいます。これを解決する方法としてstemmingとlemmatizationという2つの方法があります。

ざっくり言うとstemmingは機械的に右側を切っていく、lemmatizationはルールに従って原型などに戻していく、という操作です。その特徴から、stemmingは速いが雑、lemmatizationは正確だが大変、というのが想像できます。

stemming

stemmingには3つの方法があります。
PorterStemmerとLancasterStemmerは80年代に開発されました。LancasterStemmerのほうが、大胆にカットしていきます。
SnowballStemmer は21世紀初頭に開発され、PorterのVer2と位置付けられています。PorterStemmerよりも語幹変化ルールが洗練されているとのことです。

まずはそれぞれのライブラリをimportします。

from nltk.stem import PorterStemmer
from nltk.stem import LancasterStemmer
from nltk.stem.snowball import SnowballStemmer

お題はこちら。
まずはトークン化が終わったという前提で、以下の単語をstemmingしてみます。

tokens = ["organize", "organization", "organizing"]

それぞれのstemming手法を初期化しておきます。

porter_stemmer = PorterStemmer()
lancaster_stemmer = LancasterStemmer()
snoball_stemmer = SnowballStemmer('english')

では3つを比べてみましょう。
PorterStemmerから

token_list = []
for token in tokens:
    token_list.append(porter_stemmer.stem(token))
token_list    

['organ', 'organ', 'organ']

LancasterStemmer

token_list = []
for token in tokens:
    token_list.append(lancaster_stemmer.stem(token))
token_list

['org', 'org', 'org']

SnowballStemmer

token_list = []
for token in tokens:
    token_list.append(snoball_stemmer.stem(token))
token_list  


['organ', 'organ', 'organ']

PorterとSnowballは同じ、Lancasterはやはり大胆カットですね。

PorterとSnowballで違いは出るのでしょうか。以下のトークンを使ってやってみたところ・・・

tokens = ["certainly", "quickly", "running"]

結果は、
Porter:['certainli', 'quickli', 'run']
Snowball:['certain', 'quick', 'run']

Snowballのほうが確かに改良版という感じがしますね。

Lemmatization

LemmatizationにはWordNetLemmatizerとwordnetを使います。wordnetはPrinceton大学で開発された辞書で、単語の意味情報や品詞情報などが格納されているみたいです。

import nltk
from nltk.stem import WordNetLemmatizer
from nltk.corpus import wordnet
nltk.download('wordnet')

まずは、lemmatizerを初期化します。

# Init the Wordnet Lemmatizer
lemmatizer = WordNetLemmatizer()

簡単な例から。

print(lemmatizer.lemmatize("countries"))

このようにすると、countries -->countryとなります。

こちらをlemmatizeしてみましょう。

tokens = ["democratic", "countries", "had", "parties"]

token_list = []
for token in tokens:
    token_list.append(lemmatizer.lemmatize(token))
token_list

結果はこのようになります。

['democratic', 'country', 'had', 'party']

複数形は単数形に変わりましたが、hadはそのままですね。

そこで「hadは動詞ですよ」ということをlemmatizerに教えてあげると・・・

print(lemmatizer.lemmatize("had","v"))

「have」と、原型が返ってきます。しかし、一つ一つ品詞を教えていくのは大変ですね。

そこで、単語の品詞を調べるaveraged_perceptron_taggerというタグモデルを使います。このタグモデルをダウンロードします。

nltk.download('averaged_perceptron_tagger')

そのうえで、以下のようにしてみると、

print(nltk.pos_tag(['had']))

このような結果が返ってきます。

[('had', 'VBD')]

これは、動詞の過去形ということを表しています。ここで重要なのは最初のVです。つまり「これは動詞です」、と言うことがわかります。

これを使って、品詞情報を取り出す関数を作ってみます。

def get_wordnet_pos(word):
    #品詞情報を取り出します
    tag = nltk.pos_tag([word])[0][1][0].upper()
    tag_dict = {"J": wordnet.ADJ,
                "N": wordnet.NOUN,
                "V": wordnet.VERB,
                "R": wordnet.ADV}

    return tag_dict.get(tag, wordnet.NOUN)

この関数の概略を説明します。
pos_tag[word]で、品詞のタグをリストとしてとして取り出します。例:[('had', 'VBD')]
次に、リスト内の最初の要素(('had', 'VBD'))の2番目の要素( 'VBD')から最初の文字('V')を取得します。
そしてupper() は取得した文字を大文字に変換します。これは後で比較するために使います。
これらの情報から、tag_dictという品詞のマッピングを示す辞書を作ります。
"J" は形容詞(Adjective)
"N" は名詞(Noun)
"V" は動詞(Verb)
"R" は副詞(Adverb)
tag_dict.get(tag, wordnet.NOUN) で値を返します。品詞が辞書に存在する場合はtagに品詞の頭文字を返し、存在しない場合は、デフォルトで名詞(wordnet.NOUN)を返します。

これを使って"had"を変換してみます。

word = 'had'
print(get_wordnet_pos(word))
print(lemmatizer.lemmatize(word, get_wordnet_pos(word)))

結果は「v:動詞」と判定され、それを使って"have"と変換されました。

では、これらを使って今回のお題の最終的なLemmatizeをします。

tokens = ["democratic", "countries", "had", "parties"]

import nltk
from nltk.stem import WordNetLemmatizer
from nltk.corpus import wordnet

def get_wordnet_pos(word):
    #品詞情報を取り出します
    tag = nltk.pos_tag([word])[0][1][0].upper()
    tag_dict = {"J": wordnet.ADJ,
                "N": wordnet.NOUN,
                "V": wordnet.VERB,
                "R": wordnet.ADV}

    return tag_dict.get(tag, wordnet.NOUN)


tokens = ["democratic", "countries", "had", "parties"]

lemmatizer = WordNetLemmatizer()
token_list = []
for token in tokens:
    token_list.append(lemmatizer.lemmatize(token, get_wordnet_pos(token)))
token_list

このようにきれいにlemmaされました。

['democratic', 'country', 'have', 'party']

おまけ

stemmingとLemimatizationは、目的によって使い分けることになります。
ざっくりと速く解析したいときはstemming、正確にじっくりとやりたいときや、機械翻訳をするときなどはLemimatizationを使うことになりそうです。

ちなみに、wordnetに入っているシノニムを確認するには以下のようにします。

wordnet.synonyms('car')

すると以下のように"car"に対するシノニムが確認できます。

[['auto', 'automobile', 'machine', 'motorcar'],
['railcar', 'railroad_car', 'railway_car'],
['gondola'],
['elevator_car'],
['cable_car']]

以上です。

この記事が気に入ったらサポートをしてみませんか?