Somiglianza del coseno tra 2 elenchi di numeri


119

Ho bisogno di calcolare la somiglianza del coseno tra due liste , diciamo ad esempio lista 1 che è dataSetIe lista 2 che è dataSetII. Non posso usare niente come numpy o un modulo di statistiche. Devo usare moduli comuni (matematica, ecc.) (E il minor numero di moduli possibile, per ridurre il tempo speso).

Diciamo che dataSetIè [3, 45, 7, 2]ed dataSetIIè [2, 54, 13, 15]. La lunghezza delle liste è sempre uguale.

Ovviamente, la somiglianza del coseno è compresa tra 0 e 1 e , per il gusto di ciò, verrà arrotondata al terzo o quarto decimale con format(round(cosine, 3)).

Grazie mille in anticipo per l'aiuto.


29
Adoro il modo in cui SO ha distrutto l'anima da questa domanda per i compiti per renderla un bel riferimento generale. OP dice " Non posso usare numpy , devo seguire la matematica dei pedoni" e la risposta più alta è "dovresti provare scipy, usa numpy". I meccanici SO assegnano un distintivo d'oro alla domanda popolare.
Nikana Reklawyks

1
Nikana Reklawyks, questo è un ottimo punto. Ho avuto quel problema sempre più spesso con StackOverflow. E ho ricevuto diverse domande contrassegnate come "duplicati" di alcune domande precedenti, perché i moderatori non si sono presi il tempo per capire cosa rendeva unica la mia domanda.
LRK9

@NikanaReklawyks, è fantastico. Guarda il suo profilo, racconta la storia di uno dei maggiori contributori dello 0,01% di SO, sai?
Nathan Chappell il

Risposte:


175

Dovresti provare SciPy . Ha una serie di utili routine scientifiche, ad esempio "routine per calcolare numericamente integrali, risolvere equazioni differenziali, ottimizzazione e matrici sparse". Utilizza NumPy ottimizzato superveloce per il suo numero di scricchiolio. Vedere qui per l'installazione.

Si noti che spatial.distance.cosine calcola la distanza e non la somiglianza. Quindi, devi sottrarre il valore da 1 per ottenere la somiglianza .

from scipy import spatial

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

125

un'altra versione basata su numpyonly

from numpy import dot
from numpy.linalg import norm

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

3
Molto chiara come la definizione, ma forse np.inner(a, b) / (norm(a) * norm(b))è meglio capire. dotpuò ottenere lo stesso risultato innerdei vettori.
Belter

15
Cordiali saluti, questa soluzione è significativamente più veloce sul mio sistema rispetto all'utilizzo scipy.spatial.distance.cosine.
Ozzah

La somiglianza del coseno di @ZhengfangXin varia da -1 a 1 per definizione
dontloo

2
Ancora più breve:cos_sim = (a @ b.T) / (norm(a)*norm(b))
Statistiche di apprendimento per esempio

Questo è di gran lunga l'approccio più veloce rispetto ad altri.
Jason Youn

73

È possibile utilizzare i documenti delcosine_similarity modulo di funzionesklearn.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
Solo un promemoria che il passaggio di array di una dimensione come dati di input è deprecato in sklearn versione 0.17 e solleverà ValueError in 0.19.
Chong Tang,

4
Qual è il modo corretto per farlo con sklearn dato questo avviso di deprecazione?
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 indovina intendi? Ma cosa significa quel risultato che ritorna? È un nuovo array 2d, non una somiglianza con il coseno.
Isbister

10
Racchiudilo con un'altra parentesicosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush

34

Non credo che le prestazioni contino molto qui, ma non posso resistere. La funzione zip () ricopia completamente entrambi i vettori (più di una trasposizione di matrice, in realtà) solo per ottenere i dati in ordine "pitonico". Sarebbe interessante temporizzare l'implementazione di dadi e bulloni:

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

Questo passa attraverso il rumore simile al C dell'estrazione di elementi uno alla volta, ma non esegue la copia di array in blocco e ottiene tutto ciò che è importante in un unico ciclo for e utilizza una singola radice quadrata.

ETA: chiamata di stampa aggiornata per essere una funzione. (L'originale era Python 2.7, non 3.3. La corrente gira sotto Python 2.7 con from __future__ import print_functionun'istruzione.) L'output è lo stesso, in entrambi i casi.

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

