>開発>python>[Pythonで数学]コサイン距離とコサイン類似度

コサイン距離

コサイン距離は、2つのベクトルがどのくらい違う方向を向いているのかを表し、0から2の範囲の値をとる。0であれば「最も似ている」、2であれば「最も似ていない」、ということになる。ベクトルの長さは関係なく、なす角によって2つのベクトルの方向性の違いがわかる。

ベクトルx : \(\displaystyle x = \left( \begin{array}{c} x_1 \\ x_2 \\ \vdots \\ x_n \end{array}\right) \)

ベクトルy : \(\displaystyle y = \left( \begin{array}{c} y_1 \\ y_2 \\ \vdots \\ y_n \end{array}\right) \)

コサイン距離 : \(\displaystyle d(x, y) = 1 – \cos\theta = 1 – \frac{x \cdot y}{||x||||y||}\)

Pythonでコサイン距離を求める

ユーザーA、B、C、Dの1週間の食事(7日×3食)で肉と魚どちらが多いのかアンケートを取り、肉と魚どちらの傾向が強いかをベクトル図で表してみる。

from matplotlib import pylab as plt

fig, ax = plt.subplots()
plt.xlabel('Meat')
plt.xlim([0, 20])
plt.ylabel('Fish')
plt.ylim([0, 20])

meal= {'A': [12, 4], 'B': [13, 3], 'C': [3, 12], 'D': [6, 12]}

for i, j in meal.items():
    ax.annotate(i, xy=(j[0], j[1]))
    ax.quiver(0, 0, j[0], j[1], angles='xy', scale_units='xy', scale=1)

AとBは肉を食べる傾向にあり、CとDは魚を食べる傾向があるように見える。

次は、コサイン距離を求めてみる。

import numpy as np
import pandas as pd

def cosine(x, y):
    # コサイン距離
    d = 1 - x.dot(y) / (np.linalg.norm(x) * np.linalg.norm(y))
    return d

meal = {'A': np.array([12, 4]), 'B': np.array([13, 3]), 'C': np.array([3, 12]), 'D': np.array([6, 12])}

idx = list()
distance_list = list()

for i in meal.keys():
    for j in meal.keys():
        idx.append('{} & {}'.format(i, j))
        distance_list.append([cosine(meal[i], meal[j])])

pd.DataFrame(distance_list, index=idx, columns=['コサイン距離'])
コサイン距離
A & A2.220446e-16
A & B4.504527e-03
A & C4.631245e-01
A & D2.928932e-01
B & A4.504527e-03
B & B0.000000e+00
B & C5.455297e-01
B & D3.631186e-01
C & A4.631245e-01
C & B5.455297e-01
C & C-2.220446e-16
C & D2.381294e-02
D & A2.928932e-01
D & B3.631186e-01
D & C2.381294e-02
D & D1.110223e-16

コサイン距離では、BとCが一番離れておりグラフを見てもBとCが一番離れていると直感的にわかる。コサイン距離の値は0~2をとります。従って、上記では指数表示となっていますが全て0~2の範囲内です。

scipyでのコサイン距離

import scipy.spatial

meal= {'A': [12, 4], 'B': [13, 3], 'C': [3, 12], 'D': [6, 12]}
result = scipy.spatial.distance.cosine(meal['B'], meal['C'])
print(result)
0.5455296583121261

指数表示でなく小数点で表示されているが、BとCのコサイン距離と結果が同じになっている。

コサイン類似度

2つのデータを2本のn次元のベクトルとすると、コサイン距離が大きいということは、2つのベクトルは類似していないということであり、これは相関関係が小さいと言うことができる。

Pythonでコサイン類似度

まず、相関関係を求めてみる。データセットはKaggleから頂いた(2021-22 NBA Season Active NBA Players)を使ってみる。全NBA選手の身長と体重のデータセットです。Muhammet Ali Büyüknacarさん、度々ありがとうございます。

import scipy
from scipy.stats import pearsonr
from matplotlib import pylab as plt

# NBA選手
players = pd.read_csv('データセットのcsvのパス')

# グラフ描画
plt.scatter(players['Height_i'], players['Weight'])

# 身長と体重の相関関係
result = pearsonr(players['Height_i'], players['Weight'])[0]
print(result)
0.3136257663596601

結果からは身長と体重の相関関係はそれほどなさそうです。

次にコサイン類似度を求めてみる。コサイン距離は0~2の範囲をとるので、1からコサイン距離を引くとコサイン距離の逆、つまりコサイン類似度を表すことになる。また、コサイン類似度を求める前に、全選手の身長、体重からそれぞれ平均値を差し引き、平均身長と平均体重を0にしておく。

import scipy

# NBA選手
players = pd.read_csv('/content/drive/MyDrive/nba_players.csv')

# 全選手の身長と体重から平均値を差し引く
players['Height_i'] = players['Height_i'] - np.mean(players['Height_i'])
players['Weight'] = players['Weight'] - np.mean(players['Weight'])

# コサイン距離
d = scipy.spatial.distance.cosine(players['Height_i'], players['Weight'])
print(d)

# コサイン類似度
s = 1 - scipy.spatial.distance.cosine(players['Height_i'], players['Weight'])
print(s)
コサイン距離:0.6863742336403398
コサイン類似度:0.3136257663596602

相関関係と同じ値が出力された。

数学

記事を読んでいただきありがとうございました。

Page Top