Jaki jest najbardziej efektywny sposób mapowania funkcji na tablicy numpy? Sposób, w jaki robiłem to w moim bieżącym projekcie, jest następujący:
import numpy as np
x = np.array([1, 2, 3, 4, 5])
# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])
Wydaje się jednak, że jest to prawdopodobnie bardzo nieefektywne, ponieważ używam interpretacji listy, aby skonstruować nową tablicę jako listę Pythona przed przekształceniem jej z powrotem w tablicę numpy.
Czy możemy zrobić lepiej?
python
performance
numpy
Ryan
źródło
źródło
squarer(x)
?x = np.array([1, 2, 3, 4, 5]); x**2
działaOdpowiedzi:
Testowałem wszystkie sugerowane metody plus
np.array(map(f, x))
zperfplot
(moim małym projektem).Jeśli funkcja, którą próbujesz wektoryzować, jest już wektoryzowana (jak w
x**2
przykładzie w oryginalnym poście), użycie jej jest znacznie szybsze niż cokolwiek innego (zwróć uwagę na skalę logu):Jeśli faktycznie potrzebujesz wektoryzacji, nie ma większego znaczenia, którego wariantu używasz.
Kod do odtworzenia wykresów:
źródło
f(x)
fabułę. Może nie mieć zastosowania do każdegof
, ale ma zastosowanie tutaj, i jest to najszybsze rozwiązanie, jeśli ma zastosowanie.vf = np.vectorize(f); y = vf(x)
wygrywa w przypadku krótkich nakładów.pip install -U perfplot
) widzę komunikat:AttributeError: 'module' object has no attribute 'save'
podczas wklejania przykładowego kodu.Co powiesz na używanie
numpy.vectorize
.źródło
The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop.
W innych pytaniach znalazłem, żevectorize
może to podwoić szybkość iteracji użytkownika. Ale prawdziwe przyspieszenie polega na prawdziwychnumpy
operacjach tablicowych.squarer(x)
działałby już dla tablic innych niż 1d.vectorize
tylko naprawdę ma przewagę nad rozumieniem listy (jak ta w pytaniu), a nie ponadsquarer(x)
.TL; DR
Jak zauważył @ user2357112 , „bezpośrednia” metoda zastosowania funkcji jest zawsze najszybszym i najprostszym sposobem mapowania funkcji na tablicach Numpy:
Ogólnie należy unikać
np.vectorize
, ponieważ nie działa on dobrze i ma (lub miał) wiele problemów . Jeśli masz do czynienia z innymi typami danych, możesz sprawdzić inne metody przedstawione poniżej.Porównanie metod
Oto kilka prostych testów służących do porównania trzech metod mapowania funkcji, w tym przykładzie z użyciem Pythona 3.6 i NumPy 1.15.4. Po pierwsze, funkcje konfiguracji do testowania:
Testowanie z pięcioma elementami (posortowanymi od najszybszego do najwolniejszego):
Z setkami elementów:
I z tysiącami elementów tablicy lub więcej:
Różne wersje Python / NumPy i optymalizacja kompilatora przyniosą różne wyniki, więc wykonaj podobny test dla swojego środowiska.
źródło
count
argumentu i wyrażenia generatora,np.fromiter
jest to znacznie szybsze.'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
f(x)
, które pokonuje wszystko inne o rząd wielkości .f
ma 2 zmienne, a tablica jest 2D?Wokół jest numexpr , numba i cython , celem tej odpowiedzi jest wzięcie pod uwagę tych możliwości.
Ale najpierw określmy oczywiste: bez względu na to, jak mapujesz funkcję Pythona na tablicę numpy, pozostaje ona funkcją Pythona, co oznacza dla każdej oceny:
Float
).Tak więc, która maszyna jest używana do przechodzenia przez tablicę, nie odgrywa dużej roli z powodu wspomnianego powyżej narzutu - pozostaje znacznie wolniejsza niż korzystanie z wbudowanej funkcjonalności numpy.
Rzućmy okiem na następujący przykład:
np.vectorize
jest wybierany jako reprezentant klasy metod czysto pythonowych. Używającperfplot
(patrz kod w załączniku do tej odpowiedzi) otrzymujemy następujące czasy działania:Widzimy, że podejście numpy jest 10x-100x szybsze niż wersja czysto pythonowa. Spadek wydajności w przypadku większych rozmiarów macierzy jest prawdopodobnie spowodowany tym, że dane nie pasują już do pamięci podręcznej.
Warto również wspomnieć, że
vectorize
również zużywa dużo pamięci, więc często użycie pamięci to szyjka butelki (patrz powiązane pytanie SO ). Zauważ też, że dokumentacja tego numpy nanp.vectorize
stwierdza, że jest „przede wszystkim dla wygody, a nie dla wydajności”.Gdy pożądana jest wydajność, należy użyć innych narzędzi, oprócz napisania rozszerzenia C od zera, istnieją następujące możliwości:
Często słyszy się, że numpy-wydajność jest tak dobra, jak to tylko możliwe, ponieważ pod maską jest czystym C. Ale jest jeszcze wiele do zrobienia!
Wektoryzowana wersja numpy wykorzystuje wiele dodatkowej pamięci i dostęp do pamięci. Biblioteka Numexp próbuje kafelkować tablice numpy, a tym samym uzyskać lepsze wykorzystanie pamięci podręcznej:
Prowadzi do następującego porównania:
Nie mogę wyjaśnić wszystkiego na powyższym wykresie: na początku możemy zobaczyć większy narzut dla biblioteki numexpr, ale ponieważ lepiej wykorzystuje pamięć podręczną, dla większych tablic jest około 10 razy szybszy!
Innym podejściem jest skompilowanie funkcji przez jit, a tym samym uzyskanie prawdziwego UFunc w czystym C. Oto podejście Numba:
Jest 10 razy szybszy niż oryginalne podejście numpy:
Jednak zadanie jest kłopotliwie równoległe, dlatego moglibyśmy również użyć
prange
do równoległego obliczenia pętli:Zgodnie z oczekiwaniami funkcja równoległa jest wolniejsza dla mniejszych wejść, ale szybsza (prawie współczynnik 2) dla większych rozmiarów:
Podczas gdy numba specjalizuje się w optymalizacji operacji za pomocą tablic numpy, Cython jest bardziej ogólnym narzędziem. Bardziej skomplikowane jest wyodrębnienie tej samej wydajności, co w przypadku numba - często jest to zależne od llvm (numba) vs lokalnego kompilatora (gcc / MSVC):
Cython powoduje nieco wolniejsze funkcje:
Wniosek
Oczywiście testowanie tylko jednej funkcji niczego nie dowodzi. Należy również pamiętać, że dla wybranego przykładu funkcji przepustowość pamięci była szyjką butelki dla rozmiarów większych niż 10 ^ 5 elementów - dlatego mieliśmy taką samą wydajność dla numba, numexpr i cython w tym regionie.
Ostatecznie ostateczna odpowiedź zależy od rodzaju funkcji, sprzętu, dystrybucji Pythona i innych czynników. Na przykład Anaconda-dystrybucji używa Intela VML dla funkcji NumPy i tym samym przewyższa Numba (chyba że korzysta SVML, zobacz ten SO-post ) łatwo za transcendentalne funkcje jak
exp
,sin
,cos
i podobne - patrz np następującym SO-post .Jednak na podstawie tego dochodzenia i z dotychczasowego doświadczenia powiedziałbym, że numba wydaje się najłatwiejszym narzędziem o najlepszym działaniu, o ile nie są zaangażowane żadne funkcje transcendentalne.
Rysowanie czasów pracy za pomocą pakietu perfplot:
źródło
Operacje arytmetyczne na tablicach są automatycznie stosowane elementarnie, z wydajnymi pętlami na poziomie C, które pozwalają uniknąć narzutu interpretera, który miałby zastosowanie do pętli lub rozumienia na poziomie Pythona.
Większość funkcji, które chcesz zastosować elementarnie do tablicy NumPy, po prostu będzie działać, choć niektóre mogą wymagać zmian. Na przykład
if
nie działa elementarnie. Chcesz przekonwertować je na konstrukcje takie jaknumpy.where
:staje się
źródło
Wierzę, że w nowszej wersji (używam 1.13) numpy możesz po prostu wywołać funkcję, przekazując tablicę numpy do funkcji, którą napisałeś dla typu skalarnego, automatycznie zastosuje wywołanie funkcji do każdego elementu nad tablicą numpy i zwróci ci kolejna tablica liczb
źródło
**
operator stosuje obliczenia do każdego elementu tt
. To zwykły numpy. Zawinięcie go wlambda
nic nie robi.W wielu przypadkach numpy.apply_along_axis najlepszym wyborem będzie . Zwiększa wydajność o około 100x w porównaniu z innymi podejściami - i to nie tylko w przypadku trywialnych funkcji testowych, ale także w przypadku bardziej złożonych kompozycji funkcji od numpy i scipy.
Kiedy dodam metodę:
do kodu perfplot, otrzymuję następujące wyniki:
źródło
Wygląda na to, że nikt nie wspominał o wbudowanej fabrycznej metodzie produkcji
ufunc
w paczkach numpy:np.frompyfunc
którą przetestowałem ponownienp.vectorize
i osiągnąłem lepsze wyniki o około 20 ~ 30%. Oczywiście będzie działał dobrze jak przepisany kod C, a nawetnumba
(którego nie testowałem), ale może być lepszą alternatywą niżnp.vectorize
Testowałem także większe próbki, a poprawa jest proporcjonalna. Zobacz dokumentację również tutaj
źródło
Jak wspomniano w tym poście , wystarczy użyć wyrażeń generatora takich jak:
źródło
Wszystkie powyższe odpowiedzi dobrze się porównują, ale jeśli potrzebujesz użyć niestandardowej funkcji do mapowania, a masz
numpy.ndarray
i musisz zachować kształt tablicy.Porównałem tylko dwa, ale zachowa kształt
ndarray
. Do porównania użyłem tablicy z 1 milionem wpisów. Tutaj używam funkcji kwadratowej, która jest również wbudowana w numpy i ma świetne zwiększenie wydajności, ponieważ tam, gdzie było coś potrzebne, możesz użyć wybranej funkcji.Wynik
tutaj możesz wyraźnie zobaczyć, że
numpy.fromiter
działa świetnie, biorąc pod uwagę proste podejście, a jeśli jest dostępna wbudowana funkcja, skorzystaj z niej.źródło
Posługiwać się
numpy.fromfunction(function, shape, **kwargs)
Zobacz „ https://docs.scipy.org/doc/numpy/reference/generated/numpy.fromfunction.html ”
źródło