2012-06-29 7 views
7

Bu hesaplama tüm Python döngüler ortadan kaldırabilir: x[i], y[j], z[k] uzunluğu N ve x, y vektörleridirBu üçlü döngüyü, 2d dizilerinde numpy olarak nasıl düzenleyebilirim?

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

, z uzunluğu A, B ilk boyutlarına sahip, C s.t. çıktı şekli (A,B,C) şeklindedir ve her öğe üçlü ürün (öğe-bilge) toplamıdır.

3 ilâ 1 döngüden (aşağıya kod) alabilirim, ancak numaralı hatayı durdurmaya çalıştığım halde son döngüyü ortadan kaldırıyorum.

Gerekirse A=B=C yapabilirim (az miktarda dolgu ile). senin ilmek sürümleri eşdeğerdir

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

:

# 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) 
+0

Bu ev ödevi mi? – Dhara

+6

Ne yazık ki, bu bir ev ödevi problemi değil. Aslında "Nasıl Nasıl Vektörelize Edilirim?" Konulu dersler/ders kitapları var olsaydı heyecanlandım. –

cevap

9

Eğer numpy> 1.6 kullanıyorsanız, müthiş np.einsum işlevi yoktur. Gerçek problemde dizilerinizin boyutuna ulaştıktan sonra bunun verimlilik açısından nasıl adil olacağından emin değilim (aslında bu boyutlara geçtiğimde makinemde bir segfault alıyorum). Bu tür problemler için sıklıkla tercih ettiğim diğer çözüm, yöntemi cython kullanarak yeniden yazmaktır.

+0

Vay, bu harika, hiç bir fikri yoktu, teşekkürler! –

8

einsum'u kullanmak, sizin durumunuzda çok anlam ifade eder; ama bunu kolayca elle yapabilirsiniz. Hile dizileri birbirine karşı yayılabilir yapmaktır. Bu, her dizinin kendi ekseni boyunca bağımsız olarak değişmesi için onları yeniden şekillendirmek anlamına gelir. Ardından onları bir araya getirin, yayınlamaya numpy dikkat edin; ve son (en sağdaki) eksen boyunca toplanır.

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

Dilim gösterimini kullanarak bu biraz daha genel yapabilir, (aşağıda ayrıca None ile çalışmak, böylece, None eşittir) newaxis değer ve sum negatif eksen değerleri kabul gerçeği (-1 ile son, -2, sonuncuyu ifade eder, vb.). Bu şekilde, dizilerin orijinal şeklini bilmek zorunda değilsiniz; son eksenleri uyumlu olduğu sürece, bu ilk üçünü birlikte yayınlayacaktır:

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