找到两个矩阵之间的最小余弦距离
Find minimum cosine distance between two matrices
我有两个 2D np.arrays
我们称它们为 A
和 B
,它们都具有形状。对于二维数组 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 函数可以更快地实现以下(使用一些线性代数)。
那么有什么方法可以更快地达到我想要的效果吗?
从 cosine docs
我们得到以下信息 -
scipy.spatial.distance.cosine(u, v) :计算一维数组之间的余弦距离。
u
和v
之间的余弦距离定义为
其中 u⋅v
是 u
和 v
的点积。
使用上面的公式,我们将得到一个使用 `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
使用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
我有两个 2D np.arrays
我们称它们为 A
和 B
,它们都具有形状。对于二维数组 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 函数可以更快地实现以下(使用一些线性代数)。
那么有什么方法可以更快地达到我想要的效果吗?
从 cosine docs
我们得到以下信息 -
scipy.spatial.distance.cosine(u, v) :计算一维数组之间的余弦距离。
u
和v
之间的余弦距离定义为
其中 u⋅v
是 u
和 v
的点积。
使用上面的公式,我们将得到一个使用 `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
使用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