Numpy Broadcast 执行欧式距离矢量化

Posted

技术标签:

【中文标题】Numpy Broadcast 执行欧式距离矢量化【英文标题】:Numpy Broadcast to perform euclidean distance vectorized 【发布时间】:2015-03-12 22:57:39 【问题描述】:

我有 2 x 4 和 3 x 4 的矩阵。我想找到行间的欧几里得距离,最后得到一个 2 x 3 矩阵。这是带有一个 for 循环的代码,该循环计算 a 中每个行向量与所有 b 行向量的欧几里德距离。如何在不使用 for 循环的情况下做同样的事情?

 import numpy as np
a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
dists = np.zeros((2, 3))
for i in range(2):
      dists[i] = np.sqrt(np.sum(np.square(a[i] - b), axis=1))

【问题讨论】:

【参考方案1】:

这里是原始输入变量:

A = np.array([[1,1,1,1],[2,2,2,2]])
B = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
A
# array([[1, 1, 1, 1],
#        [2, 2, 2, 2]])
B
# array([[1, 2, 3, 4],
#        [1, 1, 1, 1],
#        [1, 2, 1, 9]])

A 是一个 2x4 数组。 B 是一个 3x4 数组。

我们希望在一个完全向量化的操作中计算欧几里得距离矩阵操作,其中dist[i,j] 包含 A 中的第 i 个实例与 B 中的第 j 个实例之间的距离。因此,dist 在此示例中为 2x3。

距离

表面上可以用 numpy as 编写

dist = np.sqrt(np.sum(np.square(A-B))) # DOES NOT WORK
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# ValueError: operands could not be broadcast together with shapes (2,4) (3,4)

但是,如上所示,问题在于逐元素减法运算A-B 涉及不兼容的数组大小,特别是第一维中的 2 和 3。

A has dimensions 2 x 4
B has dimensions 3 x 4

为了进行元素减法,我们必须填充 A 或 B 以满足 numpy 的广播规则。我将选择用一个额外的维度填充 A,使其变为 2 x 1 x 4,这允许数组的维度排列以进行广播。有关 numpy 广播的更多信息,请参阅 tutorial in the scipy manual 和 this tutorial 中的最后一个示例。

您可以使用np.newaxis 值或np.reshape 命令执行填充。我在下面显示:

# First approach is to add the extra dimension to A with np.newaxis
A[:,np.newaxis,:] has dimensions 2 x 1 x 4
B has dimensions                     3 x 4

# Second approach is to reshape A with np.reshape
np.reshape(A, (2,1,4)) has dimensions 2 x 1 x 4
B has dimensions                          3 x 4

如您所见,使用任何一种方法都可以使尺寸对齐。我将使用np.newaxis 的第一种方法。所以现在,这将创建 A-B,它是一个 2x3x4 数组:

diff = A[:,np.newaxis,:] - B
# Alternative approach:
# diff = np.reshape(A, (2,1,4)) - B
diff.shape
# (2, 3, 4)

现在我们可以将差分表达式放入dist方程语句中得到最终结果:

dist = np.sqrt(np.sum(np.square(A[:,np.newaxis,:] - B), axis=2))
dist
# array([[ 3.74165739,  0.        ,  8.06225775],
#        [ 2.44948974,  2.        ,  7.14142843]])

请注意,sum 超过 axis=2,这意味着在 2x3x4 数组的第三个轴上求和(其中轴 id 以 0 开头)。

如果你的数组很小,那么上面的命令就可以正常工作。但是,如果您有大型数组,那么您可能会遇到内存问题。请注意,在上面的示例中,numpy 在内部创建了一个 2x3x4 数组来执行广播。如果我们将 A 的维度推广为 a x z 并将 B 的维度推广为 b x z,那么 numpy 将在内部创建一个 a x b x z 数组用于广播。

我们可以通过做一些数学运算来避免创建这个中间数组。因为您将欧几里得距离计算为差平方和,所以我们可以利用差平方和可以重写的数学事实。

请注意,中间项涉及 element-wise 乘法的总和。这个乘法之和被称为点积。因为 A 和 B 都是一个矩阵,那么这个运算实际上就是一个矩阵乘法。因此,我们可以将上面的内容重写为:

