numpy.amax () znajdzie maksymalną wartość w tablicy, a numpy.amin () zrobi to samo dla wartości minimalnej. Jeśli chcę znaleźć zarówno max, jak i min, muszę wywołać obie funkcje, co wymaga dwukrotnego przepuszczenia (bardzo dużej) tablicy, co wydaje się powolne.
Czy w numpy API jest funkcja, która znajduje zarówno max, jak i min za pomocą jednego przejścia przez dane?
amax
amin
minmax
do biblioteki, której dotyczy problem ( github.com/numpy/numpy/issues/9836 ).Odpowiedzi:
Nie. W chwili pisania tego tekstu nie ma takiej funkcji. (I tak, jeśli nie było takiej funkcji, jej wyniki byłyby znacznie lepsze niż dzwonienie
numpy.amin()
inumpy.amax()
kolejno na dużej tablicy.)źródło
Nie sądzę, aby dwukrotne przepuszczenie tablicy było problemem.Rozważmy następujący pseudokod:Chociaż jest tu tylko 1 pętla, nadal są 2 sprawdzenia. (Zamiast 2 pętli z 1 czekiem każda). Naprawdę jedyne, co oszczędzasz, to narzut 1 pętli. Jeśli tablice są naprawdę duże, jak mówisz, to narzut jest niewielki w porównaniu z rzeczywistym obciążeniem pracą pętli. (Zauważ, że to wszystko jest zaimplementowane w C, więc pętle i tak są mniej więcej wolne).
EDYTUJ Przepraszamy 4 z was, którzy głosowali za i wierzyli we mnie. Zdecydowanie możesz to zoptymalizować.
Oto kod Fortran, który można wkompilować w moduł Pythona za pośrednictwem
f2py
(możeCython
przyjdzie guru i porówna to ze zoptymalizowaną wersją C ...):Skompiluj przez:
A teraz jesteśmy w miejscu, w którym możemy to przetestować:
Wyniki są dla mnie nieco oszałamiające:
Muszę powiedzieć, że nie do końca to rozumiem. Porównywanie tylko
np.min
zminmax1
iminmax2
nadal jest przegraną bitwą, więc nie jest to tylko kwestia pamięci ...uwagi - Zwiększanie rozmiaru o współczynnik
10**a
i zmniejszanie liczby powtórzeń o współczynnik10**a
(utrzymywanie stałego rozmiaru problemu) zmienia wydajność, ale nie w pozornie spójny sposób, co pokazuje, że istnieje pewna zależność między wydajnością pamięci a narzutem wywołań funkcji w pyton. Nawet porównanie prostejmin
implementacji w fortran beats numpy's przez współczynnik około 2 ...źródło
i < minval
jest prawdziwe,i > maxval
to zawsze jest fałszywe, więc wystarczy wykonać średnio tylko 1,5 sprawdzenia na iterację, gdy sekundaif
jest zastępowana przezelif
.f2py
po prostu zawija kodowany ręcznie Fortran, tak aby był wywoływany przez Pythona. „Sprawiedliwszy” test to prawdopodobnie ręczne kodowanie C, a następnie użycief2py
(!), Aby opakować go w Pythonie. Jeśli pozwalasz C ++, to Shed Skin może być idealnym miejscem do równoważenia łatwości kodowania z wydajnością.Istnieje funkcja do znajdowania (max-min) o nazwie numpy.ptp, jeśli jest to dla Ciebie przydatne:
ale nie sądzę, aby można było znaleźć zarówno wartość minimalną, jak i maksymalną przy jednym przejściu.
EDYCJA: ptp po prostu wywołuje min i max pod maską
źródło
Możesz użyć Numba , który jest dynamicznym kompilatorem Pythona obsługującym NumPy używającym LLVM. Wynikowa implementacja jest dość prosta i przejrzysta:
Powinien też być szybszy niż
min() & max()
implementacja Numpy . A wszystko to bez konieczności pisania jednej linii kodu w języku C / Fortran.Wykonaj własne testy wydajności, ponieważ zawsze zależy to od architektury, danych, wersji pakietów ...
źródło
numba
funkcję raz przed testem porównawczym, aby upewnić się, że jest skompilowana w JIT ?. Ponadto, jeśli używaszipython
, dla uproszczenia, sugerowałbym użycie%timeit whatever_code()
do pomiaru czasu wykonania.elif
pozwala na to, aby twoje minimum było większe niż twoje maksimum. Np. W przypadku tablicy o długości 1, max będzie taka sama jak ta wartość, podczas gdy min to + nieskończoność. Nie jest to wielka sprawa dla jednorazowego, ale nie jest to dobry kod, który można wrzucić głęboko w brzuch bestii produkcyjnej.Ogólnie rzecz biorąc, można zmniejszyć liczbę porównań dla algorytmu minmax, przetwarzając jednocześnie dwa elementy i porównując tylko mniejszy z tymczasowym minimum, a większy z tymczasowym maksimum. Średnio potrzeba tylko 3/4 porównań niż podejście naiwne.
Można to zaimplementować w c lub fortran (lub jakimkolwiek innym języku niskiego poziomu) i powinno być prawie nie do pobicia pod względem wydajności. używamnumba aby zilustrować zasadę i uzyskać bardzo szybką, niezależną od typu implementację:
Jest zdecydowanie szybszy niż naiwne podejście, które przedstawił Peque :
Zgodnie z oczekiwaniami nowa implementacja minmax zajmuje tylko około 3/4 czasu, jaki zajęła naiwna implementacja (
2.1 / 2.75 = 0.7636363636363637
)źródło
Aby uzyskać kilka pomysłów na liczby, których można się spodziewać, biorąc pod uwagę następujące podejścia:
(
extrema_loop_*()
podejścia są podobne do tego, co jest proponowane tutaj , podczas gdyextrema_while_*()
podejścia są oparte na kodzie z tego miejsca )Następujące terminy:
wskazują, że
extrema_while_*()
są najszybsi iextrema_while_nb()
najszybsi. W każdym razie, takżeextrema_loop_nb()
iextrema_loop_cy()
roztwory do przewyższają NumPy tylko do podejścia (użyciunp.max()
inp.min()
oddzielnie).Na koniec zwróć uwagę, że żaden z nich nie jest tak elastyczny jak
np.min()
/np.max()
(pod względem obsługi n-dim,axis
parametrów itp.).(pełny kod jest dostępny tutaj )
źródło
extrema_while_nb
Nikt nie wspomniał o numpy.percentile , więc pomyślałem, że tak. Jeśli poprosisz o
[0, 100]
percentyle, otrzymasz tablicę dwóch elementów, min (0-ty percentyl) i maksymalny (100-ty percentyl).Nie spełnia to jednak celu PO: nie jest szybsze niż oddzielnie min i max. Jest to prawdopodobnie spowodowane niektórymi maszynami, które pozwoliłyby na nie-ekstremalne percentyle (trudniejszy problem, który powinien zająć więcej czasu).
Przyszła wersja Numpy mogłaby umieścić specjalny przypadek, aby pominąć normalne obliczanie percentyla, jeśli tylko
[0, 100]
jest to wymagane. Bez dodawania czegokolwiek do interfejsu istnieje sposób, aby poprosić Numpy'ego o min i max w jednym wywołaniu (w przeciwieństwie do tego, co zostało powiedziane w akceptowanej odpowiedzi), ale standardowa implementacja biblioteki nie wykorzystuje tego przypadku, aby to zrobić wart.źródło
To stary wątek, ale w każdym razie, jeśli ktoś jeszcze raz na to spojrzy ...
Szukając jednocześnie wartości min i max, można zmniejszyć liczbę porównań. Jeśli porównujesz dane zmiennoprzecinkowe (a wydaje mi się, że tak jest), może to zaoszczędzić trochę czasu, chociaż nie jest to złożoność obliczeniowa.
Zamiast (kod Pythona):
możesz najpierw porównać dwie sąsiednie wartości w tablicy, a następnie porównać tylko mniejszą z bieżącym minimum, a większą z bieżącym maksimum:
Kod tutaj jest napisany w Pythonie, najwyraźniej ze względu na szybkość używałbyś C, Fortran lub Cython, ale w ten sposób wykonujesz 3 porównania na iterację, z len (ar) / 2 iteracjami, dając porównania 3/2 * len (ar). W przeciwieństwie do tego, wykonując porównanie „w sposób oczywisty”, wykonujesz dwa porównania na iterację, co prowadzi do porównań 2 * len (ar). Oszczędza 25% czasu porównania.
Może ktoś kiedyś uzna to za przydatne.
źródło
np.bincount
, patrz tutaj . Nie wykorzystuje sztuczki, którą wskazałeś, bo okazało się, że jest nawet 2x wolniejsze niż podejście naiwne. Istnieje link z PR do niektórych kompleksowych testów porównawczych obu metod.Na pierwszy rzut oka wydaje się, że załatwia sprawę:
numpy.histogram
... ale jeśli spojrzysz na źródło tej funkcji, po prostu wywołuje
a.min()
ia.max()
niezależnie, a zatem nie pozwala uniknąć problemów z wydajnością, o których mowa w tym pytaniu. :-(Podobnie
scipy.ndimage.measurements.extrema
wygląda na możliwość, ale też po prostu dzwonia.min()
ia.max()
samodzielnie.źródło
np.histogram
nie zawsze działa w tym przypadku, ponieważ zwracane(amin, amax)
wartości dotyczą minimalnych i maksymalnych wartości pojemnika. Jeśli mam, na przykłada = np.zeros(10)
,np.histogram(a, bins=1)
zwraca(array([10]), array([-0.5, 0.5]))
. W takim przypadku użytkownik szuka(amin, amax)
= (0, 0).Zresztą i tak było to dla mnie warte wysiłku, więc każdemu zainteresowanemu zaproponuję najtrudniejsze i najmniej eleganckie rozwiązanie. Moim rozwiązaniem jest zaimplementowanie wielowątkowego algorytmu min-max w jednym przebiegu w C ++ i użycie go do utworzenia modułu rozszerzenia Python. Ten wysiłek wymaga trochę narzutu, aby nauczyć się korzystać z interfejsów API Python i NumPy C / C ++, a tutaj pokażę kod i podam kilka małych wyjaśnień i odniesień dla każdego, kto chce podążać tą ścieżką.
Wielowątkowe min./maks
Nie ma tu nic ciekawego. Tablica jest podzielona na fragmenty o rozmiarze
length / workers
. Wartości min / max są obliczane dla każdego fragmentu w afuture
, które są następnie skanowane pod kątem globalnego min / max.Moduł rozszerzeń języka Python
Tutaj zaczyna się brzydko ... Jednym ze sposobów wykorzystania kodu C ++ w Pythonie jest zaimplementowanie modułu rozszerzającego. Ten moduł można zbudować i zainstalować przy użyciu
distutils.core
standardowego modułu. Pełny opis tego, co się z tym wiąże, znajduje się w dokumentacji Pythona: https://docs.python.org/3/extending/extending.html . UWAGA: z pewnością istnieją inne sposoby uzyskania podobnych wyników, cytując https://docs.python.org/3/extending/index.html#extending-index :Zasadniczo ta trasa jest prawdopodobnie bardziej akademicka niż praktyczna. Mając to na uwadze, następnym krokiem było trzymanie się samouczka i utworzenie pliku modułu. Jest to zasadniczo szablon dla distutils, aby wiedzieć, co zrobić z kodem i stworzyć z niego moduł Pythona. Zanim to zrobisz, prawdopodobnie dobrze jest utworzyć wirtualne środowisko Pythona, aby nie zanieczyszczać pakietów systemowych (patrz https://docs.python.org/3/library/venv.html#module-venv ).
Oto plik modułu:
W tym pliku znajduje się znaczące wykorzystanie Pythona, a także NumPy API, aby uzyskać więcej informacji, odwiedź: https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple i NumPy : https://docs.scipy.org/doc/numpy/reference/c-api.array.html .
Instalowanie modułu
Następną rzeczą do zrobienia jest użycie distutils do zainstalowania modułu. Wymaga to pliku instalacyjnego:
Aby ostatecznie zainstalować moduł, wykonaj
python3 setup.py install
z poziomu środowiska wirtualnego.Testowanie modułu
Na koniec możemy przetestować, czy implementacja C ++ faktycznie przewyższa naiwne użycie NumPy. Aby to zrobić, oto prosty skrypt testowy:
Oto wyniki, które otrzymałem, robiąc to wszystko:
Są one znacznie mniej zachęcające, niż wskazują wyniki wcześniej w wątku, które wskazywały na około 3,5-krotne przyspieszenie i nie obejmowały wielowątkowości. Wyniki, które osiągnąłem, są dość rozsądne, spodziewałbym się, że narzut wątków i będzie dominował w czasie, aż tablice staną się bardzo duże, w którym to momencie wzrost wydajności zacznie zbliżać się do
std::thread::hardware_concurrency
wzrostu x.Wniosek
Wydaje się, że z pewnością jest miejsce na optymalizacje specyficzne dla aplikacji w niektórych kodach NumPy, w szczególności w odniesieniu do wielowątkowości. Nie jest dla mnie jasne, czy warto podjąć ten wysiłek, ale z pewnością wydaje się to dobrym ćwiczeniem (lub czymś w tym rodzaju). Myślę, że nauczenie się niektórych z tych „narzędzi stron trzecich”, takich jak Cython, może być lepszym sposobem wykorzystania czasu, ale kto wie.
źródło
v = min_max_it->get();
. Teget
metody bloki aż wynik jest gotowy i zwraca go. Ponieważ pętla przechodzi przez każdą przyszłość, nie zakończy się, dopóki wszystkie nie zostaną ukończone. future.get ()Najkrótszy sposób, jaki wymyśliłem, to:
Ale ponieważ sortuje tablicę, nie jest najbardziej wydajna.
Innym krótkim sposobem byłoby:
Powinno to być bardziej wydajne, ale wynik jest obliczany i zwracana jest wartość zmiennoprzecinkowa.
źródło