7

この計算ですべての Python ループを削除できますか :

result[i,j,k] = (x[i] * y[j] * z[k]).sum()

ここでx[i]y[j]z[k]は長さのベクトルで、 、は長Nx、、stの最初の次元を持ち、出力は形状であり、各要素は三重積 (要素ごと) の合計です。yzABC(A,B,C)

ループを 3 つから 1 つに減らすことはできますが (以下のコード)、最後のループを排除しようとして立ち往生しています。

必要に応じて、A=B=C(少量のパディングを介して)作成できます。

# Example with 3 loops, 2 loops, 1 loop (testing omitted)

N = 100 # more like 100k in real problem
A =   2 # more like 20 in real problem
B =   3 # more like 20 in real problem
C =   4 # more like 20 in real problem

import numpy
x = numpy.random.rand(A, N)
y = numpy.random.rand(B, N)
z = numpy.random.rand(C, N)

# outputs of each variant
result_slow = numpy.empty((A,B,C))
result_vec_C = numpy.empty((A,B,C))
result_vec_CB = numpy.empty((A,B,C))

# 3 nested loops
for i in range(A):
    for j in range(B):
        for k in range(C):
            result_slow[i,j,k] = (x[i] * y[j] * z[k]).sum()

# vectorize loop over C (2 nested loops)
for i in range(A):
    for j in range(B):
        result_vec_C[i,j,:] = (x[i] * y[j] * z).sum(axis=1)

# vectorize one C and B (one loop)
for i in range(A):
    result_vec_CB[i,:,:] = numpy.dot(x[i] * y, z.transpose())

numpy.testing.assert_almost_equal(result_slow, result_vec_C)
numpy.testing.assert_almost_equal(result_slow, result_vec_CB)
4

2 に答える 2

9

numpy > 1.6 を使用している場合、素晴らしいnp.einsum関数があります。

np.einsum('im,jm,km->ijk',x,y,z)

これは、ループしたバージョンと同等です。実際の問題で配列のサイズに達すると、これがどのように効率的に公平になるかわかりません(実際にそれらのサイズに移動すると、マシンでセグメンテーション違反が発生します)。この種の問題に対して私がよく好むもう 1 つの解決策は、cython を使用してメソッドを書き直すことです。

于 2012-06-29T16:34:55.767 に答える
8

あなたの場合、使用einsumは非常に理にかなっています。しかし、これは手で簡単に行うことができます。秘訣は、配列を互いにブロードキャスト可能にすることです。これは、各配列が独自の軸に沿って独立して変化するように、配列を再形成することを意味します。numpy次に、ブロードキャストを処理して、それらを掛け合わせます。次に、最後 (一番右) の軸に沿って合計します。

>>> x = numpy.arange(2 * 4).reshape(2, 4)
>>> y = numpy.arange(3 * 4).reshape(3, 4)
>>> z = numpy.arange(4 * 4).reshape(4, 4)
>>> (x.reshape(2, 1, 1, 4) * 
...  y.reshape(1, 3, 1, 4) *
...  z.reshape(1, 1, 4, 4)).sum(axis=3)
array([[[  36,   92,  148,  204],
        [  92,  244,  396,  548],
        [ 148,  396,  644,  892]],

       [[  92,  244,  396,  548],
        [ 244,  748, 1252, 1756],
        [ 396, 1252, 2108, 2964]]])

newaxisスライス表記、値 ( に等しいNoneため、以下でも同様に機能します)、および負の軸値Noneを受け入れるという事実(最後を示し、次を示す)を使用して、これをもう少し一般化することができます。-最後など)。この方法では、配列の元の形状を知る必要はありません。最後の軸に互換性がある限り、これは最初の 3 つを一緒にブロードキャストします。sum-1-2

>>> (x[:, numpy.newaxis, numpy.newaxis, :] *
...  y[numpy.newaxis, :, numpy.newaxis, :] *
...  z[numpy.newaxis, numpy.newaxis, :, :]).sum(axis=-1)
array([[[  36,   92,  148,  204],
        [  92,  244,  396,  548],
        [ 148,  396,  644,  892]],

       [[  92,  244,  396,  548],
        [ 244,  748, 1252, 1756],
        [ 396, 1252, 2108, 2964]]])
于 2012-06-29T18:52:27.787 に答える