iMind Developers Blog

iMind開発者ブログ

gensimでword2vec

概要

Pythonでword2vecを実行する簡易な例として、gensimでWikipediaのリンク情報を用いて各記事の距離を出すコードを書く。

バージョン情報

  • Python 3.6.8
  • gensim==3.6.0
  • word2vec==0.9.4+2.g8204e5c

導入

必要なものはcondaで入れられる。gensimのword2vecを利用する。

$ conda install gensim

サンプルデータ

abstractにリンク情報がいるので下記から jawiki-latest-abstract.xml.gz をダウンロードする。

https://dumps.wikimedia.org/jawiki/latest/

全件実行すると時間がかかるのでここでは上から50万件だけ取得している。(この処理には数分かかることがあります)

import gzip
from lxml import etree

# 取得するドキュメント数
max_doc_count = 500000

# 現在のドキュメント数
doc_count = 0

# abstract.xmlを読み進みながらタイトル情報とリンク情報をファイルに書いていく
with gzip.open('data/jawiki-latest-abstract.xml.gz') as f, gzip.open('data/sublinks.txt.gz', 'wt') as w:
    for action, elem in etree.iterparse(f, tag=['doc', 'title', 'sublink']):
        if elem.tag == 'doc':
            doc_count += 1
            if doc_count > max_doc_count:
                break
            if doc_count % 100000 == 0:
                print('%d/%d' % (doc_count, max_doc_count))
            w.write('\n')
        elif elem.tag == 'title':
            w.write(elem.text.replace('Wikipedia:', '').replace('(曖昧さ回避)', '').strip() + ' ')
        elif elem.tag == 'sublink':
            anchor = elem.find('anchor')
            if not anchor is None and not anchor.text is None:
                w.write(elem.find('anchor').text.strip() + ' ')

これで各記事が改行区切りになってタイトル及びリンク情報が出力された。

アンパサンド 歴史 手書き プログラミング言語 その他 符号位置 脚注 
言語 概要 歴史 起源 変化 世界の言語 言語の数と範囲の不確定 普段話されている言語の人口順位(上位10言語) 言語と国家 公用語、共通語、民族語 言語の生物学 言語に関する脳の領域 ヒトの発達における言語機能の獲得 脚注 関連項目 外部リンク

word2vecの実行

出来上がったファイルをword2vecで学習してみる。(この処理には数分〜数十分かかる上に使用しているPCのコアをほとんど専有します)

import multiprocessing
from gensim.models import word2vec

# 対象データをLinseSentence(one sentence = one lineとして扱う)で読み込む
sentences = word2vec.LineSentence('data/sublinks.txt.gz')

# 学習処理
model = word2vec.Word2Vec(sentences,
                          sg=1,
                          size=128,
                          min_count=1,
                          window=10,
                          hs=1,
                          negative=0,
                          iter=30,
                          workers=multiprocessing.cpu_count())

# モデルの保存
model.save('model.bin')

Word2Vecに渡すパラメータ

parameter 概要
sg 1はskip-gram、それ以外はCBOW
size 生成されるベクトルの次元数
window current wordの前n、後ろnが学習に用いられる
alpha 初期learning rate
min_alpha 最小learning rate
seed ランダムのシード
min_count 登場回数がmin_count回未満の語彙は無視する
max_vocab_size 語彙のサイズ制限
trim_rule 利用する語彙の数が指定より多い場合に刈り取るルール。
batch_words バッチサイズ
hs 1ならソフトマックスを利用
negative 0より大きければ関連しない語彙を使ってネガティブサンプリングをする(hs=0にすること)
sorted_vocab 1なら語彙を出現頻度でソートする
sample 高頻出の語彙を無視する割合
iter iterateする回数
workers 並列数

類似度の取得

most_similarで類似した単語を取得してみる。

words = ['ブルボン朝', 'プログラミング言語', 'ゲーム']

for word in words:
    similar_words = model.wv.most_similar(positive=word, topn=10)
    similar_words = ', '.join(['{}:{}'.format(w[0], w[1]) for w in similar_words])
    print('========== {} =========='.format(word))
    print(similar_words + '\n')

