【问题标题】:Efficient way to compute cosine similarity between 1D array and all rows in a 2D array计算一维数组与二维数组中所有行之间余弦相似度的有效方法
【发布时间】:2019-02-02 12:54:34
【问题描述】:

我有一个形状为(300, ) 的一维数组和一个形状为(400, 300) 的二维数组。现在,我想计算这个二维数组中每一行与一维数组的余弦相似度。因此,我的结果应该是 (400, ) 的形状,表示这些向量的相似程度。

我最初的想法是使用for 循环遍历二维数组中的行,然后计算向量之间的余弦相似度。使用广播方法有更快的替代方法吗?

这是一个人为的例子:

In [29]: vec = np.random.randn(300,)
In [30]: arr = np.random.randn(400, 300)

下面是我想计算一维数组之间相似度的方法:

inn = (vec * arr[0]).sum()  
vecnorm = numpy.sqrt((vec * vec).sum())  
rownorm = numpy.sqrt((arr[0] * arr[0]).sum())  
similarity_score = inn / vecnorm / rownorm  

如何将其概括为 arr[0] 被二维数组替换?

【问题讨论】:

  • 你的输出是(300,)?如果你有 400 个向量要“测试”,那么你的输出将是 (400,),一个简单的点积就可以了......
  • @Julien 感谢您发现错字。改正了
  • 你的余弦相似度计算是什么?您可以给我们一个完整的工作示例,其中包含 (4,3) 和 (3,) 形状的数组。
  • @hpaulj 用这些细节更新了问题。请检查!
  • 对于两个二维数组的通用解决方案,请参阅我的另一篇文章stackoverflow.com/a/61643023/13484859

标签: python arrays numpy cosine-similarity


【解决方案1】:

你可以使用cdist:

import numpy as np
from scipy.spatial.distance import cdist


x = np.random.rand(1, 300)
Y = np.random.rand(400, 300)

similarities = 1 - cdist(x, Y, metric='cosine')
print(similarities.shape)

输出

(1, 400)

注意cdist返回的是cosine_distance(更多here),也就是1 - cosine_similarity,所以需要转换结果。

【讨论】:

    【解决方案2】:

    cos相似度的分子可以表示为矩阵相乘,然后分母就可以了:)。

    a_norm = np.linalg.norm(a, axis=1)
    b_norm = np.linalg.norm(b)
    (a @ b) / (a_norm * b_norm)
    

    其中a 是二维数组,b 是一维数组(即向量)

    【讨论】:

    • 这种方法比使用来自 scipy 的 cdist 的方法快 10 倍
    【解决方案3】:

    这里有一个与@Bi Rico's post 相同的方法,但einsum 用于norm 计算 -

    den = np.sqrt(np.einsum('ij,ij->i',arr,arr)*np.einsum('j,j',vec,vec))
    out = arr.dot(vec) / den
    

    此外,我们可以使用vec.dot(vec) 替换np.einsum('j,j',vec,vec) 以获得一些边际改进。

    时间安排 -

    In [45]: vec = np.random.randn(300,)
        ...: arr = np.random.randn(400, 300)
    
    # @Bi Rico's soln with norm
    In [46]: %timeit (np.linalg.norm(arr, axis=1) * np.linalg.norm(vec))
    10000 loops, best of 3: 100 µs per loop
    
    In [47]: %timeit np.sqrt(np.einsum('ij,ij->i',arr,arr)*np.einsum('j,j',vec,vec))
    10000 loops, best of 3: 77.4 µs per loop
    

    在更大的数组上 -

    In [48]: vec = np.random.randn(3000,)
        ...: arr = np.random.randn(4000, 3000)
    
    In [49]: %timeit (np.linalg.norm(arr, axis=1) * np.linalg.norm(vec))
    10 loops, best of 3: 22.2 ms per loop
    
    In [50]: %timeit np.sqrt(np.einsum('ij,ij->i',arr,arr)*np.einsum('j,j',vec,vec))
    100 loops, best of 3: 8.18 ms per loop
    

    【讨论】:

      猜你喜欢
      • 2018-03-27
      • 2015-08-04
      • 2018-10-02
      • 2015-01-30
      • 2021-04-02
      • 2020-03-16
      • 1970-01-01
      • 2023-03-13
      • 2017-03-19
      相关资源
      最近更新 更多