Similitude cosinus entre 2 listes de nombres


119

J'ai besoin de calculer la similitude cosinus entre deux listes , disons par exemple la liste 1 qui est dataSetIet la liste 2 qui est dataSetII. Je ne peux rien utiliser comme numpy ou un module de statistiques. Je dois utiliser des modules communs (mathématiques, etc) (et le moins de modules possible, en plus, pour réduire le temps passé).

Disons dataSetIest [3, 45, 7, 2]et dataSetIIest [2, 54, 13, 15]. La longueur des listes est toujours égale.

Bien sûr, la similitude cosinus est comprise entre 0 et 1 , et pour cela, elle sera arrondie à la troisième ou quatrième décimale avec format(round(cosine, 3)).

Merci d'avance pour votre aide.


29
J'aime la façon dont SO a écrasé l'âme de cette question de devoirs pour en faire une belle référence générale. OP dit " Je ne peux pas utiliser numpy , je dois suivre la voie mathématique piétonne", et la première réponse est "vous devriez essayer scipy, il utilise numpy". Les mécaniciens SO accordent un badge d'or à la question populaire.
Nikana Reklawyks

1
Nikana Reklawyks, c'est un excellent point. J'ai eu ce problème de plus en plus souvent avec StackOverflow. Et j'ai eu plusieurs questions marquées comme "doublons" d'une question précédente, parce que les modérateurs n'ont pas pris le temps de comprendre ce qui rendait ma question unique.
LRK9

@NikanaReklawyks, c'est génial. Regardez son profil, il raconte l'histoire de l'un des meilleurs contributeurs de SO, vous savez?
Nathan Chappell

Réponses:


175

Vous devriez essayer SciPy . Il a un tas de routines scientifiques utiles, par exemple, "des routines pour calculer des intégrales numériquement, résoudre des équations différentielles, l'optimisation et des matrices clairsemées". Il utilise le NumPy optimisé ultra-rapide pour son calcul des nombres. Voir ici pour l'installation.

Notez que spatial.distance.cosine calcule la distance et non la similitude. Donc, vous devez soustraire la valeur de 1 pour obtenir la similitude .

from scipy import spatial

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

125

une autre version basée numpyuniquement sur

from numpy import dot
from numpy.linalg import norm

cos_sim = dot(a, b)/(norm(a)*norm(b))

3
Très clair comme la définition, mais peut np.inner(a, b) / (norm(a) * norm(b))- être vaut mieux comprendre. dotpeut obtenir le même résultat que innerpour les vecteurs.
Belter

15
Pour info, cette solution est nettement plus rapide sur mon système que l'utilisation scipy.spatial.distance.cosine.
Ozzah

La similarité du cosinus @ZhengfangXin varie de -1 à 1 par définition
dontloo

2
Encore plus court:cos_sim = (a @ b.T) / (norm(a)*norm(b))
Apprentissage des statistiques par exemple

C'est de loin l'approche la plus rapide par rapport aux autres.
Jason Youn le

73

Vous pouvez utiliser cosine_similaritydes documents de formulaire de fonctionsklearn.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]])

21
Juste un rappel que Passer des tableaux à une dimension en tant que données d'entrée est obsolète dans sklearn version 0.17, et augmentera ValueError dans 0.19.
Chong Tang

4
Quelle est la bonne façon de faire cela avec sklearn compte tenu de cet avertissement d'obsolescence?
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 tu veux dire? Mais qu'est-ce que ce résultat signifie qu'il revient? C'est un nouveau tableau 2D, pas une similitude cosinus.
Isbister

10
Joignez-le à un autre supportcosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush

34

Je ne pense pas que la performance compte beaucoup ici, mais je ne peux pas résister. La fonction zip () recopie complètement les deux vecteurs (plutôt une transposition matricielle, en fait) juste pour obtenir les données dans l'ordre "Pythonic". Il serait intéressant de chronométrer la mise en œuvre des écrous et boulons:

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

Cela passe par le bruit de type C d'extraction d'éléments un par un, mais ne fait pas de copie de tableau en bloc et fait tout ce qui est important dans une seule boucle for, et utilise une seule racine carrée.

ETA: appel d'impression mis à jour pour être une fonction. (L'original était Python 2.7, pas 3.3. Le courant fonctionne sous Python 2.7 avec unfrom __future__ import print_function instruction.) La sortie est la même, de toute façon.

CPYthon 2.7.3 sur 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

Ainsi, la voie unpythonic est environ 3,6 fois plus rapide dans ce cas.


