寻找两个矩阵之间的最小余弦距离。

4

我有两个2D的np.arrays,让我们称它们为AB,它们都具有相同的形状。对于2D数组A中的每个向量,我需要找到矩阵B中具有最小余弦距离的向量。为此,我只需在双重循环内部查找最小值。因此,我基本上执行以下操作:

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

在上面的代码中,一个循环被隐藏在一个推导式后面。一切正常,但双重循环使它变得太慢了(我试图用双重推导式重写它,这使事情变得稍微快了一点,但仍然很慢)。
我相信有一个numpy函数可以更快地实现以下功能(使用一些线性代数)。
那么有没有一种更快的方法来实现我想要的结果?
2个回答

3
余弦文档中我们可以得到以下信息: scipy.spatial.distance.cosine(u, v):计算1-D数组之间的余弦距离。 uv之间的余弦距离定义为 enter image description here 其中u⋅v是向量uv的点积。
使用上述公式,我们可以使用`NumPy的广播能力来实现一个矢量化的解决方案,如下所示 -
# 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, ...A.npyB.npy - Salvador Dali
1
还有scipy.spatial.distance.cdist可以计算不同输入之间的距离。但是看起来它比这里的解决方案慢一些。 - user2034412
@SalvadorDali 我认为 scipy.spatial.distance.cdist 不会给你与余弦距离相同的结果。要验证,请查看这两种方法的最小距离,而不仅仅是最小参数。 - Divakar
我发布了一个版本,其中包括scipy.spatial.distance.cdist,对于随机输入返回相同的结果,但速度较慢。 我无法访问A.npy或B.npy,因此无法在那里检查结果。 如果您使用我发布的内容,则可能需要自己调整它以使其按照您想要的方式处理NaN。 - user2034412

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接