Similaridade de cosseno entre 2 listas de números

118

Preciso calcular a semelhança do cosseno entre duas listas , digamos, por exemplo, a lista 1 que é dataSetIe a lista 2 que é dataSetII. Não posso usar nada como o numpy ou um módulo de estatísticas. Devo usar módulos comuns (matemática, etc) (e o mínimo de módulos possível, para reduzir o tempo gasto).

Digamos que dataSetIé [3, 45, 7, 2]e dataSetIIé [2, 54, 13, 15]. O comprimento das listas é sempre igual.

Claro, a semelhança do cosseno está entre 0 e 1 e , por causa disso, será arredondada para a terceira ou quarta casa decimal com format(round(cosine, 3)).

Muito obrigado desde já por ajudar.

Rob Alsod
fonte
29
Eu amo a maneira como TANTO esmaguei a alma dessa questão do dever de casa para torná-la uma boa referência geral. OP diz " Não posso usar o numpy , devo seguir o caminho da matemática para pedestres", e a resposta principal é "você deve tentar o scipy, ele usa o numpy". SO mecânicos concedem um distintivo de ouro à pergunta popular.
Nikana Reklawyks
1
Nikana Reklawyks, esse é um excelente ponto. Eu tive esse problema cada vez mais com StackOverflow. E eu tive várias perguntas marcadas como "duplicatas" de alguma pergunta anterior, porque os moderadores não tiveram tempo para entender o que tornava minha pergunta única.
LRK9 de
@NikanaReklawyks, isso é ótimo. Olhe para o perfil dele, ele conta a história de um dos principais contribuintes de 0,01% da SO, sabe?
Nathan Chappell

Respostas:

174

Você deve tentar SciPy . Ele tem um monte de rotinas científicas úteis, por exemplo, "rotinas para calcular integrais numericamente, resolver equações diferenciais, otimização e matrizes esparsas". Ele usa o NumPy super rápido otimizado para processamento de números. Veja aqui para instalar.

Observe que spatial.distance.cosine calcula a distância e não a similaridade. Portanto, você deve subtrair o valor de 1 para obter a similaridade .

from scipy import spatial

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

outra versão baseada numpyapenas em

from numpy import dot
from numpy.linalg import norm

cos_sim = dot(a, b)/(norm(a)*norm(b))
dontloo
fonte
3
Muito clara como a definição, mas talvez np.inner(a, b) / (norm(a) * norm(b))seja melhor entender. dotpode obter o mesmo resultado que innerpara vetores.
Belter
15
Para sua informação, esta solução é significativamente mais rápida no meu sistema do que em uso scipy.spatial.distance.cosine.
Ozzah
@ZhengfangXin cosseno similaridade varia de -1 a 1 por definição
dontloo
2
Ainda mais curto:cos_sim = (a @ b.T) / (norm(a)*norm(b))
Estatísticas de aprendizagem por exemplo
Esta é de longe a abordagem mais rápida em comparação com outras.
Jason Youn,
73

Você pode usar documentos decosine_similarity formulário de funçãosklearn.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
fonte
21
Apenas um lembrete de que a passagem de matrizes de uma dimensão como dados de entrada está obsoleta na versão 0.17 do sklearn e aumentará ValueError em 0.19.
Chong Tang
4
Qual é a maneira correta de fazer isso com sklearn dado este aviso de suspensão de uso?
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 acho que você quer dizer? Mas o que esse resultado significa que ele retorna? É uma nova matriz 2D, não uma semelhança de cosseno.
Isbister
10
Coloque mais um colchetecosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush
34

Acho que o desempenho não importa muito aqui, mas não consigo resistir. A função zip () copia completamente os dois vetores (mais como uma transposição de matriz, na verdade) apenas para obter os dados na ordem "Pythônica". Seria interessante cronometrar a implementação de porcas e parafusos:

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

Isso passa pelo ruído semelhante ao C de extrair elementos um por vez, mas não faz nenhuma cópia de array em massa e faz tudo que é importante em um único loop for e usa uma única raiz quadrada.

ETA: chamada de impressão atualizada para ser uma função. (O original era Python 2.7, não 3.3. O atual é executado no Python 2.7 com uma from __future__ import print_functioninstrução.) A saída é a mesma, de qualquer maneira.

CPYthon 2.7.3 em 3.0 GHz 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

