एक अंक के प्रत्येक कक्ष में एक फ़ंक्शन का कुशल मूल्यांकन


124

एक NumPy सरणी A को देखते हुए , प्रत्येक सेल में समान फ़ंक्शन, f लागू करने का सबसे तेज़ / सबसे कुशल तरीका क्या है ?

  1. मान लीजिए कि हम A (i, j) को असाइन करेंगे को f (A (i, j))

  2. कार्यक्रम, f , में बाइनरी आउटपुट नहीं है, इस प्रकार मास्क (आईएनजी) संचालन मदद नहीं करेगा।

क्या "स्पष्ट" डबल लूप चलना (प्रत्येक कोशिका के माध्यम से) इष्टतम समाधान है?


जवाबों:


165

आप बस फ़ंक्शन को वेक्टर कर सकते हैं और फिर इसे हर बार जब भी आपको ज़रूरत हो, इसे सीधे एक Numpy सरणी में लागू कर सकते हैं:

import numpy as np

def f(x):
    return x * x + 3 * x - 2 if x > 0 else x * 5 + 8

f = np.vectorize(f)  # or use a different name if you want to keep the original f

result_array = f(A)  # if A is your Numpy array

वेक्टरिंग करते समय स्पष्ट आउटपुट प्रकार को निर्दिष्ट करना बेहतर है:

f = np.vectorize(f, otypes=[np.float])

19
मुझे डर है कि वेक्टरकृत फ़ंक्शन "सरणी" डबल लूप पुनरावृत्ति और सभी सरणी तत्वों के माध्यम से असाइनमेंट से तेज नहीं हो सकता है। विशेष रूप से, क्योंकि यह परिणाम को एक नए बनाए गए चर (और सीधे इनपुट पर नहीं) में संग्रहीत करता है । आपके उत्तर के लिए बहुत बहुत धन्यवाद हालांकि :)
पीटर

1
@ अभिनेता: आह, अब मैं देख रहा हूं कि आपने अपने मूल प्रश्न में परिणाम को पूर्व सरणी में वापस करने का उल्लेख किया है। मुझे क्षमा करें मुझे याद है कि जब पहली बार इसे पढ़ा था। हाँ, उस स्थिति में डबल लूप तेज़ होना चाहिए। लेकिन क्या आपने सरणी के चपटा दृश्य पर एक भी लूप की कोशिश की है? यह थोड़ा तेज़ हो सकता है , क्योंकि आप थोड़ा लूप ओवरहेड बचा लेते हैं और प्रत्येक पुनरावृत्ति पर Numpy को एक कम गुणा और जोड़ (डेटा ऑफ़सेट की गणना के लिए) करने की आवश्यकता होती है। इसके अलावा यह मनमाने ढंग से आयामों के लिए काम करता है। बहुत छोटे सरणियों पर धीमा हो सकता है, थो।
ब्लबरडाइब्लूब

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

ध्यान दें कि vectorizeरिटर्न प्रकार कैसे निर्धारित करता है। इससे कीड़े पैदा हुए हैं। frompyfuncथोड़ा तेज़ है, लेकिन एक dtype ऑब्जेक्ट सरणी देता है। दोनों स्केलर को फ़ीड करते हैं, न कि पंक्तियों या स्तंभों को।
हंपुलज

