データサイエ「ソ」ティストは語る

データサイエンティストではない、パチもんのデータサイエ「ソ」ティストのブログ

日本語WikipediaとRのwordVectorsパッケージで“言葉遊び”

執筆時のBGM:アイドルネッサンス「夏の決心」


アイドルネッサンス「夏の決心」(MV)

まえがき

世の中の「でーたさいえんてぃすと」はとうの昔に通り過ぎてしまったところだろうけど、「言葉の意味」をベクトル空間にマッピングして、様々に操作(計算)できるword2vecで遊んでみた記録。

前提

今回は、慣れているR(最近はMicrosoft R Open)のwordVectorsパッケージを使う。また、テキストデータは、昨年夏前ころにダウンロードした、Wikipedia日本語版のダンプXMLを使う。

作業環境

たまたま、自分のPCがそうだ、というだけなのだけど。ただ、メモリは十分に必要。ソフトウェア類は、基本的に最新のものをダウンロードすればよい。





  • Git ※アクセスするとインストーラのダウンロードが始まる
    たぶん、いろいろなOSSツールを入れるとGitも入ってくるだろうから、オフィシャルの配布物じゃなくてもよいと思う
  • Windows版nkf ※パスの通ったフォルダに配置する

使用するデータ

上記のように、Wikipedia日本語版のダンプXMLを使う。基本的には、 https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2 をダウンロードすればよいと思うが、以前WP2TXTを実行した環境の都合で、分割されたデータ https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles[1-4].xml.bz2 を使った。いずれにしても、ファイルサイズが巨大なので、時間とディスク容量に注意する。

ダウンロードしたデータは、bz2圧縮されたままで処理できるが、環境がしょぼい(メモリ4GB程度)と、メモリ不足で処理が進まなかったので、場合によっては事前に展開だけしておく。

 for i in *.xml.bz2
     do
         bzip2 -d $i
     done

なお、今回は、昨年の夏ごろ(2015年6月ころ)にダウンロードしたデータが手元にあったので、それを使っている。そのため、収録記事数などは現在の最新のものとは異なる。

作業手順

流れは、以下のようになる。

  1. Wikipedia XMLをプレインテキストに変換する(WP2TXT)
  2. テキストの分かち書きMeCab + mecab-ipadic-neologd)
  3. RおよびRtoolsのインストール
  4. word2vecのコンパイルWindows版word2vec)
  5. wordVectorsパッケージのインストール(R)

Wikipedia XMLをプレインテキストに変換する(WP2TXT)

WP2TXTを使う。事前にRubyなどはインストールしておく。

 git clone https://github.com/yohasebe/wp2txt.git

とするなり、zipファイルをダウンロードするなりして、WP2TXTを入手する。なお、スクリプトの実行には、いくつかのライブラリのインストールが必要なので、以下のようにする。

  gem install nokogiri
  gem install htmlentities
  gem install trollop

ダウンロードしたWP2TXTのbinフォルダに移動し、コマンドプロンプトを開く(パスがわかればどこでもいいけど)。そして、以下のように実行して、ヘルプが表示されることを確認する。

  ruby wp2txt --help

  WP2TXT extracts plain text data from Wikipedia
  dump file (encoded in XML/compressed with Bzip2) 
  stripping all the MediaWiki markups and other metadata.
  
  Usage: wp2txt [options]
  where [options] are:
    -i, --input-file              Wikipedia dump file with .bz2
                                  (compressed) or .txt (uncompressed)
                                       format
    -o, --output-dir=<s>          Output directory (default:
                                  C:/Users/XXX/XXX/wp2txt/bin)
...

表示されなかったら、不足したライブラリをインストールするなど、どうにかする。

準備ができたら、WikipediaXMLデータをパースし、プレインテキストに変換する。この際、後で分かち書きする際に邪魔になりそうな要素を、様々なオプションを使って除去する必要がある。また、処理には非常に長い時間がかかるので、注意する。

実行例

  ruby wp2txt -i jawiki-latest-pages-articles1.xml.bz2 -o C:\Users\XXX\Desktop
  --no-list --no-heading --no-title --no-multiline -f 70
  (実際には1行で続けて書く)

