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


[言語処理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)からどうぞ。

## 第10章: ベクトル空間法 (II)
>第10章では，前章に引き続き単語ベクトルの学習に取り組む．

### 95. WordSimilarity-353での評価
>94で作ったデータを用い，各モデルが出力する類似度のランキングと，人間の類似度判定のランキングの間のスピアマン相関係数を計算せよ．

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

```python:main.py
# coding: utf-8
import numpy as np

fname_input = 'combined_out.tab'

with open(fname_input, 'rt') as data_file:

	# 類似度の配列作成
	human_score = []
	my_score = []
	N = 0

	for line in data_file:
		cols = line.split('\t')
		human_score.append(float(cols[2]))
		my_score.append(float(cols[3]))
		N += 1

# ソート
human_index_sorted = np.argsort(human_score)
my_index_sorted = np.argsort(my_score)

# 順位の配列作成
human_order = [0] * N
my_order = [0] * N
for i in range(N):
	human_order[human_index_sorted[i]] = i
	my_order[my_index_sorted[i]] = i

# スピアマン相関係数算出
total = 0
for i in range(N):
	total += pow(human_order[i] - my_order[i], 2)
result = 1 - (6 * total) / (pow(N, 3) - N)

print(result)
```

#### 実行結果：

##### [問題85](http://qiita.com/segavvy/items/f1a7f3200c3b771e8568)の単語ベクトルに対する結果

```console:問題85の単語ベクトルに対する結果
0.22645511508225769
```

##### [問題90](http://qiita.com/segavvy/items/890d34a40991dd634cdf)の単語ベクトルに対する結果

```console:問題90の単語ベクトルに対する結果
0.5013384068756902
```

### スピアマンの順位相関係数

スピアマン相関係数は、今回の問題のような2つのランキングに対して、どれくらい相関があるかを示す値です。詳細は「スピアマンの順位相関係数」でググるとたくさん出てくるので割愛しますが、次の式で求めることができます。

$$ \rho = 1 - \frac{6 \sum D^2}{N^3 - N} $$

ここで$ D $は個々のデータに対する2つのランキングの順位の差で、$ N $はデータ数です。最大が1で、値が大きいほど相関があります。今回の問題では値が大きければ大きいほど、人間の判断に近いランキングであると言えます。

$ D $を求めるためには、まず[問題94](http://qiita.com/segavvy/items/4c3295bf2c0321bea5e2)で作った「combined_out.tab」の各行に対して、人間の類似度における順位と単語ベクトルにおける順位を求める必要があります。単純にソートしてしまうと元の行が分からなくなって順位差が求められなくなってしまうので、[問題75](http://qiita.com/segavvy/items/5ded025346c9621aedb2)で使った[`Numpy.argsort()`](https://docs.scipy.org/doc/numpy-dev/reference/generated/numpy.argsort.html#numpy.argsort)でソート後のインデックスを求めて、それを使って新たに各行に対する順位の配列を作る形にしてみました。

結果はword2vecの圧勝です。word2vec、すごいですね。

　
*（2017年04月23日の18:01追記）
shiracamusさんにアドバイスいただきました。データをクラス化すれば後からそれぞれの順位を追加できるので、もっとスッキリしたコードになります。他にもtotalの算出部分やべき乗の演算子`**`などアドバイスいただきました。以下、修正いただいたコードです。ありがとうございます！*

```python:main2.py（shiracamusさんにアドバイスいただいた版）
# coding: utf-8

fname_input = 'combined_out.tab'

class Data:
    def __init__(self, human_score, my_score):
        self.human_score = human_score
        self.my_score = my_score

    def __repr__(self):
        return 'Data%s' % repr(self.__dict__)

# データ配列作成
with open(fname_input) as data_file:
    def read_data():
        for line in data_file:
            word1, word2, human_score, my_score = line.split('\t')
            yield Data(float(human_score), float(my_score))
    data = list(read_data())

# 順位付け
data_sorted_by_human_score = sorted(data, key=lambda data: data.human_score)
for order, d in enumerate(data_sorted_by_human_score):
    d.human_order = order

data_sorted_by_my_score = sorted(data, key=lambda data: data.my_score)
for order, d in enumerate(data_sorted_by_my_score):
    d.my_order = order

# スピアマン相関係数算出
N = len(data)
total = sum((d.human_order - d.my_order) ** 2 for d in data)
result = 1 - (6 * total) / (N ** 3 - N)

print(result)
```

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

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