numpy.tensordot函数如何逐步工作?

时间:2018-08-23 15:42:07

标签: python numpy

我是numpy的新手,因此在可视化numpy.tensordot()函数的工作时遇到了一些问题。根据{{​​1}}的文档,轴在参数中传递,其中轴= 0或1表示法线矩阵乘法,而轴= 2表示收缩。

有人可以解释在给定的例子中乘法将如何进行吗?

  

示例1:tensordot为什么会在轴= 2时引发错误?
  示例2:a=[1,1] b=[2,2] for axes=0,1

2 个答案:

答案 0 :(得分:2)

编辑:此答案的最初重点是在axes是元组的情况下,为每个参数指定一个或多个轴。这种用法使我们能够对传统的dot执行变体,特别是对于大于2d的数组(我在链接问题中的回答也为https://stackoverflow.com/a/41870980/901925)。标量轴是一个特例,它被转换为元组版本。因此,它的核心仍然是dot产品。

作为元组的轴

In [235]: a=[1,1]; b=[2,2]

ab是列表; tensordot将它们变成数组。

In [236]: np.tensordot(a,b,(0,0))
Out[236]: array(4)

由于它们都是一维数组,因此我们将轴值指定为0。

如果我们尝试指定1:

In [237]: np.tensordot(a,b,(0,1))
---------------------------------------------------------------------------
   1282     else:
   1283         for k in range(na):
-> 1284             if as_[axes_a[k]] != bs[axes_b[k]]:
   1285                 equal = False
   1286                 break

IndexError: tuple index out of range

正在检查a的轴0的大小是否与b的轴1的大小匹配。但是由于b是1d,所以无法检查。

In [239]: np.array(a).shape[0]
Out[239]: 2
In [240]: np.array(b).shape[1]
IndexError: tuple index out of range

您的第二个示例是2d数组:

In [242]: a=np.array([[1,1],[1,1]]); b=np.array([[2,2],[2,2]])

指定a的最后一个轴和b的第一个轴(倒数第二个),生成常规矩阵(点)乘积:

In [243]: np.tensordot(a,b,(1,0))
Out[243]: 
array([[4, 4],
       [4, 4]])
In [244]: a.dot(b)
Out[244]: 
array([[4, 4],
       [4, 4]])

更好的诊断值:

In [250]: a=np.array([[1,2],[3,4]]); b=np.array([[2,3],[2,1]])
In [251]: np.tensordot(a,b,(1,0))
Out[251]: 
array([[ 6,  5],
       [14, 13]])
In [252]: np.dot(a,b)
Out[252]: 
array([[ 6,  5],
       [14, 13]])

In [253]: np.tensordot(a,b,(0,1))
Out[253]: 
array([[11,  5],
       [16,  8]])
In [254]: np.dot(b,a)      # same numbers, different layout
Out[254]: 
array([[11, 16],
       [ 5,  8]])
In [255]: np.dot(b,a).T
Out[255]: 
array([[11,  5],
       [16,  8]])

另一对:

In [256]: np.tensordot(a,b,(0,0))
In [257]: np.dot(a.T,b)
轴的

(0,1,2)是错误的。 axis参数应为2个数字或2个元组,分别与2个参数相对应。

tensordot中的基本处理是对输入进行转置和整形,以便随后可以将结果传递给常规矩阵乘积(a的倒数,b的倒数第二)的np.dot

轴为标量

如果我对tensordot代码的理解正确,则axes参数将转换为两个列表,其中包括:

def foo(axes):
    try:
        iter(axes)
    except Exception:
        axes_a = list(range(-axes, 0))
        axes_b = list(range(0, axes))
    else:
        axes_a, axes_b = axes
    try:
        na = len(axes_a)
        axes_a = list(axes_a)
    except TypeError:
        axes_a = [axes_a]
        na = 1
    try:
        nb = len(axes_b)
        axes_b = list(axes_b)
    except TypeError:
        axes_b = [axes_b]
        nb = 1

    return axes_a, axes_b

对于标量值为0,1,2,结果为:

In [281]: foo(0)
Out[281]: ([], [])
In [282]: foo(1)
Out[282]: ([-1], [0])
In [283]: foo(2)
Out[283]: ([-2, -1], [0, 1])

axes=1与在元组中指定的相同:

In [284]: foo((-1,0))
Out[284]: ([-1], [0])

对于2:

In [285]: foo(((-2,-1),(0,1)))
Out[285]: ([-2, -1], [0, 1])

在我的最新示例中,axes=2与在2个数组的所有轴上指定dot相同:

In [287]: np.tensordot(a,b,axes=2)
Out[287]: array(18)
In [288]: np.tensordot(a,b,axes=((0,1),(0,1)))
Out[288]: array(18)

这与对平面的一维数组视图执行dot相同:

In [289]: np.dot(a.ravel(), b.ravel())
Out[289]: 18

我已经演示了axes=1情况下这些阵列的常规点积。

axes=0axes=((),())相同,两个数组没有求和轴:

In [292]: foo(((),()))
Out[292]: ([], [])

np.tensordot(a,b,((),()))np.tensordot(a,b,axes=0)

在输入数组为1d时,是-2翻译中的foo(2)给您带来了问题。 axes=1是一维数组的“收缩”。换句话说,不要从字面上太用文字描述。他们只是试图描述代码的作用。它们不是正式规范。

等效总和

我认为einsum的轴规格更清晰,功能更强大。这是0,1,2

的等效项
In [295]: np.einsum('ij,kl',a,b)
Out[295]: 
array([[[[ 2,  3],
         [ 2,  1]],

        [[ 4,  6],
         [ 4,  2]]],


       [[[ 6,  9],
         [ 6,  3]],

        [[ 8, 12],
         [ 8,  4]]]])
In [296]: np.einsum('ij,jk',a,b)
Out[296]: 
array([[ 6,  5],
       [14, 13]])
In [297]: np.einsum('ij,ij',a,b)
Out[297]: 18

axis = 0情况下,等同于:

np.dot(a[:,:,None],b[:,None,:])

它添加了一个新的最后一个轴和新的第二个到最后一个轴,并对它们进行了常规的点积求和。但是我们通常在广播中进行这种“外部”乘法:

a[:,:,None,None]*b[None,None,:,:]

虽然在轴上使用0,1,2很有趣,但实际上并没有增加新的计算能力。元组的轴更强大,更有用。

代码摘要(重要步骤)

1-按照上述axes函数的摘录,将axes_a转换为axes_bfoo

2-将ab分成数组,并获得形状和ndim

3-检查要累加(收缩)的轴上的匹配尺寸

4-构造一个newshape_anewaxes_a;对于b(复杂步骤)

5-at = a.transpose(newaxes_a).reshape(newshape_a);对于b

6-res = dot(at, bt)

7-将res重塑为所需的返回形状

5和6是计算核心。 4从概念上讲是最复杂的步骤。对于所有axes值,计算结果都是相同的,是dot乘积,但设置有所不同。

超过0,1,2

虽然文档中只标出了标量轴的0,1,2,但代码并不限于这些值

In [331]: foo(3)
Out[331]: ([-3, -2, -1], [0, 1, 2])

如果输入为3,则轴= 3应该起作用:

In [330]: np.tensordot(np.ones((2,2,2)), np.ones((2,2,2)), axes=3)
Out[330]: array(8.)

或更笼统地说:

In [325]: np.tensordot(np.ones((2,2,2)), np.ones((2,2,2)), axes=0).shape
Out[325]: (2, 2, 2, 2, 2, 2)
In [326]: np.tensordot(np.ones((2,2,2)), np.ones((2,2,2)), axes=1).shape
Out[326]: (2, 2, 2, 2)
In [327]: np.tensordot(np.ones((2,2,2)), np.ones((2,2,2)), axes=2).shape
Out[327]: (2, 2)
In [328]: np.tensordot(np.ones((2,2,2)), np.ones((2,2,2)), axes=3).shape
Out[328]: ()

如果输入为0d,则轴= 0有效(轴= 1无效):

In [335]: np.tensordot(2,3, axes=0)
Out[335]: array(6)

你能解释一下吗?

In [363]: np.tensordot(np.ones((4,2,3)),np.ones((2,3,4)),axes=2).shape
Out[363]: (4, 4)

我玩过3d数组的其他标量轴值。虽然可以提出可行的形状对,但更明确的元组轴值更易于使用。 0,1,2选项是仅适用于特殊情况的快捷方式。元组方法更易于使用-尽管我仍然更喜欢einsum表示法。

答案 1 :(得分:1)

示例1-0:LinearLayout dataOne = findViewById(R.is.data_one); TextView dataOneTitle = dataOne.findViewById(R.id.title);

在这种情况下, a b 都具有单个轴并具有形状np.tensordot([1, 1], [2, 2], axes=0)

(2,)参数可以转换为(( a 的最后 0 个轴),(第一个 0 个轴( b )),或者在这种情况下为axes=0。这些是将要收缩的轴。

所有其他轴均不会收缩。由于 a b 均具有第0个轴,而没有其他轴,因此它们是((), ())轴。

tensordot操作如下(大致):

((0,), (0,))

请注意,由于 a b 之间共有2个轴,并且由于我们要收缩其中的0个轴,因此结果有2个轴。形状为[ [x*y for y in b] # all the non-contraction axes in b for x in a # all the non-contraction axes in a ] ,因为它们是分别在 a b 中按顺序排列的各个未收缩轴的形状。

示例1-1:(2,2)

np.tensordot([1, 1], [2, 2], axes=1)参数可以转换为(( a 的最后 1 个轴),(第一个 1 个轴( b )),或者在这种情况下为axes=0。这些是将要收缩的轴

所有其他轴均不会收缩。由于我们已经在收缩每个轴,所以其余的轴是((0,), (0,))

tensordot操作如下:

((), ())

请注意,由于我们要收缩所有轴,因此结果是标量(或0形张量)。在numpy中,您只会得到形状为sum( # summing over contraction axis [x*y for x,y in zip(a, b)] # contracted axes must line up ) 的张量,代表0个轴,而不是实际的标量。

示例1-2:()

之所以不起作用,是因为 a b 都没有两个单独的轴可以收缩。

示例2-1:np.tensordot([1, 1], [2, 2], axes=2)

我跳过了几个示例,因为它们不够复杂,无法比我不认为的前几个示例更加清晰。

在这种情况下, a b 都具有两个轴(使这个问题更加有趣),并且它们两者的形状均为np.tensordot([[1,1],[1,1]], [[2,2],[2,2]], axes=1)

(2,2)参数仍然代表 a 的最后 1 轴和 b <的前 1 轴/ strong>,留下axes=1。这些是将要收缩的轴。

剩余的轴不会收缩,并且会影响最终解决方案的形状。这些是((1,), (0,))

然后我们可以构建张量运算。为了争辩,将 a b 假定为numpy数组,以便我们可以使用数组属性并使问题更清晰(例如((0,), (1,)))。

b=np.array([[2,2],[2,2]])

结果的形状为[ [ sum( # summing the contracted indices [x*y for x,y in zip(v,w)] # axis 1 of a and axis 0 of b must line up for the summation ) for w in b.T # iterating over axis 1 of b (i.e. the columns) ] for v in a # iterating over axis 0 of a (i.e. the rows) ] ,因为它们是未收缩的轴。