※最後の-fオプションは出力ファイルのサイズ指定。何度か試してみて、70MBにしている。環境によっては、もう少し大きくてもよいだろう。

これで、指定したフォルダにテキストファイルが順次出力される。すごく時間がかかる。あまり具体的に覚えていないが、昨年この作業を行った時は、丸2日くらいかかったようだ。ちなみに、英語版Wikipediaのサイズだと、丸1週間かかった。

出力の結合と文字コード変換

次に、できあがったテキストファイルを結合する(せっかく分割して出力したのだが)。コマンドプロンプトでテキストファイルがあるフォルダに移動し、以下のようにする。

 type jawiki-latest-*.txt > jawiki.txt

また、テキストの文字コードUTF-8になっているので、このあとWindows環境で扱うために、Shift_JIS(CP932)に変換する。

  for %%f in (jawiki-latest-*.txt) do nkf -sLw --overwrite %%f

テキストの分かち書きMeCab + mecab-ipadic-neologd)

Wikipediaテキストデータの準備ができたら、次に、テキストを形態素単位で分かち書きにする。word2vecは日本語の「区切り」を知らないので、事前に区切っておく必要がある。分かち書き自体は、MeCab-O wakatiオプションで簡単にできる。

ただし、MeCabに標準で付属する辞書(IPADIC)は古いので、最近出てきた新しい言葉などを意図しないところで区切ってしまうことがあるため、より新しい辞書であるmecab-ipadic-neologdを導入する。なお、mecab-ipadic-neologdは、基本的にはUNIX / Linux環境でコンパイルして作成するため、何らかの作業環境が必要だ。UNIX / Linux環境でWindows向けの辞書を作成する方法は、mecab-ipadic-neologdをNMeCab用にshift-jisでコンパイルしたなどを参照する。単に、辞書作成スクリプトmake-mecab-ipadic-neologd.shの中で-t UTF8となっている部分を、-t shift-jisにするだけ。実際には、Shift_JISで表現できない文字(記号、絵文字など)が大量にスキップされるが、しょうがない。

なお、現時点の最新の辞書(20160317-01)をWindows用にコンパイルしたものをGoogleドライブに置いた。

作成した辞書を、MeCabの辞書フォルダ(例えば C:\Program Files (x86)\MeCab\dic)に配置し、設定ファイル(C:\Program Files (x86)\MeCab\etc\mecabrc)を以下のように書き換える。

  ;dicdir =  $(rcpath)\..\dic\ipadic
  dicdir =  $(rcpath)\..\dic\mecab-ipadic-neologd
  ※IPADICを使う設定をコメントアウトし、mecab-ipadic-neologdを使うようにする

試しに、コマンドプロンプトで以下のように入力し、新しい言葉が適切に区切られることを確認する。……無駄に長い例だけど、ようは新しい言葉(馬名、人名)がちゃんと認識できていればよい。

  echo リオンディーズ、マカヒキ、エアスピネル、サトノダイヤモンドなど、史上最高メンバーの皐月賞!きゃりーぱみゅぱみゅも興味津々!(嘘) | mecab
  リオンディーズ  名詞,固有名詞,一般,*,*,*,リオンディーズ,リオンディーズ,リオンディーズ
  、      記号,読点,*,*,*,*,、,、,、
  マカヒキ        名詞,固有名詞,一般,*,*,*,マカヒキ,マカヒキ,マカヒキ
  、      記号,読点,*,*,*,*,、,、,、
  エアスピネル    名詞,固有名詞,一般,*,*,*,エアスピネル,エアスピネル,エアスピネル
  、      記号,読点,*,*,*,*,、,、,、
  サトノダイヤモンド      名詞,固有名詞,一般,*,*,*,サトノダイヤモンド,サトノダイヤモンド,サトノダイヤモンド
  など    助詞,副助詞,*,*,*,*,など,ナド,ナド
  、      記号,読点,*,*,*,*,、,、,、
  史上    名詞,一般,*,*,*,*,史上,シジョウ,シジョー
  最高    名詞,一般,*,*,*,*,最高,サイコウ,サイコー
  メンバー        名詞,一般,*,*,*,*,メンバー,メンバー,メンバー
  の      助詞,連体化,*,*,*,*,の,ノ,ノ
  皐月賞  名詞,固有名詞,一般,*,*,*,皐月賞,サツキショウ,サツキショー
  が      助詞,格助詞,一般,*,*,*,が,ガ,ガ
  来る    動詞,自立,*,*,カ変・来ル,基本形,来る,クル,クル
  !      記号,一般,*,*,*,*,!,!,!
  きゃりーぱみゅぱみゅ    名詞,固有名詞,一般,*,*,*,きゃりーぱみゅぱみゅ,キャリーパミュパミュ,キャリーパミュパミュ
  も      助詞,係助詞,*,*,*,*,も,モ,モ
  興味津々        名詞,一般,*,*,*,*,興味津々,キョウミシンシン,キョーミシンシン
  !      記号,一般,*,*,*,*,!,!,!
 (      記号,括弧開,*,*,*,*,(,(,(
  嘘      名詞,一般,*,*,*,*,嘘,ウソ,ウソ
  )      記号,括弧閉,*,*,*,*,),),)
  EOS

