python - Numpy.median.reduceat জন্য দ্রুত বিকল্প




performance numpy-ufunc (3)

একটি উপায় groupby এখানে groupby ব্যবহারের জন্য groupby ব্যবহার করা হবে। সময়গুলির আরও ভাল ধারণা দেওয়ার জন্য আমি ইনপুট আকারকে কিছুটা স্ফীত করেছি (যেহেতু ডিএফ তৈরির ক্ষেত্রে ওভারহেড রয়েছে)।

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 দেয়:

%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)

যাইহোক, কিছুটা পুনর্ব্যবহারের ব্যয়ে, খাঁটি আঙ্কুল ব্যবহার করে দিবাকারের উত্তরটি এখানে আসে:

%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://stackoverflow.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://stackoverflow.com/a/58788199/4799172
<I gave up after 1 minute>

# jitted (using @numba.njit('f8[:](f8[:], i4[:]') on Windows) from  https://stackoverflow.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)

এই উত্তরের সাথে সম্পর্কিত, অসম সংখ্যার উপাদানগুলির সাথে গ্রুপ রয়েছে এমন একটি অ্যারের উপরে মিডিয়ানদের গণনা করার কোনও দ্রুত উপায় আছে?

উদাহরণ:

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,    ... ]

এবং তারপরে আমি সংখ্যা অনুসারে এবং প্রতিটি গ্রুপের 1.00 - 1.025 = -0.025 মধ্যকার পার্থক্যটি গণনা করতে চাই (উদাহরণস্বরূপ 0 গ্রুপের 1.00 - 1.025 = -0.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]

কখনও কখনও আপনার অ-পরিচয়যুক্ত নাম্বার কোডটি লিখতে হবে যদি আপনি সত্যিই আপনার গণনাটি গতি বাড়িয়ে তুলতে চান যা আপনি নেটিভ নিম্পের সাথে না করতে পারেন।

numba আপনার অজগর কোডটি নিম্ন-স্তরের সি 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

%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 স্পিডআপের সমান (অবশ্যই ধীর লুপী কোডের তুলনায়)। আরেকটি উত্সাহটি হ'ল (নেটিভ নিম্পির সাথে আদর্শ ভেক্টরাইজেশনের বিপরীতে) আমাদের এই গতি অর্জনের জন্য অতিরিক্ত মেমরির প্রয়োজন ছিল না, এটি সর্বোত্তম এবং সংকলিত নিম্ন-স্তরের কোড সম্পর্কে যা চালানো শেষ হয়।

উপরের ফাংশনটি ধরে নিয়েছে যে নিম্পি ইন অ্যারেগুলি ডিফল্টরূপে int64 হয় যা উইন্ডোজে আসলে হয় না। সুতরাং বিকল্পটি হ'ল কল থেকে স্বাক্ষরটি numba.njit সরিয়ে ফেলা, যথাযথ-ইন-টাইম সংকলন ট্রিগার করে। তবে এর অর্থ এই যে ফাংশনটি প্রথম নির্বাহের সময় সংকলিত হবে, যা সময়সীমার ফলাফলের সাথে হস্তক্ষেপ করতে পারে (আমরা হয় প্রতিনিধি ডেটা ধরণের সাহায্যে ম্যানুয়ালি একবার ফাংশনটি সম্পাদন করতে পারি, বা কেবল গ্রহণ করতে পারি যে প্রথম সময় নির্বাহের কাজটি অনেক ধীর হবে, যা হওয়া উচিত) অবহেলা করা)। এটি হ'ল আমি হ'ল একটি স্বাক্ষর নির্দিষ্ট করে বাধা দেওয়ার চেষ্টা করেছি, যা সময়ের-সংকলনকে ট্রিগার করে।

যাইহোক, সঠিকভাবে জেআইটি ক্ষেত্রে ডেকরেটারটি আমাদের দরকার just

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

নোট করুন যে জিট-সংকলিত ফাংশনটির জন্য আমি উপরে বর্ণিত সময়গুলি কেবলমাত্র ফাংশনটি সংকলিত হয়ে গেলে প্রয়োগ হয়। এটি হয় সংজ্ঞায় হয় ( numba.njit সংকলন সহ, যখন একটি স্পষ্ট স্বাক্ষর numba.njit পাস করা numba.njit ), বা প্রথম ফাংশন কল চলাকালীন (অলস সংকলন সহ, যখন স্বাক্ষরটি numba.njit পাস হয় numba.njit )। যদি ফাংশনটি কেবল একবার সম্পাদন করতে চলেছে তবে এই পদ্ধতির গতির জন্য সংকলনের সময়টিও বিবেচনা করা উচিত। সংকলন + নির্বাহের মোট সময় অসম্পূর্ণ রানটাইমের চেয়ে কম হলে (সাধারণত উপরের ক্ষেত্রে এটি সত্য, যেখানে নেটিভ পাইথন ফাংশনটি খুব ধীর) এটি সাধারণত ফাংশন সংকলন করার উপযুক্ত। এটি বেশিরভাগ ক্ষেত্রে ঘটে যখন আপনি আপনার সংকলিত ফাংশনটিকে অনেক বার কল করে যাচ্ছেন।

যেমন একটি মন্তব্যে max9111 উল্লেখ করেছে, numba একটি গুরুত্বপূর্ণ বৈশিষ্ট্য numba cache কীওয়ার্ড cache=True পাস করার numba.jit cache=True 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