使用 Numpy 在元组列表之间进行外部减法
Taking an outer subtraction between a list of tuples using Numpy
注意: 我不是在寻找有关如何使用 np.subtract.outer
的语法,而是试图解决我在其应用过程中遇到的一个非常具体的问题.
我有一个元组列表 -
a = [(0,0), (1,0), (1,1), (2,0), (2,2)]
a = np.array(a)
我正在尝试在此列表(元组)的每个元素之间实现完全矢量化减法。我可以使用 itertools
进行迭代,如下所示,期望 5x5x2 为 -
#My attempt with iteration (producing expected results)
np.array([np.array(i[0])-np.array(i[1]) for i in itertools.product(a, a)]).reshape(5,5,2)
#RESULT (5x5x2)
array([[[ 0, 0],
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]],
[[ 1, 0],
[ 0, 0],
[ 0, -1],
[-1, 0],
[-1, -2]],
[[ 1, 1],
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 2, 0],
[ 1, 0],
[ 1, -1],
[ 0, 0],
[ 0, -2]],
[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]]])
但是,当使用 np.subtract.outer()
时。我最终得到一个形状为 5x2x5x2 的数组,如下所示 -
#My attempt with vectorization (producing unexpected results)
np.subtract.outer(a,a) #5x2x5x2
array([[[[ 0, 0], #This matrix is same as ....
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]],
[[ 0, 0], #This one...
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]]],
[[[ 1, 1], #However, this ...
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 0, 0], #And this, dont repeat! ..
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]]],
[[[ 1, 1], #This ...
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 1, 1], #And this do..
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]]],
[[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]],
[[ 0, 0],
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]]],
[[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]],
[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]]]])
经过检查,我发现一些矩阵实际上重复了两次,而对于其他矩阵,输出中并非如此(如上所述)。
我的问题是-
- 为什么会出现这种重复?
- 如何在此示例中正确使用
np.subtract.outer
?
- (or,)如何将矩阵转换为预期的矩阵? (5x2x5x2) -> (5x5x2)
注意:这只是一个玩具示例。元组的原始列表是巨大的并且是 float32。
查看 ufunc.outer
(https://numpy.org/doc/stable/reference/generated/numpy.ufunc.outer.html) 的文档,似乎 np.subtract.outer
将在对角线 i=j 上计算两次外积。在我看来,似乎没有办法只选择一个或另一个。我建议使用广播方法,它可以让您决定要在哪些维度上计算外积。 a[:,None]-a[None,:]
给出与 itertools
示例相同的结果。
numpy.ufunc.outer 按预期工作。相反,您正在将矩阵解析为 itertools.product,从而在计算循环时损失了 2 倍(您的对)。
- numpy.ufunc.outer(A,B):
将您的输入展平为一维数组。输出的长度为 len(A) x len(B)。在您的情况下是:(5x2) x (5x2) = 100。这正是您看到的结果。
- itertools.product(A,B):
只会进入您对象的第一个深度并应用产品。由于它们由长度为 2 的元组组成,因此您对
p[0]-p[1]
的调用是两个元组的减法。因此,对于所有元素的可能组合,产品本身缺少 2。
将 flatten() 应用于 intertools.product 的输入将得到与 ufunc.outer
相同的结果
a = np.array([(0,0), (1,1)]).flatten()
b = np.array([(2,2), (3,3)]).flatten()
c = np.subtract.outer(a, b).flatten()
d = np.asarray([p[0]-p[1] for p in product(a,b)]).flatten()
all(c==d)
> true
编辑:
更具体地回答你的问题:
- 复制发生在 numpy.ufunc.outer 压平每个输入。
- 如果您坚持使用
outer
,您可能会拆分输入,对外部进行两次操作,然后将它们合并回去:
a = np.array([(0,0), (1,0), (1,1), (2,0), (2,2)])
b = np.asarray([p[0]-p[1] for p in product(a,a)])
c = np.subtract.outer(a[:,0], a[:,0])
d = np.subtract.outer(a[:,1], a[:,1])
e = np.vstack((c.flatten(),d.flatten())).T
np.all(b==e)
> true
无需使用outer
。只需使用 broadcasting
:
In [5]: a[:,None,:]-a[None,:,:]
Out[5]:
array([[[ 0, 0],
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]],
[[ 1, 0],
[ 0, 0],
[ 0, -1],
[-1, 0],
[-1, -2]],
[[ 1, 1],
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 2, 0],
[ 1, 0],
[ 1, -1],
[ 0, 0],
[ 0, -2]],
[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]]])
a
是 (5,2),通过 None
扩展,2 项是 (5,1,2) 和 (1,5,2),它们一起构成 (5 ,5,2) 数组。
outer
可以通过以下方式使用:
np.subtract.outer(a,a)[:,np.arange(2),:,np.arange(2)].transpose(1,2,0)
从 (5,2,5,2) 中删除重复项并重新排序轴。但它慢了 3 倍。
注意: 我不是在寻找有关如何使用 np.subtract.outer
的语法,而是试图解决我在其应用过程中遇到的一个非常具体的问题.
我有一个元组列表 -
a = [(0,0), (1,0), (1,1), (2,0), (2,2)]
a = np.array(a)
我正在尝试在此列表(元组)的每个元素之间实现完全矢量化减法。我可以使用 itertools
进行迭代,如下所示,期望 5x5x2 为 -
#My attempt with iteration (producing expected results)
np.array([np.array(i[0])-np.array(i[1]) for i in itertools.product(a, a)]).reshape(5,5,2)
#RESULT (5x5x2)
array([[[ 0, 0],
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]],
[[ 1, 0],
[ 0, 0],
[ 0, -1],
[-1, 0],
[-1, -2]],
[[ 1, 1],
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 2, 0],
[ 1, 0],
[ 1, -1],
[ 0, 0],
[ 0, -2]],
[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]]])
但是,当使用 np.subtract.outer()
时。我最终得到一个形状为 5x2x5x2 的数组,如下所示 -
#My attempt with vectorization (producing unexpected results)
np.subtract.outer(a,a) #5x2x5x2
array([[[[ 0, 0], #This matrix is same as ....
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]],
[[ 0, 0], #This one...
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]]],
[[[ 1, 1], #However, this ...
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 0, 0], #And this, dont repeat! ..
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]]],
[[[ 1, 1], #This ...
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 1, 1], #And this do..
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]]],
[[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]],
[[ 0, 0],
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]]],
[[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]],
[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]]]])
经过检查,我发现一些矩阵实际上重复了两次,而对于其他矩阵,输出中并非如此(如上所述)。
我的问题是-
- 为什么会出现这种重复?
- 如何在此示例中正确使用
np.subtract.outer
? - (or,)如何将矩阵转换为预期的矩阵? (5x2x5x2) -> (5x5x2)
注意:这只是一个玩具示例。元组的原始列表是巨大的并且是 float32。
查看 ufunc.outer
(https://numpy.org/doc/stable/reference/generated/numpy.ufunc.outer.html) 的文档,似乎 np.subtract.outer
将在对角线 i=j 上计算两次外积。在我看来,似乎没有办法只选择一个或另一个。我建议使用广播方法,它可以让您决定要在哪些维度上计算外积。 a[:,None]-a[None,:]
给出与 itertools
示例相同的结果。
numpy.ufunc.outer 按预期工作。相反,您正在将矩阵解析为 itertools.product,从而在计算循环时损失了 2 倍(您的对)。
- numpy.ufunc.outer(A,B): 将您的输入展平为一维数组。输出的长度为 len(A) x len(B)。在您的情况下是:(5x2) x (5x2) = 100。这正是您看到的结果。
- itertools.product(A,B):
只会进入您对象的第一个深度并应用产品。由于它们由长度为 2 的元组组成,因此您对
p[0]-p[1]
的调用是两个元组的减法。因此,对于所有元素的可能组合,产品本身缺少 2。
将 flatten() 应用于 intertools.product 的输入将得到与 ufunc.outer
相同的结果a = np.array([(0,0), (1,1)]).flatten()
b = np.array([(2,2), (3,3)]).flatten()
c = np.subtract.outer(a, b).flatten()
d = np.asarray([p[0]-p[1] for p in product(a,b)]).flatten()
all(c==d)
> true
编辑: 更具体地回答你的问题:
- 复制发生在 numpy.ufunc.outer 压平每个输入。
- 如果您坚持使用
outer
,您可能会拆分输入,对外部进行两次操作,然后将它们合并回去:
a = np.array([(0,0), (1,0), (1,1), (2,0), (2,2)])
b = np.asarray([p[0]-p[1] for p in product(a,a)])
c = np.subtract.outer(a[:,0], a[:,0])
d = np.subtract.outer(a[:,1], a[:,1])
e = np.vstack((c.flatten(),d.flatten())).T
np.all(b==e)
> true
无需使用outer
。只需使用 broadcasting
:
In [5]: a[:,None,:]-a[None,:,:]
Out[5]:
array([[[ 0, 0],
[-1, 0],
[-1, -1],
[-2, 0],
[-2, -2]],
[[ 1, 0],
[ 0, 0],
[ 0, -1],
[-1, 0],
[-1, -2]],
[[ 1, 1],
[ 0, 1],
[ 0, 0],
[-1, 1],
[-1, -1]],
[[ 2, 0],
[ 1, 0],
[ 1, -1],
[ 0, 0],
[ 0, -2]],
[[ 2, 2],
[ 1, 2],
[ 1, 1],
[ 0, 2],
[ 0, 0]]])
a
是 (5,2),通过 None
扩展,2 项是 (5,1,2) 和 (1,5,2),它们一起构成 (5 ,5,2) 数组。
outer
可以通过以下方式使用:
np.subtract.outer(a,a)[:,np.arange(2),:,np.arange(2)].transpose(1,2,0)
从 (5,2,5,2) 中删除重复项并重新排序轴。但它慢了 3 倍。