Python Numpy广播以执行欧几里德距离矢量化

Python Numpy广播以执行欧几里德距离矢量化,python,numpy,machine-learning,vectorization,Python,Numpy,Machine Learning,Vectorization,我有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 ran

我有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))

只需在正确的位置使用
np.newaxis

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

此功能已经包含在中,我建议使用它,因为它将在引擎盖下进行矢量化和高度优化。但是,正如另一个答案所表明的,你可以自己做这件事

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

我最近在使用深度学习时也遇到了同样的问题(斯坦福cs231n,作业1),但当我使用

 np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))
有一个错误

MemoryError
<>这意味着我的内存用完了(实际上,在中间产生了一个500×5000×1024的数组。它太大了!)

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

代码:


以下是原始输入变量:

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作为

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以满足numpy的广播规则。我将选择用一个额外的维度填充A,使其成为2 x 1 x 4,从而允许阵列的维度排列以进行广播。有关numpy广播的更多信息,请参阅中的和最后一个示例

您可以使用
np.newaxis
值或
np.reformate
命令执行填充。我将展示以下两个方面:

# 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
用于广播的数组

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
我们可以通过做一些数学运算来避免创建这个中间数组。因为你把欧几里德距离计算为平方差之和,我们可以利用数学事实,即平方差之和可以重写

请注意,中间项涉及元素相乘的总和。这个乘以的和被称为点积。因为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阵列

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
为了完整性,让我们再次检查
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

点乘代替元素乘法和的使用也在中讨论。

使用也适用于广播。为轴指定整数值将使用向量范数,默认为欧几里德范数

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

添加一些东西;然而,引用
中的话,有些情况下广播是一个坏主意,因为它会导致内存的低效使用,从而降低计算速度,广播是一个坏主意的情况,因为它会导致内存使用效率低下,从而降低计算速度。
我正在处理同一个问题,但使用此无循环实现得到的结果与使用一个和两个循环解决方案得到的结果不匹配。请您解释一下,仅在右侧使用np.newaxis如何
放置
是否有效?如果你能从
a
是2x4而
b
是3x4这一事实开始,那就太好了。这个答案非常有用,尤其是解决广播问题的部分。谢谢@StackOverflowUser2010精彩的回答!但我有一个问题,因为似乎您仍然需要广播总和中的第一个和最后一个数组。这仍然是可取的吗?