言語処理100本ノック-88:類似度の高い単語10件

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の88本目「類似度の高い単語10件」の記録です。
すべての単語から似たやつを抜き出します。これまた、自分のメールボックスや議事録等からやってみたい処理ですね。
技術的には前回の内容とほぼ同じです。

# 参考リンク

|リンク|備考|
|:--|:--|
|[088.類似度の高い単語10件.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)/088.%E9%A1%9E%E4%BC%BC%E5%BA%A6%E3%81%AE%E9%AB%98%E3%81%84%E5%8D%98%E8%AA%9E10%E4%BB%B6.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:88](http://qiita.com/segavvy/items/26ec387217b030a15c21)|言語処理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)」*を使っています。

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

# 回答
## 回答プログラム [088.類似度の高い単語10件.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)/088.%E9%A1%9E%E4%BC%BC%E5%BA%A6%E3%81%AE%E9%AB%98%E3%81%84%E5%8D%98%E8%AA%9E10%E4%BB%B6.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)

# 'Englandの単語ベクトルを読み込み、ノルムも計算
v1 = matrix_x300[group_t.index.get_loc('England')]
v1_norm = np.linalg.norm(v1)


# コサイン類似度計算
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番最後から-11(-12)までを1件ずつ出力(トップはEngland自身)
for index in cos_sim_sorted[:-12:-1]:
    print('{}\t{}'.format(group_t.index[index], cos_sim[index]))
```

## 回答解説
コサイン類似度計算部分を関数にしました。`count_nonzero`関数で判定して、ベクトルがすべてゼロの場合は-1を返すようにしています。

```python
# コサイン類似度計算
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))
```

配列に対して内包表記で一気に結果を出しています。

```python
cos_sim = [get_cos_similarity(matrix_x300[i]) for i in range(len(group_t))]
```

上記の計算に対して、numpyだとこっちの`apply_along_axis`を使った方が速いかとも思いましたが、むしろ遅かったので非採用です。

```python
cos_sim = np.apply_along_axis(get_cos_similarity, 1, matrix_x300)
```

最後の出力結果です。ScotlandやItalyなどが上位にいます。
Japanもいるのが意外です。島国だからなのでしょうか。

```bash
England	1.0000000000000002
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
```