言語処理100本ノック-90(Gensim使用):word2vecによる学習

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の90本目「word2vecによる学習」の記録です。
今まで第9章でやってきた内容をパッケージ使って簡単にやってしまおう、という設問です。メモリ不足などを気にしながら必死にやってきた内容が3行程度のコードでできてしまうのは、拍子抜けな反面素晴らしさを痛感します。
今回は設問で指定されているGoogle社の[word2vec](https://code.google.com/p/word2vec/)を使わずにオープンソースの[Geinsim](https://radimrehurek.com/gensim/)を使っています。パッケージの更新頻度も多く、よく使われていると聞いたことがあるからです(にわか知識で、しっかりと調べていません)。

# 参考リンク

|リンク|備考|
|:--|:--|
|[090.word2vecによる学習.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/10.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(II)/090.word2vec%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:90](https://qiita.com/segavvy/items/890d34a40991dd634cdf)|言語処理100本ノックで常にお世話になっています|

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.15|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.6.9|pyenv上でpython3.6.9を使っています<br>3.7や3.8系を使っていないことに深い理由はありません<br>パッケージはvenvを使って管理しています|

上記環境で、以下のPython追加パッケージを使っています。通常のpipでインストールするだけです。

|種類|バージョン|
|:--|:--|
|gensim|3.8.1|
|numpy|1.17.4|

# 課題
## [第10章: ベクトル空間法 (II)](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch10)
> 第10章では，前章に引き続き単語ベクトルの学習に取り組む．

### 90. word2vecによる学習
> 81で作成したコーパスに対して[word2vec](https://code.google.com/p/word2vec/)を適用し，単語ベクトルを学習せよ．さらに，学習した単語ベクトルの形式を変換し，86-89のプログラムを動かせ．

# 回答
## 回答プログラム [090.word2vecによる学習.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/10.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(II)/090.word2vec%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92.ipynb)

```python
from pprint import pprint

from gensim.models import word2vec

corpus = word2vec.Text8Corpus('./../09.ベクトル空間法 (I)/081.corpus.txt')

model = word2vec.Word2Vec(corpus, size=300)
model.save('090.word2vec.model')

# 86. 単語ベクトルの表示
pprint(model.wv['United_States'])

# 87. 単語の類似度
print(np.dot(model.wv['United_States'], model.wv['U.S']) / (np.linalg.norm(model.wv['United_States']) * np.linalg.norm(model.wv['U.S'])))

# 88. 類似度の高い単語10件
pprint(model.wv.most_similar('England'))

# 89. 加法構成性によるアナロジー
# vec("Spain") - vec("Madrid") + vec("Athens")
pprint(model.wv.most_similar(positive=['Spain', 'Athens'], negative=['Madrid']))
```

## 回答解説
### 単語ベクトル生成
まずはファイル読込です。`Text8Corpus`関数を使っている例が多いなと思い、そもそも`Text8Corpus`とは何と思って調べました。
[記事「日本語版text8コーパスを作って分散表現を学習する」](https://hironsan.hatenablog.com/entry/japanese-text8-corpus)によるとtext8とは以下の処理をしたWikipediaのデータらしい。

- テキストと画像キャプションは保持
- テーブルや外国語バージョンへのリンクを除去
- 引用、脚注、マークアップを除去
- ハイパーテキストはアンカーテキストだけ保持。それ以外は除去
- 数字はつづりを変換。たとえば、"20"は"two zero"に変換
- 大文字を小文字に変換
- a-zの範囲に入らない文字はスペースに変換

大文字はあった気もするが、だいたい条件に合致している気がしたので`Text8Corpus`を使いました。

```python
corpus = word2vec.Text8Corpus('./../09.ベクトル空間法 (I)/081.corpus.txt')
```

あとは`Word2Vec`関数を使うだけで300次元の単語ベクトル完成です。4分弱で生成できました。すごい・・・
オプションは使わなかったですが、[gemsimのword2vecのオプション一覧](https://qiita.com/mergit/items/822dc49343c887019d44)がわかりやすかったです。

```python
model = word2vec.Word2Vec(corpus, size=300)
```

そして、後続のノックのためにファイル保存しておきます。

```python
model.save('090.word2vec.model')
```

そうすると以下の3ファイルができるようです。1つでないのが気持ち悪いです。

|ファイル|サイズ|
|:-:|:-:|
|090.word2vec.model|5MB|
|090.word2vec.model.trainables.syn1neg.npy|103MB|
|090.word2vec.model.wv.vectors.npy|103MB|

### 86. 単語ベクトルの表示
> 85で得た単語の意味ベクトルを読み込み，"United States"のベクトルを表示せよ．ただし，"United States"は内部的には"United_States"と表現されていることに注意せよ．

`model.wv`にベクトルが入っているので指定してあげるだけです。

```python
pprint(model.wv['United_States'])
```

```bash
array([ 2.3478289 , -0.61461514,  0.0478639 ,  0.6709404 ,  1.1090833 ,
       -1.0814637 , -0.78162867, -1.2584596 , -0.04286158,  1.2928476 ,
結果略
```

### 87. 単語の類似度
> 85で得た単語の意味ベクトルを読み込み，"United States"と"U.S."のコサイン類似度を計算せよ．ただし，"U.S."は内部的に"U.S"と表現されていることに注意せよ．

`model`を使って第9章と同じベクトル同士のコサイン類似度計算をします。
第9章では0.837516976284694だったので、より類似度が高い数値が出ています。

```python
print(np.dot(model.wv['United_States'], model.wv['U.S']) / (np.linalg.norm(model.wv['United_States']) * np.linalg.norm(model.wv['U.S'])))
```

```bash
0.8601596
```

### 88. 類似度の高い単語10件
> 85で得た単語の意味ベクトルを読み込み，"England"とコサイン類似度が高い10語と，その類似度を出力せよ．

`modst_similar`関数を使うだけで出力できます。

```python
pprint(model.wv.most_similar('England'))
```

```bash
[('Scotland', 0.7884809970855713),
 ('Wales', 0.7721374034881592),
 ('Ireland', 0.6838206052780151),
 ('Britain', 0.6335258483886719),
 ('Hampshire', 0.6147407293319702),
 ('London', 0.6021863222122192),
 ('Cork', 0.5809425115585327),
 ('Manchester', 0.5767091512680054),
 ('Liverpool', 0.5765234231948853),
 ('Orleans', 0.5624016523361206)]
```

ちなみに第9章での結果は以下の通りでしたが、今回はイギリスに関連する単語がより上位に出てきていて、より正しいデータが出力されていることがわかります。

```bash
Scotland    0.6364961613062289
Italy   0.6033905306935802
Wales   0.5961887337227456
Australia   0.5953277272306978
Spain   0.5752511915429617
Japan   0.5611603300967408
France  0.5547284075334182
Germany 0.5539239745925412
United_Kingdom  0.5225684232409136
Cheshire    0.5125286144779688
```

### 89. 加法構成性によるアナロジー
> 85で得た単語の意味ベクトルを読み込み，vec("Spain") - vec("Madrid") + vec("Athens")を計算し，そのベクトルと類似度の高い10語とその類似度を出力せよ．

`modst_similar`関数に`positive`と`negative`を渡すと計算して、類似度の高い10語を出力してくれます。

```python
pprint(model.wv.most_similar(positive=['Spain', 'Athens'], negative=['Madrid']))
```

```bash
[('Denmark', 0.7606724500656128),
 ('Italy', 0.7585107088088989),
 ('Austria', 0.7528095841407776),
 ('Greece', 0.7401891350746155),
 ('Egypt', 0.7314825057983398),
 ('Russia', 0.7225484848022461),
 ('Great_Britain', 0.7184625864028931),
 ('Norway', 0.7148114442825317),
 ('Rome', 0.7076312303543091),
 ('kingdom', 0.6994863748550415)]
```

ちなみに第9章での結果は以下の通りでしたが、今回はギリシャも4位に出てきていてより正しいデータが出力されていることがわかります。

```bash
Spain   0.8178213952646727
Sweden  0.8071582503798717
Austria 0.7795030693787409
Italy   0.7466099164394225
Germany 0.7429125848677439
Belgium 0.729240312232219
Netherlands 0.7193045612969573
Télévisions   0.7067876635156688
Denmark 0.7062857691945504
France  0.7014078181006329
```