1
@ गैब्रिएल सिर्फ np.vectorizeमेरे फंक्शन (जो
आरके 45



0

मेरा मानना ​​है कि मैंने एक बेहतर समाधान पाया है। फ़ंक्शन को अजगर सार्वभौमिक फ़ंक्शन ( प्रलेखन देखें ) में बदलने का विचार है , जो हुड के तहत समानांतर गणना का अभ्यास कर सकता है।

कोई व्यक्ति ufuncC में अपना स्वनिर्धारित लिख सकता है, जो निश्चित रूप से अधिक कुशल है, या आह्वान करके np.frompyfunc, जो अंतर्निहित कारखाना विधि है। परीक्षण के बाद, यह np.vectorizeनिम्न से अधिक कुशल है :

f = lambda x, y: x * y
f_arr = np.frompyfunc(f, 2, 1)
vf = np.vectorize(f)
arr = np.linspace(0, 1, 10000)

%timeit f_arr(arr, arr) # 307ms
%timeit f_arr(arr, arr) # 450ms

मैंने बड़े नमूनों का भी परीक्षण किया है, और सुधार आनुपातिक है। अन्य तरीकों के प्रदर्शन की तुलना के लिए, इस पोस्ट को देखें


0

जब 2d-array (या nd-array) C- या F-contiguous होता है, तो 2d-array पर किसी फंक्शन को मैप करने का यह काम व्यावहारिक रूप से उसी तरह होता है, जैसे कि 1d-array पर किसी फंक्शन को मैप करने का काम। इसे इस तरह से देखना है, उदाहरण के लिए np.ravel(A,'K')

उदाहरण के लिए 1d-array के संभावित समाधान पर चर्चा की गई है

हालाँकि, जब 2d-array की मेमोरी सन्निहित नहीं होती है, तो स्थिति थोड़ी अधिक जटिल हो जाती है, क्योंकि यदि कोई गलत क्रम में धुरी को संभाला जाता है, तो संभव कैश से बचना चाहेंगे।

Numpy में पहले से ही सर्वोत्तम संभव क्रम में कुल्हाड़ियों को संसाधित करने के लिए एक मशीनरी है। इस मशीनरी का उपयोग करने की एक संभावना है np.vectorize। हालांकि, खाँसी के दस्तावेज में np.vectorizeकहा गया है कि यह "मुख्य रूप से सुविधा के लिए प्रदान किया गया है, प्रदर्शन के लिए नहीं" - एक धीमा अजगर फ़ंक्शन पूरे संबद्ध ओवरहेड के साथ एक धीमी अजगर फ़ंक्शन रहता है! एक और मुद्दा इसकी विशाल मेमोरी खपत है - उदाहरण के लिए इस एसओ-पोस्ट को देखें

जब कोई सी-फंक्शन का प्रदर्शन करना चाहता है, लेकिन सुपी की मशीनरी का उपयोग करता है, तो इसका एक अच्छा समाधान है, उदाहरण के लिए, ufuncs के निर्माण के लिए सुंबा का उपयोग करना:

# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x

यह आसानी से धड़कता है np.vectorizeलेकिन यह भी जब एक ही फ़ंक्शन को संख्यात्मक-सरणी गुणन / जोड़ के रूप में प्रदर्शन किया जाएगा, अर्थात

# numpy-functionality
def f(x):
    return x+2*x*x+4*x*x*x

# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"

समय-माप-कोड के लिए इस उत्तर का परिशिष्ट देखें:

यहाँ छवि विवरण दर्ज करें

नुम्बा का संस्करण (हरा) अजगर-कार्य (यानी np.vectorize) की तुलना में लगभग 100 गुना तेज है , जो आश्चर्य की बात नहीं है। लेकिन यह भी सुपी-कार्यक्षमता की तुलना में लगभग 10 गुना तेज है, क्योंकि सुन्न संस्करण को मध्यवर्ती सरणियों की आवश्यकता नहीं है और इस प्रकार कैश का अधिक कुशलता से उपयोग करता है।


जबकि सुंबा का ufunc दृष्टिकोण प्रयोज्य और प्रदर्शन के बीच एक अच्छा व्यापार है, लेकिन यह अभी भी सबसे अच्छा नहीं है जो हम कर सकते हैं। फिर भी किसी भी कार्य के लिए चांदी की गोली या एक दृष्टिकोण सबसे अच्छा नहीं है - किसी को यह समझना होगा कि सीमा क्या है और उन्हें कैसे कम किया जा सकता है।

उदाहरण के लिए, ट्रान्सेंडैंटल फ़ंक्शंस (उदाहरण expके लिए sin, cos) सुंबा , खसखस पर कोई लाभ नहीं प्रदान करता है np.exp(कोई अस्थायी सरणियाँ नहीं हैं - गति का मुख्य स्रोत)। हालाँकि, मेरा एनाकोंडा इंस्टालेशन इंटेल के VML का उपयोग वैक्टर के लिए 8192 से बड़ा है - यह सिर्फ ऐसा नहीं कर सकता है यदि मेमोरी सन्निहित नहीं है। इसलिए इंटेल के VML का उपयोग करने में सक्षम होने के लिए तत्वों को किसी सन्निहित मेमोरी में कॉपी करना बेहतर हो सकता है:

import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
    return np.exp(x)

def np_copy_exp(x):
    copy = np.ravel(x, 'K')
    return np.exp(copy).reshape(x.shape) 

तुलना की निष्पक्षता के लिए, मैंने वीएमएल के समानांतरकरण को बंद कर दिया है (परिशिष्ट में कोड देखें):

यहाँ छवि विवरण दर्ज करें

जैसा कि एक बार देखा जा सकता है, वीएमएल एक बार किक मारता है, नकल का ओवरहेड क्षतिपूर्ति से अधिक होता है। फिर भी एक बार L3 कैश के लिए डेटा बहुत बड़ा हो जाता है, लाभ कम से कम होता है क्योंकि कार्य एक बार फिर मेमोरी-बैंडविड्थ-बाउंड हो जाता है।

दूसरी ओर, सुंबा इंटेल की SVML का उपयोग कर सकता है, जैसा कि इस पोस्ट में बताया गया है :

from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')

import numba as nb

@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
    return np.exp(x)

और समानांतर उपज के साथ VML का उपयोग करना:

यहाँ छवि विवरण दर्ज करें

सुंबा के संस्करण में ओवरहेड कम है, लेकिन कुछ आकारों के लिए वीएमएल अतिरिक्त नकल के बावजूद एसवीएमएल को हरा देता है - जो कि थोड़ा आश्चर्य की बात नहीं है क्योंकि सुंबा के यूफंक्स को समानांतर नहीं किया गया है।


लिस्टिंग:

बहुपद समारोह की A. तुलना:

import perfplot
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        f,
        vf, 
        nb_vf
        ],
    logx=True,
    logy=True,
    xlabel='len(x)'
    ) 

