一部分に重みを加えたベクトルの cos 類似度の計算
※あくまで本記事が役に立つのはレアケースかと思います.
組み合わせの数,ベクトルの次元数および重みづけする次元数を考慮してから読んで欲しい記事です.
後輩のアルゴリズムがどうしても計算量がかかってしまうとのことで,cos 類似度の再計算を避ける方法として1つ出した案です.ipynb は github に公開しています. github.com
初期設定
import numpy as np import copy # 重みづけする値 weight = 20 # 重みづけする index indexes = [j for j in range(2, 4)] indexes
[2, 3]
ベクトルの生成
x = np.random.randn(2, 100) x[:, :5]
array([[ 1.30042348, -1.82751569, 0.84084007, 1.88565653, -0.24484313], [-1.26761704, 0.86831757, -0.44518288, 0.33844744, -0.40704083]])
一部分に重みを加えたベクトルの生成(チェックのため)
w = copy.copy(x) w[:, indexes] *= weight w[:, :5]
array([[ 1.30042348, -1.82751569, 16.81680149, 37.71313064, -0.24484313], [-1.26761704, 0.86831757, -8.90365757, 6.76894872, -0.40704083]])
cos 類似度の関数の定義
# linalg.norm でも出せますが,この方が後々楽なので def cos_sim(x, y): return (x @ y.T)/(np.sqrt(x @ x.T)*np.sqrt(y @ y.T))
cos 類似度の関数の計算
cos_sim(x[0], x[1])
-0.12419248186849967
一部分に重みを加えたベクトルの cos 類似度を計算するための関数の定義
dot の場合であれば,重みによって変わった値だけ計算すればよいです.
- 例えば,<1, 3, 4>というベクトルの dot 積は26です.
- 3という値を重み2によって2倍したベクトル<1, 6, 4>の dot 積は53です.
- この時の変化量は (2の2乗 - 1)* (3の2乗)=27すなわち((重みの2乗)-1) * (値の2乗)=変化量によって求めることができます.
要するに,元の26という値を持っていれば,変化した値のみを計算することで一部分に重みを加えたベクトルの dot 積が出せます.
こうすることで,ベクトルの全ての要素に対する計算を避けることができ,計算が繰り返される場合には計算量が削減できます.
同様に(大変なので,すいません) cos 類似度の分母も変更すると次の関数のようになります.
def weight_cos_sim(x, y, indexes, w): return (x @ y.T + (w**2-1) * np.sum(x[indexes] * y[indexes].T) )/\ (np.sqrt(x @ x.T + (w**2-1) * np.sum(x[indexes] * x[indexes].T))*\ np.sqrt(y @ y.T + (w**2-1) * np.sum(y[indexes] * y[indexes].T)))
(重みを再利用する場合)一部分に重みを加えたベクトルのcos 類似度を計算するための関数の定義
def reusedweight_cos_sim(x, y, values, indexes, w): return (values[0] + (w**2-1) * np.sum(x[indexes] * y[indexes].T) )/\ (np.sqrt(values[1] + (w**2-1) * np.sum(x[indexes] * x[indexes].T))*\ np.sqrt(values[2] + (w**2-1) * np.sum(y[indexes] * y[indexes].T)))
一部分に重みを加えたベクトルの cos 類似度の関数の計算
# チェック cos_sim(w[0], w[1])
0.14208709983961829
# 本番 weight_cos_sim(x[0], x[1], indexes, weight)
0.14208709983961826
reused
# (重みを再利用する場合の)本番 reusedweight_cos_sim(x[0], x[1], [x[0] @ x[1].T, x[0] @ x[0].T, x[1] @ x[1].T], indexes, weight)
0.14208709983961826
まとめ
以上のような形で,同じ結果を得ることができました. 実際に,この関数を用いて効率が良くなるのは,組み合わせの数が多い場合です.
組み合わせが多い場合,計算済みの値を用いて計算を効率化できます. 雑ですが以上です