Edited at

gensimによるword2vecの利用例

More than 1 year has passed since last update.


はじめに

最近、単語の分散表現を学び、使ったので、その際に得た知識をまとめておく。

この記事では、MeCab、gensimを用いて、夏目漱石の『こころ』に登場する単語の類似度を計算する。

GitHubにこの記事で使用する全てのコードをアップしてある。

https://github.com/hsoccer/my_word2vec


環境

OS X El Capitan

Python3系


MeCabのインストール

以下のページ等を参考にインストールする。

追加の辞書であるmecab-ipadic-neologdもインストールしておく。

https://qiita.com/taroc/items/b9afd914432da08dafc8


MeCabの辞書を強化する

デフォルトの辞書では弱いので、Wikipediaの見出語を全て辞書に加えておく。

user.dicという名前で保存しておく。

以下のページが非常に参考になる。

http://aidiary.hatenablog.com/entry/20101230/1293691668


gensimのインストール

ターミナル上で$ pip install gensimでうまくいくはず。


データのダウンロード

以下の青空文庫のサイトから『こころ』のzipファイルをダウンロードして、これ以降で書いていくコードと同じファイルに入れておく。

http://www.aozora.gr.jp/cards/000148/card773.html

zipファイルを解凍すると、全文のテキストファイルになる(kokoro.txtで保存)が、本文の前後に不要な部分があるので、削除しておく。(しなくてもそんなに変わらないかもしれない。)


本文を分かち書きする

分かち書きとは、各文章を文節ごとに分けた形で書きなおすことである。

もしイメージが湧かなければ、調べるか、以下のコードを実行してみると良い。


Wakati.py

import MeCab

class Wakati(object):

"""
==========================================================
ファイルに存在する文章を指定の辞書を用いてMeCabによって形態素に分ける
==========================================================
【関数説明】
__init__ : コンストラクタ
wakati : 文章を分かち書きする
output : 結果をファイルに出力する
"""

def __init__(self, file_dir, dic_dir=None, user_dir=None, hinshis=["動詞", "形容詞", "形容動詞", "助動詞"]):
"""
==========================================================
コンストラクタ
==========================================================
【変数説明】
file_dir : 入力となる文章のディレクトリ
dic_dir : システム辞書のディレクトリ([ex] /usr/local/lib/mecab/dic/mecab-ipadic-neologd)
user_dir : ユーザー辞書のディレクトリ([ex] /Users/PCのユーザ名/...)
hinshis : 活用する語
tagger : MeCab用のtagger(詳細はMeCab自体のドキュメント参照)
f : 入力ファイル
splited_text : 各行を分かち書きしたもの(splited_line)をリストで格納する(Noneで初期化)
out_dir : 出力ファイルのディレクトリ(Noneで初期化)
"""

if dic_dir is not None and user_dir is not None:
self.tagger = MeCab.Tagger("mecabrc -d {} -u {}".format(dic_dir, user_dir))
elif dic_dir is not None:
self.tagger = MeCab.Tagger("mecabrc -d {}".format(dic_dir))
else:
self.tagger = MeCab.Tagger("mecabrc")
self.f = open(file_dir, 'r')
self.hinshis = hinshis
self.splited_text = None
self.out_dir = None

def wakati(self):
"""
==========================================================
文章全体を分かち書きし、self.splited_textに格納する
その際、活用された語については原形に直す
==========================================================
【変数説明】
line : 入力文章の一行(更新されていく)
splited_line : 各行の文章を分かち書きしたもののリスト
node : 各単語のノード
word : 単語
feature : 単語の情報
hinshi : 品詞
kata : 活用形
genkei : 原形
"""

line = self.f.readline()
splited_text = []
while line:
node = self.tagger.parseToNode(line).next
splited_line = []
while node.surface:
word = node.surface
feature = node.feature.split(',')
hinshi = feature[0]
kata = feature[5]
genkei = feature[6]
if hinshi in self.hinshis:
if kata != "基本形":
word = genkei
splited_line.append(word)
node = node.next
splited_text.append(splited_line)
line = self.f.readline()
self.splited_text = splited_text
self.f.close()

def output(self, out_dir):
"""
==========================================================
self.splited_textをファイルに出力する
==========================================================
【変数説明】
out_dir : 出力ファイルのディレクトリ
fout : 出力ファイル
"""

assert self.splited_text is not None
if self.out_dir is None:
self.out_dir = out_dir
self.fout = open(self.out_dir, 'w')
for line in self.splited_text:
self.fout.write(" ".join(line) + " ")
self.fout.close()



wakati_text.py

from Wakati import Wakati

w = Wakati("kokoro.txt", システム辞書のディレクトリ, ユーザ辞書のディレクトリ)
w.wakati()
w.output("out.txt")


上記のコードを実行すると、out.txtという名前で分かち書きされたテキストファイルができる。

これを見ればやっていることが分かると思う。

【追記】 (2017/9/30)

Wakatiクラスのdic_dirのデフォルトを設定した。(それに伴って、以下のVectorizerクラスの引数も変更した。)


word2vecを使って単語をベクトル化する

先ほどインストールしたgensimというライブラリを用いて、『こころ』に登場する単語を分散表現する。


Vectorizer.py

from Wakati import Wakati

from gensim.models import word2vec
import logging

class Vectorizer(Wakati):

