CreateField Blog

オープンソースを使って個人でWebサービスを開発・運営していたブログ

word2vecをDockerでプレーンテキストから簡単に使えるようにしました

はじめに

Dockerで簡単に使えるようにしてみた第2弾です。前回は、専門用語を自動抽出してくれるTermExtractをプレーンテキストで簡単に使えるようにしたDockerファイルについて紹介しました。

最近はword2vecが非常に話題になっていますが、word2vecは環境構築周りやテキストの前処理等がなかなかめんどくさいです。

そこで、word2vecと、プレーンテキストをRE2による正規表現フィルタ、ICUによるNFKC正規化(全角文字→半角文字変換等)、MeCabによる分かち書き等を実行してくれる自作のC++プログラムstring-splitterが自動で環境構築されるDockerファイルを作りました。

https://github.com/naoa/docker-word2vec

このDockerファイルには、単語間のベクトル距離が近い類義語っぽいものを出力するdistanceコマンドやking - man + woman = queenといった関係の類推語っぽいものを出力するword-analogyコマンドの他に、 ベクトルを自由に足し引き演算(+-)できるようにしたword2vec-calcコマンドも含まれています。

さらに実験的に、Word2vecの並列実行時の学習速度の改善で紹介されていた並列処理の高速化パッチも自動で適用されるようになっています。

これでDockerを実行できる環境があれば、スクリプトを組まなくとも、
(1)プレーンなテキストをdocker run経由でstring-splitterコマンドで分かち書き、
(2)分かち書きテキストをdocker run経由でword2vecコマンドで学習、
(3)学習済みモデルをdocker run経由でword2vec-calcコマンドでベクトル演算、
の3ステップで簡単*1にword2vecを試すことができます。

また、word2vec-calcコマンドではファイルからの入力やCSV(カンマ区切り)やTSV(タブ区切り)の出力形式も用意しているので、 単語を一気に読み込ませて、全文検索データベース用の同義語データを得るといったことも簡単にできるようになっています。

Dockerのインストール

Dockerが入っていない場合、まずはDockerをインストールしてください。Dockerは今結構旬なプロダクトなので導入に困ることはないんじゃないかなと思います。 CentOSなら以下のように簡単に導入できます。

% rpm --import http://ftp.riken.jp/Linux/fedora/epel/RPM-GPG-KEY-EPEL
% yum localinstall -y http://ftp-srv2.kddilabs.jp/Linux/distributions/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
% yum install -y docker-io
% service docker start
% chkconfig docker on

イメージ構築

まずは、Dockerイメージを作成します。コンテナとファイル共有するために/var/lib/word2vecディレクトリを作成しています

% git clone git@github.com:naoa/docker-word2vec.git
% cd docker-word2vec
% mkdir /var/lib/word2vec
% docker build -t naoa/word2vec .

Dockerイメージの構築が完了すると、以下のようにしてコンテナにターミナル接続することができます。

% docker run -v /var/lib/word2vec:/var/lib/word2vec \
  -i -t naoa/word2vec /bin/bash
% exit

Docker越しのコマンドは非常に長くなってしまいますが、たとえば、以下のようにエイリアスを張れば短くすることができます。

% alias word2vec="docker run -v /var/lib/word2vec:/var/lib/word2vec \
  -a stdin -a stdout -a stderr -i naoa/word2vec word2vec"
% alias word2vec-calc="docker run -v /var/lib/word2vec:/var/lib/word2vec \
  -a stdin -a stdout -a stderr -i naoa/word2vec word2vec-calc"
% alias string-splitter="docker run -v /var/lib/word2vec:/var/lib/word2vec \
  -a stdin -a stdout -a stderr -i naoa/word2vec string-splitter"

以下の作業は、コンテナ上ではなくホスト上でエイリアスが張られている前提で進めます。なお、コンテナ上で共有ディレクトリ以外に保存したファイルはコミットしなければコンテナの終了とともに消えるため注意してください。

MeCab辞書のコンテナへの共有

