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

【Python】Word2Vecの使い方

はじめに

既に様々な場所で取り上げられているWord2Vecですが、改めてPythonを使ったWord2Vecの使い方を紹介します。
使い方と言っても特に難しい事はなく、コーパス(テキストや発話を大規模に集めてデータベース化した言語資料)からモデルを作成し、作成したモデルを様々な事例に活用するだけです。

Word2Vecとは?

名前の通り、単語をベクトル化する手法のことです。そして、このベクトル化したものを単語の分散表現と呼びます。

2013年にGoogleが開発&公開したもので、その仕組みについては書籍やネットなどで数多く発表されています。
最近では、朝日新聞社が自社の保有記事をコーパスとしたモデルを公開する1など、自然言語処理の分野では欠かせない技術の一つになっています。

基本的には、同じような意味や使われ方をする単語は同じような文脈の中に登場するという考えのもと、単語をベクトル化しています。
このため、同じような意味や使われ方をする単語同士はベクトル空間上で近い場所に存在、つまりコサイン類似度が高くなります。
そして、

  • 「王様」ー「男性」+「女性」=「お姫様」
  • 「日本」ー「東京」+「ソウル」=「韓国」

というように、単語の足し算や引き算が可能になりました。

Word2Vecが登場するまでは、単語の表現方法としてはone-hotベクトルや単語文脈行列をSVD(特異値分解)で次元圧縮したベクトルなどが使われていましたが、2017年現在ではWord2Vecがデファクトスタンダードと言っても過言ではない状況になりました。

Word2Vecのモデル作成

コーパスの用意

まずは、モデル作成に必要なコーパスを用意する必要があります。
今回は誰でも入手可能なWikipediaのデータを使用します。

日本語のWikipediaのデータはダンプ化されたものが用意されています。
https://dumps.wikimedia.org/jawiki/latest/

Wikipediaの記事本文が欲しいので、

$ curl https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2 -o jawiki-latest-pages-articles.xml.bz2

として記事本文のXMLファイルをダウンロードします。
テキストデータですが容量が約3GBあるので、ダウンロードにかなり時間が掛かります。

そして、XMLファイルをパースして記事本文だけを取り出します。
今回はWikiExtractorというGithubで公開されているフリーのソフトウェアを利用しました。
使い方はREADMEにも記載されていますが、インストールしてコマンド一発叩くだけです。

$ python setup.py install
$ python WikiExtractor.py jawiki-latest-pages-articles.xml.bz2

こちらもダウンロードと同様にかなり時間が掛かります。
結果はtextディレクトリ配下に

text/AA/wiki_00
text/AA/wiki_01
...
text/AZ/wiki_24

というように数千ファイルに分割されて出力されますが、

$ find text/ | grep wiki | awk '{system("cat "$0" >> wiki.txt")}'

のようにawkcatコマンドを使えば一つのファイルに一発で集約できます。
なお、WikiExtractorは英語のWikipediaのデータを想定して開発されたものだと思いますが、日本語のWikipediaのデータでも問題なくパースできました。

これでコーパスの用意は完了です。

モデルの作成

次に、モデルを作成します。

今回はgensimというPythonのフリーのライブラリを利用しました。
pipを使えばすぐにインストールできます。

$ pip install --upgrade gensim

gensimを使ってモデルを作成する場合、コーパスを事前に分かち書き(文章において語の区切りに空白を挟んで記述すること)にする必要があります。
これはMeCabを使えば、すぐにできます。なお、MeCabのインストール方法は割愛します。

$ mecab -Owakati wiki.txt -o wiki_wakati.txt

ただ、原因はよく分かりませんが、このままだとwiki_wakati.txtにバイナリ文字が混じってしまいます。
このため、nkfコマンドを使ってuft-8にビシッと揃えます。

$ nkf -w --overwrite wiki_wakati.txt

コーパスを分かち書きにできれば、モデルを作成します。
loggingを使えば、モデル作成の進捗状況が標準出力に表示されるのでお薦めです。
また、size, min_count, windowなどのパラメータの意味についてはマニュアルをご覧下さい。

