如何在numpy 3路外产品?

时间:2017-03-31 20:03:38

标签: python arrays numpy

关于numpy.outer [link]

给定两个向量a = [a0, a1, ..., aM]b = [b0, b1, ..., bN],外积将为M * N矩阵。

  1. 但是如何实现3阵列外部产品,这意味着:给定 第三个向量c = [c0, c1, ..., cP],如何获得外部产品 3个numpy数组之间。

  2. 以及如何在numpy中为n-array获取n-way外积,对于einsum的方法,如何将'i,j,k->ijk'更改为处理"n"

3 个答案:

答案 0 :(得分:4)

直接的做法是充分利用广播:

a[:,None,None] * b[None,:,None] * c[None,None,:]

np.ix_会以适度的速度为您重塑

In [919]: np.ix_(a,b,c)
Out[919]: 
(array([[[0]],

        [[1]],

        [[2]],

        [[3]],

        [[4]]]), array([[[10],
         [11],
         [12],
         [13]]]), array([[[20, 21, 22]]]))

,结果数组可以乘以

np.prod(np.ix_(a,b,c))

einsum版本简单,快速

np.einsum('i,j,k',a,b,c)

学习所有3种方法是个好主意。

嵌套outer的问题是期望输入为1d,或者使它们变平。它可以使用,但需要一些重塑

np.outer(a,np.outer(b,c)).reshape(a.shape[0],b.shape[0],c.shape[0])

答案 1 :(得分:2)

你可以use einsum

>>> numpy.einsum('i,j,k->ijk', [1, 2], [3, 4, 5], [6,7,8,9])
array([[[18, 21, 24, 27],
        [24, 28, 32, 36],
        [30, 35, 40, 45]],

       [[36, 42, 48, 54],
        [48, 56, 64, 72],
        [60, 70, 80, 90]]])

您不能使用einsum跨越任意数量的向量。索引只能是字母,所以at most 52 vectors are allowed in this form(尽管it will raise "too many operands" when 32 vectors are provided):

def nary_outer_einsum_52(*vectors):
    import string
    subscripts = string.ascii_letters[:len(vectors)]
    subscripts = ','.join(subscripts) + '->' + subscripts
    # expands to `numpy.einsum('a,b,c,d,e->abcde', v[0], v[1], v[2], v[3], v[4])`
    return numpy.einsum(subscripts, *vectors)

einsum支持使用数字索引而不是字母的另一种形式,遗憾的是only support up to 26 vectors,因为它只是在内部转换为字母版本。我建议使用它,直到我提到的错误得到修复。

def nary_outer_einsum_26(*vectors):
    operations = (x for i, v in enumerate(vectors) for x in (v, [i]))
    # expands to `numpy.einsum(v[0], [0], v[1], [1], v[2], [2], v[3], [3], v[4], [4])`
    return numpy.einsum(*operations)

numpy.ix_@PaulPanzer's answer)可以很容易地扩展到N-ary案例。仍然存在32个向量的实现限制:

def nary_outer_ix(*vectors):
    return numpy.prod(numpy.ix_(*vectors), axis=0)

timeit with 3 vectors:

>>> timeit.Timer('nary_outer_einsum_52([1,2], [3,4,5], [6,7,8,9])', globals=globals()).autorange()
(100000, 0.8991979530110257)   # 9 µs / iteration
>>> timeit.Timer('nary_outer_einsum_26([1,2], [3,4,5], [6,7,8,9])', globals=globals()).autorange()
(100000, 1.0089023629989242)   # 10 µs / iteration
>>> timeit.Timer('nary_outer_ix([1,2], [3,4,5], [6,7,8,9])', globals=globals()).autorange()
(10000, 0.30978472300921567)   # 31 µs / iteration

有26个载体:

>>> timeit.Timer('nary_outer_einsum_52(*([[1]] * 26))', globals=globals()).autorange()
(10000, 0.6589978060073918)   # 66 µs / iteration
>>> timeit.Timer('nary_outer_einsum_26(*([[1]] * 26))', globals=globals()).autorange()
(10000, 0.7502327310066903)   # 75 µs / iteration
>>> timeit.Timer('nary_outer_ix(*([[1]] * 26))', globals=globals()).autorange()
(1000, 0.2026848039968172)    # 203 µs / iteration

有33个载体:

>>> nary_outer_einsum_52(*([[1]] * 33))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in nary_outer_einsum_52
  File "/usr/local/lib/python3.6/site-packages/numpy/core/einsumfunc.py", line 948, in einsum
    return c_einsum(*operands, **kwargs)
ValueError: too many operands
>>> nary_outer_ix(*([[1]] * 33))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in nary_outer_ix
  File "/usr/local/lib/python3.6/site-packages/numpy/lib/index_tricks.py", line 83, in ix_
    new = new.reshape((1,)*k + (new.size,) + (1,)*(nd-k-1))
ValueError: sequence too large; cannot be greater than 32

答案 2 :(得分:2)

您可以使用numpy.ix_;它为其操作数添加了轴,使它们形成一个开放的网格。在A,B,C的形状下面是(2, 1, 1), (1, 3, 1)(1, 1, 4),所以只需将它们相乘就可以得到外部产品。

a = np.arange(1, 3)
b = np.arange(1, 4)
c = np.arange(1, 5)
A,B,C = np.ix_(a,b,c)
A*B*C
# array([[[ 1,  2,  3,  4],
#         [ 2,  4,  6,  8],
#         [ 3,  6,  9, 12]],
#
#        [[ 2,  4,  6,  8],
#         [ 4,  8, 12, 16],
#         [ 6, 12, 18, 24]]])