青空文庫のデータを利用してWord2Vecを試してみた(Dockerfileつき)

# はじめに

時間があったのでWord2Vecを試してみたのでまとめます。
参考にさせていただいたのは下記記事です。

- [15分でできる日本語Word2Vec](https://qiita.com/makaishi2/items/63b7986f6da93dc55edd)
- [Word2Vecの学習済み日本語モデルを読み込んで使う](https://qiita.com/omuram/items/6570973c090c6f0cb060)

本記事ではパラメータのチューニングや細かい点についてはふれません。
Dockerfileは作成しておいたのでとりあえず環境を作って試してみたい方には最速でお試しできると思いますのでよかったらどうぞ。

# Word2Vecを試してみる

プログラムは[こちら](https://github.com/masayuki5160/word2vec-test)に公開しています。
READMEにもざっくりと記載していますがここではもう少し細かく記述していきます。

```
$ git clone git@github.com:masayuki5160/word2vec-test.git
$ cd word2vec-test

# build docker image from Dockerfile
$ docker build -t masayuki5160/python3 .
```

GitHubからリポジトリをcloneしてきたら登録してあるDockerfileを利用してdockerイメージのビルドをしていきます。
名前はmasayuki5160/python3としていますが好みのもので大丈夫です。

Dockerfileは以下の通りです。
gensimのインストールと青空文庫のデータ(「三四郎」を選択してます)をwgetしてきています。
青空文庫のデータは一旦/tmpに保存しています。

```Dockerfile:Dockerfile
FROM python:3.6

VOLUME /home/word2vec-test

RUN apt-get update && \
    apt-get -y install sudo vim unzip && \
    pip install --upgrade gensim && \
    pip install janome && \
    wget -O /tmp/794_ruby_4237.zip http://www.aozora.gr.jp/cards/000148/files/794_ruby_4237.zip && \
    unzip /tmp/794_ruby_4237.zip -d /tmp
```

ビルドが成功したらコンテナを起動します。
cloneしてきたリポジトリ内のpythonスクリプトを利用したいのでここではカレントディレクトリとコンテナ内のディレクトリのマッピングもついでにしてます。

```
# run docker image(masayuki5160/python3)
$ docker run -v $(pwd):/home/word2vec-test/ -p 5000:5000 -it masayuki5160/python3 /bin/bash
```

これで準備は完了したのでモデルを作成します。

```
# generate model, and save it. 
$ python createModel.py
```

useModel.pyでは上記で作成したモデルをloadしています。

```python:useModel.py
import gensim

model = gensim.models.Word2Vec.load('sanshiro.model')

# ベクトルを取り出す
print(model["世間"])

# 類似の単語を探す
print(model.most_similar("日本"))

# 単語の加減を計算して類似の単語を探す
print(model.most_similar(positive=['東京'], negative=['人']))
```

useModel.pyをしてみるとこんな感じに出力されます。
(あんまり整理してなくてすいませんが。。)

```
$ python useModel.py 
[ 0.59944725 -0.25200576  0.3852602   0.04730325  0.13314192  0.54461247
  0.0471745   0.391928   -0.7717757   0.2524366  -0.21390346 -0.5256151
 -0.34063724  0.1979729   0.701523    0.64141107  0.73465765  0.00901113
  0.96841425 -0.12650324  0.150137    0.26460552 -0.11457528 -0.6921538
 -0.50044954  0.2765003   0.27651525 -0.18274967 -0.0200913   0.10246506
  0.15906459 -1.0402849   0.5950975  -0.60760593 -0.8825854  -0.04164106
  0.23078144  0.11897992 -0.45213798  0.7351423   0.40106112  0.541665
  0.5919396   0.11623159 -0.36421928  1.0413207   0.3224229   0.7492864
  0.15403754 -0.39072967 -0.16116981 -0.15397906  0.21622561  0.08972786
  0.02676369  0.8212711   0.13864756 -0.26221758  0.2627808   0.30080497
  0.3675749  -0.46718222 -0.44438344  0.4253955  -0.13240519 -0.1371136
 -0.20774814  0.37195185  0.42730212 -0.7800508  -0.2636935  -0.33571747
  0.28241587 -0.30477676 -0.09428921 -0.27701733 -0.25254947  0.23744197
  0.16743279 -0.23830141  0.5384211   0.7084161  -0.6604415  -0.4665711
 -0.37388822  0.83966595 -0.37005633  0.42384714  0.22337277  0.08281619
 -0.7998488  -0.22002839 -0.05608908 -0.33207807  0.48652875  0.2967433
  0.3217622  -0.42405376  0.15407468  0.03133222]
/usr/local/lib/python3.6/site-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int):
[('界', 0.6109025478363037), ('堪える', 0.5765180587768555), ('社会', 0.571060299873352), ('圧迫', 0.5427102446556091), ('決心', 0.5345108509063721), ('有す', 0.5281721949577332), ('公', 0.5189093947410583), ('人生', 0.5097593069076538), ('感謝', 0.4961530268192291), ('説く', 0.4923514723777771)]
[('度胸', 0.41439834237098694), ('心持ち', 0.3946040868759155), ('要領', 0.3866702914237976), ('恐れ', 0.34306836128234863), ('違う', 0.32507050037384033), ('感じ', 0.3220743238925934), ('はじめ', 0.3191494047641754), ('かた', 0.30798274278640747), ('性質', 0.2992369830608368), ('いなか者', 0.29894697666168213)]
```

# webapi経由で指定した単語に類似した単語を取得してみる
せっかくなのでflaskを利用して簡単なwebapiも作ってみました。

```python:webapi.py
# -*- coding: utf-8 -*-
from flask import Flask, jsonify
import gensim

model = gensim.models.Word2Vec.load('sanshiro.model')
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

@app.route('/')
def index():
   return 'Hello flask'

# 類似の単語を取得する
@app.route('/similar/<string:word>')
def getSimilar(word):
	similarWords = model.most_similar(word)
	words = []
	for word in similarWords:
		words.append({'word':word[0], 'value':word[1]})
	return jsonify({'words': words})

#エラー時のリターン
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not found'}), 404

#サーバー起動
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)
```

以下のようにflaskサーバを起動します。

```
# start api server
$ python webapi.py
```

コンテナの起動時にポート5000番をマッピングしているのでhttp://localhost:5000/similar/[word] のURLで指定した単語に類似した単語を取得できます。
以下はhttp://localhost:5000/similar/男 としてローカルPCからDockerコンテナへアクセスし取得した結果です。

```json
{
  "words": [
    {
      "value": 0.41406938433647156, 
      "word": "じいさん"
    }, 
    {
      "value": 0.35695937275886536, 
      "word": "迷子"
    }, 
    {
      "value": 0.32412219047546387, 
      "word": "コート"
    }, 
    {
      "value": 0.31509068608283997, 
      "word": "女"
    }, 
    {
      "value": 0.31481245160102844, 
      "word": "ただ"
    }, 
    {
      "value": 0.3141239583492279, 
      "word": "あっち"
    }, 
    {
      "value": 0.3029976487159729, 
      "word": "連中"
    }, 
    {
      "value": 0.29525405168533325, 
      "word": "便所"
    }, 
    {
      "value": 0.2852158546447754, 
      "word": "はやす"
    }, 
    {
      "value": 0.274425208568573, 
      "word": "あぐら"
    }
  ]
}
```

# さいごに

まずは使ってみることでなんとなく雰囲気を掴めたのでよかったかなと思います。
Word2Vecについてもgensimについてもまだ理解が足りてないですが利用する流れはなんとなく掴めたのでデータを追加するなりパラメータのチューニングをしてみるなり試してみることにします。