word2vecでより意味のある関係を得たいのであれば、文書のタイトル等抽出しやすい箇所からキーワードを拾い上げたり、前回紹介したTermExtractなどを使って、MeCab辞書をカスタマイズしたほうがいいと思います。
MeCab辞書のカスタマイズが終了したら、MeCab辞書をホストとコンテナの共有フォルダ/var/lib/word2vec/にコピーします。

% cp -rf /usr/lib64/mecab/dic/naist-jdic/ /var/lib/word2vec/naist-jdic

プレーンテキストを分かち書き

string-splitterコマンドを使ってプレーンテキストをword2vecで学習させる用のテキストファイルに変換します。
デフォルトの設定では、<>タグ、改行コード、一部の記号(\,.;:&^/-#'"()[]{}])が除去され、NFKC正規化+アルファベットの大文字小文字変換されてから分かち書きされます。
ここではコンテナからでも読み取れるように分かち書き済みテキストは共有フォルダ/var/lib/word2vec/wakati.txtに出力されるようにしています。

% cat example/open_source.txt | \
  string-splitter --mecab_dic /var/lib/word2vec/naist-jdic \
  > /var/lib/word2vec/wakati.txt

また、カスタマイズしたMeCab辞書を使用するために--mecab_dicオプションで/var/lib/word2vec/naist-jdicを指定しています。

string-splitterコマンドは、このほかMeCabを使って日本語の活用形を基本形に戻したり英語版WordNetを使って英語の複数形や過去形を基本形に戻したりするオプションも含まれています。

詳細はGitHubを参照してください。

分かち書き済みテキストをトレーニング

分かち書き済みテキスト/var/lib/word2vec/wakati.txtを読み込んで、/var/lib/word2vec/learn.binに学習済みモデルファイルを出力します。

% word2vec -train /var/lib/word2vec/wakati.txt -output /var/lib/word2vec/learn.bin \
  -size 200 -window 5 -negative 0 -hs 1 -sample 1e-3 -threads 12 -binary 1

学習モデルでベクトルの演算

トレーニングが完了するとコンテナ上のword2vec-calcコマンドで/var/lib/word2vec/learn.binを読み込ませることによりベクトル演算することができます。 なお、word2vec-calcコマンドではdistanceコマンド等と違い、--file_pathでファイルを指定するようになっているため、注意してください*2

word2vec-calcコマンドでは、単語半角スペース演算子(+-)半角スペース単語・・・で自由にベクトルの足し引きができるようになっています。たぶん100個ぐらいつなげられると思います。単語1個の場合はdistanceコマンド相当、単語2 - 単語1 + 単語3にするとword-analogyコマンド相当になると思います。

試しに前回構築した特許文書を学習させたモデルを使って、ベクトルの足し算をしてみます。なお、ここでは、echoコマンドを使ってコンテナの標準入力に渡していますが対話的に実行することも可能です。

% echo "データベース + 車両" | word2vec-calc --file_path /var/lib/word2vec/learn.bin --output 1
>
Word: データベース  Position in vocabulary: 1228

Word: 車両  Position in vocabulary: 305
0.743926        車両データ
0.739224        運行情報データベース
0.722270        走行履歴記憶部
0.712590        走行履歴データ
0.712389        自車両情報取得部
0.711377        走行経路情報
0.709190        車両運行情報
0.708623        中古車データベース
0.708502        状況認識部
0.707862        自車両情報

「データベース」に「車両」を足すことによって、車両関係のデータベースに関連するぽいような言葉が得られました。

echo "筆記具 + 消す" | word2vec-calc --file_path /var/lib/word2vec/jpa_abs.bin --output 1
>
Word: 筆記具  Position in vocabulary: 6017

Word: 消す  Position in vocabulary: 22672
0.674807        消しゴム
0.657548        筆記
0.655728        筆記用具
0.644496        万年筆
0.620492        描線
0.616586        マーカーペン
0.606570        サインペン
0.602623        化粧直し
0.597417        消せる

「筆記具」に「消す」を足すと最上位には「消しゴム」が現れました。それ以外はあまりそれっぽくないのでたまたまかもしれませんが、以下のように「消しゴム」は「筆記具」からの距離が4位なので少なくとも「筆記具」に「消す」を足すことで「消しゴム」に近づいているものと思われます。

echo "筆記具" | word2vec-calc --file_path /var/lib/word2vec/jpa_abst5.bin --output 1
>
Word: 筆記具  Position in vocabulary: 6017
0.784545        ボールペン
0.765932        筆記
0.763255        万年筆
0.754146        消しゴム
0.732143        水性ボールペン
0.720880        水性インキ

今度はパソコンの部品の「ディスプレイ」と「ハードディスク」と「ネットワーク」と「キーボード」を足してみます。

echo "ディスプレイ + ハードディスク + ネットワーク + キーボード" | word2vecalc --file_path /var/lib/word2vec/jpa_abst5.bin --output 1
>
Word: ディスプレイ  Position in vocabulary: 1262

Word: ハードディスク  Position in vocabulary: 4647

Word: ネットワーク  Position in vocabulary: 547

Word: キーボード  Position in vocabulary: 3742
0.736513        デスクトップ
0.711561        コンピュータ装置
0.705553        パソコン
0.702222        インタフェース
0.699718        コンピュータ用
0.689817        マルチウインドウ
0.687212        インターフェース
0.685114        携帯型コンピュータ
0.682025        コンピュータ機器
0.678986        コンピュータ本体
0.676609        画面共有システム
0.676334        プログラマブル表示器
0.674826        デスクトップ型
0.672900        コンピュータ
0.671351        汎用コンピュータ

上位にパソコンぽいものが得られましたね。

「動画」から「動き」を引いて見ます。

% echo "動画 - 動き" | word2vec-calc --file_path /var/lib/word2vec/jpa_abst5in --output 1
>
Word: 動画  Position in vocabulary: 2047

Word: 動き  Position in vocabulary: 1561
0.491828        静止画ファイル
0.480281        動画ファイル
0.456786        動画データ
0.456166        映像ファイル
0.451632        再生時間取得手段

「消しゴム」と同じように最上位以外は動画要素満々ですが、以下のように「静止画データ」は「動画」から7位でしたので、一応「動き」が減算されて、「静止画ファイル」が上位表示されたように見えます。

% echo "動画" | word2vec-calc --file_path /var/lib/word2vec/jpa_abst5.bin --output 1
>
Word: 動画  Position in vocabulary: 2047
0.742014        動画データ
0.740587        動画像データ
0.732553        動画ファイル
0.707877        静止画と動画
0.705789        高精細静止画
0.699955        映像
0.697672        映像データ
0.687976        静止画データ

このようにword2vec-calcコマンドでは、単語の足し算、引き算を自由に組み合わせることができます。 このほかword2vec-calcコマンドでは、オプションで出力を正規表現でフィルタさせたりCSVやTSVで出力できるようにしたりしています。
また、ファイルから一括で解析させることもできるので、全文検索データベース用の同義語(シノニム)ファイルを作ったりしやすくしています。

詳細はGitHubを参照してください。

おわりに

Dockerファイルを作っておけば、C言語やC++でライブラリに依存しまくってプログラムを作ったとしても、 簡単に環境構築できるため、非常に便利だなぁと思いました。

上記で実験した特許のデータはコーパスサイズ6.4Gとあまり規模を大きくしていないので、さらにコーパスを大きくしたらどうなるかも実験したいと思っています。 特許のデータは全体で数百GiBになるので分野を区切ったり色々実験したいと思います。

次は連想検索エンジンGETAssocのDockerファイルを作る予定です。 GETAssocは、word2vecのスキップグラムのように単語間のつながりまでは考慮していませんが、 文書に含まれる単語の共起表現から連想されるワードを高速、且つ、結構な精度で抽出することができます。 また、類似文書検索や文書から簡単に専門的な文書を検索することができます(参考)。

連想検索エンジンGETAssocはインデックス(NWAM)を作るのがめんどうだったり、データストアがなかったりするのですが、 このあたりは国産の超高速な全文検索エンジンGroongaと連携させて楽にできるようにしたいと考えています。

*1:Dockerコンテナでの作業イメージが沸かない方には簡単じゃないかもしれません。また、バージョンによってはホストの標準入力をコンテナの標準入力に渡すのがうまくいかないかも。

*2:なぜ、このようにしたかと言うと、単語の演算式を標準入力から渡せるようにしたかったからです。