6.10. שידור (Broadcasting)

כאשר אופרטור בינארי מקבל שני מערכים שצורותיהם אינן תואמות בדיוק, numpy אינו מעלה שגיאה – הוא משדר. שידור הוא קבוצה קטנה של כללים שמחליטים האם שתי צורות תואמות, ואם כן, כיצד הקטנה יותר נמתחת באופן וירטואלי כדי להתאים לגדולה.

6.10.1. הכללים

כאשר לשני האופרנדים יש צורות A ו-B, numpy עובד עליהן בשני שלבים.

  1. התאמת הדרגות. אם לאופרנד אחד יש פחות צירים מהשני, numpy מרפד באופן וירטואלי את חזית צורתו בצירים בגודל 1 עד ששתי הצורות בעלות אותו מספר צירים. אופרנד חד-ממדי בצורה (3,) בזיווג עם אופרנד דו-ממדי בצורה (2, 3) הופך ל-(1, 3) מול (2, 3).

  2. בדיקת כל ציר. במעבר על הצורות שכעת באורך שווה ציר אחר ציר, כל זוג גדלים חייב לקיים אחד משני תנאים: הגדלים שווים, או שאחד מהם הוא 1. ציר בגודל 1 נמתח באופן וירטואלי לגודל הצד השני לצורך הפעולה. הזוג (1, 3) מול (2, 3) תואם משום שלציר הראשון יש 1 (נמתח ל-2) והציר השני תואם (3 == 3); לתוצאה יש צורה (2, 3).

אם זוג צירים כלשהו אינו מקיים אף אחד מהתנאים, הצורות אינן תואמות והאופרטור מעלה ValueError.

6.10.2. דוגמאות

סקלר מול מערך כלשהו. הסקלר מתנהג כצורה (1,) ונמתח לכל דבר:

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10                 # (2, 3) + scalar -> (2, 3)

וקטור חד-ממדי על פני מטריצה דו-ממדית. כלל 1 מוסיף מלפנים ציר בגודל 1 כדי להפוך את (3,) ל-(1, 3); כלל 2 לאחר מכן מותח את אותה שורה כלפי מטה בכל עמודה של a

row = np.array([100, 200, 300], dtype=np.float)
a + row                # (2, 3) + (3,) -> (2, 3)

שני מערכים חד-ממדיים באורך שווה מתחברים אלמנט-אלמנט – ללא צורך בשידור:

np.arange(4) + np.arange(4)

וקטור עמודה מול וקטור שורה מפיק צורה ”חיצונית“ דו-ממדית: (4, 1) בזיווג עם (3,) הופך ל-(4, 1) מול (1, 3) לאחר ריפוד הדרגה, וכלל 2 מותח כל אופרנד לאורך הציר שלו בגודל 1:

x = np.array([1, 2, 3, 4]).reshape((4, 1))     # column
y = np.array([10, 20, 30])                      # row
x + y                                           # (4, 3) matrix

אותם כללי צורה חלים על כל ufunc דו-ארגומנטי, כולל arctan2()

np.arctan2(y, 1.0)
np.arctan2(y, x)

6.10.3. מה שידור אינו מקצה

המתיחה היא וירטואלית. numpy עובר על שני האופרנדים יחד, וקורא מחדש את הקטן יותר לאורך ציר השידור שלו במקום להעתיקו. נתוני המערך הקצר יותר לעולם אינם משוכפלים בזיכרון.

גודל מערך הפלט הוא מה שחשוב לזיכרון. a + row מקצה פלט בצורת a, ולא בצורת a ועוד צורת row. שרשראות שידור ארוכות עדיין עלולות להפיק תוצרי ביניים גדולים.

6.10.4. כאשר שידור משתבש

הכשל הקלאסי הוא שתי צורות שלאף אחת מהן אין ציר בגודל 1 להימתח והגדלים אינם תואמים – (3, 4) מול (4, 3), למשל. כלל 2 אינו יכול להתאים 3 מול 4, ולכן numpy מעלה ValueError.

בעיה עדינה יותר היא שידור שמצליח, אך לא באופן שהיישום התכוון אליו. (5,) מול (5, 1) הוא המקרה הקנוני: ריפוד הדרגה הופך את (5,) ל-(1, 5), ששודר מול (5, 1) כדי להפיק מטריצה (5, 5) – הצירוף החיצוני של שני הווקטורים, ולא התוצאה האלמנט-אלמנטית באורך 5 שהיישום כנראה רצה. במקרה של ספק, הדפיסו את shape בשני הצדדים ועברו צעד-צעד על הכללים לפני שתפנו אל reshape() או transpose().