2个数字列表之间的余弦相似度


118

我需要计算两个列表之间的余弦相似度,比如说列表1是,列表2是。我不能使用任何东西,例如numpy或统计模块。我必须使用通用模块(数学等)(并尽可能减少模块数量,以减少花费的时间)。dataSetIdataSetII

假设dataSetIis [3, 45, 7, 2]dataSetIIis [2, 54, 13, 15]。列表的长度始终相等。

当然,余弦相似度在0到1之间,因此,它将用舍入到小数点后三位或四位format(round(cosine, 3))

预先非常感谢您的帮助。


29
我喜欢这样一种方式,将灵魂从这个家庭作业问题中解脱出来,使其成为一个很好的通用参考文献。OP说“ 我不能使用numpy,我必须走行人数学的方式”,而最高答案是“您应该尝试scipy,它使用numpy”。SO机械师为这个受欢迎的问题授予了金牌。
Nikana Reklawyks

1
Nikana Reklawyks,这是一个很好的观点。我越来越多地使用StackOverflow来解决这个问题。我已经将几个问题标记为较早的问题的“重复项”,因为主持人没有花时间去理解使我的问题独特的原因。
LRK9 '11

@NikanaReklawyks,太好了。看一下他的个人资料,它讲述了SO最高.01%贡献者之一的故事,您知道吗?
内森·查佩尔

Answers:


174

您应该尝试SciPy。它具有许多有用的科学例程,例如“用于数值计算积分,求解微分方程,优化和稀疏矩阵的例程”。它使用超快速优化的NumPy进行数字运算。请参阅此处进行安装。

注意space.distance.cosine计算的是distance,而不是相似度。因此,必须从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
仅供参考,该解决方案在我的系统上比使用速要快得多scipy.spatial.distance.cosine
奥扎

@ZhengfangXin余弦相似度的定义范围是-1到1
dontloo

2
甚至更短:cos_sim = (a @ b.T) / (norm(a)*norm(b))
以示例方式学习统计数据(

与其他方法相比,这是迄今为止最快的方法。
Jason Youn

72

您可以使用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]])

20
提醒一下,在sklearn版本0.17中,不建议将一维数组作为输入数据使用,它将在0.19中引发ValueError。
崇棠

4
给定此弃用警告,使用sklearn执行此操作的正确方法是什么?
艾略特

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

2
@ bobo32余弦相似度(np.array([1,0,-1])。reshape(-1,0),np.array([-1,-1,0])。reshape(-1,0))我你猜是什么意思?但是,该结果意味着它返回了什么?它是一个新的2d数组,而不是余弦相似度。
Isbister

10
用另一个支架将其围起来cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush

33

我认为这里的性能并不重要,但是我无法抗拒。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一样的噪声,一次提取元素,但没有批量数组复制,并且所有重要的事情都在单个for循环中完成,并且使用单个平方根。

预计到达时间:将打印调用更新为功能。(原始版本是Python 2.7,而不是3.3。当前版本在带from __future__ import print_function声明的Python 2.7下运行。)两种方法的输出都是相同的。

在3.0GHz Core 2 Duo上的CPYthon 2.7.3:

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

因此,在这种情况下,非Python方式要快3.6倍。


2
什么是cosine_measure在这种情况下?
MERose

1
@MERose:cosine_measurecosine_similarity只是同一计算的不同实现。等效于将两个输入数组都缩放为“单位矢量”并获得点积。
Mike Housky

3
我猜也是一样。但这没有帮助。您介绍了两种算法的时间比较,但只介绍了其中一种。
MERose

@MERose哦,对不起。 cosine_measure是pkacprzak先前发布的代码。该代码是“其他”全标准Python解决方案的替代方案。
Mike Housky

谢谢,这很棒,因为它没有使用任何库,而且很清楚可以理解其背后的数学
原理

16

不使用任何进口

math.sqrt(x)

可以替换为

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不是最快的。我进行了分析,发现scipy中的余弦需要大量时间才能将向量从python列表转换为numpy数组。

在此处输入图片说明


您如何确定这是最快的?
杰鲁·卢克

@JeruLuke我已经在答案的开头粘贴了基准测试结果的链接: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)

您可以在计算后将其取整:

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,就好像它们完全一样。知道有什么问题吗?
罗伯·阿索德,2013年

该修复程序在这里起作用。不错的工作!参见下文,了解更丑陋但更快速的方法。
Mike Housky

如果必须在矩阵内而不是两个向量之间计算相似度,那么如何修改此代码?我以为我用一个矩阵和转置矩阵代替第二个矢量,有点似乎不起作用。
学生

您可以使用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(vec1.keys())和set(vec2.keys())”行中使用set。
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或类似的东西)
Marco Ottina

1

如果您碰巧已经在使用PyTorch,则应使用其CosineSimilarity实现

假设你有两个nnumpy.ndarrayS,v1v2,即它们的形状都是(n,)。这就是它们的余弦相似度的方法:

import torch
import torch.nn as nn

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

或者,假设你有两个numpy.ndarray小号w1w2,其形状都是(m, n)。以下内容为您提供了余弦相似度列表,每一个都是in中的一行w1和in中的相应行之间的余弦相似性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确保分裂。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.