【问题标题】:Cosine Similarity between 2 Number Lists2个数字列表之间的余弦相似度
【发布时间】:2013-08-24 23:37:20
【问题描述】:

我想计算两个列表之间的余弦相似度,例如列表1是dataSetI和列表2是dataSetII。 p>

假设dataSetI[3, 45, 7, 2]dataSetII[2, 54, 13, 15]。列表的长度总是相等。我想将余弦相似度报告为 0 到 1 之间的数字。

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]

def cosine_similarity(list1, list2):
  # How to?
  pass

print(cosine_similarity(dataSetI, dataSetII))

【问题讨论】:

  • 我喜欢这种从这个家庭作业问题中粉碎灵魂的方式,使其成为一个很好的通用参考。 OP 说“我不能使用 numpy,我必须采用行人数学方法”,而最佳答案是“你应该尝试 scipy,它使用 numpy”。 SO 机制为热门问题授予金徽章。
  • Nikana Reklawyks,这是一个很好的观点。 StackOverflow 越来越多地遇到这个问题。我有几个问题被标记为与之前的一些问题“重复”,因为版主没有花时间了解是什么让我的问题与众不同。
  • @NikanaReklawyks,这很棒。看看他的个人资料,它讲述了 SO 0.01% 的顶级贡献者之一的故事,你知道吗?
  • 好吧,我清理了这个问题。现在这是一个通用问题,它仍然没有显示任何研究工作,但是嘿耸耸肩

标签: python cosine-similarity


【解决方案1】:

你应该试试SciPy。它有许多有用的科学例程,例如“用于数值计算积分、求解微分方程、优化和稀疏矩阵的例程”。它使用超快优化的 NumPy 进行数字运算。安装见here

请注意,spatial.distance.cosine 计算的是距离,而不是相似度。因此,您必须从 1 中减去该值才能获得 相似度

from scipy import spatial

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

【讨论】:

  • 为什么 scipy.spatial.distance.cosine "distance.cosine([1, 0, 0], [0, 1, 0])" 中给出的示例返回 "1.0"? (我认为这应该是零,不是吗?)
  • @Z.LI 不,因为它是距离而不是相似度,所以 1.0 是正确的。相似度是一维的,在这种情况下显然为零。
【解决方案2】:

仅基于numpy 的另一个版本

from numpy import dot
from numpy.linalg import norm

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

【讨论】:

  • 定义很清楚,但也许np.inner(a, b) / (norm(a) * norm(b)) 更好理解。对于向量,dot 可以得到与 inner 相同的结果。
  • 仅供参考,这个解决方案在我的系统上比使用 scipy.spatial.distance.cosine 快得多。
  • @ZhengfangXin 余弦相似度定义为-1到1
  • 更短:cos_sim = (a @ b.T) / (norm(a)*norm(b))
  • 如下所述,这对于较小的阵列来说性能要好得多,但随着阵列变得越来越大,改进往往会逐渐减少。
【解决方案3】:

你可以使用cosine_similarity函数形式sklearn.metrics.pairwisedocs

In [23]: from sklearn.metrics.pairwise import cosine_similarity

In [24]: cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Out[24]: array([[-0.5]])

【讨论】:

  • 提醒一下,在 sklearn 版本 0.17 中不推荐将一维数组作为输入数据传递,并将在 0.19 中引发 ValueError。
  • 鉴于此弃用警告,使用 sklearn 执行此操作的正确方法是什么?
  • @Elliott one_dimension_array.reshape(-1,1)
  • @bobo32 cosine_similarity(np.array([1, 0, -1]).reshape(-1,0), np.array([-1, -1, 0]).reshape (-1,0)) 我猜你的意思是?但是这个结果意味着它返回了什么呢?它是一个新的二维数组,而不是余弦相似度。
  • 再用一个括号括起来cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
【解决方案4】:

我认为性能在这里并不重要,但我无法抗拒。 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 循环中完成所有重要的事情,并使用单个平方根。

ETA:将打印调用更新为函数。 (原来是 Python 2.7,而不是 3.3。当前在 Python 2.7 下运行,带有 from __future__ import print_function 语句。)无论哪种方式,输出都是相同的。

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

