how to 3-way outer product in numpy?

前端 未结 3 608
刺人心
刺人心 2021-01-21 07:42

About the numpy.outer [link] .

Given two vectors, a = [a0, a1, ..., aM] and b = [b0, b1, ..., bN], the outer product will be M*N matrix.

3条回答
  •  时光说笑
    2021-01-21 08:17

    You could use einsum for this:

    >>> 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]]])
    

    You cannot cross arbitrary amount of vectors using einsum. The indices can only be letters, so at most 52 vectors are allowed in this form (although 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 supports another form which uses numeric indices instead of letters, which unfortunately only support up to 26 vectors because it just translates to the letter version internally. I do not recommend using this until the bug I mentioned is fixed.

    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) can easily be scaled up to the N-ary case. Still, there is an implementation-limit of 32 vectors:

    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
    

    with 26 vectors:

    >>> 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
    

    with 33 vectors:

    >>> nary_outer_einsum_52(*([[1]] * 33))
    Traceback (most recent call last):
      File "", line 1, in 
      File "", 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 "", line 1, in 
      File "", 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
    

提交回复
热议问题