環境が整ったら、本題に戻って、Wikipediaのテキストを分かち書きする。テキストファイルがあるフォルダで、以下のようにする。-O wakatiオプションで分かち書きをする。-b 81920は、標準のバッファサイズでは溢れてしまったため、とりあえず10倍にしている設定。

  for %%f in (*.txt) do mecab -b 81920 -O wakati %%f > wakati_%%f
  type wakati_jawiki-latest-pages-articles*.txt >> wakati_jawiki.txt

これで、Wikipedia日本語版のテキストを分かち書きしたデータが手に入った。

RおよびRtoolsの導入

ここは、書くまでもないところだけど、最新のRまたはMicrosoft R Openと、Rtoolsをダウンロード、インストールする。

word2vecのコンパイルWindows版word2vec)

ようやく、word2vecを導入する。オリジナルのword2vecはUNIX / Linux用のコードで、そのままではWindowsコンパイルできない(らしい)ので、Windows版word2vecを使う。こちらも、コンパイル環境が必要だが、Rtoolsが入っていれば、それで事足りる。詳しい手順などは、Windowsにword2vecをインストールする方法を参照。

単純には、以下のような手順になる。

  git clone https://github.com/zhangyafeikimi/word2vec-win32.git
  cd word2vec-win32
  make
  ...
  demo-word.sh

これで、一通りコンパイルが完了し、サンプルの実行まで進む。フォルダ内に、word2vec.exedistance.exeなどができるので、それら実行ファイルを、パスの通ったフォルダに配置する。

wordVectorsパッケージのインストール(R)

大方の準備が整ったので、Rからword2vecを呼び出すための、wordVectorsパッケージをインストールする。Rを起動し、まずはdevtoolsパッケージをインストールする。

  > install.packages("devtools")

次に、wordVectorsパッケージをGitHubからインストールする。

  > require(devtools)
  > install_github("bmschmidt/wordVectors")

合わせて、word2vecモデルを操作するときに便利なmagrittrパッケージも導入する。

  > install.packages("magrittr")

これで、Rからword2vecを呼び出す環境が整った。

20160502追記

「この前までできていたのに」wordVectorsパッケージの更新かなにかで、筆者のCore i7-4770環境では、インストールができなくなっていた。RToolsが提供するgcc 4.6.3では、ソース中の最適化フラグ-march=nativeがHaswellアーキテクチャに対応していないらしい。そのため、解決するには、いったんgit cloneソースコードをダウンロードし、Makevars.winファイルの当該部分を書き換える必要がある。

ソースを修正したうえで、コマンドラインから以下のようにしてコンパイルする(Windowsの場合)。

 Rcmd INSTALL --build wordVectors(フォルダ)

これでパッケージのzipファイルができるので、インストールする。

word2vecモデルの作成

上記の環境構築が終わったら、Wikipediaのテキストデータと、RのwordVectorsパッケージを使い、word2vecの意味空間モデル(何のこっちゃ……)を作成する。Rを起動し、テキストデータがあるフォルダに移動したうえで、以下のように実行する。

  > require(wordVectors)
  > train_word2vec("wakati_jawiki.txt",output="jawiki.vectors",threads=3,vectors=200,window=12)

