こんにちは、@Yoshimiです。
自然言語処理の文章が肯定的か否定的かの判定を目的とした極性判定(ネガポジ判定)をゴールとして、形態素解析や形態素解析の結果からBag of Wordsの集計やTF-IDFなどの特徴量算出を行う方法について、初歩的な解説を行っていきます。
基本的なモジュール
MeCab
オープンソースの形態素解析エンジンです。
Janome
Pythonで書かれた辞書を内包する形態素解析エンジンです。
gensim
文書のトピックモデルを実行するライブラリで、単語の意味の近さを計算したり、関係性の足し算や引き算などが可能になります。
NLTK
自然言語処理全般をサポートするライブラリ。英語で形態素解析を行う場合は、このライブラリが利用されます。
MeCabとgensimを使ってみます。
まずは自然言語処理の基本モジュールであるMeCabとgensim、基本的なモジュールをインストールします。
!pip install mecab-python3 !pip install gensim !apt install aptitude swig !aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
これで準備ができました。
!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git !echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a
形態素解析
Tiggerクラスをインスタンス化し、parseメゾットに文を文字列で指定します。なお、Taggerクラスをインスタンス化するときに引数に”-Ochasen”を指定しています。ChaSenと呼ばれるツールを用いた、形態素解析の出力形式で実行されます。
import MeCab txt = '吾輩は猫である。しかし、犬かもしれないと思っている。そこにあるのはなんだ?教えてくれ。' # 形態素解析の結果をChaSenの出力形式で表示 t = MeCab.Tagger('-Ochasen') result = t.parse(txt) print(result)
は ハ は 助詞-係助詞
猫 ネコ 猫 名詞-一般
で デ だ 助動詞 特殊・ダ 連用形
ある アル ある 助動詞 五段・ラ行アル 基本形
。 。 。 記号-句点
しかし シカシ しかし 接続詞
、 、 、 記号-読点
犬 イヌ 犬 名詞-一般
かも カモ かも 助詞-副助詞
しれ シレ しれる 動詞-自立 一段 未然形
ない ナイ ない 助動詞 特殊・ナイ 基本形
と ト と 助詞-格助詞-引用
思っ オモッ 思う 動詞-自立 五段・ワ行促音便 連用タ接続
て テ て 助詞-接続助詞
いる イル いる 動詞-非自立 一段 基本形
。 。 。 記号-句点
そこ ソコ そこ 名詞-代名詞-一般
に ニ に 助詞-格助詞-一般
ある アル ある 動詞-自立 五段・ラ行 基本形
の ノ の 名詞-非自立-一般
は ハ は 助詞-係助詞
なん ナン なん 名詞-代名詞-一般
だ ダ だ 助動詞 特殊・ダ 基本形
? ? ? 記号-一般
教え オシエ 教える 動詞-自立 一段 連用形
て テ て 助詞-接続助詞
くれ クレ くれる 動詞-非自立 一段・クレル 連用形
。 。 。 記号-句点
EOS
# 形態素解析の結果を、開業で区切りとして行ごとに分割 result = result.splitlines() # EOSの行は対象外とする for res in result[:-1]: # タブを区切りとして各要素に分割 res_split = res.split('\t') print(res_split)
[’は’, ‘ハ’, ‘は’, ‘助詞-係助詞’, ”, ”]
[’猫’, ‘ネコ’, ‘猫’, ‘名詞-一般’, ”, ”]
[’で’, ‘デ’, ‘だ’, ‘助動詞’, ‘特殊・ダ’, ‘連用形’]
[’ある’, ‘アル’, ‘ある’, ‘助動詞’, ‘五段・ラ行アル’, ‘基本形’]
[’。’, ‘。’, ‘。’, ‘記号-句点’, ”, ”]
[’しかし’, ‘シカシ’, ‘しかし’, ‘接続詞’, ”, ”]
[’、’, ‘、’, ‘、’, ‘記号-読点’, ”, ”]
[’犬’, ‘イヌ’, ‘犬’, ‘名詞-一般’, ”, ”]
[’かも’, ‘カモ’, ‘かも’, ‘助詞-副助詞’, ”, ”]
[’しれ’, ‘シレ’, ‘しれる’, ‘動詞-自立’, ‘一段’, ‘未然形’]
[’ない’, ‘ナイ’, ‘ない’, ‘助動詞’, ‘特殊・ナイ’, ‘基本形’]
[’と’, ‘ト’, ‘と’, ‘助詞-格助詞-引用’, ”, ”]
[’思っ’, ‘オモッ’, ‘思う’, ‘動詞-自立’, ‘五段・ワ行促音便’, ‘連用タ接続’]
[’て’, ‘テ’, ‘て’, ‘助詞-接続助詞’, ”, ”]
[’いる’, ‘イル’, ‘いる’, ‘動詞-非自立’, ‘一段’, ‘基本形’]
[’。’, ‘。’, ‘。’, ‘記号-句点’, ”, ”]
[’そこ’, ‘ソコ’, ‘そこ’, ‘名詞-代名詞-一般’, ”, ”]
[’に’, ‘ニ’, ‘に’, ‘助詞-格助詞-一般’, ”, ”]
[’ある’, ‘アル’, ‘ある’, ‘動詞-自立’, ‘五段・ラ行’, ‘基本形’]
[’の’, ‘ノ’, ‘の’, ‘名詞-非自立-一般’, ”, ”]
[’は’, ‘ハ’, ‘は’, ‘助詞-係助詞’, ”, ”]
[’なん’, ‘ナン’, ‘なん’, ‘名詞-代名詞-一般’, ”, ”]
[’だ’, ‘ダ’, ‘だ’, ‘助動詞’, ‘特殊・ダ’, ‘基本形’]
[’?’, ‘?’, ‘?’, ‘記号-一般’, ”, ”]
[’教え’, ‘オシエ’, ‘教える’, ‘動詞-自立’, ‘一段’, ‘連用形’]
[’て’, ‘テ’, ‘て’, ‘助詞-接続助詞’, ”, ”]
[’くれ’, ‘クレ’, ‘くれる’, ‘動詞-非自立’, ‘一段・クレル’, ‘連用形’]
[’。’, ‘。’, ‘。’, ‘記号-句点’, ”, ”]
Bag of Words (Bow)
Bag of Wordsは各文書の形態素解析の結果をもとに、単語ごとに出現回数をカウントしたものです。
import MeCab docs = ['子供が走る','車が走る','ランナーが走る','車が停まる','船が海をわたる'] words_list=[] #形態素解析の結果をChasenの出力形式で表示する t = MeCab.Tagger('-Ochasen') # 各文に形態素解析を実行する for s in docs: s_parsed = t.parse(s) words_s=[] #形態素解析をリストにまとめる for line in s_parsed.splitlines()[:-1]: words_s.append(line.split('\t')[0]) words_list.append(words_s) print(words_list)
[‘車’, ‘が’, ‘走る’],
[‘ランナー’, ‘が’, ‘走る’],
[‘車’, ‘が’, ‘停’, ‘まる’],
[‘船’, ‘が’, ‘海’, ‘を’, ‘わたる’]]
Bag of Wordsを計算するときは、行が文、列が単語の行列に各文の単語の出現回数を格納する。そのため、それ俺の単語が対応する列をl関連づける必要がある。そのために、単語と1対1で対応する整数を保持する辞書を作成する。
# 生成する辞書 word2int = {} i = 0 # 各文sのの単語のリストに対して処理を反復 for words in words_list: # 文書内の各単語に対して処理を反復 for word in words: # 単語の辞書に含まれていなければ追加して対応する整数を割り当てる if word not in word2int: word2int[word] = i i += 1 print(word2int)
上記は、以下のように対応づけられました。
- ‘子供’は整数0
- ‘が’は整数1
- ‘走る’は整数2
この結果に対して、Bowを計算し文書×単語の行列を生成する。
import numpy as np # Bowを計算し、文書×単語の行列を生成 bow = np.zeros((len(words_list), len(word2int)), dtype=np.int) # 各行の単語を抽出し、単語の出現回数をカウント for i, words in enumerate(words_list): for word in words: bow[i, word2int[word]] += 1 print(bow)
[0 1 1 1 0 0 0 0 0 0 0]
[0 1 1 0 1 0 0 0 0 0 0]
[0 1 0 1 0 1 1 0 0 0 0]
[0 1 0 0 0 0 0 1 1 1 1]]
得られた行列は3つの文書で7個の単語が出現した回数を表しています。列名がないとどの単語に対応するか把握しづらいので、pandasのデータフレーム に変換して単語の列名を付与するとわかりやすくなります。
import pandas as pd pd.DataFrame(bow, columns=list(word2int))
gensimライブラリを用いた計算
gensimライブラリを用いてBag of Wordsの計算を行う方法です。
gensimライブラリを用いて辞書を作成します。corporaモジュールのDictionaryクラスをインスタンス化することで行えます。インスタンス化する際、上記で作成した変数word2intを引数に指定します。
from gensim import corpora word2int_gs = corpora.Dictionary(words_list) print(word2int_gs)
11 unique tokens:
で、辞書には11個の単語が格納されていることがわかります。辞書の中では各単語は整数として表現されます。
単語とこの整数の対応は属性token2idで参照できます。参照してみましょう。
# 単語と整数の対応 print(word2int_gs.token2id)
各文書にそれぞれの単語が出現する回数をカウントする。
# 1ばんめの文書に含まれる単語の出現回数をカウント print(word2int_gs.doc2bow(words_list[0]))
「(0, 1)」は単語0(’が’)が1回出現したことを表している。(1, 1)は単語1(’子供’)が1回出現したという意味。
import numpy as np from gensim import matutils # bag of wordsを計算し、文書×単語の行列を生成 bow_gs = np.array( [matutils.corpus2dense( [word2int_gs.doc2bow(words)], num_terms=len(word2int)).T[0] for words in words_list] ).astype(np.int) print(bow_gs)
[1 0 1 1 0 0 0 0 0 0 0]
[1 0 1 0 1 0 0 0 0 0 0]
[1 0 0 1 0 1 1 0 0 0 0]
[1 0 0 0 0 0 0 1 1 1 1]]
/usr/local/lib/python3.6/dist-packages/gensim/matutils.py:502: FutureWarning: arrays to stack must be passed as a “sequence” type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
result = np.column_stack(sparse2full(doc, num_terms) for doc in corpus)
#pandasのデータフレーム へ変換 bow_gs_df = pd.DataFrame(bow_gs, columns=list(word2int_gs.values())) bow_gs_df
sciit-learnを用いた計算
BoWを計算するには、単語をスペース区切りにする必要がある。
words_split = np.array([''.join(words) for words in words_list]) print(words_split)
CountVectorizerをインスタンス化し、fit_transformメソッドに上記生成した文のリストを入力します。fit_transformメソッドの戻り値は疎行列のデータ形成になっているので、toarrayメソッドにより通常Numpy配列に変換します。
from sklearn.feature_extraction.text import CountVectorizer # bag of Wordsを計算 vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b') bow_vec = vectorizer.fit_transform(words_split) bow_vec.toarray()
[0, 0, 0, 0, 1],
[1, 0, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 1, 0, 0]])