言語処理100本ノック-89:加法構成性によるアナロジー

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の89本目「加法構成性によるアナロジー」の記録です。
「加法構成性」ということでベクトル演算をして結果を求めます。有名な「王 + 女性 - 男性 = 王女」の計算ですね。「上司 - 有能 = ?」のような計算で、世の中のいろいろなことで試してみたい演算です。

# 参考リンク

|リンク|備考|
|:--|:--|
|[089.加法構成性によるアナロジー.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/09.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(I)/089.%E5%8A%A0%E6%B3%95%E6%A7%8B%E6%88%90%E6%80%A7%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A2%E3%83%8A%E3%83%AD%E3%82%B8%E3%83%BC.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:89](https://qiita.com/segavvy/items/2d21c9b5ab7e338dcf51)|言語処理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でインストールするだけです。

|種類|バージョン|
|:--|:--|
|numpy|1.17.4|
|pandas|0.25.3|

# 課題
## [第9章: ベクトル空間法 (I)](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch9)
>[enwiki-20150112-400-r10-105752.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r10-105752.txt.bz2)は，2015年1月12日時点の英語のWikipedia記事のうち，約400語以上で構成される記事の中から，ランダムに1/10サンプリングした105,752記事のテキストをbzip2形式で圧縮したものである．このテキストをコーパスとして，単語の意味を表すベクトル（分散表現）を学習したい．第9章の前半では，コーパスから作成した単語文脈共起行列に主成分分析を適用し，単語ベクトルを学習する過程を，いくつかの処理に分けて実装する．第9章の後半では，学習で得られた単語ベクトル（300次元）を用い，単語の類似度計算やアナロジー（類推）を行う．
>
>なお，問題83を素直に実装すると，大量（約7GB）の主記憶が必要になる． メモリが不足する場合は，処理を工夫するか，1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)を用いよ．

今回は*「1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)」*を使っています。

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

# 回答
## 回答プログラム [089.加法構成性によるアナロジー.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/09.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(I)/089.%E5%8A%A0%E6%B3%95%E6%A7%8B%E6%88%90%E6%80%A7%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A2%E3%83%8A%E3%83%AD%E3%82%B8%E3%83%BC.ipynb)

```python
import numpy as np
import pandas as pd

# 保存時に引数を指定しなかったので'arr_0'に格納されている
matrix_x300 = np.load('085.matrix_x300.npz')['arr_0']

print('matrix_x300 Shape:', matrix_x300.shape)

group_t = pd.read_pickle('./083_group_t.zip')


# 'vec("Spain") - vec("Madrid") + vec("Athens") のベクトル計算
vec = matrix_x300[group_t.index.get_loc('Spain')] \
      - matrix_x300[group_t.index.get_loc('Madrid')] \
      + matrix_x300[group_t.index.get_loc('Athens')]
vec_norm = np.linalg.norm(vec)

# コサイン類似度計算
def get_cos_similarity(v2):
    
    # ベクトルが全てゼロの場合は-1を返す
    if np.count_nonzero(v2) == 0:
        return -1
    else:
        return np.dot(v1, v2) / (v1_norm * np.linalg.norm(v2))

cos_sim = [get_cos_similarity(matrix_x300[i]) for i in range(len(group_t))]
print('Cosign Similarity result length:', len(cos_sim))

# インデックスを残してソート
cos_sim_sorted = np.argsort(cos_sim)

# 昇順でソートされた配列の1番最後から-10(-11)までを1件ずつ出力
for index in cos_sim_sorted[:-11:-1]:
    print('{}\t{}'.format(group_t.index[index], cos_sim[index]))
```

## 回答解説
今回のメインの部分です。
ただ足し算、引き算をしているだけです。

```python
# 'vec("Spain") - vec("Madrid") + vec("Athens") のベクトル計算
vec = matrix_x300[group_t.index.get_loc('Spain')] \
      - matrix_x300[group_t.index.get_loc('Madrid')] \
      + matrix_x300[group_t.index.get_loc('Athens')]
```

最後の出力結果です。
Spainから首都のマドリッドを引いて、アテネを足しているので、意味としてはギリシャが正解なのでしょうか。
ギリシャは12位でコサイン類似度は0.686でした。

```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
```