一般化すると以下のように書ける。

  train_word2vec("テキストファイル名",output="出力ファイル名",threads=実行スレッド数,vectors=次元数,window=語長)

threadsは、実際にはCPUのコア数-1くらいを指定するとよいのではないだろうか。vectorswindowは、正直、適切な値はよくわからないけど、いろいろ調べる限り、vectorsはだいたい200で、windowは、5や12などいろいろな例があった。Wikipediaに関しては、5では短すぎて意味のある結果が得られにくいようなので、12とした。

これも、すごく時間がかかる。冒頭のPC環境で、3-4時間くらいかかった。処理が終了すると、上記の例では、およそ88万語について、200次元のベクトルで特徴を表したモデルが出力される。

なお、train_word2vec関数で出力されるモデルのフォーマットは以下のようになっている。

  889165 200
  JR東海道線 0.357443 -0.265362 0.144581...
  ...

一般化すると、以下のようになる。

  1行目      行数(語数)  次元数
  2行目以降  形態素 第1次元 第2次元 第3次元...

出力は、シンプルなテキストファイルなので、UNIX / Linuxのテキスト操作コマンドで、一部を抽出することなどが簡単にできる。例えば、全889165語のうち、100000語をランダムに取り出して、小さなファイルを作るには、以下のようにする。

  $ echo "100000 200" > small_jawiki.vectors
  $ tail -889165 jawiki.vectors | sort -R | head -100000 >> small_jawiki.vectors

※こんなことをして抽出したデータが、言語学的に正しいのかは知らない。

ちなみに、ここまでの操作の結果、できあがったデータ(全件、300000件抽出)を、Googleドライブに置いている。

さぁ、ようやくだ!書くだけでも2時間くらいかかっているが、これからできあがったモデルを操作していく。

word2vecモデルで遊ぶ

まず、Rを起動し、wordVectorsパッケージを読み込む。

  > require(wordVectors)

次に、作成したword2vecモデルファイル(ここでは、jawiki.vectors)を読み込む。データ量とディスク性能しだいで、かなり(4-5分程度)時間がかかる。また、データがメモリに展開されるため、メモリ消費に注意する。今回作成した、およそ89万語のデータの場合、ファイルサイズが1.65GBで、メモリに展開されると、5GBほど消費する。

  > model<-read.vectors("jawiki.vectors")

読み込んだモデルの各要素にアクセスするには、以下のようにする。

  > model # モデル全体(実際には先頭の一部だけ表示される)
  A VectorSpaceModel object of  889165  words and  200  vectors
              V1        V2        V3        V4       V5        V6
  </s>  0.001712  0.001952  0.002184 -0.000975 0.001271 -0.0004820.061107 -0.167354  0.029397 -0.006784 0.277198  0.0483190.068990 -0.281579  0.061935 -0.067788 0.242458  0.105923-0.063259 -0.292497  0.138581 -0.086513 0.291480  0.135969

  > model[1:3] # 先頭から3行を表示する
  A VectorSpaceModel object of  3  words and  200  vectors
             V1        V2       V3        V4       V5        V6
  </s> 0.001712  0.001952 0.002184 -0.000975 0.001271 -0.0004820.061107 -0.167354 0.029397 -0.006784 0.277198  0.0483190.068990 -0.281579 0.061935 -0.067788 0.242458  0.105923


  > model[["エアメサイア"]] # 指定した語の行のみ表示する
  A VectorSpaceModel object of  1  words and  200  vectors
         V1        V2        V3        V4        V5        V6 
   0.153385  0.181259 -0.093122 -0.037569 -0.604000 -0.145498

  > model[[c("エアメサイア","シーザリオ")]] # 何が返ってきているのだろう? 2つの言葉の共通次元?
  A VectorSpaceModel object of  1  words and  200  vectors
          V1         V2         V3         V4         V5         V6 
   0.0780615  0.0151365 -0.0739540  0.0080325 -0.2092705  0.0824025

その他、普通の文字列と数値からなるデータフレームなので、grep関数なども使えるが、なにせデータ量が大きいので、やたらと時間がかかる。

関数の使用(1)nearest_to関数

