Byłoby źle powiedzieć "Matlab jest zawsze szybszy niż NumPy" lub vice versa. Często ich wydajność jest porównywalna. Podczas korzystania z NumPy, aby uzyskać dobrą wydajność, należy pamiętać, że szybkość NumPy pochodzi od wywołania funkcji napisanych w języku C/C++/Fortran. Działa to dobrze, gdy stosuje się te funkcje do całych tablic. Ogólnie rzecz biorąc, uzyskuje się gorszą wydajność po wywołaniu funkcji NumPy na mniejszych tablicach lub skalarach w pętli Pythona.
Co jest nie tak z pytaniem o Python? Każda iteracja za pośrednictwem pętli Pythona to wywołanie metody next
. Każde użycie indeksowania []
jest wywołaniem metody __getitem__
. Każdy numer +=
to połączenie z numerem __iadd__
. Każdy atrybut z kropką (taki jak np. np.dot
) obejmuje wywołania funkcji. Te funkcje wywołują , co przyczynia się do znacznego ograniczenia szybkości. Haczyki te dają Pythonowi ekspresywną siłę - indeksowanie ciągów oznacza coś innego niż indeksowanie na przykład dla dyktowania. Ta sama składnia, różne znaczenia. Magię osiąga się, nadając obiektom różne metody: __getitem__
.
Ale ta ekspresyjna siła ma koszt w prędkości. Więc jeśli nie potrzebujesz całej dynamicznej ekspresji, aby uzyskać lepszą wydajność, spróbuj ograniczyć się do wywoływania funkcji NumPy na całych tablicach.
Usunąć pętlę for; jeśli to możliwe, stosuj równania "wektoryzowane". Na przykład, zamiast
for i in range(m):
delta3 = -(x[i,:]-a3[i,:])*a3[i,:]* (1 - a3[i,:])
można obliczyć delta3
dla każdego i
wszystko na raz:
delta3 = -(x-a3)*a3*(1-a3)
Podczas gdy w for-loop
delta3
jest wektorem, używając vectorized równanie delta3
jest macierzą.
Niektóre obliczenia w for-loop
nie zależą i
i dlatego powinny być podnoszone na zewnątrz pętli. Na przykład, sum2
wygląda stałej:
sum2 = sparse.beta*(-float(sparse.rho)/rhoest + float(1.0 - sparse.rho)/(1.0 - rhoest))
Oto runnable z alternatywnym przykładem realizacji (alt
) o kodzie (orig
).
Moja odniesienia timeit pokazuje 6.8x poprawę szybkości:
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
Wskazówka: Zauważ, że zostawiłem w komentarzach kształt wszystkim pośrednia tablice. Znajomość kształtu tablic pomogła mi zrozumieć, co robił twój kod. Kształt macierzy może pomóc w wyborze odpowiednich funkcji NumPy.A przynajmniej zwracanie uwagi na kształty może pomóc ci wiedzieć, czy operacja jest rozsądna. Na przykład, jeśli obliczyć
np.dot(A, B)
i A.shape = (n, m)
i B.shape = (m, p)
, następnie np.dot(A, B)
będzie tablicą kształcie (n, p)
.
To może pomóc zbudować tablice w C_CONTIGUOUS zamówienie (przynajmniej, jeśli za pomocą np.dot
). Nie może być tak jak z prędkością 3x się w ten sposób:
Poniżej x
jest taka sama jak xf
oprócz tego x
jest C_CONTIGUOUS i xf
jest F_CONTIGUOUS - i ta sama zależność dla y
i 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
benchmarki pokazują różnicę w prędkości:
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
chodzi benchmarkingu w Pythonie:
It can be misleading korzystania z różnicy w parach time.time()
wywołań odniesienia prędkości kodu w języku Python. Musisz powtórzyć pomiar wiele razy. Lepiej wyłączyć automatyczny garbage collector. Ważne jest również mierzenie dużych rozpiętości czasu (na przykład co najmniej 10 sekund powtórzeń) w celu uniknięcia błędów spowodowanych słabą rozdzielczością w taktowaniu zegara i zmniejszenia znaczenia wezwań na wywołania time.time
. Zamiast pisać cały ten kod samodzielnie, Python dostarcza ci timeit module. Zasadniczo używam tego na czas kawałków kodu, z wyjątkiem tego, że dzwonię do niego przez IPython terminal dla wygody.
Nie jestem pewien, czy ma to wpływ na testy porównawcze, ale należy pamiętać, że może to mieć znaczenie. W question I linked to, zgodnie z time.time
, dwa kawałki kodu różniły się o czynnik 1,7x, podczas gdy testy porównawcze z użyciem timeit
wykazały, że kawałki kodu przebiegały w zasadniczo identycznych ilościach czasu.
jest moduł o nazwie mlabwrap. Możesz użyć Matlab jako biblioteki Pythona, importując to. Składnia jest bardzo prosta. Znajdziesz tutaj dokumentację źródłową i szczegółową.http: //mlabwrap.sourceforge.net/ –
Spójrz na ['cython'] (http://cython.org/). Różnica w czasie jest * oczekiwana *, ponieważ MATLAB ma JIT, a CPython nie. Jeśli cały kod byłby pojedynczym numerem, wtedy czas byłby podobny, ale to, co widzisz, mogłoby tłumaczyć narzut. Pisanie rozszerzenia za pomocą cythonu jest naprawdę łatwe i możesz uzyskać duże zyski, dodając niektóre typy do zmiennych we właściwych miejscach. – Bakuriu
Jaki jest kształt "danych"? Konkretnie, w jaki sposób 'm' porównuje się z innym wymiarem? – hpaulj