介绍

        爱因斯坦求和约定(Einstein summation convention)是一种对复杂张量运算的优雅表达方式。在实现深度学习模型时,使用爱因斯坦求和约定可以编写更加紧凑和高效的代码。

        einsum省略求和符号并隐式累加重复下标和输出未指明的下标。例如将两个矩阵$A\in \mathbb{R}^{I\times K}$和$B\in \mathbb{R}^{K\times J}$相乘,接着计算每列的和,最终得到向量$c\in \mathbb{R}^J$。使用einsum就可以表示为:
$$c_j=\sum_i\sum_jA_{ik}B_{kj}=A_{ik}B_{kj}$$

省略掉中间带求和符号的表达式就是einsum表达式,其含义是计算行向量$A_{i,:}$按位乘以列向量$B_{:,j}$然后求和(由于重复下标k所以求和,这里相当于点积)。这时得到的是两矩阵相乘后的矩阵$C\in \mathbb{R}^{I\times J}$,但是由于表达式指明为$c_j$因此需要将计算得到的矩阵中的i维度进行求和(因为$c_j$中没有$i$这就实现了上面复杂的张量运算。

用法

numpy.einsum(subscripts, *operands, out=None, dtype=None, order='K', casting='safe', optimize=False)

使用爱因斯坦求和约定,可以以简单的方式表示许多常见的多维线性代数数组运算。在隐式模式下einsum计算这些值。

在显式模式下,einsum通过禁用或强制对指定的下标标签求和,可以提供更大的灵活性来计算其他数组操作,而这些操作可能不被视为经典的爱因斯坦求和操作。

  • subscripts : str

    将要求和的下标指定为下标标签的逗号分隔列表。除非包含显式指示符“->”以及精确输出形式的下标标签,否则将执行隐式(经典的爱因斯坦求和)计算。

  • operands: : list of array_like

    这些是用于操作的数组。

  • out: : ndarray, 可选参数

    如果提供的话,将在此数组中进行计算。

  • dtype: : {data-type, None}, 可选参数

    如果提供,则强制计算使用指定的数据类型。请注意,您可能还必须提供一个更宽松的转换参数以允许进行转换。默认为无。

  • order: : {‘C’, ‘F’, ‘A’, ‘K’}, 可选参数
    控制输出的内存布局。 ‘C’表示它应该是C连续的。 ‘F’表示它应该是Fortran连续的,‘A’表示如果所有输入都为‘F’,则它应该是‘F’,否则为‘C’。 ‘K’表示它应尽可能靠近输入,包括任意排列的轴,尽可能靠近布局。默认值为‘K’。

  • casting: : {‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, 可选参数

    控制可能发生的数据类型转换。不建议将其设置为‘unsafe’,因为它可能会对累积产生不利影响。

    • ‘no’ means the data types should not be cast at all.

    • ‘equiv’ means only byte-order changes are allowed.

    • ‘safe’ means only casts which can preserve values are allowed.

    • ‘same_kind’ means only safe casts or casts within a kind, like float64 to float32, are allowed.

    • ‘unsafe’ means any data conversions may be done.

    默认值为‘safe’。

  • optimize: : {False, True, ‘greedy’, ‘optimal’}, 可选参数

    控制是否应该进行中间优化。如果False和True将默认设置为‘greedy’算法,则不会进行优化。还接受来自np.einsum_path功能。看到np.einsum_path更多细节。默认为False。

  • output: : ndarray
    基于爱因斯坦求和约定的计算。

下标字符串是用逗号分隔的下标标签列表,其中每个标签均指相应操作数的维。每当重复标签时,都会对其求和,因此np.einsum('i,i', a, b)相当于np.inner(a,b)。如果标签仅出现一次,则不会被累加,因此np.einsum('i', a)产生一个观点a没有任何变化。另一个例子np.einsum('ij,jk', a, b)描述传统的矩阵乘法,等效于np.matmul(a,b)。一个操作数中重复的下标标签取对角线。例如,np.einsum('ii', a)相当于np.trace(a)

算例

对于两个向量而言

给定向量$\vec{a}$和向量$\vec{b}$(在Python中也可以说是一维数组),若 $\vec{a}=(1,2,3)$,$\vec{b}=(4,5,6)$ ,即

import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

则:

  • np.einsum('i', a)返回向量$\vec{a}$本身,即

      >>> np.einsum('i', a)
      array([1, 2, 3])
  • np.einsum('i->', a)返回向量$\vec{a}$的元素和(等价于:np.sum(a)),即

      >>> np.einsum('i->', a)
      6
  • np.einsum('i,i->i', a, b)是向量$\vec{a}$和向量$\vec{b}$的点乘(等价于:a*b)

      >>> np.einsum('i, i->i', a, b)
      array([ 4, 10, 18])
  • np.einsum('i,i', a, b)是向量$\vec{a}$和向量$\vec{b}$的内积(等价于:np.inner(a, b)

      >>> np.einsum('i, i', a, b)
      32
  • np.einsum('i, j->ij', a, b)是向量$\vec{a}$和向量 $\vec{b}$的外积(等价于:np.outer(a, b))

      >>> np.einsum('i, j->ij', a, b)
      array([[ 4,  5,  6],
         [ 8, 10, 12],
         [12, 15, 18]])

    对两个矩阵而言

    给定矩阵$A$和矩阵$B$(在Python中也可以说是二维数组),若$A=\left[ \begin{matrix} 1&2\\3&4 \end{matrix} \right]$,$B=\left[\begin{matrix}5&6\\7&8\end{matrix}\right]$

    import numpy as np
    A = np.array([[1, 2], [3, 4]])
    B = np.array([[5, 6], [7, 8]])

  • np.einsum('ij', A)返回矩阵$A$本身,即
      >>> np.einsum('ij', A)
      array([[1, 2],
             [3, 4]])
  • np.einsum('ji', A)返回矩阵$A$的转置(等价于:A.T),即
      >>> np.einsum('ji', A)
      array([[1, 3],
             [2, 4]])
  • np.einsum('ii->i', A)返回矩阵$A$对角线上的元素(等价于:np.diag(A)),即
      >>> np.einsum('ii->i', A)
      array([1, 4])
  • np.einsum('ii', A)返回矩阵$A$对角线上元素的和(等价于:np.trace(A)),即
      >>> np.einsum('ii', A)
      5
  • np.einsum('ij->', A)返回矩阵$A$所有元素的和(等价于:np.sum(A)),即
      >>> np.einsum('ij->', A)
      10
  • np.einsum('ij->j', A)返回矩阵$A$列向量的和(等价于:np.sum(A, axis=0)),即
      >>> np.einsum('ij->j', A)
      array([4, 6])
  • np.einsum('ij->i', A)返回矩阵$A$行向量的和(等价于:np.sum(A, axis=1)),即
      >>> np.einsum('ij->i', A)
      array([3, 7])
  • np.einsum('ij, ij->ij', A, B)是矩阵 $A$和矩阵$B$的点乘(等价于:A*B),即
      >>> np.einsum('ij, ij->ij', A, B)
      array([[ 5, 12],
             [21, 32]])
  • np.einsum('ij, ji->ij', A, B)是矩阵$A$点乘以矩阵$B$的转置(等价于:A*B.T),即
      >>> np.einsum('ij, ji->ij', A, B)
      array([[ 5, 14],
             [18, 32]])
  • np.einsum('ij, jk', A, B)是矩阵$A$乘以矩阵$B$ (等价于:np.dot(A, B)),即
      >>> np.einsum('ij, jk', A, B)
      array([[19, 22],
             [43, 50]])
  • np.einsum('ij, ij', A, B)是矩阵$A$和矩阵$B$的内积
      >>> np.einsum('ij, ij', A, B)
      70