einsum 椭圆的终极灵活性
Ultimate flexibility with einsum ellipses
我有一个关于 einsum ellipsis 的问题,我认为肯定会在 StackExchange 的某个地方,但不知何故我似乎找不到。
基本上我有一些代码使用 numpy 的 einsum
进行大量矩阵和向量收缩。输入通常是一些参数,然后用于创建向量和矩阵。该代码运行良好,但现在我想对其进行概括,以便可以在一定范围内扫描输入参数。最好的办法是使它们成为向量并修改我的 einsum
表达式,以便它们接受任意数量的附加维度,这些维度可以简单地进行。这个问题是问这是否可能,如果可能如何。
所以在我看来,这个问题归结为以下几点。假设我有一个 einsum
表达式,它创建了某种矩阵乘法,例如
c = np.einsum('ij,jk->ik', a, b)
现在我想向 a 和 b 添加任意数量的索引,并将它们作为额外索引简单地添加到最终矩阵中,例如
c = np.einsum('ijabc,jkde->ikabcde', a, b)
现在,当您只对 a 或 b 之一执行此操作时,您可以通过省略号轻松完成此操作
c = np.einsum('ij...,jk->ik...', a, b)
所以我的问题是你是否可以在 einsum
中以某种方式使用多个省略号,例如
c = np.einsum('ij...,jk...->ik...', a, b)
这当然会引发错误,但希望示例清楚我的意思。
einsum
支持这种'multi-ellipsis'写法吗?或者有没有其他方法可以在不循环的情况下实现这个?
我的猜测是没有这样的方法,因为必须告诉 einsum
剩余索引的放置顺序,即必须以某种方式标记省略号。
因为没有要对齐的轴,我们可以简单地使用 tensordot
让不参与求和归约的轴成为 "spread-out" 并附加 rollaxis
, 像这样 -
np.rollaxis(np.tensordot(a,b,axes=(1,0)),a.ndim-1,1)
如果你想使用einsum
,我们可以将它们重塑为3D
,这样它们的最后一个轴就是合并的轴(第三个轴以后合并为一个)然后继续使用 einsum
并最终重塑回它们的 ndim-1
形状在输出中展开,像这样 -
shp_a = a.shape
shp_b = b.shape
shp_a[:1] + shp_a[2:]
out_shp = shp_a[:1] + (shp_b[1],) + shp_a[2:] + shp_b[2:]
a3D = a.reshape(shp_a[:2]+(-1,))
b3D = b.reshape(shp_b[:2]+(-1,))
out = np.einsum('ijk,jlm->ilkm',a3D,b3D).reshape(out_shp)
我们还可以生成相应的 einsum 字符串表示法本身,从而跳过所有数组操作,从而专注于字符串操作本身以获得类似这样的结果 -
import string
def einsum_spreadout(a,b,a_axes,b_axes,a_spread_axis,b_spread_axis):
from numpy.core import numerictypes as nt
if isinstance(a_axes, (int, nt.integer)):
a_axes = (a_axes,)
if isinstance(b_axes, (int, nt.integer)):
b_axes = (b_axes,)
s = string.ascii_letters
a_str = s[:a.ndim]
b_str = s[a.ndim:a.ndim+b.ndim]
b_str_ar = np.frombuffer(b_str,dtype='S1').copy()
for (i,j) in zip(a_axes,b_axes):
b_str_ar[j] = a_str[i]
b_str = ''.join(b_str_ar)
out_str = a_str[:a_spread_axis] + b_str[:b_spread_axis]
out_str += a_str[a_spread_axis:] + b_str[b_spread_axis:]
out_str_ar = np.frombuffer(out_str,dtype='S1').copy()
out_str = ''.join(out_str_ar[~np.isin(out_str_ar,np.take(b_str_ar,b_axes))])
einsum_str = a_str+','+b_str+'->'+out_str
return np.einsum(einsum_str,a,b)
很少有示例案例运行以展示其用法 -
>>> a = np.random.rand(3,4,6,7,8)
>>> b = np.random.rand(4,5,9,10)
>>> einsum_spreadout(a,b,a_axes=1,b_axes=0,a_spread_axis=2,b_spread_axis=2).shape
(3, 5, 6, 7, 8, 9, 10)
>>> b = np.random.rand(4,5,6,10)
>>> einsum_spreadout(a,b,a_axes=(1,2),b_axes=(0,2),a_spread_axis=2,b_spread_axis=2).shape
(3, 5, 7, 8, 10)
>>> einsum_spreadout(a,b,a_axes=(1,2),b_axes=(0,2),a_spread_axis=4,b_spread_axis=4).shape
(3, 7, 5, 10, 8)
我有一个关于 einsum ellipsis 的问题,我认为肯定会在 StackExchange 的某个地方,但不知何故我似乎找不到。
基本上我有一些代码使用 numpy 的 einsum
进行大量矩阵和向量收缩。输入通常是一些参数,然后用于创建向量和矩阵。该代码运行良好,但现在我想对其进行概括,以便可以在一定范围内扫描输入参数。最好的办法是使它们成为向量并修改我的 einsum
表达式,以便它们接受任意数量的附加维度,这些维度可以简单地进行。这个问题是问这是否可能,如果可能如何。
所以在我看来,这个问题归结为以下几点。假设我有一个 einsum
表达式,它创建了某种矩阵乘法,例如
c = np.einsum('ij,jk->ik', a, b)
现在我想向 a 和 b 添加任意数量的索引,并将它们作为额外索引简单地添加到最终矩阵中,例如
c = np.einsum('ijabc,jkde->ikabcde', a, b)
现在,当您只对 a 或 b 之一执行此操作时,您可以通过省略号轻松完成此操作
c = np.einsum('ij...,jk->ik...', a, b)
所以我的问题是你是否可以在 einsum
中以某种方式使用多个省略号,例如
c = np.einsum('ij...,jk...->ik...', a, b)
这当然会引发错误,但希望示例清楚我的意思。
einsum
支持这种'multi-ellipsis'写法吗?或者有没有其他方法可以在不循环的情况下实现这个?
我的猜测是没有这样的方法,因为必须告诉 einsum
剩余索引的放置顺序,即必须以某种方式标记省略号。
因为没有要对齐的轴,我们可以简单地使用 tensordot
让不参与求和归约的轴成为 "spread-out" 并附加 rollaxis
, 像这样 -
np.rollaxis(np.tensordot(a,b,axes=(1,0)),a.ndim-1,1)
如果你想使用einsum
,我们可以将它们重塑为3D
,这样它们的最后一个轴就是合并的轴(第三个轴以后合并为一个)然后继续使用 einsum
并最终重塑回它们的 ndim-1
形状在输出中展开,像这样 -
shp_a = a.shape
shp_b = b.shape
shp_a[:1] + shp_a[2:]
out_shp = shp_a[:1] + (shp_b[1],) + shp_a[2:] + shp_b[2:]
a3D = a.reshape(shp_a[:2]+(-1,))
b3D = b.reshape(shp_b[:2]+(-1,))
out = np.einsum('ijk,jlm->ilkm',a3D,b3D).reshape(out_shp)
我们还可以生成相应的 einsum 字符串表示法本身,从而跳过所有数组操作,从而专注于字符串操作本身以获得类似这样的结果 -
import string
def einsum_spreadout(a,b,a_axes,b_axes,a_spread_axis,b_spread_axis):
from numpy.core import numerictypes as nt
if isinstance(a_axes, (int, nt.integer)):
a_axes = (a_axes,)
if isinstance(b_axes, (int, nt.integer)):
b_axes = (b_axes,)
s = string.ascii_letters
a_str = s[:a.ndim]
b_str = s[a.ndim:a.ndim+b.ndim]
b_str_ar = np.frombuffer(b_str,dtype='S1').copy()
for (i,j) in zip(a_axes,b_axes):
b_str_ar[j] = a_str[i]
b_str = ''.join(b_str_ar)
out_str = a_str[:a_spread_axis] + b_str[:b_spread_axis]
out_str += a_str[a_spread_axis:] + b_str[b_spread_axis:]
out_str_ar = np.frombuffer(out_str,dtype='S1').copy()
out_str = ''.join(out_str_ar[~np.isin(out_str_ar,np.take(b_str_ar,b_axes))])
einsum_str = a_str+','+b_str+'->'+out_str
return np.einsum(einsum_str,a,b)
很少有示例案例运行以展示其用法 -
>>> a = np.random.rand(3,4,6,7,8)
>>> b = np.random.rand(4,5,9,10)
>>> einsum_spreadout(a,b,a_axes=1,b_axes=0,a_spread_axis=2,b_spread_axis=2).shape
(3, 5, 6, 7, 8, 9, 10)
>>> b = np.random.rand(4,5,6,10)
>>> einsum_spreadout(a,b,a_axes=(1,2),b_axes=(0,2),a_spread_axis=2,b_spread_axis=2).shape
(3, 5, 7, 8, 10)
>>> einsum_spreadout(a,b,a_axes=(1,2),b_axes=(0,2),a_spread_axis=4,b_spread_axis=4).shape
(3, 7, 5, 10, 8)