Similitud de coseno entre 2 listas de números

119

Necesito calcular la similitud de coseno entre dos listas , digamos, por ejemplo, la lista 1 que es dataSetIy la lista 2 que es dataSetII. No puedo usar nada como numpy o un módulo de estadísticas. Debo usar módulos comunes (matemáticas, etc.) (y la menor cantidad posible de módulos, además, para reducir el tiempo empleado).

Digamos que dataSetIes [3, 45, 7, 2]y dataSetIIes [2, 54, 13, 15]. La longitud de las listas siempre es igual.

Por supuesto, la similitud de coseno está entre 0 y 1 , y por el bien de ella, se redondeará al tercer o cuarto decimal con format(round(cosine, 3)).

Muchas gracias de antemano por ayudar.

Rob Alsod
fuente
29
Me encanta la forma en que SO aplastó el alma de esta pregunta de tarea para que sea una buena referencia general. OP dice " No puedo usar numpy , debo seguir el camino matemático peatonal", y la respuesta principal es "deberías probar scipy, usa numpy". Los mecánicos de SO otorgan una insignia de oro a la pregunta popular.
Nikana Reklawyks
1
Nikana Reklawyks, ese es un punto excelente. He tenido ese problema cada vez más a menudo con StackOverflow. Y he tenido varias preguntas marcadas como "duplicadas" de alguna pregunta anterior, porque los moderadores no se tomaron el tiempo para comprender qué hizo que mi pregunta fuera única.
LRK9
@NikanaReklawyks, esto es genial. Mire su perfil, cuenta la historia de uno de los principales contribuyentes del .01% de SO, ¿sabe?
Nathan Chappell

Respuestas:

174

Deberías probar SciPy . Tiene un montón de rutinas científicas útiles, por ejemplo, "rutinas para calcular integrales numéricamente, resolver ecuaciones diferenciales, optimización y matrices dispersas". Utiliza NumPy superrápido optimizado para su procesamiento numérico. Consulte aquí la instalación.

Tenga en cuenta que space.distance.cosine calcula la distancia y no la similitud. Entonces, debes restar el valor de 1 para obtener la similitud .

from scipy import spatial

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]
result = 1 - spatial.distance.cosine(dataSetI, dataSetII)
charmoniumQ
fuente
122

otra versión basada en numpysolo

from numpy import dot
from numpy.linalg import norm

cos_sim = dot(a, b)/(norm(a)*norm(b))
dontloo
fuente
3
Muy claro como la definición, pero quizás np.inner(a, b) / (norm(a) * norm(b))sea ​​mejor de entender. dotpuede obtener el mismo resultado que innerpara los vectores.
Belter
15
Para su información, esta solución es significativamente más rápida en mi sistema que usar scipy.spatial.distance.cosine.
Ozzah
La similitud del coseno de @ZhengfangXin varía de -1 a 1 por definición
dontloo
2
Aún más breve:cos_sim = (a @ b.T) / (norm(a)*norm(b))
Estadísticas de aprendizaje por ejemplo
Este es, con mucho, el enfoque más rápido en comparación con otros.
Jason Youn
73

Puede utilizar documentos decosine_similarity formulario de funciónsklearn.metrics.pairwise

In [23]: from sklearn.metrics.pairwise import cosine_similarity

In [24]: cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Out[24]: array([[-0.5]])
Akavall
fuente
21
Solo un recordatorio de que Pasar matrices de una dimensión como datos de entrada está obsoleto en la versión 0.17 de sklearn y generará ValueError en 0.19.
Chong Tang
4
¿Cuál es la forma correcta de hacer esto con sklearn dada esta advertencia de desaprobación?
Elliott
2
@Elliott one_dimension_array.reshape (-1,1)
bobo32
2
@ bobo32 cosine_similarity (np.array ([1, 0, -1]). reshape (-1,0), np.array ([- 1, -1, 0]). reshape (-1,0)) I supongo que te refieres? Pero, ¿qué significa ese resultado que vuelve? Es una nueva matriz 2d, no una similitud de coseno.
Isbister
10
Ciérrelo con un paréntesis máscosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush
34

Supongo que el rendimiento no importa mucho aquí, pero no puedo resistirme. La función zip () vuelve a copiar completamente ambos vectores (más bien una transposición de matriz, en realidad) solo para obtener los datos en orden "Pythonic". Sería interesante cronometrar la implementación de tuercas y tornillos:

import math
def cosine_similarity(v1,v2):
    "compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
    sumxx, sumxy, sumyy = 0, 0, 0
    for i in range(len(v1)):
        x = v1[i]; y = v2[i]
        sumxx += x*x
        sumyy += y*y
        sumxy += x*y
    return sumxy/math.sqrt(sumxx*sumyy)

v1,v2 = [3, 45, 7, 2], [2, 54, 13, 15]
print(v1, v2, cosine_similarity(v1,v2))

Output: [3, 45, 7, 2] [2, 54, 13, 15] 0.972284251712

Eso pasa por el ruido tipo C de extraer elementos de uno en uno, pero no realiza copias masivas de matrices y hace que todo lo importante se haga en un solo bucle for, y usa una sola raíz cuadrada.

ETA: llamada de impresión actualizada para que sea una función. (El original era Python 2.7, no 3.3. El actual se ejecuta en Python 2.7 con una from __future__ import print_functiondeclaración). El resultado es el mismo, de cualquier manera.

CPYthon 2.7.3 en 3.0GHz Core 2 Duo:

>>> timeit.timeit("cosine_similarity(v1,v2)",setup="from __main__ import cosine_similarity, v1, v2")
2.4261788514654654
>>> timeit.timeit("cosine_measure(v1,v2)",setup="from __main__ import cosine_measure, v1, v2")
8.794677709375264

Entonces, la forma no pitónica es aproximadamente 3.6 veces más rápida en este caso.

Mike Housky
fuente
2
¿Qué es cosine_measureen este caso?
MERose
1
@MERose: cosine_measurey cosine_similarityson simplemente diferentes implementaciones del mismo cálculo. Equivalente a escalar ambas matrices de entrada a "vectores unitarios" y tomar el producto escalar.
Mike Housky
3
Yo habría adivinado lo mismo. Pero no ayuda. Presenta comparaciones de tiempo de dos algoritmos pero presenta solo uno de ellos.
MERose
@MERose Oh, lo siento. cosine_measurees el código publicado anteriormente por pkacprzak. Este código era una alternativa a la "otra" solución de Python totalmente estándar.
Mike Housky
gracias, esto es genial ya que no está usando ninguna biblioteca y es claro entender las matemáticas detrás de esto
grepit
18

sin usar ninguna importación

matemáticas.sqrt (x)

puede ser reemplazado con

x ** .5

sin usar numpy.dot (), debe crear su propia función de puntos usando la lista de comprensión:

def dot(A,B): 
    return (sum(a*b for a,b in zip(A,B)))

y luego es solo una simple cuestión de aplicar la fórmula de similitud del coseno:

def cosine_similarity(a,b):
    return dot(a,b) / ( (dot(a,a) **.5) * (dot(b,b) ** .5) )
Mahoma
fuente
15

Hice un punto de referencia basado en varias respuestas en la pregunta y se cree que el siguiente fragmento es la mejor opción:

def dot_product2(v1, v2):
    return sum(map(operator.mul, v1, v2))


def vector_cos5(v1, v2):
    prod = dot_product2(v1, v2)
    len1 = math.sqrt(dot_product2(v1, v1))
    len2 = math.sqrt(dot_product2(v2, v2))
    return prod / (len1 * len2)

El resultado me sorprende de que la implementación basada en scipyno sea la más rápida. Hice un perfil y descubrí que el coseno en scipy lleva mucho tiempo convertir un vector de la lista de Python a la matriz numpy.

ingrese la descripción de la imagen aquí

McKelvin
fuente
¿Cómo estás tan seguro de que este es el más rápido?
Jeru Luke
@JeruLuke He pegado el enlace de mi resultado de referencia al comienzo de la respuesta: gist.github.com/mckelvin/…
McKelvin
10
import math
from itertools import izip

def dot_product(v1, v2):
    return sum(map(lambda x: x[0] * x[1], izip(v1, v2)))

def cosine_measure(v1, v2):
    prod = dot_product(v1, v2)
    len1 = math.sqrt(dot_product(v1, v1))
    len2 = math.sqrt(dot_product(v2, v2))
    return prod / (len1 * len2)

Puede redondearlo después de calcular:

cosine = format(round(cosine_measure(v1, v2), 3))

