介绍
爱因斯坦求和约定(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
0 条评论