はじめに
この記事では,コサイン類似度の概要から,複数のベクトル群同士のコサイン類似度を一気に算出する方法をpythonコードを用いて解説します.
コサイン類似度とは
コサイン類似度を一言で表すと「2つのベクトルがどの程度似ているかを表す尺度」です.この尺度は,2つのベクトルの内積を2つのベクトルの大きさ(L2ノルム)で割ることによって計算できます.
コサイン類似度は,-1~1の範囲に正規化され,その値によって以下のように解釈が異なります.
- 1なら「2つのベクトルの成す角度が0度 → 同じ向きのベクトル → 完全に似ている」
- 0なら「2つのベクトルの成す角度が90度 → 独立・直行したベクトル → 似ている/似ていないのどちらにも無関係」
- -1なら「2つのベクトルの成す角度が180度 → 反対向きのベクトル → 完全に似ていない」
2つのベクトルの大きさにかかわらず,2つのベクトルの向きが近いほど類似度が高くなるということがポイントです.つまり,コサイン類似度は,2つのベクトル間の数値的な大きさを考慮しない場合に使用されます.
数式
2つのベクトル同士のコサイン類似度の数式は,以下の「2つのベクトル同士の内積」の公式から導けます.
\vec{a}・\vec{b} = |\vec{a}| \hspace{2pt} |\vec{b}| \cos\theta \\
= ||a|| \hspace{2pt} ||b|| \cos(a,b)
なお,|<ベクトル>|は「ベクトルの大きさ」を表し,||<ベクトル>||はベクトル空間における「長さ・距離」を表現する概念であるノルム(norm)を意味し,この公式におけるノルムはユークリッド距離であるL2ノルムを表します.
上記の公式を変形すると以下になります.これがコサイン類似度の数式となり,a = (a1, a2, …, an),b = (b1, b2, …, bn)のようなn次元に拡張したベクトルのコサイン類似度を算出できます.
\begin{align}
cos(a,b)&= \frac{a・b}{||a|| \hspace{2pt} ||b||}\\
&= \frac{\sum_{i=1}^{n}a_ib_i}{\sqrt{\sum_{i=1}^{n}a_i^2}\sqrt{\sum_{i=1}^{n}b_i^2}}
\end{align}
上記の公式をPythonコードで具体例と共に表すと以下になります.
import numpy as np
# データ作成
a = [1, 1, 0]
b = [1, 0, 1]
# コサイン類似度計算
cos = np.dot(a, b)/(np.sqrt(np.dot(a, a))*np.sqrt(np.dot(b, b)))
print(cos)
実行結果は,0.49(小数第3位以下切り捨て)となります.
複数のベクトル群同士のコサイン類似度
そして,ここからが本題です.
以下のような「おいしさ」「甘さ」「辛さ」という3つの列を持つ「3行×3列」「4行×3列」の食品群行列ベクトル(dataframe)があるとします.
おいしさ | 甘さ | 辛さ | |
---|---|---|---|
食品1 | 5 | 2 | 3 |
食品2 | 4 | 5 | 2 |
食品3 | 1 | 2 | 5 |
おいしさ | 甘さ | 辛さ | |
---|---|---|---|
食品4 | 5 | 4 | 3 |
食品5 | 3 | 5 | 2 |
食品6 | 2 | 5 | 3 |
食品7 | 3 | 1 | 1 |
食品群行列ベクトルを作成するためのコードが以下になります.
import pandas as pd
df1 = pd.DataFrame({'おいしさ': [5, 4, 1],
'甘さ': [2, 5, 2],
'辛さ': [3, 2, 5]},
index=['食品1', '食品2', '食品3'])
df2 = pd.DataFrame({'おいしさ': [5, 3, 2, 3],
'甘さ': [4, 4, 5, 1],
'辛さ': [3, 2, 3, 1]},
index=['食品4', '食品5', '食品6', '食品7'])
今から,この2つの食品群同士のコサイン類似度について一気に算出してみます.算出手順は以下です.
- 行列の片方を転置(行と列の入れ替え)して積を求める
- 手順1を2つの行列ベクトルと行列ノルムに適用
- 手順2の計算結果からコサイン類似度を算出
# 行列ベクトルの片方を転置して積を求める
df_dot = df1.dot(df2.T)
# 行列ノルムを求める
df1_norm = pd.DataFrame(np.linalg.norm(df1.values, axis=1), index = df1.index)
df2_norm = pd.DataFrame(np.linalg.norm(df2.values, axis=1), index = df2.index)
# 行列ノルムの片方を転置して積を求める
df_norm = df1_norm.dot(df2_norm.T)
# コサイン類似度を算出
df_cos = df_dot/df_norm
print(df_cos)
上記のコードを実行すれば,以下のようにコサイン類似度を算出できます.(小数第3位以下切り捨て)
食品4 | 食品5 | 食品6 | 食品7 | |
---|---|---|---|---|
食品1 | 0.96 | 0.87 | 0.76 | 0.97 |
食品2 | 0.96 | 0.99 | 0.94 | 0.85 |
食品3 | 0.72 | 0.71 | 0.79 | 0.55 |
食品2と食品5(0.99)が似ていて,食品3と食品7(0.55)が似ていないことが分かりましたね!
おまけ
先ほどの結果を用いて,コサイン類似度を高い順にソートするためのコードが以下です.
# コサイン類似度をリストに格納
cos_li = [['{0}×{1}'.format(index, col), value] for index, row in df_cos.iterrows() for col,value in row.iteritems()]
# コサイン類似度の高い順にソート
cos_sort_li = sorted(cos_li, key=lambda x: x[1], reverse=True)
# dataframeに変形
df_cos_sort = pd.DataFrame(cos_sort_li, columns=['ベクトル', 'コサイン類似度'])
print(df_cos_sort)
実行結果は以下です.(小数第3位以下を切り捨て)
ベクトル | コサイン類似度 |
---|---|
食品2×食品5 | 0.99 |
食品1×食品7 | 0.97 |
食品2×食品4 | 0.96 |
食品1×食品4 | 0.96 |
食品2×食品6 | 0.94 |
食品1×食品5 | 0.87 |
食品2×食品7 | 0.85 |
食品3×食品6 | 0.79 |
食品1×食品6 | 0.76 |
食品3×食品4 | 0.72 |
食品3×食品5 | 0.71 |
食品3×食品7 | 0.55 |
コサイン類似度の結果がかなり見やすくなりましたね!
最後に
最後までご拝読いただきありがとうございます!少しでもコサイン類似度についての理解が深まったなら幸いです.