python - Numpy.median.reduceat के लिए तेज़ विकल्प




performance numpy-ufunc (3)

इस उत्तर से संबंधित, क्या एक ऐसे सरणी पर मध्यस्थों की गणना करने का एक तेज़ तरीका है, जिसमें असमान तत्वों की संख्या वाले समूह हैं?

उदाहरण के लिए:

data =  [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67, ... ]
index = [0,    0,    1,    1,    1,    1,    2,    3,    3,    ... ]

और फिर मैं प्रति समूह और संख्या के बीच के अंतर की गणना करना चाहता हूं (जैसे कि समूह 0 का माध्य 1.025 इसलिए पहला परिणाम 1.00 - 1.025 = -0.025 )। तो ऊपर दिए गए सरणी के लिए, परिणाम इस प्रकार दिखाई देंगे:

result = [-0.025, 0.025, 0.05, -0.05, -0.19, 0.29, 0.00, 0.10, -0.10, ...]

चूंकि np.median.reduceat मौजूद नहीं है (अभी तक), क्या इसे प्राप्त करने का एक और तेज़ तरीका है? मेरी सरणी में लाखों पंक्तियाँ होंगी ताकि गति महत्वपूर्ण हो!

सूचक को सन्निहित और आदेशित किया जा सकता है (यदि वे नहीं हैं तो उन्हें बदलना आसान है)।

प्रदर्शन तुलना के लिए उदाहरण डेटा:

import numpy as np

np.random.seed(0)
rows = 10000
cols = 500
ngroup = 100

# Create random data and groups (unique per column)
data = np.random.rand(rows,cols)
groups = np.random.randint(ngroup, size=(rows,cols)) + 10*np.tile(np.arange(cols),(rows,1))

# Flatten
data = data.ravel()
groups = groups.ravel()

# Sort by group
idx_sort = groups.argsort()
data = data[idx_sort]
groups = groups[idx_sort]

एक दृष्टिकोण यह होगा कि Pandas का उपयोग विशुद्ध रूप से समूह का उपयोग करने के लिए किया जाए। मैंने टाइमिंग की बेहतर समझ देने के लिए इनपुट साइज़ को थोड़ा बढ़ाया है (क्योंकि DF बनाने में ओवरहेड है)।

import numpy as np
import pandas as pd

data =  [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0,    0,    1,    1,    1,    1,    2,    3,    3]

data = data * 500
index = np.sort(np.random.randint(0, 30, 4500))

def df_approach(data, index):
    df = pd.DataFrame({'data': data, 'label': index})
    df['median'] = df.groupby('label')['data'].transform('median')
    df['result'] = df['data'] - df['median']

निम्नलिखित समय देता है:

%timeit df_approach(data, index)
5.38 ms ± 50.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

समान नमूने के आकार के लिए, मुझे आर्यसेर का तानाशाही दृष्टिकोण प्राप्त करना चाहिए:

%timeit dict_approach(data, index)
8.12 ms ± 3.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

हालांकि, अगर हम 10 के अन्य कारक द्वारा इनपुट बढ़ाते हैं, तो समय बन जाता है:

%timeit df_approach(data, index)
7.72 ms ± 85 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit dict_approach(data, index)
30.2 ms ± 10.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

हालांकि, कुछ पुनरावृत्ति की कीमत पर, Divakar द्वारा शुद्ध खस का उपयोग करने का उत्तर आता है:

%timeit bin_median_subtract(data, index)
573 µs ± 7.48 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

नए डेटासेट के प्रकाश में (जो वास्तव में शुरुआत में सेट होना चाहिए):

%timeit df_approach(data, groups)
472 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit bin_median_subtract(data, groups) #https://.com/a/58788623/4799172
3.02 s ± 31.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dict_approach(data, groups) #https://.com/a/58788199/4799172
<I gave up after 1 minute>

# jitted (using @numba.njit('f8[:](f8[:], i4[:]') on Windows) from  https://.com/a/58788635/4799172
%timeit diffmedian_jit(data, groups)
132 ms ± 3.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

कभी-कभी आपको गैर-मुहावरेदार संख्याओं को लिखने की आवश्यकता होती है, यदि आप वास्तव में अपनी गणना को गति देना चाहते हैं, जो आप देशी अंको के साथ नहीं कर सकते।

numba अपने अजगर कोड को निम्न-स्तर सी पर संकलित करता है क्योंकि बहुत सारे खाँसी आमतौर पर सी के रूप में तेजी से होती है, यह ज्यादातर तब उपयोगी होता है जब आपकी समस्या स्वयं खतना के साथ देशी वैश्वीकरण को उधार नहीं देती है। यह एक उदाहरण है (जहां मैंने माना कि सूचकांक सन्निहित और क्रमबद्ध हैं, जो उदाहरण डेटा में भी परिलक्षित होता है):

import numpy as np
import numba

# use the inflated example of roganjosh https://.com/a/58788534
data =  [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0,    0,    1,    1,    1,    1,    2,    3,    3] 

data = np.array(data * 500) # using arrays is important for numba!
index = np.sort(np.random.randint(0, 30, 4500))               

