【问题标题】:Understanding tensordot了解张量点
【发布时间】:2017-06-11 17:36:11
【问题描述】:

在我学会了如何使用einsum 之后,我现在正在尝试了解np.tensordot 的工作原理。

但是,我有点迷茫,尤其是对于参数axes 的各种可能性。

为了理解它,由于我从未练习过张量演算,我使用以下示例:

A = np.random.randint(2, size=(2, 3, 5))
B = np.random.randint(2, size=(3, 2, 4))

在这种情况下,np.tensordot 可能有哪些不同?您将如何手动计算它?

【问题讨论】:

    标签: python numpy linear-algebra tensor dot-product


    【解决方案1】:

    上面的答案很好,对我理解tensordot有很大帮助。但它们没有显示操作背后的实际数学。这就是为什么我在 TF 2 中为自己做了类似的操作并决定在这里分享它们:

    a = tf.constant([1,2.])
    b = tf.constant([2,3.])
    print(f"{tf.tensordot(a, b, 0)}\t tf.einsum('i,j', a, b)\t\t- ((the last 0 axes of a), (the first 0 axes of b))")
    print(f"{tf.tensordot(a, b, ((),()))}\t tf.einsum('i,j', a, b)\t\t- ((() axis of a), (() axis of b))")
    print(f"{tf.tensordot(b, a, 0)}\t tf.einsum('i,j->ji', a, b)\t- ((the last 0 axes of b), (the first 0 axes of a))")
    print(f"{tf.tensordot(a, b, 1)}\t\t tf.einsum('i,i', a, b)\t\t- ((the last 1 axes of a), (the first 1 axes of b))")
    print(f"{tf.tensordot(a, b, ((0,), (0,)))}\t\t tf.einsum('i,i', a, b)\t\t- ((0th axis of a), (0th axis of b))")
    print(f"{tf.tensordot(a, b, (0,0))}\t\t tf.einsum('i,i', a, b)\t\t- ((0th axis of a), (0th axis of b))")
    
    [[2. 3.]
     [4. 6.]]    tf.einsum('i,j', a, b)     - ((the last 0 axes of a), (the first 0 axes of b))
    [[2. 3.]
     [4. 6.]]    tf.einsum('i,j', a, b)     - ((() axis of a), (() axis of b))
    [[2. 4.]
     [3. 6.]]    tf.einsum('i,j->ji', a, b) - ((the last 0 axes of b), (the first 0 axes of a))
    8.0          tf.einsum('i,i', a, b)     - ((the last 1 axes of a), (the first 1 axes of b))
    8.0          tf.einsum('i,i', a, b)     - ((0th axis of a), (0th axis of b))
    8.0          tf.einsum('i,i', a, b)     - ((0th axis of a), (0th axis of b))
    

    对于(2,2) 形状:

    a = tf.constant([[1,2],
                     [-2,3.]])
    
    b = tf.constant([[-2,3],
                     [0,4.]])
    print(f"{tf.tensordot(a, b, 0)}\t tf.einsum('ij,kl', a, b)\t- ((the last 0 axes of a), (the first 0 axes of b))")
    print(f"{tf.tensordot(a, b, (0,0))}\t tf.einsum('ij,ik', a, b)\t- ((0th axis of a), (0th axis of b))")
    print(f"{tf.tensordot(a, b, (0,1))}\t tf.einsum('ij,ki', a, b)\t- ((0th axis of a), (1st axis of b))")
    print(f"{tf.tensordot(a, b, 1)}\t tf.matmul(a, b)\t\t- ((the last 1 axes of a), (the first 1 axes of b))")
    print(f"{tf.tensordot(a, b, ((1,), (0,)))}\t tf.einsum('ij,jk', a, b)\t- ((1st axis of a), (0th axis of b))")
    print(f"{tf.tensordot(a, b, (1, 0))}\t tf.matmul(a, b)\t\t- ((1st axis of a), (0th axis of b))")
    print(f"{tf.tensordot(a, b, 2)}\t tf.reduce_sum(tf.multiply(a, b))\t- ((the last 2 axes of a), (the first 2 axes of b))")
    print(f"{tf.tensordot(a, b, ((0,1), (0,1)))}\t tf.einsum('ij,ij->', a, b)\t\t- ((0th axis of a, 1st axis of a), (0th axis of b, 1st axis of b))")
    [[[[-2.  3.]
       [ 0.  4.]]
      [[-4.  6.]
       [ 0.  8.]]]
    
     [[[ 4. -6.]
       [-0. -8.]]
      [[-6.  9.]
       [ 0. 12.]]]]  tf.einsum('ij,kl', a, b)   - ((the last 0 axes of a), (the first 0 axes of b))
    [[-2. -5.]
     [-4. 18.]]      tf.einsum('ij,ik', a, b)   - ((0th axis of a), (0th axis of b))
    [[-8. -8.]
     [ 5. 12.]]      tf.einsum('ij,ki', a, b)   - ((0th axis of a), (1st axis of b))
    [[-2. 11.]
     [ 4.  6.]]      tf.matmul(a, b)            - ((the last 1 axes of a), (the first 1 axes of b))
    [[-2. 11.]
     [ 4.  6.]]      tf.einsum('ij,jk', a, b)   - ((1st axis of a), (0th axis of b))
    [[-2. 11.]
     [ 4.  6.]]      tf.matmul(a, b)            - ((1st axis of a), (0th axis of b))
    16.0    tf.reduce_sum(tf.multiply(a, b))    - ((the last 2 axes of a), (the first 2 axes of b))
    16.0    tf.einsum('ij,ij->', a, b)          - ((0th axis of a, 1st axis of a), (0th axis of b, 1st axis of b))
    

    【讨论】:

      【解决方案2】:

      tensordot 的想法非常简单 - 我们输入数组和相应的轴,沿这些轴进行求和。参与减和的轴在输出中被删除,输入数组中的所有剩余轴都展开作为输出中的不同轴,保持输入数组的顺序喂。

      让我们看几个具有一个和两个轴减和的示例案例,并交换输入位置,看看如何在输出中保持顺序。

      我。一个轴减和

      输入:

       In [7]: A = np.random.randint(2, size=(2, 6, 5))
         ...:  B = np.random.randint(2, size=(3, 2, 4))
         ...: 
      

      案例#1:

      In [9]: np.tensordot(A, B, axes=((0),(1))).shape
      Out[9]: (6, 5, 3, 4)
      
      A : (2, 6, 5) -> reduction of axis=0
      B : (3, 2, 4) -> reduction of axis=1
      
      Output : `(2, 6, 5)`, `(3, 2, 4)` ===(2 gone)==> `(6,5)` + `(3,4)` => `(6,5,3,4)`
      

      案例 #2(与案例 #1 相同,但输入是交换的):

      In [8]: np.tensordot(B, A, axes=((1),(0))).shape
      Out[8]: (3, 4, 6, 5)
      
      B : (3, 2, 4) -> reduction of axis=1
      A : (2, 6, 5) -> reduction of axis=0
      
      Output : `(3, 2, 4)`, `(2, 6, 5)` ===(2 gone)==> `(3,4)` + `(6,5)` => `(3,4,6,5)`.
      

      二。减和的两个轴

      输入:

      In [11]: A = np.random.randint(2, size=(2, 3, 5))
          ...: B = np.random.randint(2, size=(3, 2, 4))
          ...: 
      

      案例#1:

      In [12]: np.tensordot(A, B, axes=((0,1),(1,0))).shape
      Out[12]: (5, 4)
      
      A : (2, 3, 5) -> reduction of axis=(0,1)
      B : (3, 2, 4) -> reduction of axis=(1,0)
      
      Output : `(2, 3, 5)`, `(3, 2, 4)` ===(2,3 gone)==> `(5)` + `(4)` => `(5,4)`
      

      案例#2:

      In [14]: np.tensordot(B, A, axes=((1,0),(0,1))).shape
      Out[14]: (4, 5)
      
      B : (3, 2, 4) -> reduction of axis=(1,0)
      A : (2, 3, 5) -> reduction of axis=(0,1)
      
      Output : `(3, 2, 4)`, `(2, 3, 5)` ===(2,3 gone)==> `(4)` + `(5)` => `(4,5)`
      

      我们可以将其扩展到尽可能多的轴。

      【讨论】:

      • 减和到底是什么意思?
      • @floflo29 好吧,您可能知道矩阵乘法涉及保持轴对齐的元素乘法,然后沿该共同对齐的轴对元素求和。有了这个求和,我们就失去了那个共同的轴,这就是所谓的归约,所以简而言之就是归约。
      • @BryanHead 使用np.tensordot 重新排序输出轴的唯一方法是交换输入。如果它没有为您提供您想要的,transpose 将是您的最佳选择。
      • 如果@Divakar 添加了从一维张量开始的示例以及每个条目的计算方式,那会更好。例如。 t1=K.variable([[1,2],[2,3]] ) t2=K.variable([2,3]) print(K.eval(tf.tensordot(t1,t2,axes=0))) 输出:[[[2. 3.] [4. 6.]] [[4. 6.] [6. 9.]]] 不确定输出形状如何2x2x2
      • @dereks 这篇文章中使用的 sum-reduction 术语是元素乘法和 sum-reduction 的总称。在点/张量点的上下文中,我认为这样说是安全的。抱歉,如果这令人困惑。现在,通过矩阵乘法,您有一个减和轴(第一个数组的第二个轴相对于第二个数组的第一个轴),而在 tensordot 中有多个减和轴。给出的示例显示了轴在输入数组中的对齐方式以及输出轴是如何从这些数组中获取的。
      【解决方案3】:

      tensordot 交换轴并重塑输入,以便它可以将np.dot 应用于 2 个二维数组。然后它交换并重塑回目标。实验可能比解释更容易。没有特殊的张量数学,只是扩展dot 以在更高维度上工作。 tensor 仅表示大于 2d 的数组。如果您已经对einsum 感到满意,那么将结果与之比较是最简单的。

      一个样本测试,在一对轴上求和

      In [823]: np.tensordot(A,B,[0,1]).shape
      Out[823]: (3, 5, 3, 4)
      In [824]: np.einsum('ijk,lim',A,B).shape
      Out[824]: (3, 5, 3, 4)
      In [825]: np.allclose(np.einsum('ijk,lim',A,B),np.tensordot(A,B,[0,1]))
      Out[825]: True
      

      另一个,对两个求和。

      In [826]: np.tensordot(A,B,[(0,1),(1,0)]).shape
      Out[826]: (5, 4)
      In [827]: np.einsum('ijk,jim',A,B).shape
      Out[827]: (5, 4)
      In [828]: np.allclose(np.einsum('ijk,jim',A,B),np.tensordot(A,B,[(0,1),(1,0)]))
      Out[828]: True
      

      我们可以对(1,0) 做同样的事情。考虑到维度的混合,我认为没有其他组合。

      【讨论】:

      • 我仍然没有完全掌握它 :(。在docs 的第一个示例中,他们将元素方式的 2 个数组与形状 (4,3) 相乘,然后对这 2 个数组执行 sum轴。您如何使用dot 产品获得相同的结果?
      • 我可以从 docs 重现第一个结果的方法是在 flattened 二维数组上使用 np.dotfor aa in a.T: for bb in b.T: print(aa.ravel().dot(bb.T.ravel()))
      • einsum 等效于 tensordotaxes=([1,0],[0,1]),是 np.einsum('ijk,jil->kl',a,b)。这个dot 也可以做到:a.T.reshape(5,12).dot(b.reshape(12,2))dot 介于 (5,12) 和 (12,2) 之间。 a.T 将 5 放在首位,并交换 (3,4) 以匹配 b
      猜你喜欢
      • 2019-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-22
      • 2020-06-25
      • 1970-01-01
      • 2016-07-04
      • 2021-09-29
      相关资源
      最近更新 更多