実行結果

========== ブルボン朝 ==========
ヴァロワ朝:0.8748996257781982, カペー朝:0.8611213564872742, ブルボン朝の成立と発展:0.8121912479400635, イタリア戦争・ユグノー戦争:0.7960749864578247, ルイ14世の親政期:0.7753866910934448, 西フランク王国:0.7331318855285645, ブルボン朝の財政:0.731281042098999, 外交革命と英仏植民地抗争:0.7050362229347229, 「絶対王政」とその限界:0.6720613837242126, オルレアン朝:0.6671494841575623

========== プログラミング言語 ==========
実装:0.75960373878479, 例:0.7491786479949951, Language:0.6982501745223999, 構文:0.6975838541984558, セキュリティ:0.6882963180541992, アーキテクチャ:0.6866961717605591, Java:0.6728484630584717, ライブラリ:0.6677107810974121, アルゴリズム:0.6675091981887817, 機能:0.6673283576965332

========== ゲーム ==========
アニメ:0.9362828731536865, テレビアニメ:0.932642936706543, OVA:0.9307636618614197, 劇場アニメ:0.9201751947402954, ラジオドラマ:0.9171557426452637, 実写:0.9141114950180054, 劇場版アニメ:0.9128466844558716, ドラマCD:0.9099293351173401, 特撮:0.9064611792564392, Webアニメ:0.9040362238883972

ベクトルの加算

2つの単語を指定して類似の単語を取る。

words = [('ブルボン朝', '映画'), ('プログラミング言語', 'シリコンバレー'), ('ゲーム', 'カード')]

for word in words:
    similar_words = model.wv.most_similar(positive=word, topn=10)
    similar_words = ', '.join(['{}:{}'.format(w[0], w[1]) for w in similar_words])
    print('========== {} =========='.format(word))
    print(similar_words + '\n')

実行結果

========== ('ブルボン朝', '映画') ==========
テレビドラマ:0.6558011770248413, 舞台:0.6517274379730225, 出演作品:0.639931321144104, Vシネマ:0.6336151957511902, (俳優):0.6304922699928284, オリジナルビデオ:0.6291390061378479, ドラマ:0.6249628067016602, WEBドラマ:0.6239743232727051, 出演:0.6130456924438477, webドラマ:0.6090548634529114

========== ('プログラミング言語', 'シリコンバレー') ==========
主要国の動向:0.5527448654174805, 性能比較と評価:0.543886661529541, ソフトウェア開発環境:0.530208170413971, 具体例:0.5287407040596008, 例:0.5217044353485107, プロセス・スケジューリング:0.5212462544441223, オペレーティングシステム:0.5125823020935059, 特撮とUNIVAC:0.503795862197876, グリッド・コンピューティングとクラウド・コンピューティング:0.5022801160812378, 規格:0.4977981150150299

========== ('ゲーム', 'カード') ==========
音楽CD:0.7871726751327515, ドラマCD:0.7694938778877258, アニメ:0.7693248391151428, パチスロ:0.7675846219062805, 実写:0.758178174495697, OVA:0.7452611327171326, コミック:0.7437731027603149, カードゲーム:0.7434374094009399, パチンコ:0.7356371879577637, フィギュア:0.7310464382171631

ベクトルの減算

words = [('ブルボン朝', '映画'), ('プログラミング言語', 'シリコンバレー'), ('ゲーム', 'カード')]

for word in words:
    similar_words = model.wv.most_similar(positive=[word[0]], negative=[word[1]], topn=10)
    similar_words = ', '.join(['{}:{}'.format(w[0], w[1]) for w in similar_words])
    print('========== {} =========='.format(word))
    print(similar_words + '\n')

実行結果

========== ('ブルボン朝', '映画') ==========
カペー朝:0.6545358896255493, ヴァロワ朝:0.6457802057266235, ブルボン朝の成立と発展:0.5508450865745544, イタリア戦争・ユグノー戦争:0.5444607734680176, オルレアン朝:0.5213345289230347, 西フランク王国:0.5197287797927856, ルイ14世の親政期:0.5166674852371216, 「絶対王政」とその限界:0.5053666830062866, サリカ法:0.5036784410476685, ブルボン朝の財政:0.503574788570404

