Help us understand the problem. What is going on with this article?

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした