NumPy: एक साथ अधिकतम के लिए कार्य () और मिनट ()


109

numpy.amax () एक सरणी में अधिकतम मान प्राप्त करेगा, और numpy.amin () मिनट मान के लिए भी ऐसा ही करता है। यदि मैं अधिकतम और न्यूनतम दोनों को खोजना चाहता हूं, तो मुझे दोनों फ़ंक्शन को कॉल करना होगा, जिसमें दो बार (बहुत बड़ी) सरणी से गुजरने की आवश्यकता होती है, जो धीमा लगता है।

क्या खस्ता एपीआई में एक फ़ंक्शन है जो डेटा के माध्यम से केवल एक ही पास के साथ अधिकतम और न्यूनतम दोनों पाता है?


1
कितना बड़ा है बड़ा? अगर मुझे कुछ समय मिलता है, तो मैं एक फोरट्रान कार्यान्वयन की तुलना में कुछ परीक्षण amaxamin
चलाऊंगा

1
मैं मानता हूँ "बहुत बड़ा" व्यक्तिपरक है। मेरे मामले में, मैं उन सरणियों के बारे में बात कर रहा हूं जो कुछ जीबी हैं।
स्टुअर्ट बर्ग

यह बहुत बड़ा है। मैंने इसे फोरट्रान में गणना करने के लिए एक उदाहरण तैयार किया है (भले ही आपको फोरट्रान नहीं पता है, कोड को समझना बहुत आसान होना चाहिए)। यह वास्तव में इसे फोरट्रान बनाम सुन्न के माध्यम से चलाने से फर्क पड़ता है। (संभवत:, आपको C से समान प्रदर्शन प्राप्त करने में सक्षम होना चाहिए ...) मुझे यकीन नहीं है - मुझे लगता है कि हमें यह टिप्पणी करने के लिए पर्याप्त देव की आवश्यकता होगी कि मेरे कार्य उनके मुकाबले क्यों बेहतर प्रदर्शन करते हैं ...
mgilson

बेशक, यह शायद ही एक उपन्यास विचार है। उदाहरण के लिए, बूस्ट माइनमैक्स लाइब्रेरी (C ++) उस एल्गोरिथ्म का कार्यान्वयन प्रदान करता है जिसकी मुझे तलाश है।
स्टुअर्ट बर्ग

3
वास्तव में पूछे गए सवाल का जवाब नहीं है, लेकिन शायद इस धागे पर लोगों की दिलचस्पी है। minmaxअंक में पुस्तकालय को जोड़ने के बारे में NumPy से पूछा ( github.com/numpy/numpy/issues/9836 )।
जकीरखाम

जवाबों:


49

क्या खस्ता एपीआई में एक फ़ंक्शन है जो डेटा के माध्यम से केवल एक ही पास के साथ अधिकतम और न्यूनतम दोनों पाता है?

नहीं, इस लेखन के समय, ऐसा कोई फ़ंक्शन नहीं है। (और हाँ, अगर वहाँ थे इस तरह के एक समारोह, इसके प्रदर्शन किया जाएगा काफी बेहतर बुला से numpy.amin()और numpy.amax()क्रमिक विस्तृत शृंखला पर।)


31

मुझे नहीं लगता है कि दो बार सरणी के ऊपर से गुजरना एक समस्या है। निम्नलिखित छद्म कोड पर विचार करें:

minval = array[0]
maxval = array[0]
for i in array:
    if i < minval:
       minval = i
    if i > maxval:
       maxval = i

जबकि यहां केवल 1 लूप है, फिर भी 2 चेक हैं। (1 चेक के साथ 2 लूप होने के बजाय प्रत्येक)। वास्तव में केवल एक चीज जिसे आप बचाते हैं वह है 1 लूप का ओवरहेड। यदि सरणियाँ वास्तव में बड़ी हैं जैसा कि आप कहते हैं, कि ओवरहेड वास्तविक लूप के कार्य भार की तुलना में छोटा है। (ध्यान दें कि यह सब C में लागू है, इसलिए लूप कमोबेश वैसे भी मुफ्त हैं।)


EDIT आप में से 4 को क्षमा करें जिन्होंने मुझ पर विश्वास किया और मुझ पर विश्वास किया। आप निश्चित रूप से इसे अनुकूलित कर सकते हैं।

यहाँ कुछ फोरट्रान कोड हैं जिन्हें एक अजगर मॉड्यूल में संकलित किया जा सकता है f2py(हो सकता है कि एक Cythonगुरु साथ आ सकता है और एक अनुकूलित सी संस्करण के साथ इसकी तुलना कर सकता है ...):

subroutine minmax1(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  integer i

  amin = a(1)
  amax = a(1)
  do i=2, n
     if(a(i) > amax)then
        amax = a(i)
     elseif(a(i) < amin) then
        amin = a(i)
     endif
  enddo
end subroutine minmax1

subroutine minmax2(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  amin = minval(a)
  amax = maxval(a)
end subroutine minmax2

इसके माध्यम से संकलित करें:

f2py -m untitled -c fortran_code.f90

और अब हम ऐसी जगह पर हैं जहाँ हम इसका परीक्षण कर सकते हैं:

import timeit

size = 100000
repeat = 10000

print timeit.timeit(
    'np.min(a); np.max(a)',
    setup='import numpy as np; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), " # numpy min/max"

print timeit.timeit(
    'untitled.minmax1(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax1'

print timeit.timeit(
    'untitled.minmax2(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax2'

परिणाम मेरे लिए थोड़े चौंका देने वाले हैं:

8.61869883537 # numpy min/max
1.60417699814 # minmax1
2.30169081688 # minmax2

मुझे कहना है, मैं इसे पूरी तरह से नहीं समझता। बस np.minबनाम तुलना करना minmax1और minmax2अभी भी एक हारी हुई लड़ाई है, इसलिए यह सिर्फ एक स्मृति मुद्दा नहीं है ...

नोट - के एक कारक के आकार में वृद्धि 10**aऔर घटते हुए पुनरावृत्ति के कारक से 10**a(समस्या का आकार स्थिर रखना) प्रदर्शन को बदलता है, लेकिन उचित रूप से संगत तरीके से नहीं जो दिखाता है कि स्मृति प्रदर्शन और फ़ंक्शन कॉल ओवरहेड के बीच कुछ अंतर है अजगर। यहां तक ​​कि minफोरट्रान में एक साधारण कार्यान्वयन की तुलना लगभग 2 के एक कारक द्वारा numpy की धड़कन ...


21
एकल पास का लाभ मेमोरी दक्षता है। विशेष रूप से यदि आपकी सरणी काफी बड़ी है जिसे स्वैप किया जा सकता है, तो यह बहुत बड़ा हो सकता है।
डगल

4
यह काफी हद तक सही नहीं है, क्योंकि इस तरह के सरणियों के साथ, मेमोरी की गति आमतौर पर सीमित कारक है, इसलिए यह तेजी से आधा हो सकता है ...
15

3
आपको हमेशा दो चेक की आवश्यकता नहीं है। यदि i < minvalयह सच है, तो i > maxvalहमेशा गलत होता है, इसलिए आपको केवल प्रति पुनरावृत्ति औसतन 1.5 चेक करने की आवश्यकता होती है जब दूसरा ifए द्वारा प्रतिस्थापित किया जाता है elif
फ्रेड फू

2
छोटे नोट: मुझे संदेह है कि साइथन सबसे अनुकूलित पायथन-कॉल करने योग्य सी मॉड्यूल प्राप्त करने का तरीका है। साइथन का लक्ष्य एक प्रकार का एनोटेट किया हुआ पायथन है, जो तब सी-मशीन से अनुवादित होता है, जबकि f2pyसिर्फ हाथ से कोडित फोरट्रान को लपेटता है ताकि यह पायथन द्वारा कॉल करने योग्य हो। एक "फेयरर" परीक्षण शायद हाथ-कोडिंग सी है और फिर f2pyइसे पायथन के लिए लपेटने के लिए (!) का उपयोग कर रहा है। यदि आप C ++ की अनुमति दे रहे हैं, तो प्रदर्शन के साथ कोडिंग आसानी को संतुलित करने के लिए शेड स्किन सबसे प्यारी जगह हो सकती है।
जॉन वाई

4
के रूप में सुन्न 1.8 मिनट और अधिकतम amd64 प्लेटफार्मों पर वेक्टर होते हैं, मेरे core2duo पर खस्ता प्रदर्शन करता है और साथ ही साथ यह फोरट्रान कोड भी होता है। लेकिन एक पास पास लाभप्रद होगा यदि सरणी बड़े सीपीयू कैश के आकार से अधिक हो।
13 मई को jtaylor

23

यदि आपके लिए यह उपयोगी है तो numpy.ptp नामक अधिकतम (min) खोजने के लिए एक फ़ंक्शन है :

>>> import numpy
>>> x = numpy.array([1,2,3,4,5,6])
>>> x.ptp()
5

लेकिन मुझे नहीं लगता कि एक ट्रैवर्सल के साथ मिनट और अधिकतम दोनों को खोजने का एक तरीका है।

संपादित करें: पीटीपी सिर्फ हुड के तहत न्यूनतम और अधिकतम कॉल करता है


2
यह कष्टप्रद है क्योंकि संभवतः जिस तरह से ptp को लागू किया गया है वह अधिकतम और न्यूनतम का ट्रैक रखने के लिए है!
एंडी हेडन

1
या यह सिर्फ अधिकतम और न्यूनतम कॉल कर सकता है, निश्चित नहीं
jterrace

3
बाहर PTP @hayden बदल जाता है सिर्फ कॉल अधिकतम और न्यूनतम
jterrace

1
वह नकाबपोश-सरणी कोड था; मुख्य ndarray कोड सी में है। लेकिन यह पता चला है कि C कोड भी सरणी पर दो बार पुन : प्रसारित होता है : github.com/numpy/numpy/blob/…
केन अर्नोल्ड

20

आप नुम्बा का उपयोग कर सकते हैं , जो कि एलएलवीएम का उपयोग करके एक न्यूम्पी-जागरूक गतिशील पायथन कंपाइलर है। परिणामी कार्यान्वयन बहुत सरल और स्पष्ट है:

import numpy
import numba


@numba.jit
def minmax(x):
    maximum = x[0]
    minimum = x[0]
    for i in x[1:]:
        if i > maximum:
            maximum = i
        elif i < minimum:
            minimum = i
    return (minimum, maximum)


numpy.random.seed(1)
x = numpy.random.rand(1000000)
print(minmax(x) == (x.min(), x.max()))

यह एक नेम्पी के min() & max()कार्यान्वयन से भी तेज होना चाहिए । और सभी को कोड की एक सी / फोरट्रान लाइन लिखने के बिना।

अपने स्वयं के प्रदर्शन परीक्षण करें, क्योंकि यह हमेशा आपकी वास्तुकला, आपके डेटा, आपके पैकेज संस्करणों पर निर्भर करता है ...


2
> यह एक Numpy मिन () और अधिकतम () कार्यान्वयन की तुलना में तेज़ होना चाहिए मुझे नहीं लगता कि यह सही है। numpy देशी अजगर नहीं है - यह C. `` x = numpy.random.rand (10000000) t = समय () के लिए रेंज में (1000): minmax (x) प्रिंट ('numba'), समय () है - t) t = समय () i में रेंज (१०००) के लिए: x.min () x.max () प्रिंट (time numpy ’, समय () - t)` `में परिणाम: (ba numba’, 10.299750064545386 ) ('सुन्न', 9.898081064224243)
लेखक अपातिरा

1
@AuthmanApatira: हाँ, मानदंड हमेशा ऐसे ही होते हैं, इसीलिए मैंने कहा कि इसे " और तेज़ होना चाहिए " और " अपना प्रदर्शन परीक्षण करें, क्योंकि यह हमेशा आपकी वास्तुकला, आपके डेटा ... " पर निर्भर करता है । मेरे मामले में, मैंने 3 कंप्यूटरों के साथ प्रयास किया और एक ही परिणाम मिला (Numba Numpy की तुलना में तेज़ था), लेकिन आपके कंप्यूटर के परिणामों में अंतर हो सकता है ... क्या आपने numbaबेंचमार्क से पहले फ़ंक्शन को निष्पादित करने का प्रयास किया था, यह सुनिश्चित करने के लिए कि वह JIT- संकलित है ?। इसके अलावा, यदि आप ipythonसरलता के लिए उपयोग करते हैं, तो मैं आपको %timeit whatever_code()समय निष्पादन को मापने के लिए उपयोग करने का सुझाव दूंगा।
Peque

3
@AuthmanApatira: किसी भी मामले में मैंने इस जवाब के साथ जो दिखाने की कोशिश की, वह यह है कि कभी-कभी पायथन कोड (इस मामले में Numba के साथ JIT- संकलित) सबसे तेज C- संकलित पुस्तकालय के रूप में तेज़ हो सकता है (कम से कम हम उसी क्रम के बारे में बात कर रहे हैं परिमाण), जो इस बात को ध्यान में रखकर प्रभावशाली है कि हमने शुद्ध पायथन कोड के अलावा और कुछ नहीं लिखा, क्या आप सहमत नहीं हैं? ^ ^
पीक

मैं सहमत हूं =) इसके अलावा, जुपिटर के बारे में पिछली टिप्पणी में दिए गए सुझावों के लिए धन्यवाद और समय कोड के बाहर एक बार फ़ंक्शन को संकलित करना।
लेखक अपातिरा

1
बस इस पार भाग गया, यह नहीं कि यह व्यावहारिक मामलों में मायने रखता है, लेकिन elifआपके न्यूनतम आपके अधिकतम से बड़े होने की अनुमति देता है। जैसे, लंबाई 1 की एक सरणी के साथ, अधिकतम वह मान जो भी हो, जबकि न्यूनतम + अनंत है। प्रोडक्शन बीस्ट के पेट में गहराई तक फेंकने के लिए एक बंद के लिए एक बड़ी बात नहीं है, लेकिन अच्छा कोड नहीं है।
माइक विलियमसन

12

सामान्य तौर पर आप एक समय में दो तत्वों को संसाधित करके एक माइनमैक्स एल्गोरिथ्म के लिए तुलना की मात्रा को कम कर सकते हैं और केवल छोटे की तुलना न्यूनतम और अस्थायी अधिकतम की तुलना में कम कर सकते हैं। एक भोले दृष्टिकोण की तुलना में औसतन केवल 3/4 की तुलना की जाती है।

इसे c या फोरट्रान (या किसी अन्य निम्न-स्तरीय भाषा) में लागू किया जा सकता है और प्रदर्शन के मामले में लगभग अपराजेय होना चाहिए। मैं उपयोग कर रहा हूँ सिद्धांत को स्पष्ट करने और एक बहुत तेज, dtype- स्वतंत्र कार्यान्वयन प्राप्त करने के लिए:

import numba as nb
import numpy as np

@nb.njit
def minmax(array):
    # Ravel the array and return early if it's empty
    array = array.ravel()
    length = array.size
    if not length:
        return

    # We want to process two elements at once so we need
    # an even sized array, but we preprocess the first and
    # start with the second element, so we want it "odd"
    odd = length % 2
    if not odd:
        length -= 1

    # Initialize min and max with the first item
    minimum = maximum = array[0]

    i = 1
    while i < length:
        # Get the next two items and swap them if necessary
        x = array[i]
        y = array[i+1]
        if x > y:
            x, y = y, x
        # Compare the min with the smaller one and the max
        # with the bigger one
        minimum = min(x, minimum)
        maximum = max(y, maximum)
        i += 2

    # If we had an even sized array we need to compare the
    # one remaining item too.
    if not odd:
        x = array[length]
        minimum = min(x, minimum)
        maximum = max(x, maximum)

    return minimum, maximum

यह निश्चित रूप से उस भोले दृष्टिकोण की तुलना में अधिक तेज़ है जिसे पेक ने प्रस्तुत किया है:

arr = np.random.random(3000000)
assert minmax(arr) == minmax_peque(arr)  # warmup and making sure they are identical 
%timeit minmax(arr)            # 100 loops, best of 3: 2.1 ms per loop
%timeit minmax_peque(arr)      # 100 loops, best of 3: 2.75 ms per loop

अपेक्षा के अनुसार नए मिनिमैक्स कार्यान्वयन में केवल उस समय के 3/4 का समय लगता है, जिसमें भोले का कार्यान्वयन हुआ ( 2.1 / 2.75 = 0.7636363636363637)


1
मेरी मशीन पर, आपका समाधान पीक की तुलना में तेज़ नहीं है। नंबा 0.33।
जॉन Zwinck

@johnzwinck क्या आपने मेरे जवाब में बेंचमार्क चलाया है यह एक अलग है? यदि ऐसा है तो क्या आप इसे साझा कर सकते हैं? लेकिन यह संभव है: मैंने नए संस्करणों में कुछ प्रतिगमन पर भी ध्यान दिया।
MSeifert

मैंने आपका बेंचमार्क चलाया। आपके समाधान और @ पेक के समय बहुत अधिक थे (~ 2.8 एमएस)।
जॉन Zwinck

@ जॉन्जविन यह अजीब है, मैंने इसे फिर से परीक्षण किया और मेरे कंप्यूटर पर यह निश्चित रूप से तेज है। शायद कि सुंबा और एलएलवीएम के साथ कुछ करना है जो हार्डवेयर पर निर्भर करता है।
MSeifert

मैंने अब एक और मशीन (एक बीफ़ वर्कस्टेशन) पर कोशिश की और पीक के लिए आपके लिए २.६ एमएस बनाम २.६ मिला। तो, एक छोटी जीत।
जॉन Zwinck

11

निम्नलिखित दृष्टिकोणों को ध्यान में रखते हुए, संख्याओं पर कुछ विचार प्राप्त करने की उम्मीद कर सकते हैं:

import numpy as np


def extrema_np(arr):
    return np.max(arr), np.min(arr)
import numba as nb


@nb.jit(nopython=True)
def extrema_loop_nb(arr):
    n = arr.size
    max_val = min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    return max_val, min_val
import numba as nb


@nb.jit(nopython=True)
def extrema_while_nb(arr):
    n = arr.size
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    return max_val, min_val
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True


import numpy as np


cdef void _extrema_loop_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i
    cdef long item, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    result[0] = max_val
    result[1] = min_val


def extrema_loop_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_loop_cy(arr, arr.size, result)
    return result[0], result[1]
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True


import numpy as np


cdef void _extrema_while_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i, odd
    cdef long x, y, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    result[0] = max_val
    result[1] = min_val


def extrema_while_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_while_cy(arr, arr.size, result)
    return result[0], result[1]

( extrema_loop_*()दृष्टिकोण यहां प्रस्तावित किए गए के समान हैं , जबकि extrema_while_*()दृष्टिकोण यहां से कोड के आधार पर हैं )

निम्नलिखित समय:

बी.एम.

इंगित करें कि extrema_while_*()सबसे तेज़ हैं, extrema_while_nb()सबसे तेज़ होने के साथ । किसी भी मामले में, भी extrema_loop_nb()और extrema_loop_cy()समाधान NumPy- केवल दृष्टिकोण का उपयोग करते हैंnp.max() और np.min()अलग-अलग) से ।

अंत में, ध्यान दें कि इनमें से कोई भी उतना लचीला नहीं है जितना np.min()/np.max() (एन-मंद समर्थन के मामले में, axisपैरामीटर, आदि)।

(पूरा कोड यहां उपलब्ध है )


2
अगर आप @njit (fastmath = True) का उपयोग करते हैं तो आप एक अतिरिक्त 10% की गति प्राप्त कर सकते हैंextrema_while_nb
19:02

10

किसी ने numpy.percentile का उल्लेख नहीं किया , इसलिए मुझे लगा कि मैं करूंगा। अगर तुम मांगते हो[0, 100] प्रतिशतता , तो यह आपको दो तत्वों की एक सरणी देगा, न्यूनतम (0 वाँ प्रतिशत) और अधिकतम (100 वाँ प्रतिशत)।

हालांकि, यह ओपी के उद्देश्य को पूरा नहीं करता है: यह न्यूनतम और अधिकतम से अलग नहीं है। यह शायद कुछ मशीनरी के कारण है जो गैर-चरम प्रतिशतता (एक कठिन समस्या, जो अधिक समय लेना चाहिए ) के लिए अनुमति देगा ।

In [1]: import numpy

In [2]: a = numpy.random.normal(0, 1, 1000000)

In [3]: %%timeit
   ...: lo, hi = numpy.amin(a), numpy.amax(a)
   ...: 
100 loops, best of 3: 4.08 ms per loop

In [4]: %%timeit
   ...: lo, hi = numpy.percentile(a, [0, 100])
   ...: 
100 loops, best of 3: 17.2 ms per loop

In [5]: numpy.__version__
Out[5]: '1.14.4'

Numpy का एक भविष्य संस्करण सामान्य प्रतिशतक गणना को छोड़ने के लिए एक विशेष मामले में डाल सकता है यदि केवल [0, 100]अनुरोध किया जाता है। इंटरफ़ेस में कुछ भी जोड़े बिना, एक कॉल में मिनट और अधिकतम के लिए नेम्पी से पूछने का एक तरीका है (इसके विपरीत जो स्वीकार किए गए उत्तर में कहा गया था), लेकिन लाइब्रेरी के मानक कार्यान्वयन इस मामले का फायदा नहीं उठाते हैं सार्थक।


9

यह एक पुराना धागा है, लेकिन वैसे भी, अगर किसी को भी इस पर फिर से दिखता है ...

जब एक साथ मिनट और अधिकतम की तलाश की जाती है, तो तुलना की संख्या को कम करना संभव है। यदि यह तैरता है तो आप तुलना कर रहे हैं (जो मुझे लगता है कि यह है) यह आपको कुछ समय बचा सकता है, हालांकि कम्प्यूटेशनल जटिलता नहीं।

इसके बजाय (पायथन कोड):

_max = ar[0]
_min=  ar[0]
for ii in xrange(len(ar)):
    if _max > ar[ii]: _max = ar[ii]
    if _min < ar[ii]: _min = ar[ii]

आप पहले सरणी में दो आसन्न मूल्यों की तुलना कर सकते हैं, और उसके बाद केवल वर्तमान न्यूनतम के खिलाफ छोटे की तुलना कर सकते हैं, और वर्तमान के मुकाबले बड़े वाले की तुलना में

## for an even-sized array
_max = ar[0]
_min = ar[0]
for ii in xrange(0, len(ar), 2)):  ## iterate over every other value in the array
    f1 = ar[ii]
    f2 = ar[ii+1]
    if (f1 < f2):
        if f1 < _min: _min = f1
        if f2 > _max: _max = f2
    else:
        if f2 < _min: _min = f2
        if f1 > _max: _max = f1

यहां कोड को पायथन में लिखा गया है, स्पष्ट रूप से गति के लिए आप सी या फोरट्रान या साइथॉन का उपयोग करेंगे, लेकिन इस तरह से आप प्रति पुनरावृत्ति के साथ 3 तुलना करते हैं, len (ar) / 2 पुनरावृत्तियों के साथ, 3/2 * len (ar) तुलनाएं देते हैं। जैसा कि विरोध किया गया है, तुलना "स्पष्ट तरीका" करने के लिए आप प्रति पुनरावृत्ति दो तुलना करते हैं, 2 * लेन (जैसे) तुलना करते हैं। तुलना समय का 25% आपको बचाता है।

हो सकता है कि कोई एक दिन आपको यह उपयोगी लगे।


6
क्या आपने इसे बेंचमार्क किया है? आधुनिक x86 हार्डवेयर पर आपके पास पहले संस्करण में उपयोग किए गए न्यूनतम और अधिकतम के लिए मशीन निर्देश हैं, ये शाखाओं की आवश्यकता से बचते हैं जबकि आपका कोड एक नियंत्रण निर्भरता में डालता है जो शायद हार्डवेयर के लिए भी मैप नहीं करता है।
jtaylor

मैं वास्तव में नहीं है। मौका मिलेगा तो करूंगा। मुझे लगता है कि यह बहुत स्पष्ट है कि शुद्ध अजगर कोड किसी भी समझदार संकलित कार्यान्वयन के लिए हाथ-नीचे खो देगा, लेकिन मुझे आश्चर्य है कि अगर एक स्पीडअप साइथन में देखा जा सकता है ...
बेनेट

13
Numpy में एक minmax कार्यान्वयन नहीं है, हुड, द्वारा प्रयोग किया जाता के तहत np.bincount, देखने के लिए यहाँ । यह आपके द्वारा बताए गए ट्रिक का उपयोग नहीं करता है, क्योंकि यह भोले दृष्टिकोण की तुलना में 2x धीमा तक निकला है। दोनों तरीकों के कुछ व्यापक बेंचमार्क के लिए पीआर से एक लिंक है ।
Jaime

5

पहली नज़र में, चाल करने के लिए प्रतीत होता है:numpy.histogram

count, (amin, amax) = numpy.histogram(a, bins=1)

... लेकिन अगर आप उस फ़ंक्शन के स्रोत को देखते हैं , तो यह बस कॉल करता है a.min()और a.max()स्वतंत्र रूप से, और इसलिए इस प्रश्न में संबोधित प्रदर्शन चिंताओं से बचने में विफल रहता है। :-(

इसी तरह, scipy.ndimage.measurements.extremaएक संभावना की तरह दिखता है, लेकिन यह भी, बस कॉल a.min()और a.max()स्वतंत्र रूप से।


3
np.histogramहमेशा इसके लिए काम नहीं करता क्योंकि लौटे हुए (amin, amax)मान बिन के न्यूनतम और अधिकतम मूल्यों के लिए हैं। यदि मेरे पास है, उदाहरण के लिए a = np.zeros(10), np.histogram(a, bins=1)रिटर्न (array([10]), array([-0.5, 0.5]))। उपयोगकर्ता (amin, amax)उस मामले में = (0, 0) की तलाश कर रहा है।
21

3

यह मेरे लिए वैसे भी प्रयास के लायक था, इसलिए मैं यहां सबसे कठिन और कम से कम सुरुचिपूर्ण समाधान का प्रस्ताव दूंगा जो कोई भी दिलचस्पी ले सकता है। मेरा समाधान C ++ में एक पास एल्गोरिथ्म में एक बहु-थ्रेडेड मिन-मैक्स को लागू करना है, और इसका उपयोग पायथन एक्सटेंशन मॉड्यूल बनाने के लिए किया जाता है। इस प्रयास में अजगर और न्यूमपी सी / सी ++ एपीआई का उपयोग करने के तरीके को सीखने के लिए कुछ ओवरहेड की आवश्यकता होती है, और यहां मैं कोड दिखाऊंगा और जो कोई भी इस पथ से नीचे जाने की इच्छा के लिए कुछ छोटे स्पष्टीकरण और संदर्भ देगा।

बहु थ्रेडेड मिन / मैक्स

यहां कुछ भी दिलचस्प नहीं है। सरणी आकार के टुकड़ों में टूट गई है length / workers। न्यूनतम / अधिकतम की गणना प्रत्येक chunk के लिए की जाती है future, जो तब वैश्विक मिनट / अधिकतम के लिए स्कैन की जाती है।

    // mt_np.cc
    //
    // multi-threaded min/max algorithm

    #include <algorithm>
    #include <future>
    #include <vector>

    namespace mt_np {

    /*
     * Get {min,max} in interval [begin,end)
     */
    template <typename T> std::pair<T, T> min_max(T *begin, T *end) {
      T min{*begin};
      T max{*begin};
      while (++begin < end) {
        if (*begin < min) {
          min = *begin;
          continue;
        } else if (*begin > max) {
          max = *begin;
        }
      }
      return {min, max};
    }

    /*
     * get {min,max} in interval [begin,end) using #workers for concurrency
     */
    template <typename T>
    std::pair<T, T> min_max_mt(T *begin, T *end, int workers) {
      const long int chunk_size = std::max((end - begin) / workers, 1l);
      std::vector<std::future<std::pair<T, T>>> min_maxes;
      // fire up the workers
      while (begin < end) {
        T *next = std::min(end, begin + chunk_size);
        min_maxes.push_back(std::async(min_max<T>, begin, next));
        begin = next;
      }
      // retrieve the results
      auto min_max_it = min_maxes.begin();
      auto v{min_max_it->get()};
      T min{v.first};
      T max{v.second};
      while (++min_max_it != min_maxes.end()) {
        v = min_max_it->get();
        min = std::min(min, v.first);
        max = std::max(max, v.second);
      }
      return {min, max};
    }
    }; // namespace mt_np

पायथन एक्सटेंशन मॉड्यूल

यहाँ है जहाँ चीजें बदसूरत होने लगती हैं ... पाइथन में C ++ कोड का उपयोग करने का एक तरीका एक विस्तार मॉड्यूल को लागू करना है। इस मॉड्यूल को distutils.coreमानक मॉड्यूल का उपयोग करके बनाया और स्थापित किया जा सकता है । पाइथन डॉक्यूमेंटेशन में इस बात की पूरी जानकारी दी गई है: https://docs.python.org/3/extending/extend.htmlनोट: https://docs.python.org/3/extending/index.html#extending-index पर उद्धृत करने के लिए निश्चित रूप से समान परिणाम प्राप्त करने के अन्य तरीके हैं :

यह मार्गदर्शिका केवल CPython के इस संस्करण के हिस्से के रूप में प्रदान किए गए एक्सटेंशन बनाने के लिए मूल टूल को कवर करती है। तीसरे पक्ष के उपकरण जैसे साइथॉन, सेफी, SWIG और नुम्बा दोनों पायथन के लिए C और C ++ एक्सटेंशन बनाने के लिए सरल और अधिक परिष्कृत दृष्टिकोण दोनों प्रदान करते हैं।

अनिवार्य रूप से, यह मार्ग संभवतः व्यावहारिक से अधिक अकादमिक है। उस के साथ कहा जा रहा है, मैंने आगे क्या किया, ट्यूटोरियल के बहुत करीब चिपके हुए, एक मॉड्यूल फ़ाइल बनाएं। यह अनिवार्य रूप से डिस्टुटिल के लिए बॉयलरप्लेट है यह जानने के लिए कि आपके कोड के साथ क्या करना है और इसमें से पायथन मॉड्यूल बनाएं। इनमें से किसी को करने से पहले शायद एक पायथन वर्चुअल वातावरण बनाना बुद्धिमानी है, ताकि आप अपने सिस्टम पैकेजों को प्रदूषित न करें (देखें https://docs.python.org/3/library/venv.html#module-venv )।

यहाँ मॉड्यूल फ़ाइल है:

// mt_np_forpy.cc
//
// C++ module implementation for multi-threaded min/max for np

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION

#include <python3.6/numpy/arrayobject.h>

#include "mt_np.h"

#include <cstdint>
#include <iostream>

using namespace std;

/*
 * check:
 *  shape
 *  stride
 *  data_type
 *  byteorder
 *  alignment
 */
static bool check_array(PyArrayObject *arr) {
  if (PyArray_NDIM(arr) != 1) {
    PyErr_SetString(PyExc_RuntimeError, "Wrong shape, require (1,n)");
    return false;
  }
  if (PyArray_STRIDES(arr)[0] != 8) {
    PyErr_SetString(PyExc_RuntimeError, "Expected stride of 8");
    return false;
  }
  PyArray_Descr *descr = PyArray_DESCR(arr);
  if (descr->type != NPY_LONGLTR && descr->type != NPY_DOUBLELTR) {
    PyErr_SetString(PyExc_RuntimeError, "Wrong type, require l or d");
    return false;
  }
  if (descr->byteorder != '=') {
    PyErr_SetString(PyExc_RuntimeError, "Expected native byteorder");
    return false;
  }
  if (descr->alignment != 8) {
    cerr << "alignment: " << descr->alignment << endl;
    PyErr_SetString(PyExc_RuntimeError, "Require proper alignement");
    return false;
  }
  return true;
}

template <typename T>
static PyObject *mt_np_minmax_dispatch(PyArrayObject *arr) {
  npy_intp size = PyArray_SHAPE(arr)[0];
  T *begin = (T *)PyArray_DATA(arr);
  auto minmax =
      mt_np::min_max_mt(begin, begin + size, thread::hardware_concurrency());
  return Py_BuildValue("(L,L)", minmax.first, minmax.second);
}

static PyObject *mt_np_minmax(PyObject *self, PyObject *args) {
  PyArrayObject *arr;
  if (!PyArg_ParseTuple(args, "O", &arr))
    return NULL;
  if (!check_array(arr))
    return NULL;
  switch (PyArray_DESCR(arr)->type) {
  case NPY_LONGLTR: {
    return mt_np_minmax_dispatch<int64_t>(arr);
  } break;
  case NPY_DOUBLELTR: {
    return mt_np_minmax_dispatch<double>(arr);
  } break;
  default: {
    PyErr_SetString(PyExc_RuntimeError, "Unknown error");
    return NULL;
  }
  }
}

static PyObject *get_concurrency(PyObject *self, PyObject *args) {
  return Py_BuildValue("I", thread::hardware_concurrency());
}

static PyMethodDef mt_np_Methods[] = {
    {"mt_np_minmax", mt_np_minmax, METH_VARARGS, "multi-threaded np min/max"},
    {"get_concurrency", get_concurrency, METH_VARARGS,
     "retrieve thread::hardware_concurrency()"},
    {NULL, NULL, 0, NULL} /* sentinel */
};

static struct PyModuleDef mt_np_module = {PyModuleDef_HEAD_INIT, "mt_np", NULL,
                                          -1, mt_np_Methods};

PyMODINIT_FUNC PyInit_mt_np() { return PyModule_Create(&mt_np_module); }

इस फ़ाइल में अधिक जानकारी के लिए , https://docs.python.org/3/c-api/arg.html#c.PyArg_Parseupuple , और NumPy के लिए पायथन के साथ-साथ NumPy API का भी महत्वपूर्ण उपयोग है। : https://docs.scipy.org/doc/numpy/reference/c-api.array.html

मॉड्यूल को स्थापित करना

मॉड्यूल स्थापित करने के लिए डिस्टुटिल्स का उपयोग करने के लिए अगली चीज है। इसके लिए सेटअप फ़ाइल की आवश्यकता होती है:

# setup.py

from distutils.core import setup,Extension

module = Extension('mt_np', sources = ['mt_np_module.cc'])

setup (name = 'mt_np', 
       version = '1.0', 
       description = 'multi-threaded min/max for np arrays',
       ext_modules = [module])

अंत में मॉड्यूल को स्थापित करने के लिए, python3 setup.py installअपने आभासी वातावरण से निष्पादित करें।

मॉड्यूल का परीक्षण

अंत में, हम यह देखने के लिए परीक्षण कर सकते हैं कि क्या C ++ कार्यान्वयन वास्तव में NumPy के भोलेपन का उपयोग करता है। ऐसा करने के लिए, यहां एक सरल परीक्षण स्क्रिप्ट दी गई है:

# timing.py
# compare numpy min/max vs multi-threaded min/max

import numpy as np
import mt_np
import timeit

def normal_min_max(X):
  return (np.min(X),np.max(X))

print(mt_np.get_concurrency())

for ssize in np.logspace(3,8,6):
  size = int(ssize)
  print('********************')
  print('sample size:', size)
  print('********************')
  samples = np.random.normal(0,50,(2,size))
  for sample in samples:
    print('np:', timeit.timeit('normal_min_max(sample)',
                 globals=globals(),number=10))
    print('mt:', timeit.timeit('mt_np.mt_np_minmax(sample)',
                 globals=globals(),number=10))

यहाँ परिणाम हैं जो मुझे यह सब करने से मिला:

8  
********************  
sample size: 1000  
********************  
np: 0.00012079699808964506  
mt: 0.002468645994667895  
np: 0.00011947099847020581  
mt: 0.0020772050047526136  
********************  
sample size: 10000  
********************  
np: 0.00024697799381101504  
mt: 0.002037393998762127  
np: 0.0002713389985729009  
mt: 0.0020942929986631498  
********************  
sample size: 100000  
********************  
np: 0.0007130410012905486  
mt: 0.0019842900001094677  
np: 0.0007540129954577424  
mt: 0.0029724110063398257  
********************  
sample size: 1000000  
********************  
np: 0.0094779249993735  
mt: 0.007134920000680722  
np: 0.009129883001151029  
mt: 0.012836456997320056  
********************  
sample size: 10000000  
********************  
np: 0.09471094200125663  
mt: 0.0453535050037317  
np: 0.09436299200024223  
mt: 0.04188535599678289  
********************  
sample size: 100000000  
********************  
np: 0.9537652180006262  
mt: 0.3957935369980987  
np: 0.9624398809974082  
mt: 0.4019058070043684  

ये परिणाम थ्रेड में पहले बताए गए परिणामों की तुलना में बहुत कम उत्साहजनक हैं, जो 3.5x स्पीडअप के आसपास कहीं संकेत करते हैं, और मल्टी-थ्रेडिंग को शामिल नहीं करते हैं। मेरे द्वारा प्राप्त किए गए परिणाम कुछ हद तक उचित हैं, मैं उम्मीद करूंगा कि थ्रेडिंग का ओवरहेड और उस समय तक हावी रहेगा जब तक कि ऐरे बहुत बड़े नहीं हो जाते, जिस बिंदु पर प्रदर्शन वृद्धि std::thread::hardware_concurrencyएक्स वृद्धि के दृष्टिकोण के लिए शुरू होगी ।

निष्कर्ष

वहाँ कुछ NumPy कोड के लिए आवेदन विशिष्ट अनुकूलन के लिए निश्चित रूप से कमरा है, ऐसा लगता है, विशेष रूप से बहु सूत्रण के संबंध में होगा। प्रयास के लायक है या नहीं यह मेरे लिए स्पष्ट नहीं है, लेकिन यह निश्चित रूप से एक अच्छा व्यायाम (या कुछ) जैसा लगता है। मुझे लगता है कि शायद साइथॉन जैसे कुछ "थर्ड पार्टी टूल्स" सीखना समय का बेहतर उपयोग हो सकता है, लेकिन कौन जानता है।


1
मैं आपके कोड का अध्ययन करना शुरू करता हूं, कुछ C ++ जानता हूं, लेकिन फिर भी std :: future और std :: async का उपयोग नहीं करता है। आपके 'min_max_mt' टेम्प्लेट फ़ंक्शन में, यह कैसे पता चलता है कि प्रत्येक कार्यकर्ता फायरिंग और परिणाम प्राप्त करने के बीच समाप्त हो गया है? (केवल समझने के लिए कहना, यह नहीं कहना कि इसके साथ कुछ भी गलत है)
ChrCury78

रेखा v = min_max_it->get();getविधि ब्लॉक जब तक परिणाम के लिए तैयार है और यह रिटर्न। चूंकि लूप प्रत्येक भविष्य के माध्यम से जाता है, यह तब तक खत्म नहीं होगा जब तक कि वे सभी पूरे नहीं हो जाते। future.get ()
नाथन चैपल

0

सबसे छोटा तरीका जो मैं लेकर आया हूं वह यह है:

mn, mx = np.sort(ar)[[0, -1]]

लेकिन चूंकि यह सरणी को पसंद करता है, इसलिए यह सबसे कुशल नहीं है।

एक और संक्षिप्त तरीका होगा:

mn, mx = np.percentile(ar, [0, 100])

यह अधिक कुशल होना चाहिए, लेकिन परिणाम की गणना की जाती है, और एक फ्लोट लौटाया जाता है।


शर्म की बात है, वे दो इस पृष्ठ में दूसरों की तुलना में सबसे धीमे समाधान हैं: m = np.min (ए); M = np.max (a) -> 0.54002 ||| m, M = f90_minmax1 (a) -> 0.72134 ||| m, M = numba_minmax (a) -> 0.77323 ||| m, M = np.sort (a) [[0, -1]] -> 12.01456 ||| एम, एम = np.percentile (ए, [0, 100]) -> 11.09418 ||| 100k तत्वों की एक सरणी के लिए 10000 पुनरावृत्ति के लिए सेकंड में
येशिस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.