========== ('プログラミング言語', 'シリコンバレー') ==========
実装:0.5938416719436646, TL/I:0.5894235372543335, アルゴリズム:0.581005871295929, (コンピュータ):0.5737475752830505, Ruby:0.551403820514679, Language:0.5457755327224731, 設計目標:0.5450645089149475, 構文:0.5443135499954224, C言語:0.5403240919113159, C#:0.537956953048706

========== ('ゲーム', 'カード') ==========
出演作品:0.6557350754737854, 映画:0.6530702114105225, テレビドラマ:0.634961724281311, Webドラマ:0.6255508661270142, オリジナルビデオ:0.6241406202316284, 劇場アニメ:0.6222672462463379, 吹き替え:0.6193423867225647, 特撮:0.6119524240493774, 舞台:0.6064624786376953, 舞台・ミュージカル:0.6059327125549316

ベクトルの取得する

各単語は学習時にsizeで定めた次元のベクトルで表現される。ベクトル自体は下記のように取得できる。

model['プログラム']

実行結果

array([ 0.19700082, -0.06911463, -0.11637714,  0.19883019,  0.14523488,
        0.08646227,  0.06682317,  0.01061069,  0.23645808, -0.2014449 ,
        0.00440385,  0.10765785,  0.0818133 , -0.28623682, -0.05873545,
       -0.17578019, -0.20254877,  0.15823257,  0.12411235, -0.14376928,
       -0.2812302 , -0.15298598, -0.05738884, -0.26139915, -0.04731121,
       -0.13432012,  0.14458096,  0.26207742, -0.1448085 ,  0.02130732,
       -0.31983903,  0.11999055, -0.08307765,  0.20972835,  0.03901699,
        0.10039941, -0.12654206,  0.21864639, -0.14209592, -0.09935155,
       -0.09621481,  0.14983948,  0.09671955, -0.20004761, -0.18154864,
       -0.10475357,  0.09850218, -0.15500528, -0.07648438,  0.18915482,
        0.13559149, -0.00309499,  0.09686158,  0.0896637 , -0.03575664,
        0.30757222,  0.08718527,  0.1752799 , -0.05604126, -0.03616327,
       -0.1535659 ,  0.04413694, -0.00224932, -0.01540128, -0.22142959,
        0.20358045, -0.26978752, -0.10964249,  0.03480615, -0.02054637,
       -0.0727599 , -0.01536276,  0.04357198, -0.09712161,  0.27024758,
        0.22158098, -0.2763848 , -0.02781351,  0.03704265, -0.05351185,
        0.21757951, -0.07845011,  0.00211225,  0.07099575,  0.14101945,
       -0.12364197, -0.1068197 ,  0.09143756,  0.10084849,  0.1067645 ,
        0.07869093, -0.04533485,  0.4393838 , -0.0596357 , -0.10083302,
        0.09332361, -0.06960215, -0.23350799, -0.0471145 , -0.02398371,
        0.00518164, -0.0326694 ,  0.14322706, -0.12007448,  0.05472536,
        0.28760093,  0.05060366, -0.16574176, -0.00183798,  0.00400542,
        0.14699493, -0.08662384,  0.23941778, -0.13291755, -0.00884653,
       -0.06776816, -0.11806146,  0.08735736,  0.0494101 ,  0.02532438,
       -0.1092645 , -0.24610199,  0.01216794,  0.15073413, -0.02507166,
        0.18231487,  0.09030876, -0.12625675], dtype=float32)

全キーワードとベクターの関係をdumpしたい場合は下記のように実行するとスペース区切りで各結果が出力される。

model.wv.save_word2vec_format('keyword_vectors.txt')

1行目にボキャブラリー数と次元数、次の行以降に全単語のベクトルが出力される。

1211335 128
外部リンク 0.098379135 0.04273935 0.07381522 0.06990016 ...

ベクトルから類似度を出す

単語ではなくベクトルを直指定して類似度を出す。試しにすべてのベクトルが0の場合、1の場合、-1の場合を見てみる。

