6.5. צורה וצעדים (strides)¶
הנתונים בתוך ndarray הם בלוק אחד ארוז של מספרים. המתאר (descriptor) שלפני הבלוק הזה מחליט כיצד הבלוק השטוח הזה נקרא כטנזור.
6.5.1. מה המתאר רושם¶
חמישה ערכים מתארים כיצד לקרוא את בלוק הנתונים כטנזור:
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.uint8)
a.ndim # 2 - number of dimensions
a.shape # (2, 3)- length along each dimension
a.itemsize # 1 - bytes per element (from dtype)
a.size # 6 - total number of elements
a.strides # (3, 1)- step pattern through the buffer
העוזר ndinfo() מדפיס את כולם בנוסף למיקום החוצץ הבסיסי בקריאה אחת. שני מערכים שמיקומי החוצץ שלהם תואמים חולקים זיכרון:
np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8
6.5.2. הסבר על צעדים (strides)¶
צעד (stride) הוא כמה בתים יש לדלג בבלוק הנתונים כדי לזוז איבר אחד לאורך ציר נתון. עבור מערך ה-uint8 בגודל 2x3 לעיל, הצעדים הם (3, 1): ירידה בשורה אחת מדלגת 3 בתים, תזוזה ימינה בעמודה אחת מדלגת בית אחד. זה זהה לאמירה שהשורות מאוחסנות זו אחר זו, משמאל לימין:
memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
^ row 0 ^ row 1
<------- 3 bytes ---->
כדי לקרוא את a[i, j], numpy מחשב i * strides[0] + j * strides[1] מתחילת בלוק הנתונים וקורא itemsize בתים משם. אותה נוסחה מתרחבת לכל מספר של ממדים.
פריסה זו – שורות מאוחסנות מקצה לקצה, כשהציר האחרון משתנה הכי מהר לאורך הזיכרון – נקראת סדר row-major (שורה ראשית). כל מערך ש-numpy מקצה על המצלמה משתמש בפריסה זו.
6.5.3. ל-row-major יש השלכות¶
שני דברים נובעים מ“שורות מאוחסנות זו אחר זו“ שחשובים בעת עיצוב חוצץ על המצלמה.
הציר האחרון רציף. מעבר מ-a[0, 0] ל-a[0, 1] נוגע בבית הבא. מעבר מ-a[0, 0] ל-a[1, 0] מדלג על שורה שלמה.
הציר האחרון הוא הציר המהיר עבור חשבון על מערך שלם. numpy על המצלמה תמיד עובר על הציר האחרון בפנימיות ביותר, ללא קשר לאיזה ציר במקרה ארוך יותר. ספריית numpy של המחשב השולחני מסדרת מחדש בשקט את הלולאות שלה כדי לשים את הציר הארוך ביותר בפנימיות ביותר; המצלמה אינה עושה זאת, כך שבחירת פריסה ש-numpy השולחני היה מכסה עליה עדיין עולה זמן כאן. np.sum(m, axis=1) מצמצם את הציר האחרון ורץ בכיוון הרציף; np.sum(m, axis=0) לא. כאשר ליישום יש בחירה כיצד לפרוס חוצץ, שימו את הציר הארוך אחרון כדי שפעולות לאורכו יישארו בלולאה הפנימית.
אם הפריסה מתחילה שגויה, transpose() (או הקיצור .T) מתקן אותה מבלי להעתיק את הנתונים – הוא פשוט מחליף את הצעדים:
a = b.T # now iterates fast
ביצועים כולל את דיון הביצועים המלא.
6.5.4. Reshape, transpose, חיתוך – עריכות מתאר¶
כל פעולה שרק כותבת מחדש את המתאר היא חינמית. reshape מחליף shape ו-strides חדשים על פני אותו בלוק נתונים. transpose הופך את הצעדים. a[::2] מכפיל צעד. כל אחד מחזיר תצוגה של אותו חוצץ בסיסי.
כל דבר שחייב לעבור על הנתונים ולכתוב חוצץ חדש הוא העתק. הכלל לעת עתה הוא שעריכות מתאר הן חינמיות ומעברים על נתונים אינם.
6.5.5. הערה על ndim¶
numpy על המצלמה בנוי עם ndim מרבי נתמך של 4. פעולות שהיו מייצרות מערך מדרגה גבוהה יותר מעוררות ValueError. הרוב המכריע של העבודה בצד המצלמה הוא חד-ממדי או דו-ממדי, כך שהמגבלה היא לעיתים רחוקות בעיה.