首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >numpy.tensordot函数是如何逐步工作的?

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

Stack Overflow用户
提问于 2018-08-23 15:42:07
回答 2查看 4.1K关注 0票数 4

我对numpy并不熟悉,所以我在可视化numpy.tensordot()函数的工作时遇到了一些问题。根据tensordot的文档,轴在参数中传递,其中axes=0或1表示正常矩阵乘法,而axes=2表示收缩。

有人能解释一下乘法如何处理给定的例子吗?

例1:a=[1,1] b=[2,2] for axes=0,1为什么要为axes=2抛出一个错误? 例2:a=[[1,1],[1,1]] b=[[2,2],[2,2]] for axes=0,1,2

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-08-23 16:10:58

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

轴为元组

代码语言:javascript
复制
In [235]: a=[1,1]; b=[2,2]

ab是列表;tensordot将它们转换为数组。

代码语言:javascript
复制
In [236]: np.tensordot(a,b,(0,0))
Out[236]: array(4)

因为它们都是一维数组,所以我们将轴值指定为0。

如果我们试图指定1:

代码语言:javascript
复制
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是一维的,所以它无法检查这一点。

代码语言:javascript
复制
In [239]: np.array(a).shape[0]
Out[239]: 2
In [240]: np.array(b).shape[1]
IndexError: tuple index out of range

第二个例子是2d数组:

代码语言:javascript
复制
In [242]: a=np.array([[1,1],[1,1]]); b=np.array([[2,2],[2,2]])

指定a的最后一个轴和b的第一个轴(第二个到最后一个),生成传统的矩阵(dot)产品:

代码语言:javascript
复制
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]])

更好的诊断价值:

代码语言:javascript
复制
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]])

另一对配对:

代码语言:javascript
复制
In [256]: np.tensordot(a,b,(0,0))
In [257]: np.dot(a.T,b)

(0,1,2)对于轴是完全错误的。axis参数应该是两个数字,或两个元组,对应于两个参数。

tensordot中的基本处理是对输入进行转置和整形,这样就可以将结果传递给传统的( a的最后一种,b的最后一种)矩阵积的np.dot

标量轴

如果我对tensordot代码的读取是正确的,则将axes参数转换为两个列表:

代码语言:javascript
复制
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的结果是:

代码语言:javascript
复制
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与在元组中指定相同:

代码语言:javascript
复制
In [284]: foo((-1,0))
Out[284]: ([-1], [0])

对于2人:

代码语言:javascript
复制
In [285]: foo(((-2,-1),(0,1)))
Out[285]: ([-2, -1], [0, 1])

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

代码语言:javascript
复制
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)

这与对数组的平面视图、1d视图执行dot操作相同:

代码语言:javascript
复制
In [289]: np.dot(a.ravel(), b.ravel())
Out[289]: 18

我已经演示了这些数组的传统点积,即axes=1情况。

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

代码语言:javascript
复制
In [292]: foo(((),()))
Out[292]: ([], [])

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

当输入数组为1d时,正是foo(2)翻译中的foo(2)给您带来了问题。axes=1是一维数组的“收缩”。换句话说,不要把文档中的描述这个词看得太过字面意思。它们只是试图描述代码的操作;它们不是正式的规范。

E-等价物

我认为einsum的axes规范更清晰、更强大。这是0,1,2的等价物

代码语言:javascript
复制
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

axes=0的情况相当于:

代码语言:javascript
复制
np.dot(a[:,:,None],b[:,None,:])

它增加了一个新的最后轴和新的第二至最后的轴,并做了一个传统的点积之和在这些。但是我们通常用广播来做这种“外”乘法:

代码语言:javascript
复制
a[:,:,None,None]*b[None,None,:,:]

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

代码摘要(大步骤)

1-将axes转换为axes_aaxes_b,作为上述foo函数的摘录。

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,但代码并不局限于这些值

代码语言:javascript
复制
In [331]: foo(3)
Out[331]: ([-3, -2, -1], [0, 1, 2])

如果输入为3,则axes=3应该可以工作:

代码语言:javascript
复制
In [330]: np.tensordot(np.ones((2,2,2)), np.ones((2,2,2)), axes=3)
Out[330]: array(8.)

或更广泛地说:

代码语言:javascript
复制
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,则axes=0工作(axes =1不工作):

代码语言:javascript
复制
In [335]: np.tensordot(2,3, axes=0)
Out[335]: array(6)

你能解释一下吗?

代码语言:javascript
复制
In [363]: np.tensordot(np.ones((4,2,3)),np.ones((2,3,4)),axes=2).shape
Out[363]: (4, 4)

我已经处理过其他三维数组的标量轴值。虽然可以找到可以工作的形状对,但更显式的元组轴值更容易处理。0,1,2选项是仅适用于特殊情况的捷径。元组方法使用起来容易得多--尽管我仍然更喜欢einsum表示法。

票数 7
EN

Stack Overflow用户

发布于 2018-08-23 16:42:45

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

在这种情况下,ab都有一个单轴,并且具有形状(2,)

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

其他所有的轴都不会收缩。因为每个ab都有一个0轴,而没有其他轴,所以这些是((0,), (0,))轴.

然后,tensordot操作如下(大致):

代码语言:javascript
复制
[
    [x*y for y in b]  # all the non-contraction axes in b
    for x in a        # all the non-contraction axes in a
]

注意,由于在ab之间有2个总轴可用,而且由于我们正在收缩其中的0,所以结果有2个轴。形状为(2,2),因为这些形状是a、b中各自非收缩轴的形状(按顺序排列)。

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

axes=1参数可以转换为((a的最后一个1轴),(b的第一个1轴),或者在本例中是((0,), (0,))。这些是将要收缩的轴。

所有其他轴都不会收缩。因为我们已经收缩了每个轴,剩下的轴是((), ())

然后,张量操作如下:

代码语言:javascript
复制
sum(  # summing over contraction axis
    [x*y for x,y in zip(a, b)]  # contracted axes must line up
)

请注意,由于我们正在收缩所有的轴,结果是一个标量(或0型张量)。在numpy中,您只得到一个形状为()表示0轴的张量,而不是实际的标量。

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

这不起作用的原因是,a、b都没有两个单独的轴来收缩。

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

我跳过了你的几个例子,因为它们不够复杂,不能比我认为的前几个例子更清晰。

在本例中,ab都有两个轴(允许这个问题更有趣),而且它们都具有(2,2)形状。

axes=1参数仍然表示a的最后一个1轴和b的第一个1轴,留给我们((1,), (0,))。这些是将收缩的轴。

其余轴心没有收缩,并有助于最终解决方案的形成。这些是((0,), (1,))

然后我们可以构造张量运算。为了便于讨论,假设ab是numpy数组,这样我们就可以使用数组属性并使问题变得更干净(例如,b=np.array([[2,2],[2,2]]))。

代码语言:javascript
复制
[
    [
        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)
]

结果是形状(a.shape[0], b.shape[1]),因为这些是非收缩的轴。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51989572

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档