Portanto, a forma impotônica é cerca de 3,6 vezes mais rápida neste caso.

Mike Housky
fonte
2
O que é cosine_measureneste caso?
MERose
1
@MERose: cosine_measuree cosine_similaritysão simplesmente implementações diferentes do mesmo cálculo. Equivalente a dimensionar ambas as matrizes de entrada para "vetores unitários" e obter o produto escalar.
Mike Housky,
3
Eu teria adivinhado o mesmo. Mas isso não ajuda. Você apresenta comparações de tempo de dois algoritmos, mas apresenta apenas um deles.
MERose,
@MERose Oh, desculpe. cosine_measureé o código postado anteriormente por pkacprzak. Esse código era uma alternativa à "outra" solução Python padrão.
Mike Housky
obrigado, isso é ótimo, pois não está usando nenhuma biblioteca e é claro para entender a matemática por trás disso
grepit
17

sem usar quaisquer importações

math.sqrt (x)

pode ser substituído por

x ** .5

sem usar numpy.dot (), você deve criar sua própria função de ponto usando compreensão de lista:

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

e então é apenas uma simples questão de aplicar a fórmula de similaridade de cosseno:

def cosine_similarity(a,b):
    return dot(a,b) / ( (dot(a,a) **.5) * (dot(b,b) ** .5) )
Maomé
fonte
15

Fiz um benchmark com base em várias respostas à pergunta e o seguinte snippet é considerado a melhor escolha:

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)

O resultado me surpreende que a implementação baseada em scipynão seja a mais rápida. Fiz o perfil e descobri que o cosseno em scipy leva muito tempo para lançar um vetor da lista python para a matriz numpy.

insira a descrição da imagem aqui

McKelvin
fonte
como você tem tanta certeza de que este é o mais rápido?
Jeru Luke
@JeruLuke Eu colei o link do meu resultado de benchmark bem no início da resposta: 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)

Você pode arredondá-lo após o cálculo:

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

Se você quiser realmente curto, você pode usar este one-liner:

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
fonte
Experimentei este código e não parece funcionar. Eu tentei com v1 sendo [2,3,2,5]e v2 sendo [3,2,2,0]. Ele retorna com 1.0, como se fossem exatamente iguais. Alguma idéia do que está errado?
Rob Alsod
A correção funcionou aqui. Bom trabalho! Veja abaixo uma abordagem mais feia, porém mais rápida.
Mike Housky
Como é possível adaptar este código se a similaridade deve ser calculada dentro de uma matriz e não para dois vetores? Pensei em pegar uma matriz e a matriz transposta em vez do segundo vetor, mas parece que não funciona.
estudante de
você pode usar np.dot (x, yT) para torná-lo mais simples
user702846
3

Você pode fazer isso em Python usando uma função simples:

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
fonte
3
Esta é uma implementação de texto de cosseno. Ele dará a saída errada para a entrada numérica.
alvas
Você pode explicar por que você usou set na linha "intersection = set (vec1.keys ()) & set (vec2.keys ())".
Ghos3t
Além disso, sua função parece estar esperando mapas, mas você está enviando listas de inteiros.
Ghos3t
3

Usando numpy compare uma lista de números com várias 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
fonte
1

Você pode usar esta função simples para calcular a semelhança do cosseno:

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
fonte
1
por que reinventar a roda?
Jeru Luke
@JeruLuke talvez para dar uma resposta "independente", que não requeira importação (ões) adicional (e talvez conversões de lista para numpy.array ou algo parecido)
Marco Ottina
1

Se acontecer de você já estar usando o PyTorch , você deve ir com a implementação do CosineSimilarity .

Suponha que você tenha s nbidimensionais e , ou seja, suas formas são ambas . Veja como você consegue sua similaridade de cosseno:numpy.ndarrayv1v2(n,)

import torch
import torch.nn as nn

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

Ou suponha que você tenha dois numpy.ndarrays w1e w2, cujas formas são ambos (m, n). O seguinte fornece uma lista de semelhanças de cosseno, cada uma sendo a semelhança de cosseno entre uma linha em w1e a linha correspondente em w2:

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

Todas as respostas são ótimas para situações em que você não pode usar o NumPy. Se você puder, aqui está outra abordagem:

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)

Lembre- EPSILON = 1e-07se também de garantir a divisão.

user702846
fonte