StemmingとLemmatization
テキストマイニングする前の処理として、これまで正規化、トークン化を行ってきました。この2つで文章をデータ化はできているのですが、このまま分析するにはまだ問題があります。例えば、
のような単語はほぼ同じような意味合いで使われていますが、このままでは別々のものとして扱われてしまいます。これを解決する方法として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
LancasterStemmer
token_list = []
for token in tokens:
token_list.append(lancaster_stemmer.stem(token))
token_list
SnowballStemmer
token_list = []
for token in tokens:
token_list.append(snoball_stemmer.stem(token))
token_list
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してみましょう。
token_list = []
for token in tokens:
token_list.append(lemmatizer.lemmatize(token))
token_list
結果はこのようになります。
複数形は単数形に変わりましたが、hadはそのままですね。
そこで「hadは動詞ですよ」ということをlemmatizerに教えてあげると・・・
print(lemmatizer.lemmatize("had","v"))
「have」と、原型が返ってきます。しかし、一つ一つ品詞を教えていくのは大変ですね。
そこで、単語の品詞を調べるaveraged_perceptron_taggerというタグモデルを使います。このタグモデルをダウンロードします。
nltk.download('averaged_perceptron_tagger')
そのうえで、以下のようにしてみると、
print(nltk.pos_tag(['had']))
このような結果が返ってきます。
これは、動詞の過去形ということを表しています。ここで重要なのは最初の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をします。
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されました。
おまけ
stemmingとLemimatizationは、目的によって使い分けることになります。
ざっくりと速く解析したいときはstemming、正確にじっくりとやりたいときや、機械翻訳をするときなどはLemimatizationを使うことになりそうです。
ちなみに、wordnetに入っているシノニムを確認するには以下のようにします。
wordnet.synonyms('car')
すると以下のように"car"に対するシノニムが確認できます。
以上です。
この記事が気に入ったらサポートをしてみませんか?