![](/img/trans.png)
[英]Pyspark: What is the Fastest way to Calculate Cosine Similarity against a Column of Vectors
[英]What's the fastest way in Python to calculate cosine similarity given sparse matrix data?
給定一個稀疏矩陣列表,計算矩陣中每一列(或行)之間的余弦相似度的最佳方法是什么? 我寧願不重復 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
如果您的問題是大規模二元偏好問題的典型問題,那么您在一個維度中的條目比另一維度多得多。 此外,短維度是您要計算其條目之間相似性的維度。 我們將此維度稱為“項目”維度。
如果是這種情況,請在行中列出您的“項目”並使用scipy.sparse
創建A
。 然后按照指示替換第一行。
如果您的問題不典型,則需要進行更多修改。 這些應該是基本numpy
操作與scipy.sparse
等價物的非常簡單的替換。
我已經嘗試了上面的一些方法。 但是,@zbinsd 的實驗有其局限性。 實驗中使用的矩陣稀疏度極低,而實際稀疏度通常在90%以上。 在我的情況下,稀疏的形狀為 (7000, 25000),稀疏度為 97%。 方法 4 非常慢,我不能容忍得到結果。 我使用方法6,它在10秒內完成。 令人驚訝的是,我嘗試了下面的方法,它僅在 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
嗨,你可以這樣做
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
基於 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看起來第一個算法在大多數情況下表現最好的三個.
您應該查看scipy.sparse 。 您可以像使用普通矩陣一樣對這些稀疏矩陣應用運算。
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)
如果一次傳入一對向量,這種方法似乎比使用 sklearn 的實現要快一些。
我建議分兩步運行:
1)生成映射A的映射A:列索引->非零對象
2)對於每個對象 i(行)具有非零出現(列){k1,..kn} 計算余弦相似度,僅針對聯合集 A[k1] UA[k2] U.. A[kn] 中的元素
假設一個具有高稀疏度的大稀疏矩陣,這將比蠻力獲得顯着提升
@jeff 的解決方案已更改
作為 scikit-learn 1.1.2 的版本,您不需要在 cosine_similarity 之前使用cosine_similarity
的sparse
。
您只需要cosine_similarity
from typing import Tuple
import numpy as np
import perfplot
import scipy
from sklearn.metrics.pairwise import cosine_similarity as cosine_similarity_sklearn_internal
from scipy import spatial
from scipy import sparse
import sklearn.preprocessing as pp
target_dtype = "float16"
class prettyfloat(float):
def __repr__(self):
return "%.2f" % self
def cosine_similarity_sklearn(x):
return cosine_similarity_sklearn_internal(x)
def cosine_similarity_sklearn_sparse(x):
x_sparse = sparse.csr_matrix(x)
return cosine_similarity_sklearn_internal(x_sparse)
def cosine_similarity_einsum(x, y=None):
"""
Calculate the cosine similarity between two vectors.
if x == y, only use x
"""
# cosine_similarity in einsum notation without astype
normed_x = x / np.linalg.norm(x, axis=1)[:, None]
normed_y = y / np.linalg.norm(y, axis=1)[:, None] if y else normed_x
return np.einsum("ik,jk->ij", normed_x, normed_y)
def cosine_similarity_scipy(x, y=None):
"""
Calculate the cosine similarity between two vectors.
if x == y, only use x
"""
return 1 - spatial.distance.cosine(x, x)
def setup_n(n) -> Tuple[np.ndarray, np.ndarray]:
nd_arr = np.random.randn(int(2 ** n), 512).astype(target_dtype)
return nd_arr
def equality_check(a, b):
if type(a) != np.ndarray:
a = a.todense()
if type(b) != np.ndarray:
b = b.todense()
return np.isclose(a.astype(target_dtype), b.astype(target_dtype), atol=1e-3).all()
fig = perfplot.show(
setup=setup_n,
n_range=[k for k in range(1, 10)],
kernels=[
cosine_similarity_sklearn,
cosine_similarity_sklearn_sparse,
cosine_similarity_einsum,
# cosine_similarity_scipy,
],
labels=["sk-def", "sk+sparse", "einsum"],
logx=False,
logy=False,
xlabel='2^n',
equality_check=equality_check,
)
使用 perfplot,它顯示,`來自輸入 import Tuple
import numpy as np import perfplot import scipy from sklearn.metrics.pairwise import cosine_similarity` 是最好的。
在scikit-learn==1.1.2,1.1.3
float64 和 float16 的結果可能不同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.