はじめに
この記事は完全に自分用の備忘録となります。
記事の本文ではnumpyを使ってcos類似度を計算する関数を3つほど紹介しています。
主にノルムがゼロの場合の対処方法が異なるものを紹介しています。
本文
cos類似度とは
cos類似度とはベクトル間の類似度を求める方法で以下のような式で定義されます。
この値が1に近いほどベクトルが類似しており、-1に近いほどベクトルが異なることを意味します。
\
\text{cosine_similarity}(A, B) = \frac{A \cdot B}{\|A\| \|B\|}
\
実装
ここで扱う問題点はベクトルのノルムがゼロとなるケースです。筆者は文書分類を行う際に、空の文書を扱う際にノルムがゼロになるケースがありました。
ここで実装するのは以下の3パターンです。
- ノルムがゼロの場合の対策をしない
- ノルムがゼロの場合にも値が得られるようにする
- エラーを返して終了する
対策なし
def cos_sim1(v1: np.array, v2: np.array):
sim = np.dot(v1, v2)/(np.linalg.norm(v1) * np.linalg.norm(v2))
return sim
normがゼロの時はnanが帰ってきます。
単一の値だけ欲しい時は許容できるかもしれませんが、類似度順で並べ替える際などは扱いにくいでしょう。
nan
はどの値との比較に対してもFalseを返す値なので、比較の際は注意です。
小さな値を挿入する
def cos_sim2(v1: np.array, v2: np.array, eps=1e-8) -> float:
v1_norm = np.linalg.norm(v1)
v2_norm = np.linalg.norm(v2)
v1_v2_dot = np.dot(v1, v2)
sim = v1_v2_dot/(v1_norm * v2_norm + eps)
return sim
こちらの書方は『ゼロから作るDeepLearning』に掲載されていたものです。
epsという小さな値を分母に足すことで
- ノルムがゼロでない場合→丸め誤差により吸収
- ノルムがゼロの場合→epsによりゼロ割算を防ぐ
という挙動になります。
ただ、ベクトルがゼロベクトルである場合が「データが存在しないから0」とかだと類似度が0と返されるのは嬉しくないですね。また、norm 0のベクトル同士での計算も類似度が1となります。ここでは1を返して欲しい気持ちます。
例外を返す
私は基本この関数を利用しています。ゼロ除算が発生する際にZeroDivisionErrorを返すようにすることで、対策を行う実装です。
def cos_sim3(v1: np.array, v2: np.array) -> float:
v1_norm = np.linalg.norm(v1)
v2_norm = np.linalg.norm(v2)
if v1_norm == 0 or v2_norm == 0:
raise ZeroDivisionError("one of the norms is 0")
v1_v2_dot = np.dot(v1, v2)
sim = v1_v2_dot/(v1_norm * v2_norm)
return sim
最後に
他にも様々な対策方法が考えられると思うので、この例はあくまでも1例としてお考えください。
より良い実装が思いついた方はコメント等で教えてくれると嬉しいです😀