【问题标题】:Find minimum cosine distance between two matrices找到两个矩阵之间的最小余弦距离
【发布时间】:2015-12-17 18:40:24
【问题描述】:

我有两个 2D np.arrays 我们称它们为 AB,两者都有形状。对于二维数组A 中的每个向量,我需要在矩阵B 中找到具有最小余弦距离的向量。为此,我只有一个双 for 循环,在其中我试图找到最小值。所以基本上我会做以下事情:

from scipy.spatial.distance import cosine
l, res = A.shape[0], []
for i in xrange(l):
    minimum = min((cosine(A[i], B[j]), j) for j in xrange(l))
    res.append(minimum[1])

在上面的代码中,其中一个循环隐藏在理解后面。一切正常,但是双重 for 循环让它太慢了(我尝试用双重理解重写它,这让事情变得更快了一点,但仍然很慢)。

我相信有一个 numpy 函数可以更快地实现以下目标(使用一些线性代数)。

那么有没有办法更快地实现我想要的?

【问题讨论】:

    标签: numpy scipy vectorization linear-algebra


    【解决方案1】:

    来自cosine docs 我们有以下信息 -

    scipy.spatial.distance.cosine(u, v) :计算一维数组之间的余弦距离。

    uv 之间的余弦距离定义为

    其中u⋅vuv 的点积。

    使用上面的公式,我们将有一个使用`NumPy's broadcasting capability 的矢量化解决方案,就像这样 -

    # Get the dot products, L2 norms and thus cosine distances
    dots = np.dot(A,B.T)
    l2norms = np.sqrt(((A**2).sum(1)[:,None])*((B**2).sum(1)))
    cosine_dists = 1 - (dots/l2norms)
    
    # Get min values (if needed) and corresponding indices along the rows for res.
    # Take care of zero L2 norm values, by using nanmin and nanargmin  
    minval = np.nanmin(cosine_dists,axis=1)
    cosine_dists[np.isnan(cosine_dists).all(1),0] = 0
    res = np.nanargmin(cosine_dists,axis=1)
    

    运行时测试 -

    In [81]: def org_app(A,B):
        ...:    l, res, minval = A.shape[0], [], []
        ...:    for i in xrange(l):
        ...:        minimum = min((cosine(A[i], B[j]), j) for j in xrange(l))
        ...:        res.append(minimum[1])
        ...:        minval.append(minimum[0])
        ...:    return res, minval
        ...: 
        ...: def vectorized(A,B):
        ...:     dots = np.dot(A,B.T)
        ...:     l2norms = np.sqrt(((A**2).sum(1)[:,None])*((B**2).sum(1)))
        ...:     cosine_dists = 1 - (dots/l2norms)
        ...:     minval = np.nanmin(cosine_dists,axis=1)
        ...:     cosine_dists[np.isnan(cosine_dists).all(1),0] = 0
        ...:     res = np.nanargmin(cosine_dists,axis=1)
        ...:     return res, minval
        ...: 
    
    In [82]: A = np.random.rand(400,500)
        ...: B = np.random.rand(400,500)
        ...: 
    
    In [83]: %timeit org_app(A,B)
    1 loops, best of 3: 10.8 s per loop
    
    In [84]: %timeit vectorized(A,B)
    10 loops, best of 3: 145 ms per loop
    

    验证结果 -

    In [86]: x1, y1 = org_app(A, B)
        ...: x2, y2 = vectorized(A, B)
        ...: 
    
    In [87]: np.allclose(np.asarray(x1),x2)
    Out[87]: True
    
    In [88]: np.allclose(np.asarray(y1)[~np.isnan(np.asarray(y1))],y2[~np.isnan(y2)])
    Out[88]: True
    

    【讨论】:

    • 这是一个例子:我所做的只是A = np.load('A.npy')B = np.load('B.npy')x1, y1 = org_app(A, B)x2, y2 = vectorized(A, B),然后检查 x1 和 list(x2)。我得到x1=[459, 571, 526, 477, 309, 498, 504, 4, 529, ..x2=[29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,...A.npy, B.npy
    • 还有scipy.spatial.distance.cdist 用于计算各个输入中所有对之间的距离。但是看起来比这里的解决方案要慢一些。
    • @SalvadorDali 我不认为scipy.spatial.distance.cdist 会给你与余弦距离相同的结果。要进行验证,请查看最小距离,而不仅仅是这两种方法的最小参数。
    • 我发布了一个带有scipy.spatial.distance.cdist 的版本,它对随机输入返回相同的结果,但速度较慢。我无法访问 A.npy 或 B.npy 所以我无法在那里检查结果。如果您使用我发布的内容,您可能需要对其进行修改才能以您想要的方式处理 NaN。
    【解决方案2】:

    使用scipy.spatial.distance.cdist:

    from scipy.spatial.distance import cdist
    
    def cdist_func(A, B):
        dists = cdist(A, B, 'cosine')
        return np.argmin(dists, axis=1), np.min(dists, axis=1)
    

    得到的结果与 Divakar 的回答相同:

    x2, y2 = vectorized(A, B)
    x3, y3 = cdist_func(A, B)
    
    np.allclose(x2, x3) # True
    np.allclose(y2, y3) # True
    

    但没那么快:

    %timeit vectorized(A, B) # 11.9 ms per loop
    %timeit cdist_func(A, B) # 85.9 ms per loop
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-29
      • 2017-01-13
      • 2016-08-23
      • 2015-08-28
      • 2020-10-28
      • 2021-07-15
      • 2022-11-21
      • 1970-01-01
      相关资源
      最近更新 更多