¿Cuál es la forma más rápida en Python para calcular la similitud del coseno dados los escasos datos de la matriz?
numpy pandas (6)
Dada una lista de matriz dispersa, ¿cuál es la mejor manera de calcular la similitud del coseno entre cada una de las columnas (o filas) en la matriz? Preferiría no iterar n-elija-dos veces.
Digamos que la matriz de entrada es:
A=
[0 1 0 0 1
0 0 1 1 1
1 1 0 1 0]
La representación dispersa es:
A =
0, 1
0, 4
1, 2
1, 3
1, 4
2, 0
2, 1
2, 3
En Python, es sencillo trabajar con el formato de entrada de matriz:
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
Da:
array([[ 1. , 0.40824829, 0.40824829],
[ 0.40824829, 1. , 0.33333333],
[ 0.40824829, 0.33333333, 1. ]])
Eso está bien para una entrada de matriz completa, pero realmente quiero comenzar con la representación dispersa (debido al tamaño y la dispersión de mi matriz). ¿Alguna idea sobre cómo esto podría lograrse mejor? Gracias por adelantado.
Deberías scipy.sparse
un vistazo a scipy.sparse
( scipy.sparse ). Puede aplicar operaciones en esas matrices dispersas tal como lo hace con una matriz normal.
El siguiente método es aproximadamente 30 veces más rápido que scipy.spatial.distance.pdist
. Funciona bastante rápido en matrices grandes (suponiendo que tienes suficiente RAM)
Vea a continuación una discusión sobre cómo optimizar la dispersión.
# 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
Si su problema es típico para problemas de preferencias binarias a gran escala, tiene muchas más entradas en una dimensión que en la otra. Además, la dimensión corta es aquella cuyas entradas desea calcular similitudes. Llamemos a esta dimensión la dimensión ''artículo''.
Si este es el caso, liste sus ''artículos'' en filas y cree A
usando scipy.sparse
. Luego reemplace la primera línea como se indica.
Si su problema es atípico, necesitará más modificaciones. Esos deberían ser reemplazos bastante simples de las operaciones numpy
básicas con sus equivalentes scipy.sparse
.
He intentado algunos métodos arriba. Sin embargo, el experimento de @zbinsd tiene su limitación. La dispersión de la matriz utilizada en el experimento es extremadamente baja, mientras que la verdadera dispersión suele ser superior al 90%. En mi condición, el escaso es con la forma de (7000, 25000) y la dispersión del 97%. El método 4 es extremadamente lento y no puedo tolerar los resultados. Uso el método 6 que está terminado en 10 s. Sorprendentemente, pruebo el método a continuación y está terminado en solo 0.247 s.
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
Este eficiente método está vinculado mediante la introducción de la descripción del enlace aquí
Hola, puedes hacerlo de esta manera
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
Puede calcular la similitud de coseno por pares en las filas de una matriz dispersa directamente utilizando sklearn. A partir de la versión 0.17 también es compatible con salida escasa:
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))
Resultados:
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
Si desea similitudes de coseno en columnas, simplemente transponga su matriz de entrada de antemano:
A_sparse.transpose()
Tomé todas estas respuestas y escribí un script para 1. validar cada uno de los resultados (ver la afirmación a continuación) y 2. ver cuál es el más rápido. El código y los resultados están a continuación:
# 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)
Resultados:
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