疎行列のリストが与えられた場合、行列の各列(または行)間のコサイン類似度を計算する最良の方法は何ですか? n-choose-twoを繰り返したくないです。
入力行列は次のようになります:
A=
[0 1 0 0 1
0 0 1 1 1
1 1 0 1 0]
スパース表現は次のとおりです。
A =
0, 1
0, 4
1, 2
1, 3
1, 4
2, 0
2, 1
2, 3
Pythonでは、行列入力形式を使用するのが簡単です。
import numpy as np
from sklearn.metrics import pairwise_distances
from scipy.spatial.distance import cosine
A = np.array(
[[0, 1, 0, 0, 1],
[0, 0, 1, 1, 1],
[1, 1, 0, 1, 0]])
dist_out = 1-pairwise_distances(A, metric="cosine")
dist_out
与える:
array([[ 1. , 0.40824829, 0.40824829],
[ 0.40824829, 1. , 0.33333333],
[ 0.40824829, 0.33333333, 1. ]])
フルマトリックス入力にはこれで問題ありませんが、(マトリックスのサイズとスパース性のために)スパース表現から始めたいと思います。これをどのように達成するのが最善かについてのアイデアはありますか?前もって感謝します。
Sklearnを使用して、スパース行列の行でペアワイズコサイン類似度を直接計算できます。バージョン0.17では、スパース出力もサポートしています。
from sklearn.metrics.pairwise import cosine_similarity
from scipy import sparse
A = np.array([[0, 1, 0, 0, 1], [0, 0, 1, 1, 1],[1, 1, 0, 1, 0]])
A_sparse = sparse.csr_matrix(A)
similarities = cosine_similarity(A_sparse)
print('pairwise dense output:\n {}\n'.format(similarities))
#also can output sparse matrices
similarities_sparse = cosine_similarity(A_sparse,dense_output=False)
print('pairwise sparse output:\n {}\n'.format(similarities_sparse))
結果:
pairwise dense output:
[[ 1. 0.40824829 0.40824829]
[ 0.40824829 1. 0.33333333]
[ 0.40824829 0.33333333 1. ]]
pairwise sparse output:
(0, 1) 0.408248290464
(0, 2) 0.408248290464
(0, 0) 1.0
(1, 0) 0.408248290464
(1, 2) 0.333333333333
(1, 1) 1.0
(2, 1) 0.333333333333
(2, 0) 0.408248290464
(2, 2) 1.0
列ごとのコサインの類似性が必要な場合は、事前に入力行列を転置するだけです。
A_sparse.transpose()
次の方法は、scipy.spatial.distance.pdist
の約30倍高速です。大規模なマトリックスではかなり高速に動作します(十分なRAMがあると仮定)
スパース性を最適化する方法については、以下を参照してください。
# base similarity matrix (all dot products)
# replace this with A.dot(A.T).toarray() for sparse representation
similarity = numpy.dot(A, A.T)
# squared magnitude of preference vectors (number of occurrences)
square_mag = numpy.diag(similarity)
# inverse squared magnitude
inv_square_mag = 1 / square_mag
# if it doesn't occur, set it's inverse magnitude to zero (instead of inf)
inv_square_mag[numpy.isinf(inv_square_mag)] = 0
# inverse of the magnitude
inv_mag = numpy.sqrt(inv_square_mag)
# cosine similarity (elementwise multiply by inverse magnitudes)
cosine = similarity * inv_mag
cosine = cosine.T * inv_mag
問題が大規模なバイナリ設定問題の典型的なものである場合、1つの次元に他の次元よりもはるかに多くのエントリがあります。また、短い次元は、エントリ間の類似性を計算するエントリです。このディメンションを「アイテム」ディメンションと呼びましょう。
この場合、行に「アイテム」をリストし、 scipy.sparse
を使用してA
を作成します。次に、指示に従って最初の行を置き換えます。
問題が非定型である場合は、さらに修正が必要です。これらは、基本的なnumpy
操作をscipy.sparse
に相当するものに簡単に置き換える必要があります。
上記の方法をいくつか試しました。ただし、@ zbinsdによる実験には制限があります。実験で使用される行列のスパース性は非常に低く、実際のスパース性は通常90%を超えています。私の状態では、スパースの形状は(7000、25000)で、スパース性は97%です。方法4は非常に遅く、結果を得るのは容認できません。私は10秒で終了する方法6を使用します。驚くべきことに、以下の方法を試してみましたが、わずか0.247秒で終了しました。
import sklearn.preprocessing as pp
def cosine_similarities(mat):
col_normed_mat = pp.normalize(mat.tocsc(), axis=0)
return col_normed_mat.T * col_normed_mat
この効率的な方法は リンクの説明をここに入力 によってリンクされています
これらすべての答えを取り、1。各結果を検証し(以下のアサーションを参照)、2。どれが最も速いかを確認するスクリプトを作成しました。コードと結果は次のとおりです。
# Imports
import numpy as np
import scipy.sparse as sp
from scipy.spatial.distance import squareform, pdist
from sklearn.metrics.pairwise import linear_kernel
from sklearn.preprocessing import normalize
from sklearn.metrics.pairwise import cosine_similarity
# Create an adjacency matrix
np.random.seed(42)
A = np.random.randint(0, 2, (10000, 100)).astype(float).T
# Make it sparse
rows, cols = np.where(A)
data = np.ones(len(rows))
Asp = sp.csr_matrix((data, (rows, cols)), shape = (rows.max()+1, cols.max()+1))
print "Input data shape:", Asp.shape
# Define a function to calculate the cosine similarities a few different ways
def calc_sim(A, method=1):
if method == 1:
return 1 - squareform(pdist(A, metric='cosine'))
if method == 2:
Anorm = A / np.linalg.norm(A, axis=-1)[:, np.newaxis]
return np.dot(Anorm, Anorm.T)
if method == 3:
Anorm = A / np.linalg.norm(A, axis=-1)[:, np.newaxis]
return linear_kernel(Anorm)
if method == 4:
similarity = np.dot(A, A.T)
# squared magnitude of preference vectors (number of occurrences)
square_mag = np.diag(similarity)
# inverse squared magnitude
inv_square_mag = 1 / square_mag
# if it doesn't occur, set it's inverse magnitude to zero (instead of inf)
inv_square_mag[np.isinf(inv_square_mag)] = 0
# inverse of the magnitude
inv_mag = np.sqrt(inv_square_mag)
# cosine similarity (elementwise multiply by inverse magnitudes)
cosine = similarity * inv_mag
return cosine.T * inv_mag
if method == 5:
'''
Just a version of method 4 that takes in sparse arrays
'''
similarity = A*A.T
square_mag = np.array(A.sum(axis=1))
# inverse squared magnitude
inv_square_mag = 1 / square_mag
# if it doesn't occur, set it's inverse magnitude to zero (instead of inf)
inv_square_mag[np.isinf(inv_square_mag)] = 0
# inverse of the magnitude
inv_mag = np.sqrt(inv_square_mag).T
# cosine similarity (elementwise multiply by inverse magnitudes)
cosine = np.array(similarity.multiply(inv_mag))
return cosine * inv_mag.T
if method == 6:
return cosine_similarity(A)
# Assert that all results are consistent with the first model ("truth")
for m in range(1, 7):
if m in [5]: # The sparse case
np.testing.assert_allclose(calc_sim(A, method=1), calc_sim(Asp, method=m))
else:
np.testing.assert_allclose(calc_sim(A, method=1), calc_sim(A, method=m))
# Time them:
print "Method 1"
%timeit calc_sim(A, method=1)
print "Method 2"
%timeit calc_sim(A, method=2)
print "Method 3"
%timeit calc_sim(A, method=3)
print "Method 4"
%timeit calc_sim(A, method=4)
print "Method 5"
%timeit calc_sim(Asp, method=5)
print "Method 6"
%timeit calc_sim(A, method=6)
結果:
Input data shape: (100, 10000)
Method 1
10 loops, best of 3: 71.3 ms per loop
Method 2
100 loops, best of 3: 8.2 ms per loop
Method 3
100 loops, best of 3: 8.6 ms per loop
Method 4
100 loops, best of 3: 2.54 ms per loop
Method 5
10 loops, best of 3: 73.7 ms per loop
Method 6
10 loops, best of 3: 77.3 ms per loop
チェックアウトする必要がありますscipy.sparse
( リンク )。通常のマトリックスの使用方法と同様に、これらのスパースマトリックスに演算を適用できます。
こんにちは、このようにできます
temp = sp.coo_matrix((data, (row, col)), shape=(3, 59))
temp1 = temp.tocsr()
#Cosine similarity
row_sums = ((temp1.multiply(temp1)).sum(axis=1))
rows_sums_sqrt = np.array(np.sqrt(row_sums))[:,0]
row_indices, col_indices = temp1.nonzero()
temp1.data /= rows_sums_sqrt[row_indices]
temp2 = temp1.transpose()
temp3 = temp1*temp2
def norm(vector):
return sqrt(sum(x * x for x in vector))
def cosine_similarity(vec_a, vec_b):
norm_a = norm(vec_a)
norm_b = norm(vec_b)
dot = sum(a * b for a, b in Zip(vec_a, vec_b))
return dot / (norm_a * norm_b)
一度に1組のベクトルを渡す場合、この方法はsklearnの実装を使用するよりも多少速いようです。
Vaaliのソリューションの構築:
def sparse_cosine_similarity(sparse_matrix):
out = (sparse_matrix.copy() if type(sparse_matrix) is csr_matrix else
sparse_matrix.tocsr())
squared = out.multiply(out)
sqrt_sum_squared_rows = np.array(np.sqrt(squared.sum(axis=1)))[:, 0]
row_indices, col_indices = out.nonzero()
out.data /= sqrt_sum_squared_rows[row_indices]
return out.dot(out.T)
これは、スパース行列(できればcsr_matrix)を取り、csr_matrixを返します。かなり最小限のメモリオーバーヘッドでスパース計算を使用して、より集中的な部分を実行する必要があります。 私はそれを広範囲にテストしていないので、注意してください (更新:テストとベンチマークの結果、このソリューションに自信を持っています)
また、Waylonのソリューションのスパースバージョンは、どのソリューションが実際に優れているのかわからない場合に役立ちます。
def sparse_cosine_similarity_b(sparse_matrix):
input_csr_matrix = sparse_matrix.tocsr()
similarity = input_csr_matrix * input_csr_matrix.T
square_mag = similarity.diagonal()
inv_square_mag = 1 / square_mag
inv_square_mag[np.isinf(inv_square_mag)] = 0
inv_mag = np.sqrt(inv_square_mag)
return similarity.multiply(inv_mag).T.multiply(inv_mag)
どちらのソリューションもsklearn.metrics.pairwise.cosine_similarityと同等であるようです
:-D
更新:
現在、私は既存のCython実装に対して両方のソリューションをテストしました。 https://github.com/davidmashburn/sparse_dot/blob/master/test/benchmarks_v3_output_table.txt 3つのほとんどの時間の。
2つのステップで実行することをお勧めします。
1)A:列インデックス->非ゼロオブジェクトをマッピングするマッピングAを生成する
2)ゼロ以外のオカレンス(列)を持つ各オブジェクトi(行){k1、.. kn}に対して、ユニオンセットA [k1] U A [k2] U .. A [kn]の要素のコサイン類似度を計算します。
高いスパース性を備えた大きなスパース行列を想定すると、これはブルートフォースを大幅に後押しします