6.9. פונקציות אוניברסליות¶
פונקציה אוניברסלית (ufunc) היא פונקציה מתמטית החלה על כל איבר במערך בקריאה אחת. אופרטורי החשבון שבעמוד הקודם הם פונקציות אוניברסליות הלובשות תחביר אופרטור; עמוד זה הוא הקטלוג של אלה הנקובות בשם המכסות טריגונומטריה, חזקות / לוגריתמים, עיגול וכמה נוספות.
כל ufunc מקבלת סקלר, אובייקט Python ניתן-לאיטרציה, או ndarray, ומחזירה או float יחיד (כשהקלט היה סקלר) או ndarray מסוג float:
from ulab import numpy as np
np.exp(2.0) # 7.389...
np.sin(range(4)) # 1-D float ndarray
np.sqrt([1, 4, 9, 16]) # array([1.0, 2.0, 3.0, 4.0])
a = np.arange(9).reshape((3, 3))
np.exp(a) # 3x3 float ndarray
6.9.1. הקטלוג¶
numpy חושף את הפונקציות המתמטיות שיישום משובץ נזקק להן בתדירות הגבוהה ביותר:
טריגונומטריה –
sin(),cos(),tan(),asin(),acos(),atan(),arctan2(),sinh(),cosh(),tanh(),asinh(),acosh(),atanh(),sinc().חזקות ולוגריתמים –
exp(),expm1(),log(),log10(),log2(),sqrt().
כל פונקציה מעבדת את המערך כולו בקריאת ספרייה אחת. שיפור המהירות לעומת list comprehension של Python הקורא ל-math.sin() איבר אחר איבר הוא פי 10-30 על חוצץ טיפוסי.
6.9.2. מילת המפתח out=¶
כל קריאה ל-ufunc מקצה בדרך כלל מערך תוצאה חדש להכיל את הפלט שלה. בלולאה שרצה פעמים רבות בשנייה, הקצאות אלה מצטברות ומבזבזות RAM. העברת out= – מערך float שכבר קיים, באותה צורה כמו הקלט – כותבת את התוצאה לתוך אותו מערך במקום להקצות חדש:
x = np.linspace(0, 2 * np.pi, num=256)
y = np.zeros(256)
while True:
np.sin(x, out=y)
# use y ...
אם ל-out יש dtype או צורה שגויים, הפונקציה מעלה חריגה. מילת המפתח נתמכת בכל ufunc בעמוד זה; זוהי הדרך הנקייה ביותר לשמור על לולאת עיבוד-אות בזרימה נטולת-הקצאות.
6.9.3. פונקציות ufunc דו-ארגומנטיות¶
arctan2() היא ה-ufunc הדו-ארגומנטית האמיתית היחידה ברשימה לעיל – היא מחזירה את הארקטנגנס מודע-הרביע של y / x ומפשטת את שני האופרנדים:
y = np.array([1, 2.2, 33.33, 444.444])
np.arctan2(y, 1.0) # against a scalar
np.arctan2(1.0, y) # the other way
np.arctan2(y, y) # against another array
6.9.4. הרכבת פונקציות אוניברסליות¶
פונקציות אוניברסליות מורכבות כמו כל ביטוי מערך אחר. כמה דפוסים העולים על המצלמה:
תיקון גמא (במרחב float)
gamma = 0.5
out = 255.0 * (frame / 255.0) ** gamma
מחליק מעביר-נמוכים (low-pass) פשוט (alpha קרוב ל-1.0 משמעו עדכון איטי):
alpha = 0.95
filtered = alpha * filtered + (1.0 - alpha) * sample
סיגמואיד
sigmoid = 1.0 / (1.0 + np.exp(-x))
ספקטרום הספק ב-dB
spectrum = 20.0 * np.log10(np.abs(real) + 1e-12)
6.9.5. np.vectorize¶
פונקציית Python רגילה יכולה להיות מקודמת לכזו בצורת ufunc על ידי vectorize(). האובייקט הניתן-לקריאה שמתקבל מקבל סקלרים, אובייקטים ניתנים-לאיטרציה, או ערכי ndarray
def f(x):
return x * x
vf = np.vectorize(f)
vf(44.0) # array([1936.0])
vf(np.array([1, 2, 3, 4])) # array([1.0, 4.0, 9.0, 16.0])
vf([2, 3, 4]) # array([4.0, 9.0, 16.0])
כברירת מחדל ה-dtype של התוצאה הוא float. otypes= עוקף זאת:
vf_u8 = np.vectorize(f, otypes=np.uint8)
vf_u8([1, 2, 3, 4])
# array([1, 4, 9, 16], dtype=uint8)
פונקציית Python חייבת לקבל ארגומנט יחיד ולהחזיר מספר יחיד.
vectorize() היא בעיקר תחבירית – פונקציית Python העטופה עדיין צריכה לרוץ פעם אחת לכל איבר, ולכן רוב עלות המפרש לכל-איבר ש-ufunc אמיתית חוסכת חוזרת. צפה לשיפור מהירות צנוע של 30%-50% לעומת list comprehension, לא לפי 30 של פונקציה אוניברסלית אמיתית. הכלי הנכון כשפונקציה אחת צריכה לעבוד על סקלרים, רשימות, וגם מערכים תחת אותו שם – לא כשמהירות גולמית היא המטרה.
לחתימות הקריאה המלאות של כל פונקציה אוניברסלית שרשומה לעיל, ראה numpy — פעולות מערך תואמות numpy.