2
Qu'est- cosine_measurece que dans ce cas?
MERose

1
@MERose: cosine_measureet cosine_similaritysont simplement des implémentations différentes du même calcul. Équivaut à mettre à l'échelle les deux tableaux d'entrée en «vecteurs unitaires» et à prendre le produit scalaire.
Mike Housky

3
J'aurais deviné la même chose. Mais ce n'est pas utile. Vous présentez des comparaisons temporelles de deux algorithmes mais vous n'en présentez qu'un seul.
MERose

@MERose Oh, désolé. cosine_measureest le code publié précédemment par pkacprzak. Ce code était une alternative à «l'autre» solution Python tout standard.
Mike Housky

merci, c'est génial car il n'utilise aucune bibliothèque et il est clair de comprendre le calcul derrière cela
grepit

18

sans utiliser d'importations

math.sqrt (x)

peut être remplacé par

x ** .5

sans utiliser numpy.dot (), vous devez créer votre propre fonction de point en utilisant la compréhension de liste:

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

et puis il suffit d'appliquer la formule de similarité cosinus:

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

15

J'ai fait un benchmark basé sur plusieurs réponses à la question et l'extrait suivant est considéré comme le meilleur choix:

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)

Le résultat me surprend que l'implémentation basée sur scipyne soit pas la plus rapide. J'ai profilé et je trouve que le cosinus dans scipy prend beaucoup de temps pour convertir un vecteur de la liste python en tableau numpy.

entrez la description de l'image ici


comment êtes-vous si sûr que c'est le plus rapide?
Jeru Luke

@JeruLuke J'ai collé le lien de mon résultat de référence au tout début de la réponse: 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)

Vous pouvez l'arrondir après le calcul:

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

Si vous le voulez vraiment court, vous pouvez utiliser ce 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)))

J'ai essayé ce code et cela ne semble pas fonctionner. Je l'ai essayé avec la version v1 [2,3,2,5]et la version v2 [3,2,2,0]. Il revient avec1.0 , comme s'ils étaient exactement les mêmes. Une idée de ce qui ne va pas?
Rob Alsod

Le correctif a fonctionné ici. Bon travail! Voir ci-dessous pour une approche plus moche mais plus rapide.
Mike Housky

Comment adapter ce code si la similitude doit être calculée au sein d'une matrice et non pour deux vecteurs? Je pensais prendre une matrice et la matrice transposée au lieu du deuxième vecteur, mais cela ne semble pas fonctionner.
étudiant

vous pouvez utiliser np.dot (x, yT) pour le rendre plus simple
user702846

3

Vous pouvez le faire en Python en utilisant une fonction 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)

3
Il s'agit d'une implémentation textuelle du cosinus. Cela donnera une mauvaise sortie pour l'entrée numérique.
alvas

Pouvez-vous expliquer pourquoi vous avez utilisé set dans la ligne "intersection = set (vec1.keys ()) & set (vec2.keys ())".
Ghos3t

Votre fonction semble également attendre des cartes mais vous lui envoyez des listes d'entiers.
Ghos3t

3

En utilisant numpy, comparez une liste de nombres à plusieurs listes (matrice):

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]

1

Vous pouvez utiliser cette fonction simple pour calculer la similitude cosinus:

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])))

1
pourquoi réinventer la roue?
Jeru Luke

@JeruLuke peut-être pour donner une réponse "autonome", celles qui ne nécessitent pas d'importations supplémentaires (et peut-être des conversions de liste à numpy.array ou quelque chose du genre)
Marco Ottina

0

Si vous utilisez déjà PyTorch , vous devriez opter pour leur implémentation CosineSimilarity .

Supposons que vous ayez des s à deux ndimensions numpy.ndarray, v1et v2, c'est-à-dire que leurs formes sont les deux (n,). Voici comment obtenir leur similitude cosinus:

import torch
import torch.nn as nn

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

Ou supposons que vous ayez deux numpy.ndarrays w1et w2, dont les formes sont les deux (m, n). Ce qui suit vous donne une liste de similitudes en cosinus, chacune étant la similitude en cosinus entre une ligne dans w1et la ligne correspondante dans w2:

cos(torch.tensor(w1), torch.tensor(w2)).tolist()

-1

Toutes les réponses sont parfaites pour les situations où vous ne pouvez pas utiliser NumPy. Si vous le pouvez, voici une autre approche:

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)

Gardez également à l'esprit sur le point EPSILON = 1e-07de sécuriser la division.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.