косинусное сходство документов с весами

Я пытаюсь найти косинусное сходство двух документов, представленных следующим образом:

d1: [(0,1), (3,2), (6, 1)]
d2: [(1,1), (3,1), (5,4), (6,2)]

где каждый документ представляет собой вектор веса темы, где темы — это первый элемент в кортеже, а вес — второй элемент.

Я не уверен, как в этом случае вычислить косинусное сходство с этой взвешенной схемой? Есть ли какой-нибудь модуль/пакет в Python, который позволил бы мне сделать это?


person newdev14    schedule 03.11.2016    source источник


Ответы (3)


Очень простая идея состоит в том, чтобы создать вектор весов, а затем использовать scipy.spatial.distance.cosine для вычисления косинусного расстояния (которое равно 1-сходству):

In [1]: from scipy.spatial.distance import cosine
In [2]: import numpy as np
In [3]: d1 = [(0,1), (3,2), (6, 1)]
In [4]: d2 = [(1,1), (3,1), (5,4), (6,2)]
In [5]: def get_weights(d):
   ...:     w = [ 0. ] * N
   ...:     for i, weight in d:
   ...:         w[i] = weight
   ...:     return np.array(w)
   ...: 

In [6]: w1 = get_weights(d1)
In [7]: w2 = get_weights(d2)
In [8]: 1-cosine(w1, w2)
Out[8]: 0.3481553119113957
person mdml    schedule 03.11.2016
comment
Если векторы длинные или существует множество возможных тем, их следует разрежать — в противном случае опасно делать их плотными. - person gabe; 03.11.2016
comment
Правда, если предположить, что векторы редкие, а тем МНОГО. - person mdml; 03.11.2016
comment
@mdml - Спасибо, я предполагаю, что N - это общее количество уникальных тем? Как я могу найти это? Количество тем может варьироваться от случая к случаю, мне, вероятно, нужен способ их подсчета априори. - person newdev14; 03.11.2016
comment
@ newdev14: совершенно верно, N — это количество тем. Должно быть достаточно просто, например. max( d[0] for d in d1 + d2 ) - person mdml; 04.11.2016
comment
Да, или посмотрите ответы/комментарии travelbones, у которых есть аналогичный способ получения N - person travelingbones; 04.11.2016

На первый взгляд кажется, что нет готовой функции, которая будет принимать входные данные в такой форме. У вас есть два варианта, которые будут зависеть от проблемы, размера массивов и других вещей. Вы можете либо преобразовать каждый из двух векторов веса темы в разреженные векторы scipy, а затем использовать cosine_similarity sklearn (http://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.cosine_similarity.html) ИЛИ вы можете просто написать свой собственный cosine_similarity. То, как я бы сделал последнее, - это привести каждый вектор к диктовке (для более быстрого поиска), как это.

import math

def vect_to_topic_weight(vector):
   return {a:b for a,b in vector}

def norm(vector):
   return math.sqrt(sum(vector[k]**2 for k in vector.iterkeys()))

def dot(a,b):
   return sum(a[k]*b.get(k,0) for k in a.iterkeys())

# returns the cosine_similarity, with inputs as topic_weight dicts
def cosine_similarity(a, b):
   return  dot(a,b) / float(norm(a)*norm(b))
person gabe    schedule 03.11.2016

Да, в python есть пакеты, например. симулятор косинуса scikit-learn. документация здесь. Ниже я дал вам ручной способ сделать это:

import numpy as np

d1 = dict([(0,1), (3,2), (6, 1)]) 
d2 = dict([(1,1), (3,1), (5,4), (6,2)])

l = max(d1.keys() + d2.keys()) + 1 ## Number of topics observed 

v1 = np.zeros((l,))
for i in xrange(l):
    if i in d1.keys():
        v1[i] = d1[i]

v2 = np.zeros((l,))
for i in xrange(l):
    if i in d2.keys():
        v2[i] = d2[i]

## now v1 and v2 are 1-d np arrays representing your docs. 

v1 = v1/np.sqrt(np.dot(v1,v1)) ## normalize
v2 = v2/np.sqrt(np.dot(v2,v2)) ## normalize

cos_sim = np.dot(v1,v2)  ## should get .348155...
person travelingbones    schedule 03.11.2016
comment
спасибо, но количество тем в вашей формуле - это общее количество тем в обоих документах, а не уникальное количество тем... что и нужно - person newdev14; 03.11.2016
comment
вы, вероятно, имеете в виду: max (d1.keys() + d2.keys()) + 1? - person newdev14; 03.11.2016
comment
ваш последний комментарий именно то, что у меня есть. для ясности, max( d1.keys()+d2.keys() ) = max([0,3,6] +[1,3,5,6] ) = max([0,3,6, 1, 3,5,6]) = 6. Мы хотим +1, т.к. количество тем равно 7 (0 - это тема). - person travelingbones; 04.11.2016
comment
как правило, используйте ipython и введите каждую строку (или каждый элемент подстроки) и дайте ему оценить ее, чтобы вы могли видеть, что делает код. - person travelingbones; 04.11.2016