【讨论】:

  • 在这种情况下cosine_measure 是什么?
  • @MERose: cosine_measurecosine_similarity 只是相同计算的不同实现。相当于将两个输入数组都缩放为“单位向量”并取点积。
  • 我也猜到了。但这没有帮助。您展示了两种算法的时间比较,但只展示了其中一种。
  • @MERose 哦,对不起。 cosine_measure 是 pkacprzak 之前发布的代码。此代码是“其他”全标准 Python 解决方案的替代方案。
  • 谢谢,这很棒,因为它没有使用任何库,而且很清楚理解它背后的数学原理
【解决方案5】:

不使用任何导入

数学.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) )

【讨论】:

    【解决方案6】:

    我根据问题中的几个答案做了benchmark,并且认为以下 sn-p 是最佳选择:

    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/…
    【解决方案7】:
    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, y.T) 让它更简单
    【解决方案8】:

    您可以使用这个简单的函数来计算余弦相似度:

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

    【讨论】:

    • 为什么要重新发明轮子?
    • @JeruLuke 可能会给出一个“独立”的答案,那些不需要额外导入的答案(可能从 list 转换为 numpy.array 或类似的东西)
    【解决方案9】:

    要计算的 Python 代码:

    • 余弦距离
    • 余弦相似度
    • 角距离
    • 角度相似度

    import math
    
    from scipy import spatial
    
    
    def calculate_cosine_distance(a, b):
        cosine_distance = float(spatial.distance.cosine(a, b))
        return cosine_distance
    
    
    def calculate_cosine_similarity(a, b):
        cosine_similarity = 1 - calculate_cosine_distance(a, b)
        return cosine_similarity
    
    
    def calculate_angular_distance(a, b):
        cosine_similarity = calculate_cosine_similarity(a, b)
        angular_distance = math.acos(cosine_similarity) / math.pi
        return angular_distance
    
    
    def calculate_angular_similarity(a, b):
        angular_similarity = 1 - calculate_angular_distance(a, b)
        return angular_similarity
    

    【讨论】:

      【解决方案10】:

      你可以在 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)
      

      【讨论】:

      • 这是余弦的文本实现。它会给数字输入错误的输出。
      • 你能解释一下为什么你在 "intersection = set(vec1.keys()) & set(vec2.keys())" 行中使用了 set。
      • 另外,您的函数似乎在期待地图,但您正在向它发送整数列表。
      【解决方案11】:

      使用 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]
      

      【讨论】:

        【解决方案12】:

        如果您碰巧已经在使用PyTorch,您应该使用他们的CosineSimilarity implementation

        假设你有两个n-维numpy.ndarrays,v1v2,即它们的形状都是(n,)。以下是如何获得它们的余弦相似度:

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

        或者假设你有两个numpy.ndarraysw1w2,它们的形状都是(m, n)。以下为您提供了余弦相似度列表,每个都是w1 中的一行与w2 中的相应行之间的余弦相似度:

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

        【讨论】:

        • 我建议直接使用余弦相似度的函数实现(torch.nn.functional.cosine_similarity),而不是实例化模块实现并应用张量的实例。
        【解决方案13】:

        另一个版本,如果你有一个场景,你有一个向量列表和一个查询向量,并且你想计算查询向量与列表中所有向量的余弦相似度,你可以在下面一次性完成时尚:

        >>> import numpy as np
        
        >>> A      # list of vectors, shape -> m x n
        array([[ 3, 45,  7,  2],
               [ 1, 23,  3,  4]])
        
        >>> B      # query vector, shape -> 1 x n
        array([ 2, 54, 13, 15])
        
        >>> similarity_scores = A.dot(B)/ (np.linalg.norm(A, axis=1) * np.linalg.norm(B))
        
        >>> similarity_scores
        array([0.97228425, 0.99026919])
        

        【讨论】:

          【解决方案14】:

          我们可以用简单的数学方程轻松计算余弦相似度。 Cosine_similarity = 1-(向量的点积/(向量的范数积))。我们可以定义两个函数,分别用于计算点积和范数。

          def dprod(a,b):
              sum=0
              for i in range(len(a)):
                  sum+=a[i]*b[i]
              return sum
          
          def norm(a):
          
              norm=0
              for i in range(len(a)):
              norm+=a[i]**2
              return norm**0.5
          
              cosine_a_b = 1-(dprod(a,b)/(norm(a)*norm(b)))
          

          【讨论】:

            【解决方案15】:

            所有答案都非常适合您无法使用 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 以确保部门安全。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-05-14
              • 2018-09-27
              • 2021-02-23
              • 1970-01-01
              • 2017-04-04
              • 2019-07-21
              • 2020-10-28
              相关资源
              最近更新 更多