wordVectorsパッケージには、「言葉の意味」を操作する様々な関数があるが、まずは、指定した語と(意味的に)類似した語を出力する、nearest_to関数を使ってみる。ちなみに、n=オプションで出力する要素数を指定できる。デフォルトは10。

  > nearest_to(model,model[["エアメサイア"]])
        エアメサイア ショウナンパントル       トールポピー   オレハマッテルゼ
       -2.220446e-16       2.534338e-01       3.597027e-01       3.620715e-01
  ホエールキャプチャ     グランプリボス   フサイチパンドラ   セイウンワンダー
        3.748858e-01       3.768097e-01       3.894503e-01       3.960476e-01
        伊豆急8000系     マツリダゴッホ
        4.098723e-01       4.139108e-01

謎の伊豆急8000系(2005年デビューだからか。エアメサイアは2005年の秋華賞馬。)はともかく、他はちゃんと?サンデーサイレンス産駒やマイル・中距離のGI馬が出てきている。って、読んでいる人は誰も判断がつかないだろうが。同期の三冠馬ディープインパクトについて類似した語を抽出すると、以下のようになる。

  > nearest_to(model,model[["ディープインパクト"]])
  ディープインパクト   シンボリルドルフ           宝塚記念     オグリキャップ
       -4.440892e-16       2.647716e-01       2.800557e-01       2.966380e-01 
              菊花賞   ミスターシービー           有馬記念     ジャパンカップ
        2.982116e-01       3.022025e-01       3.033070e-01       3.085915e-01
  テイエムオペラオー メジロマックイーン
        3.088792e-01       3.126304e-01

これも、読んでいる人には判断がつかないだろうが、競馬ファンからすると、「あぁ」という結果になった。シンボリルドルフミスターシービーは同じ三冠馬だし、メジロマックイーンは同じ池江泰郎厩舎の所属だった。オグリキャップテイエムオペラオーは、「熱狂」や「強さ」の面でよく対比される存在だ。映画のほうは何も出てこない。

また、tsneパッケージを使うことで、言葉の意味空間をプロットできる。

  > install.packages("tsne")
  > require(tsne)

  > horses<-nearest_to(model,model[[c("エアメサイア","シーザリオ","ラインクラフト")]])
  > plot(filter_to_rownames(model,names(horses)))

f:id:data_sciesotist:20160320213903p:plain

なんか、わかるようなわからないような結果になった。スズカフェニックスヘヴンリーロマンスなど、同じ時期にGI戦線を賑わせた馬や、2005年前後に生まれた(サクセスブロッケン!!!!!!)り、亡くなった(アドマイヤベガ)馬が出てきている。「○○は日本の競走馬。○○年、どこそこで生まれる。○○年に死亡」といった、Wikipediaの文体の特徴が反映されたのだろうか。

ともあれ、こんなふうに、ごくシンプルに、「ある言葉と似ているらしい言葉」を抽出できる。他に、reject関数で意味の除去、project関数で意味の付与ができる。

関数の使用(2)magrittrパッケージと組み合わせる

次に、word2vecモデルを操作し、ある言葉に意味を足したり引いたりしてみる。その際、magrittrパッケージを使うと、チェイン関数(%>%)でUNIX / Linuxのパイプのように処理をつなげていくことができる。

前項でパッケージのインストールはしているはずなので、require関数やlibrary関数で読み込む。

  > require(magrittr)

まずは、以下のような例を実行してみる。

  > tkd<-model[["JR東海道線"]] %>% reject(model[["JR東日本"]])
  > model %>% nearest_to(tkd)
      JR東海道線   JR東海道本線       茶沢通り         戸塚駅         鴨居駅 
       0.1276491      0.3291890      0.3423227      0.3487515      0.3505389 
          仙川駅 小田急小田原線   尻手黒川道路         長原駅         神宮前 
       0.3522090      0.3556912      0.3594673      0.3597864      0.3614786

わかるような、わからないような。小田急、茶沢通り、仙川駅、長原駅、神宮前など、なぜか世田谷・調布周辺が多く返ってくる。地図で見ると、だいたい都心から同じ方向なのだが。

……ということで、WikipediaとRのwordVectorsパッケージを使って、word2vecで遊ぶための手順をまとめてみた。