Подібність косину між двома списками номерів


119

Мені потрібно обчислити схожість косинусу між двома списками , скажімо, наприклад, список 1, який є, dataSetIі список 2, який є dataSetII. Я не можу використовувати щось, наприклад numpy або модуль статистики. Я повинен використовувати загальні модулі (математика тощо) (і при цьому найменше модулів, щоб зменшити витрачений час).

Скажімо, dataSetIє [3, 45, 7, 2]і dataSetIIє [2, 54, 13, 15]. Довжина списків завжди однакова.

Звичайно, схожість косинусу становить від 0 до 1 , і заради цього вона буде округлена до третьої чи четвертої десятків із format(round(cosine, 3)).

Заздалегідь дякую за допомогу.


29
Мені подобається, як ТАК вичавив душу від цього домашнього завдання, щоб зробити його загальним загальним посиланням. ОП каже: " Я не можу використовувати нумію , я повинен піти по математиці пішоходів", а головна відповідь "Ви повинні спробувати scipy, він використовує numpy". Тож механіки надають золотий знак популярному питанню.
Nikana Reklawyks

1
Nikana Reklawyks, це відмінний момент. У мене ця проблема все частіше виникає із StackOverflow. І у мене було кілька запитань, позначених як "дублікати" якогось попереднього запитання, тому що модератори не потребували часу, щоб зрозуміти, що зробило моє запитання унікальним.
LRK9

@NikanaReklawyks, це чудово. Подивіться на його профіль, він розповідає історію одного з найкращих учасників програми .01%, знаєте?
Натан Чаппелл

Відповіді:


174

Спробуйте спробувати SciPy . Він має купу корисних наукових процедур, наприклад, "підпрограми для обчислення інтегралів чисельно, розв'язання диференціальних рівнянь, оптимізації та розріджених матриць". Він використовує надшвидкий оптимізований NumPy для скорочення кількості. Дивіться тут про встановлення.

Зауважте, що просторовий.distance.cosine обчислює відстань , а не подібність. Отже, ви повинні відняти значення від 1, щоб отримати схожість .

from scipy import spatial

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

122

інша версія, що базується numpyтільки

from numpy import dot
from numpy.linalg import norm

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

3
Дуже чітке, як визначення, але, можливо np.inner(a, b) / (norm(a) * norm(b)), краще зрозуміти. dotможе отримати той же результат, що і innerдля векторів.
Белтер

15
FYI це рішення значно швидше в моїй системі, ніж використання scipy.spatial.distance.cosine.
Озза

@ZhengfangXin схожість косинусів коливається від -1 до 1 за визначенням
dontloo

2
Ще коротше:cos_sim = (a @ b.T) / (norm(a)*norm(b))
Статистика навчання на прикладі

Це, безумовно, найшвидший підхід порівняно з іншими.
Джейсон Юн

73

Ви можете використовувати cosine_similarityфункціональні форми sklearn.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
Просто нагадування про те, що передача масиву одного виміру як вхідних даних застаріла у sklearn версії 0.17, і підвищить ValueError у 0.19.
Чонг Тан

4
Який правильний спосіб зробити це за допомогою склеарну, враховуючи це попередження про депресію?
Елліотт

2
@Elliott one_dimension_array.reshape (-1,1)
bobo32

2
@ bobo32 cosine_s подобниity (np.array ([1, 0, -1]). переформатувати (-1,0), np.array ([- 1, -1, 0]). змінити форму (-1,0)) I здогадуєтесь ви маєте на увазі? Але що означає цей результат, що він повертається? Це новий 2d масив, а не косинусна схожість.
Ісбістер

10
Додайте його ще однією дужкоюcosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Аюш

34

Я не думаю, що тут продуктивність має велике значення, але я не можу чинити опір. Функція zip () повністю відновлює обидва вектори (насправді більше переносить матрицю) лише для отримання даних у "Pythonic" порядку. Цікаво було б вчасно реалізувати гайки-болти:

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

Це проходить через C-подібний шум вилучення елементів одночасно, але не виконує об'ємне копіювання масиву і робить все важливе в єдиному для циклу, і використовує один квадратний корінь.

ETA: оновлений виклик для друку як функція. (Оригінал був Python 2.7, а не 3.3. Поточний запуск під Python 2.7 із from __future__ import print_functionзаявою.) Вихід у будь-якому випадку однаковий.