import numpy as np
for vec in [np.zeros(128), np.ones(128), np.zeros(128)-1]:
    similar_words = model.wv.similar_by_vector(vec, topn=20)
    similar_words = ', '.join(['{}:{}'.format(w[0], w[1]) for w in similar_words])
    print(similar_words + '\n')
吉成恒次郎:0.0, 3001:0.0, アウトノエ:0.0, キ60:0.0, 建造物・施設:0.0, 吹屋:0.0, ペックルの声優:0.0, アヒルのペックル:0.0, 遠藤達哉:0.0, 5999:0.0, 5001:0.0, 吉成信貞:0.0, 道府県団、郡市町村団の青年問題研究集会:0.0, 中心的議題の変遷:0.0, 初期の青研:0.0, 開催の背景:0.0, 全国青年問題研究集会:0.0, ロジャー・コーンバーグ:0.0, バンド名について:0.0, 西郷正員:0.0

火薬委員会:0.40298330783843994, 各種課題:0.38100188970565796, 分解方法による分類:0.3773341476917267, 民事裁判の場合:0.37188655138015747, くの一教室:0.36945682764053345, 初代(1991年-1998年):0.3691774010658264, 2011年地震による運転停止:0.3674909770488739, 会計委員会:0.36542296409606934, 少年誘拐ホルマリン漬け事件:0.362601101398468, 作法委員会:0.3579235076904297, アメリカ横断ウルトラクイズのクイズ形式:0.35567548871040344, アクシー号:0.35403984785079956, 用具委員会:0.35259804129600525, ファジル・ゲビ:0.3515525758266449, お茶の間BGM:0.34840458631515503, 乗車時の注意:0.34734994173049927, 蛍光:0.34716227650642395, フォーマットの様式:0.3458346426486969, AGROVOCのWEBサービス:0.34566032886505127, 再稼働:0.3445932865142822

ワシーリー・チュイコフ:0.3842868208885193, 赤軍入隊:0.3789592981338501, 人工降雨の限界と問題点:0.37585732340812683, ヨーロッパにおける研究と実践:0.37425920367240906, キルスティン・ダンスト:0.37067508697509766, アメリカにおける研究と実践:0.3691123425960541, ロシアにおける研究と実践:0.3648466169834137, 気象調整を行う民間会社:0.3645966649055481, AKIKO:0.3640527129173279, 任期表:0.36266806721687317, アフリカにおける実施:0.3587416410446167, ジョン・キャラダイン:0.3524314761161804, 清水一家:0.35210680961608887, 仁藤拓馬:0.34970206022262573, TV番組(ナレーション作品):0.3476419448852539, インドにおける実験:0.3473058342933655, 免許の取得:0.3464545011520386, オルガ・キュリレンコ:0.34384971857070923, 幕末・明治の日本:0.34337756037712097, 三保ヶ関の代々:0.34216833114624023

モデルのセーブ/ロード

save/loadでパスを指定するだけでモデルの保存/読込は可能。

# セーブ
model.save('model.bin')

# ロード
model = word2vec.Word2Vec.load('model.bin')

進捗ログを出力する

loggingでログレベルを設定する。

import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

これでWord2Vecの学習を実行すると下記のようにprogressが表示される。

2018-10-16 05:52:18,278 : INFO : EPOCH 1 - PROGRESS: at 0.00% examples, 520 words/s, in_qsize 0, out_qsize 1
2018-10-16 05:52:19,954 : INFO : EPOCH 1 - PROGRESS: at 0.59% examples, 5251 words/s, in_qsize 0, out_qsize 0
2018-10-16 05:52:21,518 : INFO : EPOCH 1 - PROGRESS: at 1.16% examples, 5543 words/s, in_qsize 0, out_qsize 0
2018-10-16 05:52:23,958 : INFO : EPOCH 1 - PROGRESS: at 1.40% examples, 4546 words/s, in_qsize 0, out_qsize 0

データ量によっては実行に何日もかかるので進捗表示がされていないといつ終わるのかわからず不安な夜を過ごすことになる。

改定履歴

Author: Masato Watanabe, Date: 2019-05-03, 記事投稿