我可以在`numpy.einsum`中使用超过26个字母吗?

时间:2016-06-13 15:56:18

标签: python numpy numpy-einsum

我使用np.einsum来乘以概率表,如:

np.einsum('ijk,jklm->ijklm', A, B)

问题在于我总共处理超过26个随机变量(轴),所以如果我为每个随机变量分配一个字母,那么我就会用掉字母。有没有其他方法可以指定上述操作来避免此问题,而不会诉诸于np.sumnp.dot操作的混乱?

3 个答案:

答案 0 :(得分:6)

简短的回答是,您可以使用52个字母(上部和下部)中的任何一个。这是英语中的所有字母。任何更高级的轴名称都必须映射到那些52或一组等效的数字上。实际上,在任何一个einsum调用中,您都希望使用这些中的一小部分。

@kennytm建议使用替代输入语法。一些样本运行表明这不是一个解决方案。 26仍然是实际限制(尽管有可疑的错误信息)。

In [258]: np.einsum(np.ones((2,3)),[0,20],np.ones((3,4)),[20,2],[0,2])
Out[258]: 
array([[ 3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.]])

In [259]: np.einsum(np.ones((2,3)),[0,27],np.ones((3,4)),[27,2],[0,2])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-259-ea61c9e50d6a> in <module>()
----> 1 np.einsum(np.ones((2,3)),[0,27],np.ones((3,4)),[27,2],[0,2])

ValueError: invalid subscript '|' in einstein sum subscripts string, subscripts must be letters

In [260]: np.einsum(np.ones((2,3)),[0,100],np.ones((3,4)),[100,2],[0,2])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-260-ebd9b4889388> in <module>()
----> 1 np.einsum(np.ones((2,3)),[0,100],np.ones((3,4)),[100,2],[0,2])

ValueError: subscript is not within the valid range [0, 52]

我不完全确定为什么你需要超过52个字母(大写和小写),但我确定你需要做某种映射。您不想一次使用超过52个轴编写einsum字符串。产生的迭代器太大(对于内存或时间)。

我想象一下可以用作的某种映射函数:

 astr = foo(A.names, B.names)
 # foo(['i','j','k'],['j','k','l','m'])
 # foo(['a1','a2','a3'],['a2','a3','b4','b5'])
 np.einsum(astr, A, B)

https://github.com/hpaulj/numpy-einsum/blob/master/einsum_py.py

einsum的Python版本。粗略地说einsum解析下标字符串,创建一个op_axes列表,可以在np.nditer中使用该列表来设置所需的产品总和计算。使用此代码,我可以看看翻译是如何完成的:

来自__name__块中的示例:

    label_str, op_axes = parse_subscripts('ik,kj->ij',    Labels([A.ndim,B.ndim]))
    print op_axes
    # [[0, -1, 1], [-1, 1, 0], [0, 1, -1]]  fine
    # map (4,newaxis,3)(newaxis,3,2)->(4,2,newaxis)
    print sum_of_prod([A,B],op_axes)

您的示例,具有完整的诊断输出

In [275]:  einsum_py.parse_subscripts('ijk,jklm->ijklm',einsum_py.Labels([3,4])) 
jklm
{'counts': {105: 1, 106: 2, 107: 2, 108: 1, 109: 1}, 
 'strides': [], 
 'num_labels': 5, 
 'min_label': 105, 
 'nop': 2, 
 'ndims': [3, 4], 
 'ndim_broadcast': 0, 
 'shapes': [], 
 'max_label': 109}
[('ijk', [105, 106, 107], 'NONE'), 
 ('jklm', [106, 107, 108, 109], 'NONE')]
 ('ijklm', [105, 106, 107, 108, 109], 'NONE')
iter labels: [105, 106, 107, 108, 109],'ijklm'
op_axes [[0, 1, 2, -1, -1], [-1, 0, 1, 2, 3], [0, 1, 2, 3, 4]]

Out[275]: 
(<einsum_py.Labels at 0xb4f80cac>,
 [[0, 1, 2, -1, -1], [-1, 0, 1, 2, 3], [0, 1, 2, 3, 4]])

使用'ajk,jkzZ->ajkzZ'更改标签,但会产生相同的op_axes

这是翻译功能的初稿。它应该适用于任何列表(可清洗项目):

