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

经过检查,我发现一些矩阵实际上重复了两次,而对于其他矩阵,输出中并非如此(如上所述)。

我的问题是-

  1. 为什么会出现这种重复?
  2. 如何在此示例中正确使用 np.subtract.outer
  3. (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

编辑: 更具体地回答你的问题:

  1. 复制发生在 numpy.ufunc.outer 压平每个输入。
  2. 如果您坚持使用 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 倍。