"""
==========================================================
Wakatiをベースとして、分かち書きしたものを学習して分散表現する
==========================================================
【関数説明】
__init__ : コンストラクタ
vectorize : 分散表現を作る
_train : gensimを使ってword2vecする
save_model : 作ったモデルを保存する
"""

def __init__(self, file_dir, dic_dir=None, user_dir=None, out_dir="out.txt", hinshis=["動詞", "形容詞", "形容動詞", "助動詞"]):
"""
==========================================================
コンストラクタ
Wakatiを使って文章を分かち書きしておく
==========================================================
【変数説明】
file_dir : 入力となる文章のディレクトリ
dic_dir : システム辞書のディレクトリ(/usr/local/lib/mecab/dic/mecab-ipadic-neologd)
user_dir : ユーザー辞書のディレクトリ(/Users/ユーザ名/Desktop/word2vec/user.dic)
out_dir : 分かち書きされた文章のファイルのディレクトリ
hinshis : 活用する語
model : モデル(Noneで初期化)
"""

Wakati.__init__(self, file_dir, dic_dir, user_dir, hinshis)
self.out_dir = out_dir
self.model = None
self.wakati()
self.output(self.out_dir)

def vectorize(self, other_file_dir=None, sg=1, size=300, min_count=10, window=5, hs=0, negative=15, iter=15):
"""
==========================================================
単語の分散表現を作成
==========================================================
【変数説明】
out_dir : 分かち書きされた文章のファイル
other_file_dir : out_dirを使わない場合のファイル名(Noneで初期化)
sentences : 分かち書きされ、空白区切の文章全文
"""

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
if other_file_dir is None:
sentences = word2vec.Text8Corpus(self.out_dir)
else:
sentences = word2vec.Text8Corpus(other_file_dir)
self._train(sentences, sg, size, min_count, window, hs, negative, iter)

def _train(self, sentences, sg=1, size=300, min_count=10, window=5, hs=0, negative=15, iter=15):
"""
==========================================================
gensimによる学習
==========================================================
【変数説明】
sentences : 分かち書きされ、空白区切の文章全文
word2vecの引数 : 詳細はgensimのドキュメント参照
"""

self.model = word2vec.Word2Vec(sentences, sg=sg, size=size, min_count=min_count, window=window, hs=hs, negative=negative, iter=iter)

def save_model(self, model_dir):
"""
==========================================================
モデルを保存する
==========================================================
【変数説明】
model_dir : モデルを保存するディレクトリ
"""

assert self.model is not None
self.model.save(model_dir)



『こころ』に登場する単語を解析

ここまでで作ったモデルを用いて、『こころ』における「人間」という単語との類似度が高い単語をピックアップする。


example.py

from Vectorizer import Vectorizer

"""
==========================================================
自作のWord2Vecを使って、夏目漱石の『こころ』を解析する。
(『こころ』は青空文庫(http://www.aozora.gr.jp/cards/000148/card773.html)から
ダウンロードして同じディレクトリに入れておく)
==========================================================
【関数説明】
kokoro : 『こころ』のモデルを作成
"""

def kokoro():
"""
==========================================================
『こころ』から作成されたモデルを返す
==========================================================
【変数説明】
dic_dir : システム辞書のディレクトリ(このPCでは"/usr/local/lib/mecab/dic/mecab-ipadic-neologd")
user_dir : ユーザー辞書のディレクトリ(このPCでは"/Users/ユーザ名/Desktop/word2vec/user.dic")
"""

#Vectorizerインスタンスを作成
#__init__で文章が分かち書きされたファイル(out.txt)が同じディレクトリ内に作られる。
#dic_dir、user_dirを各自のPCに合わせて設定し、Vectorizerの引数にもたせても良い。
v = Vectorizer(file_dir="kokoro.txt")

#単語の分散表現のモデル作成
v.vectorize()

#できたモデルを返す
return v.model

if __name__ == "__main__":

#『こころ』から作成されたモデル
model = kokoro()

#「人間」という単語に最も近い10単語を表示する
#resultは(単語, コサイン距離)からなるリスト
result = model.most_similar(positive="人間")
for pair in result:
word = pair[0]
distance = pair[1]
print(word, distance)

#完成したモデルを保存したければ以下のコードのコメントアウトを外す
#v.save_model("kokoro.model")


このコードを実行すると、(私の環境では)以下のような結果になった。


result.txt

幸福 0.7769338488578796

矛盾 0.7726519107818604
愛 0.763999342918396
変化 0.7608857154846191
判断 0.7593793869018555
嫌い 0.7444355487823486
つまり 0.7347712516784668
従妹 0.7326171398162842
苦痛 0.718117892742157
感じ 0.7159429788589478

本文を読まなくても、なかなか重そうな内容であることが一目で分かる。


最後に

この記事では、単に単語の類似度を調べただけだが、実際には、ベクトル化した単語からなる文章を時系列データとしてLSTMに突っ込む等応用が効く。

最初にも書いたが、https://github.com/hsoccer/my_word2vecにほぼ同じコードが載っているので、合わせてご覧いただければ幸いである。

コードのコメントとして、極力丁寧に変数の説明を入れたつもりなので、いろいろと変更して、結果の違いを観察すると楽しい。


参考

https://qiita.com/taroc/items/b9afd914432da08dafc8

http://aidiary.hatenablog.com/entry/20101230/1293691668

http://www.aozora.gr.jp/cards/000148/card773.html

https://radimrehurek.com/gensim/models/word2vec.html