「Matlab は常に NumPy より速い」と言うのは間違っています。多くの場合、それらのパフォーマンスは同等です。NumPy を使用する場合、優れたパフォーマンスを得るには、NumPy の速度は C/C++/Fortran で記述された基本的な関数を呼び出すことから得られることに注意する必要があります。これらの関数を配列全体に適用すると、うまく機能します。一般に、Python ループ内の小さな配列またはスカラーでこれらの NumPy 関数を呼び出すと、パフォーマンスが低下します。
あなたが尋ねる Python ループの何が問題なのですか? next
Python ループのすべての反復は、メソッドの呼び出しです。[]
インデックスを
使用するたびに、__getitem__
メソッドが呼び出されます。Every+=
は への呼び出し__iadd__
です。すべてのドット属性ルックアップ ( like などnp.dot
) には、関数呼び出しが含まれます。これらの関数呼び出しは、速度を大幅に低下させます。これらのフックは、Python の表現力を高めます。たとえば、文字列のインデックス作成は、dict のインデックス作成とは異なる意味を持ちます。同じ構文、異なる意味。この魔法は、オブジェクトにさまざまなメソッドを与えることによって実現され__getitem__
ます。
しかし、その表現力にはスピードが犠牲になります。したがって、動的な表現力がすべて必要ない場合は、パフォーマンスを向上させるために、配列全体での NumPy 関数呼び出しに制限してみてください。
したがって、for ループを削除します。可能であれば、「ベクトル化された」方程式を使用します。たとえば、代わりに
for i in range(m):
delta3 = -(x[i,:]-a3[i,:])*a3[i,:]* (1 - a3[i,:])
delta3
それぞれi
を一度に計算できます。
delta3 = -(x-a3)*a3*(1-a3)
はベクトルfor-loop
delta3
ですが、ベクトル化された方程式を使用するdelta3
と行列になります。
の計算の一部はfor-loop
依存しないi
ため、ループの外側に持ち上げる必要があります。たとえばsum2
、定数のように見えます。
sum2 = sparse.beta*(-float(sparse.rho)/rhoest + float(1.0 - sparse.rho) / (1.0 - rhoest) )
alt
コード ( ) の代替実装 ( ) を使用した実行可能な例を次に示しますorig
。
私の timeit ベンチマークは、速度が 6.8 倍向上したことを示しています。
In [52]: %timeit orig()
1 loops, best of 3: 495 ms per loop
In [53]: %timeit alt()
10 loops, best of 3: 72.6 ms per loop
import numpy as np
class Bunch(object):
""" http://code.activestate.com/recipes/52308 """
def __init__(self, **kwds):
self.__dict__.update(kwds)
m, n, p = 10 ** 4, 64, 25
sparse = Bunch(
theta1=np.random.random((p, n)),
theta2=np.random.random((n, p)),
b1=np.random.random((p, 1)),
b2=np.random.random((n, 1)),
)
x = np.random.random((m, n))
a3 = np.random.random((m, n))
a2 = np.random.random((m, p))
a1 = np.random.random((m, n))
sum2 = np.random.random((p, ))
sum2 = sum2[:, np.newaxis]
def orig():
partial_j1 = np.zeros(sparse.theta1.shape)
partial_j2 = np.zeros(sparse.theta2.shape)
partial_b1 = np.zeros(sparse.b1.shape)
partial_b2 = np.zeros(sparse.b2.shape)
delta3t = (-(x - a3) * a3 * (1 - a3)).T
for i in range(m):
delta3 = delta3t[:, i:(i + 1)]
sum1 = np.dot(sparse.theta2.T, delta3)
delta2 = (sum1 + sum2) * a2[i:(i + 1), :].T * (1 - a2[i:(i + 1), :].T)
partial_j1 += np.dot(delta2, a1[i:(i + 1), :])
partial_j2 += np.dot(delta3, a2[i:(i + 1), :])
partial_b1 += delta2
partial_b2 += delta3
# delta3: (64, 1)
# sum1: (25, 1)
# delta2: (25, 1)
# a1[i:(i+1),:]: (1, 64)
# partial_j1: (25, 64)
# partial_j2: (64, 25)
# partial_b1: (25, 1)
# partial_b2: (64, 1)
# a2[i:(i+1),:]: (1, 25)
return partial_j1, partial_j2, partial_b1, partial_b2
def alt():
delta3 = (-(x - a3) * a3 * (1 - a3)).T
sum1 = np.dot(sparse.theta2.T, delta3)
delta2 = (sum1 + sum2) * a2.T * (1 - a2.T)
# delta3: (64, 10000)
# sum1: (25, 10000)
# delta2: (25, 10000)
# a1: (10000, 64)
# a2: (10000, 25)
partial_j1 = np.dot(delta2, a1)
partial_j2 = np.dot(delta3, a2)
partial_b1 = delta2.sum(axis=1)
partial_b2 = delta3.sum(axis=1)
return partial_j1, partial_j2, partial_b1, partial_b2
answer = orig()
result = alt()
for a, r in zip(answer, result):
try:
assert np.allclose(np.squeeze(a), r)
except AssertionError:
print(a.shape)
print(r.shape)
raise
ヒント:すべての中間配列の形状をコメントに残したことに注意してください。配列の形状を知ることで、コードが何をしているかを理解することができました。配列の形状は、使用する適切な NumPy 関数を導くのに役立ちます。または、少なくとも、形状に注意を払うことは、操作が賢明かどうかを知るのに役立ちます. たとえば、計算すると
np.dot(A, B)
と、そしてA.shape = (n, m)
はshape の配列になります。B.shape = (m, p)
np.dot(A, B)
(n, p)
配列を C_CONTIGUOUS 順で構築すると役立ちます (少なくとも を使用する場合np.dot
)。そうすることで、最大 3 倍の速度アップが得られる可能性があります。
以下x
は、が C_CONTIGUOUS で F_CONTIGUOUS で
あることをxf
除いて、 と同じです。また、との関係も同じです。x
xf
y
yf
import numpy as np
m, n, p = 10 ** 4, 64, 25
x = np.random.random((n, m))
xf = np.asarray(x, order='F')
y = np.random.random((m, n))
yf = np.asarray(y, order='F')
assert np.allclose(x, xf)
assert np.allclose(y, yf)
assert np.allclose(np.dot(x, y), np.dot(xf, y))
assert np.allclose(np.dot(x, y), np.dot(xf, yf))
%timeit
ベンチマークは速度の違いを示しています。
In [50]: %timeit np.dot(x, y)
100 loops, best of 3: 12.9 ms per loop
In [51]: %timeit np.dot(xf, y)
10 loops, best of 3: 27.7 ms per loop
In [56]: %timeit np.dot(x, yf)
10 loops, best of 3: 21.8 ms per loop
In [53]: %timeit np.dot(xf, yf)
10 loops, best of 3: 33.3 ms per loop
Python でのベンチマークについて:
time.time()
Python でコードの速度をベンチマークするために呼び出しのペアの違いを使用すると、誤解を招く可能性があります。何度も測定を繰り返す必要があります。自動ガベージ コレクタを無効にすることをお勧めします。time.time
また、クロック タイマーの精度が低いことによるエラーを回避し、コール オーバーヘッドの重要性を軽減するために、長いスパン(少なくとも 10 秒相当の繰り返しなど)を測定することも重要です。すべてのコードを自分で書く代わりに、Python はtimeit モジュールを提供します。便宜上IPython ターミナルを介して呼び出していることを除いて、私は本質的にそれを使用してコードの断片を計っています。
これがベンチマークに影響を与えているかどうかはわかりませんが、違いが生じる可能性があることに注意してください. 私がリンクした質問では、2 つのコードによるとtime.time
、1.7 倍の係数で異なっていましたが、使用したベンチマークtimeit
では、コードの断片が本質的に同じ時間で実行されたことが示されました。