Quindi, il modo non pitonico è circa 3,6 volte più veloce in questo caso.


2
Cosa c'è cosine_measurein questo caso?
MERose

1
@MERose: cosine_measuree cosine_similaritysono semplicemente diverse implementazioni dello stesso calcolo. Equivale a ridimensionare entrambi gli array di input a "vettori unitari" e prendere il prodotto scalare.
Mike Housky

3
Avrei immaginato lo stesso. Ma non è d'aiuto. Presenti confronti temporali di due algoritmi ma ne presenti solo uno.
MERose

@MERose Oh, mi dispiace. cosine_measureè il codice pubblicato in precedenza da pkacprzak. Questo codice era un'alternativa all '"altra" soluzione Python standard.
Mike Housky

grazie, è fantastico dato che non utilizza alcuna libreria ed è chiaro capire la matematica che c'è dietro
grepit

18

senza utilizzare alcuna importazione

Math.sqrt (x)

può essere sostituito con

x ** .5

senza usare numpy.dot () devi creare la tua funzione punto usando la comprensione dell'elenco:

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

e quindi è solo una semplice questione di applicare la formula di somiglianza del coseno:

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

15

Ho eseguito un benchmark basato su diverse risposte alla domanda e si ritiene che il seguente frammento sia la scelta migliore:

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)

Il risultato mi sorprende che l'implementazione basata su scipynon sia la più veloce. Ho profilato e ho scoperto che il coseno in scipy richiede molto tempo per eseguire il cast di un vettore dall'elenco python all'array numpy.

inserisci qui la descrizione dell'immagine


come fai a essere sicuro che questo sia il più veloce?
Jeru Luke

@JeruLuke Ho incollato il link del mio risultato di riferimento all'inizio della risposta: 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)

Puoi arrotondarlo dopo il calcolo:

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

Se lo vuoi davvero breve, puoi usare questo 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)))

Ho provato questo codice e non sembra funzionare. L'ho provato con l'essere v1 [2,3,2,5]e l'essere v2 [3,2,2,0]. Ritorna con 1.0, come se fossero esattamente la stessa cosa. Qualche idea su cosa sia sbagliato?
Rob Alsod

La correzione ha funzionato qui. Bel lavoro! Vedi sotto per un approccio più brutto ma più veloce.
Mike Housky

Come è possibile adattare questo codice se la somiglianza deve essere calcolata all'interno di una matrice e non per due vettori? Ho pensato di prendere una matrice e la matrice trasposta invece del secondo vettore, ma sembra che non funzioni.
studente

puoi usare np.dot (x, yT) per renderlo più semplice
user702846

3

Puoi farlo in Python usando una semplice funzione:

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
Questa è un'implementazione testuale del coseno. Fornirà l'output sbagliato per l'input numerico.
alvas

Puoi spiegare perché hai usato set nella riga "intersection = set (vec1.keys ()) & set (vec2.keys ())".
Ghos3t

Anche la tua funzione sembra aspettarsi mappe ma le stai inviando elenchi di numeri interi.
Ghos3t

3

Utilizzando numpy confronta un elenco di numeri con più elenchi (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

È possibile utilizzare questa semplice funzione per calcolare la somiglianza 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])))

1
perché reinventare la ruota?
Jeru Luke

@JeruLuke forse per dare una risposta "stand alone", quelle che non richiedono importazione aggiuntiva (e forse conversioni da list a numpy.array o qualcosa del genere)
Marco Ottina

1

Se ti capita di utilizzare già PyTorch , dovresti andare con la loro implementazione CosineSimilarity .

Supponi di avere s nbidimensionali e , cioè le loro forme sono entrambe . Ecco come ottenere la loro somiglianza con il coseno:numpy.ndarrayv1v2(n,)

import torch
import torch.nn as nn

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

Oppure supponi di avere due numpy.ndarrays w1e w2, le cui forme sono entrambe (m, n). Di seguito viene fornito un elenco di somiglianze del coseno, ciascuna delle quali è la somiglianza del coseno tra una riga in w1e la riga corrispondente in w2:

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

-1

Tutte le risposte sono ottime per situazioni in cui non puoi usare NumPy. Se puoi, ecco un altro approccio:

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)

Anche tenere a mente circa EPSILON = 1e-07per garantire la divisione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.