Косинусное сходство между 2 списками номеров


119

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

Допустим, dataSetIесть [3, 45, 7, 2]и dataSetIIесть [2, 54, 13, 15]. Длина списков всегда одинакова.

Конечно, косинусное сходство находится между 0 и 1 , и для этого оно будет округлено до третьего или четвертого десятичного знака с format(round(cosine, 3)).

Заранее большое спасибо за помощь.


29
Мне нравится, как ТАК раздавил душу из этого домашнего задания, чтобы сделать его хорошим общим справочным. OP говорит: « Я не могу использовать numpy , я должен идти по математике для пешеходов», а главный ответ - «вам следует попробовать scipy, он использует numpy». Механика SO дает золотой значок на популярный вопрос.
— Никана Реклавикс

1
Никана Реклавикс, это отличный аргумент. Со StackOverflow у меня возникала эта проблема все чаще и чаще. И у меня было несколько вопросов, отмеченных как «дубликаты» какого-то предыдущего вопроса, потому что модераторы не нашли времени, чтобы понять, что делало мой вопрос уникальным.
— LRK9

@NikanaReklawyks, это здорово. Посмотрите на его профиль, он рассказывает историю одного из 0,01% лучших участников SO, понимаете?
— Натан Чаппелл,

Ответы:


175

Вам следует попробовать SciPy . В нем есть множество полезных научных процедур, например, «процедуры для численного вычисления интегралов, решения дифференциальных уравнений, оптимизации и разреженных матриц». Он использует сверхбыстрый оптимизированный NumPy для обработки чисел. Смотрите здесь для установки.

Обратите внимание, что space.distance.cosine вычисляет расстояние , а не сходство. Итак, вы должны вычесть значение из 1, чтобы получить сходство .

from scipy import spatial

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

125

другая версия, основанная 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для векторов.
— Belter

15
К вашему сведению, это решение значительно быстрее в моей системе, чем при использовании 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
Как правильно сделать это с помощью sklearn, учитывая это предупреждение об устаревании?
— 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 угадайте вы имеете в виду? Но что этот результат означает, что он возвращается? Это новый 2d-массив, а не косинусное подобие.
— Isbister

10
Приложите его с еще одним кронштейномcosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
— Ayush

34

Я не думаю, что производительность здесь имеет большое значение, но я не могу сопротивляться. Функция zip () полностью копирует оба вектора (фактически, это больше похоже на транспонирование матрицы) просто для того, чтобы получить данные в «питоническом» порядке. Было бы интересно приурочить реализацию по мелочам:

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-подобный шум извлечения элементов по одному, но не выполняет массовое копирование массива и выполняет все важное за один цикл for и использует единственный квадратный корень.

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 09

@MERose Ой, извини. cosine_measureэто код, опубликованный ранее pkacprzak. Этот код был альтернативой «другому» полностью стандартному решению Python.
— Майк Хаски

спасибо, это здорово, так как он не использует никакой библиотеки и ясно понять математику, стоящую за ним
— grepit

18

без использования импорта

Math.sqrt (х)

можно заменить на

х ** .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не самая быстрая. Я профилировал и обнаружил, что косинус в scipy занимает много времени, чтобы преобразовать вектор из списка python в массив 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

Можете ли вы объяснить, почему вы использовали set в строке «crossction = 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
зачем изобретать велосипед?
— Джеру Лука

@JeruLuke, возможно, чтобы дать "отдельный" ответ, те, которые не требуют дополнительного импорта (и, возможно, преобразования из списка в numpy.array или что-то в этом роде)
— Марко Оттина

0

Если вы уже используете PyTorch , вам следует использовать их реализацию CosineSimilarity. .

Предположим, у вас есть 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.