def translate(ll):
    mset=set()
    for i in ll: 
        mset.update(i)
    dd={k:v for v,k in enumerate(mset)}
    x=[''.join([chr(dd[i]+97) for i in l]) for l in ll]
    #  ['cdb', 'dbea', 'cdbea']
    y=','.join(x[:-1])+'->'+x[-1]
    # 'cdb,dbea->cdbea'

In [377]: A=np.ones((3,1,2),int)
In [378]: B=np.ones((1,2,4,3),int)
In [380]: ll=[list(i) for i in ['ijk','jklm','ijklm']]
In [381]: y=translate(ll)
In [382]: y
Out[382]: 'cdb,dbea->cdbea'

In [383]: np.einsum(y,A,B).shape
Out[383]: (3, 1, 2, 4, 3)

使用set映射索引对象意味着最终的索引字符是无序的。只要您指定不应成为问题的RHS。我也忽略了ellipsis

=================

einsum输入的列表版本将转换为einsum_list_to_subscripts()numpy/core/src/multiarray/multiarraymodule.c)中的下标字符串版本。它将ELLIPSIS替换为&#39; ...&#39;。如果( s < 0 || s > 2*26) s是其中一个子列表中的数字,则会引发[0,52]错误消息。并使用

s转换为字符串
        if (s < 26) {
            subscripts[subindex++] = 'A' + s;
        }
        else {
            subscripts[subindex++] = 'a' + s;

但看起来第二种情况不起作用;我得到像26这样的错误。

ValueError: invalid subscript '{' in einstein sum subscripts string, subscripts must be letters

如果'a'+s

,则s>26错误
In [424]: ''.join([chr(ord('A')+i) for i in range(0,26)])
Out[424]: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

In [425]: ''.join([chr(ord('a')+i) for i in range(0,26)])
Out[425]: 'abcdefghijklmnopqrstuvwxyz'

In [435]: ''.join([chr(ord('a')+i) for i in range(26,52)])
Out[435]: '{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94'

'a'+s错了;应该是:

In [436]: ''.join([chr(ord('a')+i-26) for i in range(26,52)])
Out[436]: 'abcdefghijklmnopqrstuvwxyz'

我提交了https://github.com/numpy/numpy/issues/7741

此错误之后存在此错误表明子列表格式不常见,并且在该列表中使用大数字的频率更低。

答案 1 :(得分:3)

您可以使用einsum(op0, sublist0, op1, sublist1, ..., [sublistout])表单而不是i,j,ik->ijk,API不限于52轴*。这个详细形式如何对应于ijk表单显示为in the documentation

OP的

np.einsum('ijk,jklm->ijklm', A, B)

将写为

np.einsum(A, [0,1,2], B, [1,2,3,4], [0,1,2,3,4])

(*注意:实现仍然限制在26个轴上。有关解释,请参阅@hpaulj's answerhis bug report

来自numpy的例子的等价:

>>> np.einsum('ii', a)
>>> np.einsum(a, [0,0])

>>> np.einsum('ii->i', a)
>>> np.einsum(a, [0,0], [0])

>>> np.einsum('ij,j', a, b)
>>> np.einsum(a, [0,1], b, [1])

>>> np.einsum('ji', c)
>>> np.einsum(c, [1,0])

>>> np.einsum('..., ...', 3, c)
>>> np.einsum(3, [...], c, [...])

>>> np.einsum('i,i', b, b)
>>> np.einsum(b, [0], b, [0])

>>> np.einsum('i,j', np.arange(2)+1, b)
>>> np.einsum(np.arange(2)+1, [0], b, [1])

>>> np.einsum('i...->...', a)
>>> np.einsum(a, [0, ...], [...])

>>> np.einsum('ijk,jil->kl', a, b)
>>> np.einsum(a, [0,1,2], b, [1,0,3], [2,3])

答案 2 :(得分:2)

如果你在谈论你的例子中的字母ijk并且有更多可用的字母字符,那么你就不能。

在einsum numpy代码herehere中,numpy正在使用isalpha逐个检查每个字符,并且似乎无法创建超过1个字符的名称。< / p>

也许您可以使用大写字母,但问题的主要答案是您不能拥有超过1个字符的轴的名称。