# jit-decorate; original is available as .py_func attribute
@numba.njit('f8[:](f8[:], i8[:])') # explicit signature implies ahead-of-time compile
def diffmedian_jit(data, index): 
    res = np.empty_like(data) 
    i_start = 0 
    for i in range(1, index.size): 
        if index[i] == index[i_start]: 
            continue 

        # here: i is the first _next_ index 
        inds = slice(i_start, i)  # i_start:i slice 
        res[inds] = data[inds] - np.median(data[inds]) 

        i_start = i 

    # also fix last label 
    res[i_start:] = data[i_start:] - np.median(data[i_start:])

    return res

और यहाँ IPython के %timeit मैजिक का उपयोग करते हुए कुछ समय दिया गया है:

>>> %timeit diffmedian_jit.py_func(data, index)  # non-jitted function
... %timeit diffmedian_jit(data, index)  # jitted function
...
4.27 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
65.2 µs ± 1.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

इन नंबरों के सवाल में अद्यतन उदाहरण डेटा का उपयोग करना (यानी पायथन फ़ंक्शन का रनटाइम बनाम जेआईटी-त्वरित फंक्शनलियो का रनटाइम) हैं

>>> %timeit diffmedian_jit.py_func(data, groups) 
... %timeit diffmedian_jit(data, groups)
2.45 s ± 34.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
93.6 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

यह छोटे मामले में 65x स्पीडअप और बड़े कोड में 26x स्पीडअप (धीमी गति से कोड की तुलना में, बेशक) त्वरित मात्रा का उपयोग करता है। एक और उल्टा यह है कि (सामान्य संख्या के साथ विशिष्ट वैश्वीकरण के विपरीत) हमें इस गति को प्राप्त करने के लिए अतिरिक्त मेमोरी की आवश्यकता नहीं थी, यह सभी अनुकूलित और संकलित निम्न-स्तरीय कोड के बारे में है जो कि समाप्त हो रहा है।

उपरोक्त फ़ंक्शन मानता है कि डिफ़ॉल्ट रूप से सुन्न int सरणियां64 हैं, जो वास्तव में विंडोज पर मामला नहीं है। तो एक विकल्प यह है कि कॉल को numba.njit करने के लिए हस्ताक्षर को हटा दें। numba.njit , उचित उचित समय संकलन का ट्रिगर करता है। लेकिन इसका मतलब यह है कि फ़ंक्शन को पहले निष्पादन के दौरान संकलित किया जाएगा, जो समय परिणामों के साथ मध्यस्थता कर सकता है (हम या तो मैन्युअल रूप से फ़ंक्शन को एक बार निष्पादित कर सकते हैं, प्रतिनिधि डेटा प्रकारों का उपयोग कर सकते हैं, या बस स्वीकार कर सकते हैं कि पहली बार निष्पादन बहुत धीमा होगा, जिसे करना चाहिए अनदेखा किया जाए)। यह वही है जो मैंने एक हस्ताक्षर को निर्दिष्ट करके रोकने की कोशिश की थी, जो समय-समय पर संकलन से आगे बढ़ता है।

वैसे भी, ठीक से जेआईटी मामले में डेकोरेटर की हमें जरूरत है

@numba.njit
def diffmedian_jit(...):

ध्यान दें कि ऊपर दिए गए समय जो मैंने जीट-संकलित फ़ंक्शन के लिए दिखाए थे, केवल एक बार फ़ंक्शन संकलित होने के बाद लागू होते हैं। यह या तो परिभाषा पर होता है (उत्सुक संकलन के साथ, जब एक स्पष्ट हस्ताक्षर को numba.njit को पास किया numba.njit ), या पहले फ़ंक्शन कॉल के दौरान (आलसी संकलन के साथ, जब कोई हस्ताक्षर numba.njit को पास नहीं किया numba.njit )। यदि फ़ंक्शन केवल एक बार निष्पादित होने जा रहा है, तो इस विधि की गति के लिए संकलन समय पर भी विचार किया जाना चाहिए। यह आम तौर पर केवल संकलित कार्यों के लायक है यदि संकलन + निष्पादन का कुल समय अप्रयुक्त रनटाइम से कम है (जो उपरोक्त मामले में वास्तव में सच है, जहां देशी पायथन फ़ंक्शन बहुत धीमा है)। यह ज्यादातर तब होता है जब आप अपने संकलित फ़ंक्शन को बहुत बार कॉल कर रहे होते हैं।

जैसा कि max9111 ने एक टिप्पणी में उल्लेख किया है, numba की एक महत्वपूर्ण विशेषता numba के लिए cache कीवर्ड है। cache=True पासिंग cache=True टू numba.jit संकलित फ़ंक्शन को डिस्क में संग्रहीत करेगा, ताकि दिए गए पायथन मॉड्यूल के अगले निष्पादन के दौरान फ़ंक्शन को numba.jit : संकलित करने के बजाय वहां से लोड किया जाएगा, जो फिर से आपको लंबे समय में रनटाइम को छोड़ सकता है।


हो सकता है कि आपने पहले से ही ऐसा किया हो, लेकिन यदि नहीं, तो देखें कि क्या यह काफी तेज है:

median_dict = {i: np.median(data[index == i]) for i in np.unique(index)}
def myFunc(my_dict, a): 
    return my_dict[a]
vect_func = np.vectorize(myFunc)
median_diff = data - vect_func(median_dict, index)
median_diff

आउटपुट:

array([-0.025,  0.025,  0.05 , -0.05 , -0.19 ,  0.29 ,  0.   ,  0.1  ,
   -0.1  ])






numpy-ufunc