CPYthon 2.7.3 на 3,0 ГГц 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

Отже, непітонічний шлях у цьому випадку приблизно в 3,6 рази швидший.


2
Що cosine_measureв цьому випадку?
MERose

1
@MERose: cosine_measureі cosine_similarityпросто різні реалізації одного і того ж обчислення. Еквівалентно масштабуванню обох вхідних масивів на "одиничні вектори" та взяття крапкового добутку.
Майк Госкі

3
Я б здогадався про те саме. Але це не корисно. Ви представляєте порівняння часу двох алгоритмів, але представляєте лише один з них.
MERose

@MERose О, вибачте. cosine_measure- код, опублікований раніше pkacprzak. Цей код був альтернативою "іншому" загально стандартному рішенням Python.
Майк Госкі

дякую, це чудово, оскільки він не використовує жодної бібліотеки, і зрозуміти математику за цим
зрозуміло

18

без використання жодного імпорту

math.sqrt (x)

можна замінити на

х ** .5

не використовуючи numpy.dot (), ви повинні створити власну точкову функцію, використовуючи розуміння списку:

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

і тоді його просто просте застосування формули подібності косинусу:

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

15

Я зробив орієнтир на основі декількох відповідей на запитання, і наступний фрагмент вважається найкращим вибором:

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)

Результат мене дивує, що реалізація на основі scipyне найшвидша. Я профілював і виявив, що косинусу в науці потрібно багато часу, щоб передати вектор зі списку пітонів до масиву numpy.

введіть тут опис зображення


як ти так впевнений, що це найшвидше?
Джеру Лука

@JeruLuke Я вклеював посилання мого результату порівняння на самому початку відповіді: gist.github.com/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)

Ви можете округлити його після обчислення:

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

Якщо ви хочете, щоб він був дуже коротким, ви можете скористатись цією одноколіркою:

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

Я спробував цей код, і він, здається, не працює. Я спробував це з v1 істотою [2,3,2,5]і v2 істотою [3,2,2,0]. Він повертається з 1.0, як ніби вони були абсолютно однакові. Будь-яка ідея, що не так?
Роб Альсод

Тут виправлена ​​помилка. Хороша робота! Дивіться нижче для більш потворного, але швидшого підходу.
Майк Госкі

Як можливо адаптувати цей код, якщо схожість має бути обчислена в матриці, а не для двох векторів? Я думав, що я беру матрицю і перекладену матрицю замість другого вектора, біт, здається, не працює.
студент

ви можете використовувати np.dot (x, yT), щоб зробити це простіше
user702846

3

Ви можете зробити це в Python, використовуючи просту функцію:

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
Це текстова реалізація косинуса. Це дасть неправильний вихід для числового введення.
alvas

Чи можете ви пояснити, чому ви використовували набір у рядку "intersection = set (vec1.keys ()) & set (vec2.keys ())".
Ghos3t

Також ваша функція, здається, очікує карти, але ви надсилаєте їй списки цілих чисел.
Ghos3t

3

Використовуючи numpy порівняйте один список чисел із кількома списками (матрицею):

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

Ви можете використовувати цю просту функцію для обчислення подібності косинусу:

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
навіщо винаходити колесо?
Jeru Luke

@JeruLuke, можливо, дати відповідь "самостійно", ті, які не потребують додаткового імпорту (і, можливо, переходів зі списку в numpy.array чи щось подібне)
Marco Ottina

1

Якщо ви вже користуєтесь PyTorch , вам слід запустити їх реалізацію CosineS Sličity .

Припустимо, у вас є nдвовимірні numpy.ndarrays, v1і v2, тобто їх форми є обома (n,). Ось як ви отримуєте їхню косинусну схожість:

import torch
import torch.nn as nn

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

Або припустимо, у вас є два numpy.ndarrays w1і w2, форми яких обидві (m, n). Нижче наведено список схожих косинусів, кожен з яких є схожістю косинусів між рядком у w1та відповідним рядком у w2:

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

-1

Усі відповіді чудово підходять для ситуацій, коли ви не можете використовувати NumPy. Якщо можете, ось інший підхід:

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)

Також пам’ятайте про те, EPSILON = 1e-07щоб забезпечити поділ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.