बी की तुलना exp:

import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        nb_vexp, 
        np.exp,
        np_copy_exp,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)',
    )

0

उपरोक्त सभी उत्तर अच्छी तरह से तुलना करते हैं, लेकिन यदि आपको मानचित्रण के लिए कस्टम फ़ंक्शन का उपयोग करने की आवश्यकता है, और आपके पास है numpy.ndarray, और आपको सरणी के आकार को बनाए रखने की आवश्यकता है।

मैंने सिर्फ दो की तुलना की है, लेकिन इसका आकार बरकरार रहेगा ndarray । मैंने तुलना के लिए 1 मिलियन प्रविष्टियों के साथ सरणी का उपयोग किया है। यहां मैं स्क्वायर फ़ंक्शन का उपयोग करता हूं। मैं सामान्य मामले को n आयामी सरणी के लिए प्रस्तुत कर रहा हूं। दो आयामी के लिए सिर्फ iter2 डी के लिए बनाते हैं ।

import numpy, time

def A(e):
    return e * e

def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([A(x) for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((A(x) for x in y.reshape(-1)), y.dtype).reshape(y.shape)
    print(time.time() - now)
    now = time.time()
    numpy.square(y)  
    print(time.time() - now)

उत्पादन

>>> timeit()
1.162431240081787    # list comprehension and then building numpy array
1.0775556564331055   # from numpy.fromiter
0.002948284149169922 # using inbuilt function

यहां आप numpy.fromiterउपयोगकर्ता वर्ग फ़ंक्शन को स्पष्ट रूप से देख सकते हैं , अपनी पसंद का उपयोग कर सकते हैं । यदि आप कार्य करते हैं, तो यह निर्भर करता है i, j कि सरणी के सूचकांकों, सरणी के आकार पर पुनरावृति for ind in range(arr.size), numpy.unravel_indexप्राप्त करने के लिए उपयोग करेंi, j, .. अपने 1D सूचकांक और सरणी के आकार के आधार पर numpy.unravel_index

यह उत्तर यहाँ अन्य प्रश्न पर मेरे उत्तर से प्रेरित है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.