はじめに
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と連携させて楽にできるようにしたいと考えています。