然后我们可以编写以下 numpy 代码:

threeSums = np.sum(np.square(A)[:,np.newaxis,:], axis=2) - 2 * A.dot(B.T) + np.sum(np.square(B), axis=1)
dist = np.sqrt(threeSums)
dist
# array([[ 3.74165739,  0.        ,  8.06225775],
#        [ 2.44948974,  2.        ,  7.14142843]])

请注意,上面的答案与之前的实现完全相同。同样,这里的优点是我们不需要为广播创建中间的 2x3x4 数组。

为了完整起见,让我们再次检查threeSums 中每个加法的维度是否允许广播。

np.sum(np.square(A)[:,np.newaxis,:], axis=2) has dimensions 2 x 1
2 * A.dot(B.T) has dimensions                               2 x 3
np.sum(np.square(B), axis=1) has dimensions                 1 x 3

因此,正如预期的那样,最终的 dist 数组的尺寸为 2x3。

this tutorial 中也讨论了这种使用点积代替逐元素乘法之和的方法。

【讨论】:

这个答案非常有用,尤其是解决广播问题的部分。谢谢@***user2010 精彩的答案!但是我有一个问题,因为您似乎仍然需要广播总和中的第一个和最后一个数组。这仍然是可取的吗?【参考方案2】:

我最近在使用深度学习(stanford cs231n,Assignment1)时遇到了同样的问题,但是当我使用时

 np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))

出现错误

MemoryError

那表示我的内存用完了(实际上,它在中间产生了一个 500*5000*1024 的数组。它是如此巨大!)

为了防止这个错误,我们可以使用一个公式来简化:

代码:

import numpy as np
aSumSquare = np.sum(np.square(a),axis=1);
bSumSquare = np.sum(np.square(b),axis=1);
mul = np.dot(a,b.T);
dists = np.sqrt(aSumSquare[:,np.newaxis]+bSumSquare-2*mul)

【讨论】:

添加一些东西;引用自official documentThere are, however, cases where broadcasting is a bad idea because it leads to inefficient use of memory that slows computation.There are, however, cases where broadcasting is a bad idea because it leads to inefficient use of memory that slows computation. 我正在解决同样的问题,但我使用这种无循环实现得到的结果与我使用一循环和二循环解决方案得到的结果不匹配【参考方案3】:

只需在正确的地方使用np.newaxis

 np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))

【讨论】:

您能解释一下Simply using np.newaxis at the right place 的工作原理吗?如果您可以从 a 是 2x4 和 b 是 3x4 的事实开始,那就太好了。【参考方案4】:

此功能已包含在 scipy's spatial module 中,我建议使用它,因为它将在后台进行矢量化和高度优化。但是,正如其他答案所表明的那样,您可以通过多种方式自己做到这一点。

import numpy as np
a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))
# array([[ 3.74165739,  0.        ,  8.06225775],
#       [ 2.44948974,  2.        ,  7.14142843]])
from scipy.spatial.distance import cdist
cdist(a,b)
# array([[ 3.74165739,  0.        ,  8.06225775],
#       [ 2.44948974,  2.        ,  7.14142843]])

【讨论】:

【参考方案5】:

使用numpy.linalg.norm 也适用于广播。为axis 指定整数值将使用向量范数,默认为欧几里得范数。

import numpy as np

a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
np.linalg.norm(a[:, np.newaxis] - b, axis = 2)

# array([[ 3.74165739,  0.        ,  8.06225775],
#       [ 2.44948974,  2.        ,  7.14142843]])

【讨论】:

以上是关于Numpy Broadcast 执行欧式距离矢量化的主要内容,如果未能解决你的问题,请参考以下文章

高效精确地计算欧式距离

Python (3) 如何计算欧式距离

numpy中的矢量化矩阵曼哈顿距离

在计算距离和 np.sum 时优化 numpy 矢量化

欧式距离标准化欧式距离马氏距离余弦距离

如何在不使用 numpy 或 zip 的情况下找到两个列表之间的欧几里得距离?