Si lo quieres realmente corto, puedes usar este de una sola línea:

from math import sqrt
from itertools import izip

def cosine_measure(v1, v2):
    return (lambda (x, y, z): x / sqrt(y * z))(reduce(lambda x, y: (x[0] + y[0] * y[1], x[1] + y[0]**2, x[2] + y[1]**2), izip(v1, v2), (0, 0, 0)))
pkacprzak
fuente
Probé este código y no parece funcionar. Lo intenté con v1 siendo [2,3,2,5]y v2 siendo [3,2,2,0]. Vuelve con 1.0, como si fueran exactamente iguales. ¿Alguna idea de lo que está mal?
Rob Alsod
La solución funcionó aquí. ¡Buen trabajo! Vea a continuación un enfoque más feo pero más rápido.
Mike Housky
¿Cómo es posible adaptar este código si la similitud debe calcularse dentro de una matriz y no para dos vectores? Pensé que tomaba una matriz y la matriz transpuesta en lugar del segundo vector, pero parece que no funciona.
estudiante
puede usar np.dot (x, yT) para hacerlo más simple
user702846
3

Puedes hacer esto en Python usando una función simple:

def get_cosine(text1, text2):
  vec1 = text1
  vec2 = text2
  intersection = set(vec1.keys()) & set(vec2.keys())
  numerator = sum([vec1[x] * vec2[x] for x in intersection])
  sum1 = sum([vec1[x]**2 for x in vec1.keys()])
  sum2 = sum([vec2[x]**2 for x in vec2.keys()])
  denominator = math.sqrt(sum1) * math.sqrt(sum2)
  if not denominator:
     return 0.0
  else:
     return round(float(numerator) / denominator, 3)
dataSet1 = [3, 45, 7, 2]
dataSet2 = [2, 54, 13, 15]
get_cosine(dataSet1, dataSet2)
rajeshwerkushwaha
fuente
3
Esta es una implementación de texto del coseno. Dará una salida incorrecta para la entrada numérica.
alvas
¿Puedes explicar por qué usaste set en la línea "intersection = set (vec1.keys ()) & set (vec2.keys ())".
Ghos3t
Además, su función parece estar esperando mapas pero le está enviando listas de enteros.
Ghos3t
3

Usando numpy compare una lista de números con múltiples listas (matriz):

def cosine_similarity(vector,matrix):
   return ( np.sum(vector*matrix,axis=1) / ( np.sqrt(np.sum(matrix**2,axis=1)) * np.sqrt(np.sum(vector**2)) ) )[::-1]
sten
fuente
1

Puede utilizar esta sencilla función para calcular la similitud del coseno:

def cosine_similarity(a, b):
return sum([i*j for i,j in zip(a, b)])/(math.sqrt(sum([i*i for i in a]))* math.sqrt(sum([i*i for i in b])))
Isira
fuente
1
¿Por qué reinventar la rueda?
Jeru Luke
@JeruLuke tal vez para dar una respuesta "independiente", las que no requieren importación (es) adicional (es) (y tal vez conversiones de la lista a numpy.array o algo así)
Marco Ottina
1

Si ya está usando PyTorch , debería ir con su implementación CosineSimilarity .

Suponga que tiene s nbidimensionales y , es decir, sus formas son ambas . Así es como obtienes su similitud de coseno:numpy.ndarrayv1v2(n,)

import torch
import torch.nn as nn

cos = nn.CosineSimilarity()
cos(torch.tensor([v1]), torch.tensor([v2])).item()

O supongamos que tiene dos numpy.ndarrays w1y w2, cuyas formas son a la vez (m, n). A continuación, obtendrá una lista de similitudes de coseno, cada una de las cuales es la similitud de coseno entre una fila de w1y la fila correspondiente en w2:

cos(torch.tensor(w1), torch.tensor(w2)).tolist()
Yuxuan Chen
fuente
-1

Todas las respuestas son excelentes para situaciones en las que no puede usar NumPy. Si puede, aquí tiene otro enfoque:

def cosine(x, y):
    dot_products = np.dot(x, y.T)
    norm_products = np.linalg.norm(x) * np.linalg.norm(y)
    return dot_products / (norm_products + EPSILON)

También tenga en cuenta a cerca EPSILON = 1e-07de asegurar la división.

user702846
fuente