from gensim.models import word2vec
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences = word2vec.Text8Corpus('./wiki_wakati.txt')

model = word2vec.Word2Vec(sentences, size=200, min_count=20, window=15)
model.wv.save_word2vec_format("./wiki.vec.pt", binary=True)

wiki.vec.ptが作成したモデルです。
これでモデルの作成は完了です。

Word2Vecの活用事例

類義語の発見

Word2Vecを使えば、類義語を見つけることができます。
例えば、講義の類義語を見つけたい場合、次のように数行書くだけです。

from gensim.models import KeyedVectors

wv = KeyedVectors.load_word2vec_format('./wiki.vec.pt', binary=True)
results = wv.most_similar(positive=['講義'])
for result in results:
    print(result)

結果は以下のようになります。
コサイン類似度が高い順に並んでおり、概ね良さそうな結果になっていますね。

('授業', 0.6795130968093872)
('講読', 0.6785069704055786)
('講演', 0.662272572517395)
('講話', 0.6550379991531372)
('聴講', 0.6494878530502319)
('講座', 0.6335989832878113)
('レクチャー', 0.6315985918045044)
('復習', 0.580752968788147)
('進講', 0.5763939619064331)
('大学院生', 0.5596680641174316)

テキスト間の類似度

Word2Vecを使えば、類義語の発見だけでなくテキスト間の類似度も計算することができます。
これも次のように30行程度で書くことができます。

ここでは、テキストを構成する単語(名詞、動詞、形容詞に限定)のベクトルの平均を当該テキストのベクトルと見なします。
もちろん単純平均だけでなく、tf-idfを使って単語毎に重みを付けることなども可能です。

2020/07/21追記
重み付けの方法は色々と提案されていますが、最近の論文を幾つか読むとSIF(smooth inverse frequency)による重み付けが精度が高くなるようです。気になる人は調べてみて下さい。

import MeCab
from gensim.models import KeyedVectors
import numpy as np

mt = MeCab.Tagger('')
wv = KeyedVectors.load_word2vec_format('./wiki.vec.pt', binary=True)

# テキストのベクトルを計算
def get_vector(text):
    sum_vec = np.zeros(200)
    word_count = 0
    node = mt.parseToNode(text)
    while node:
        fields = node.feature.split(",")
        # 名詞、動詞、形容詞に限定
        if fields[0] == '名詞' or fields[0] == '動詞' or fields[0] == '形容詞':
            sum_vec += wv[node.surface]
            word_count += 1
        node = node.next

    return sum_vec / word_count


# cos類似度を計算
def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))


if __name__ == "__main__":
    v1 = get_vector('昨日、お笑い番組を見た。')
    v2 = get_vector('昨夜、テレビで漫才をやっていた。')
    v3 = get_vector('昨日、公園に行った。')

    print(cos_sim(v1, v2))
    print(cos_sim(v1, v3))

結果は以下のようになります。
1つ目のテキストに対して、3つ目よりも2つ目のテキストの方が類似度が高くなっており、こちらも概ね良さそうな結果になっていますね。

0.496827236741
0.130445822764

さらなる活用事例

テキストをベクトル化さえできれば、

  • 口コミのポジネガ判定
  • アンケート回答のクラスタリング
  • コンテンツに含まれるテキストの類似性に基づくレコメンド

など様々な活用方法が考えられます。

自分たちの研究や業務ではどのような場面に活かせそうかという事を考えてみて下さい。
そして、是非手元で動かして、Word2Vecの威力を知って頂けると嬉しいです。

kenta1984
研究員、機械学習エンジニア。東芝、リクルート、クックパッドを経て、現在はグロービスAI経営教育研究所で機械学習、自然言語処理の研究開発に従事。
globis
グロービスは 1992 年の創業以来、社会人を対象とした MBA、人材育成の領域で Ed-Tech サービスを提供し、現在は日本 No.1 の実績があります。これらの資産と、さらに IT や AI を活用することで、アジア No.1 を目指しています。
http://www.globis.co.jp/
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
ユーザーは見つかりませんでした