素人の言語処理100本ノック:89


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

## 第9章: ベクトル空間法 (I)
>[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)を用いよ．

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

#### 出来上がったコード：

```python:main.py
# coding: utf-8
import pickle
from collections import OrderedDict
from scipy import io
import numpy as np

fname_dict_index_t = 'dict_index_t'
fname_matrix_x300 = 'matrix_x300'


def cos_sim(vec_a, vec_b):
	'''コサイン類似度の計算
	ベクトルvec_a、vec_bのコサイン類似度を求める

	戻り値：
	コサイン類似度
	'''
	norm_ab = np.linalg.norm(vec_a) * np.linalg.norm(vec_b)
	if norm_ab != 0:
		return np.dot(vec_a, vec_b) / norm_ab
	else:
		# ベクトルのノルムが0だと似ているかどうかの判断すらできないので最低値
		return -1


# 辞書読み込み
with open(fname_dict_index_t, 'rb') as data_file:
		dict_index_t = pickle.load(data_file)

# 行列読み込み
matrix_x300 = io.loadmat(fname_matrix_x300)['matrix_x300']

# vec("Spain") - vec("Madrid") + vec("Athens")とのコサイン類似度算出
vec = matrix_x300[dict_index_t['Spain']] \
		- matrix_x300[dict_index_t['Madrid']] \
		+ matrix_x300[dict_index_t['Athens']]
distances = [cos_sim(vec, matrix_x300[i])
		for i in range(0, len(dict_index_t))]

# 上位10件を表示
index_sorted = np.argsort(distances)
keys = list(dict_index_t.keys())
for index in index_sorted[:-11:-1]:
	print('{}\t{}'.format(keys[index], distances[index]))
```

#### 実行結果：
```console:実行結果
Spain	0.8915792748600528
Sweden	0.8719563254078373
Italy	0.8157221349558227
Austria	0.8086425542832402
Netherlands	0.7820356485764023
Denmark	0.7785976171354217
Belgium	0.7654520863664993
Greece	0.7513058649568729
Norway	0.749115358268825
France	0.7441934553247148
```

### 加法構成性によるアナロジー
加法構成性というのは、どうやらベクトルの足し算や引き算で意味を計算できることを指すようです。アナロジーとは類推のことで、ここではすでに確認できた関係を他のことに当てはめて推測することを指すようです。

[問題85](http://qiita.com/segavvy/items/f1a7f3200c3b771e8568)で作ったベクトルは加法構成性を持つので、差を計算すると単語の関係が取り出せます。例えば問題にあるように「Spain」から「Madrid」を引き算すると、「国と首都の関係のような使われ方をする単語」というような意味を取り出せます。これを利用して首都「Athens」に対する国を類推するのが今回の問題です。

　vec("Spain") - vec("Madrid") $ \fallingdotseq $ vec("？") - vec("Athens")

この関係が成り立つ想定のため、式を変形すると

　vec("？") $ \fallingdotseq $ vec("Spain") - vec("Madrid") + vec("Athens") 

となります。この右辺を計算して、そのベクトルに近い単語を[問題88](http://qiita.com/segavvy/items/26ec387217b030a15c21)のように調べればOKです。

### 実行結果について
国と首都の関係であれば正解は「Greece」ですが、結果は8位でした。ただ、結果にはヨーロッパの国だけが並んでいるので、ざっくり想定通りの類推ができているといえそうです。周辺の単語の情報だけでベクトル化しているため限界はありますが、なかなか面白いです。

　
90本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第9章で用いているデータのライセンスは[クリエイティブ・コモンズ 表示-継承 3.0 非移植](https://creativecommons.org/licenses/by-sa/3.0/legalcode)（[日本語訳](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)）です。*