dask.array.einsum
dask.array.einsum¶
- dask.array.einsum(subscripts, *operands, out=None, dtype=None, order='K', casting='safe', optimize=False)[源代码]¶
此文档字符串是从 numpy.einsum 复制的。
Dask 版本可能存在一些不一致性。
Dask 添加了一个额外的仅关键字参数
split_every。- split_every: int >= 2 或 dict(axis: int), 可选
确定递归聚合的深度。默认为
None,这将让 dask 启发式地决定一个合适的默认值。
对操作数进行爱因斯坦求和约定评估。
使用爱因斯坦求和约定,许多常见的多维线性代数数组操作可以用一种简单的方式表示。在 隐式 模式下,einsum 计算这些值。
在 显式 模式下,einsum 提供了进一步的灵活性,通过禁用或强制对指定的下标标签进行求和,计算可能不被视为经典爱因斯坦求和运算的其他数组操作。
请参阅注释和示例以获得澄清。
- 参数
- 下标str
指定求和的下标为逗号分隔的下标标签列表。除非包含显式指示符 ‘->’ 以及精确输出形式的下标标签,否则将执行隐式(经典爱因斯坦求和)计算。
- 操作数类似数组的列表
这些是操作的数组。
- 出ndarray, 可选 (Dask 不支持)
如果提供了,计算结果将存入这个数组。
- dtype{数据类型, None}, 可选
如果提供,强制计算使用指定的数据类型。请注意,您可能还需要提供一个更宽松的 casting 参数以允许转换。默认值为 None。
- 顺序{‘C’, ‘F’, ‘A’, ‘K’}, 可选
控制输出的内存布局。’C’ 表示应该是 C 连续的。’F’ 表示应该是 Fortran 连续的,’A’ 表示如果输入都是 ‘F’,则应该是 ‘F’,否则是 ‘C’。’K’ 表示应该尽可能接近输入的布局,包括任意排列的轴。默认是 ‘K’。
- 类型转换{‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, 可选
控制可能发生的数据转换类型。将其设置为’不安全’是不推荐的,因为它可能会对累积产生不利影响。
‘no’ 意味着数据类型根本不应该被转换。
‘equiv’ 意味着只允许字节顺序的改变。
‘安全’ 意味着只允许那些可以保留值的转换。
‘same_kind’ 意味着只允许安全转换或在同一类型内的转换,例如从 float64 到 float32。
‘unsafe’ 意味着可能会进行任何数据转换。
默认是 ‘安全’。
- 优化{False, True, ‘greedy’, ‘optimal’}, 可选
控制是否进行中间优化。如果为 False,则不会进行优化;如果为 True,则默认使用 ‘greedy’ 算法。还可以接受来自
np.einsum_path函数的显式收缩列表。有关更多详细信息,请参阅np.einsum_path。默认为 False。
- 返回
- 输出ndarray
基于爱因斯坦求和约定的计算。
参见
注释
1.6.0 新版功能.
爱因斯坦求和约定可以用来计算许多多维线性代数数组操作。einsum 提供了一种简洁的表示方法。
以下是 einsum 可以计算的操作的非详尽列表,以及示例:
数组的迹,
numpy.trace()。返回对角线,
numpy.diag()。数组轴求和,
numpy.sum().转置和排列,
numpy.transpose()。- 矩阵乘法和点积,
numpy.matmul()
- 矩阵乘法和点积,
- 向量的内积和外积,
numpy.inner()
- 向量的内积和外积,
- 广播、逐元素乘法和标量乘法
numpy.multiply().
张量收缩,
numpy.tensordot().- 链式数组操作,按高效计算顺序进行,
下标字符串是一个逗号分隔的下标标签列表,其中每个标签对应于相应操作数的一个维度。每当一个标签重复时,它会被求和,因此
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)。在 隐式模式 中,选择的下标很重要,因为输出的轴会按字母顺序重新排序。这意味着
np.einsum('ij', a)不会影响一个二维数组,而np.einsum('ji', a)会取其转置。此外,np.einsum('ij,jk', a, b)返回矩阵乘法,而np.einsum('ij,jh', a, b)返回乘法的转置,因为下标 ‘h’ 先于下标 ‘i’。在 显式模式 下,可以通过指定输出下标标签直接控制输出。这需要使用标识符 ‘->’ 以及输出下标标签列表。此功能增加了函数的灵活性,因为可以在需要时禁用或强制求和。调用
np.einsum('i->', a)类似于np.sum(a)如果a是一个一维数组,而np.einsum('ii->i', a)类似于np.diag(a)如果a是一个方形的二维数组。不同之处在于 einsum 默认不允许广播。此外,np.einsum('ij,jh->ih', a, b)直接指定了输出下标标签的顺序,因此返回矩阵乘法,与隐式模式中的示例不同。要启用和控制广播,请使用省略号。默认的NumPy风格广播是通过在每个项的左侧添加一个省略号来完成的,例如
np.einsum('...ii->...i', a)。np.einsum('...i->...', a)类似于对于任意形状的数组a的np.sum(a, axis=-1)。要沿着第一个和最后一个轴取迹,可以执行np.einsum('i...i', a),或者要进行矩阵-矩阵乘积,但使用最左边的索引而不是最右边的索引,可以执行np.einsum('ij...,jk...->ik...', a, b)。当只有一个操作数时,不会对任何轴进行求和,并且没有提供输出参数时,将返回操作数的视图而不是新数组。因此,取对角线如
np.einsum('ii->i', a)会生成一个视图(在版本1.10.0中更改)。einsum 还提供了一种替代方式来提供下标和操作数,即
einsum(op0, sublist0, op1, sublist1, ..., [sublistout])。如果输出形状未在此格式中提供,einsum 将以隐式模式计算,否则将显式执行。下面的示例具有使用两种参数方法的相应 einsum 调用。1.10.0 新版功能.
从 einsum 返回的视图现在在输入数组可写时也是可写的。例如,
np.einsum('ijk...->kji...', a)现在将具有与np.swapaxes(a, 0, 2)相同的效果,而np.einsum('ii->i', a)将返回一个二维数组对角线的可写视图。1.12.0 新版功能.
添加了
optimize参数,该参数将优化 einsum 表达式的收缩顺序。对于具有三个或更多操作数的收缩,这可以在计算过程中以更大的内存占用为代价,极大地提高计算效率。通常应用一种 ‘贪婪’ 算法,经验测试表明该算法在大多数情况下返回最优路径。在某些情况下,’最优’ 将通过更昂贵、穷尽的搜索返回最佳路径。对于迭代计算,建议计算一次最优路径,并通过提供该路径作为参数来重复使用。下面给出一个示例。
更多详情请参见
numpy.einsum_path()。示例
>>> a = np.arange(25).reshape(5,5) >>> b = np.arange(5) >>> c = np.arange(6).reshape(2,3)
矩阵的迹:
>>> np.einsum('ii', a) 60 >>> np.einsum(a, [0,0]) 60 >>> np.trace(a) 60
提取对角线(需要显式形式):
>>> np.einsum('ii->i', a) array([ 0, 6, 12, 18, 24]) >>> np.einsum(a, [0,0], [0]) array([ 0, 6, 12, 18, 24]) >>> np.diag(a) array([ 0, 6, 12, 18, 24])
沿轴求和(需要显式形式):
>>> np.einsum('ij->i', a) array([ 10, 35, 60, 85, 110]) >>> np.einsum(a, [0,1], [0]) array([ 10, 35, 60, 85, 110]) >>> np.sum(a, axis=1) array([ 10, 35, 60, 85, 110])
对于更高维度的数组,可以通过省略号对单个轴进行求和:
>>> np.einsum('...j->...', a) array([ 10, 35, 60, 85, 110]) >>> np.einsum(a, [Ellipsis,1], [Ellipsis]) array([ 10, 35, 60, 85, 110])
计算矩阵的转置,或重新排序任意数量的轴:
>>> np.einsum('ji', c) array([[0, 3], [1, 4], [2, 5]]) >>> np.einsum('ij->ji', c) array([[0, 3], [1, 4], [2, 5]]) >>> np.einsum(c, [1,0]) array([[0, 3], [1, 4], [2, 5]]) >>> np.transpose(c) array([[0, 3], [1, 4], [2, 5]])
向量内积:
>>> np.einsum('i,i', b, b) 30 >>> np.einsum(b, [0], b, [0]) 30 >>> np.inner(b,b) 30
矩阵向量乘法:
>>> np.einsum('ij,j', a, b) array([ 30, 80, 130, 180, 230]) >>> np.einsum(a, [0,1], b, [1]) array([ 30, 80, 130, 180, 230]) >>> np.dot(a, b) array([ 30, 80, 130, 180, 230]) >>> np.einsum('...j,j', a, b) array([ 30, 80, 130, 180, 230])
广播和标量乘法:
>>> np.einsum('..., ...', 3, c) array([[ 0, 3, 6], [ 9, 12, 15]]) >>> np.einsum(',ij', 3, c) array([[ 0, 3, 6], [ 9, 12, 15]]) >>> np.einsum(3, [Ellipsis], c, [Ellipsis]) array([[ 0, 3, 6], [ 9, 12, 15]]) >>> np.multiply(3, c) array([[ 0, 3, 6], [ 9, 12, 15]])
向量外积:
>>> np.einsum('i,j', np.arange(2)+1, b) array([[0, 1, 2, 3, 4], [0, 2, 4, 6, 8]]) >>> np.einsum(np.arange(2)+1, [0], b, [1]) array([[0, 1, 2, 3, 4], [0, 2, 4, 6, 8]]) >>> np.outer(np.arange(2)+1, b) array([[0, 1, 2, 3, 4], [0, 2, 4, 6, 8]])
张量收缩:
>>> a = np.arange(60.).reshape(3,4,5) >>> b = np.arange(24.).reshape(4,3,2) >>> np.einsum('ijk,jil->kl', a, b) array([[4400., 4730.], [4532., 4874.], [4664., 5018.], [4796., 5162.], [4928., 5306.]]) >>> np.einsum(a, [0,1,2], b, [1,0,3], [2,3]) array([[4400., 4730.], [4532., 4874.], [4664., 5018.], [4796., 5162.], [4928., 5306.]]) >>> np.tensordot(a,b, axes=([1,0],[0,1])) array([[4400., 4730.], [4532., 4874.], [4664., 5018.], [4796., 5162.], [4928., 5306.]])
可写返回数组(自版本1.10.0起):
>>> a = np.zeros((3, 3)) >>> np.einsum('ii->i', a)[:] = 1 >>> a array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])
省略号使用示例:
>>> a = np.arange(6).reshape((3,2)) >>> b = np.arange(12).reshape((4,3)) >>> np.einsum('ki,jk->ij', a, b) array([[10, 28, 46, 64], [13, 40, 67, 94]]) >>> np.einsum('ki,...k->i...', a, b) array([[10, 28, 46, 64], [13, 40, 67, 94]]) >>> np.einsum('k...,jk', a, b) array([[10, 28, 46, 64], [13, 40, 67, 94]])
链式数组操作。对于更复杂的收缩操作,通过重复计算’贪婪’路径或预先计算’最优’路径并重复应用它,使用 einsum_path 插入(自版本 1.12.0 起),可能会实现速度提升。性能改进在大数组中尤为显著:
>>> a = np.ones(64).reshape(2,4,8)
基本 einsum:~1520ms (在3.1GHz Intel i5上进行基准测试。)
>>> for iteration in range(500): ... _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a)
次优的 `einsum`(由于重复路径计算时间):~330ms
>>> for iteration in range(500): ... _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, ... optimize='optimal')
贪婪 `einsum`(更快的最优路径近似):~160ms
>>> for iteration in range(500): ... _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize='greedy')
最佳 `einsum`(某些用例中的最佳使用模式):~110ms
>>> path = np.einsum_path('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, ... optimize='optimal')[0] >>> for iteration in range(500): ... _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize=path)