【自然言語処理の基礎】MeCabとgensimを使ってみよう

こんにちは、@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, ‘車’: 3, ‘ランナー’: 4, ‘停’: 5, ‘まる’: 6, ‘船’: 7, ‘海’: 8, ‘を’: 9, ‘わたる’: 10}

上記は、以下のように対応づけられました。

  • ‘子供’は整数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)
[[1 1 1 0 0 0 0 0 0 0 0]
[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)
Dictionary(11 unique tokens: [‘が’, ‘子供’, ‘走る’, ‘車’, ‘ランナー’]…)

11 unique tokens:で、辞書には11個の単語が格納されていることがわかります。辞書の中では各単語は整数として表現されます。

単語とこの整数の対応は属性token2idで参照できます。参照してみましょう。
 

# 単語と整数の対応
print(word2int_gs.token2id)
{‘が’: 0, ‘子供’: 1, ‘走る’: 2, ‘車’: 3, ‘ランナー’: 4, ‘まる’: 5, ‘停’: 6, ‘わたる’: 7, ‘を’: 8, ‘海’: 9, ‘船’: 10}

各文書にそれぞれの単語が出現する回数をカウントする。

# 1ばんめの文書に含まれる単語の出現回数をカウント
print(word2int_gs.doc2bow(words_list[0]))
[(0, 1), (1, 1), (2, 1)]

「(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 1 1 0 0 0 0 0 0 0 0]
[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()
array([[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1],
[1, 0, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 1, 0, 0]])

なりたい自分になれる
スキルアップならUdemy

私も利用し、高収入エンジニアになったのよ。未経験から機械学習、データサイエンティスト、アプリ開発エンジニアを目指せるコンテンツが多数あります。優秀な講師が多数!割引を利用すれば1,200円〜から動画購入可能です。!

ABOUTこの記事をかいた人

大学卒業して、キラキラしていたのでIT業界にはいりましたが、中身はブラックでした!!だから、投資技術を磨いて早くリタイヤしたいです。株価、Python、機械学習をもうもう勉強中です。経済的自由を手に入れて農家やりたい!