Broadcasting#
Broadcasting הוא מנגנון שמאפשר ל־NumPy לבצע פעולות אריתמטיות בין מערכים בצורות שונות, ע״י שימוש חוזר במערך קטן יותר מול מערך גדול יותר.
ללא Broadcasting , אם נרצה להוסיף וקטור קבוע לכל שורה במטריצה, נאלץ להקצות מערך בגודל הסופי ולהשתמש בלולאה (יקר חישובית) על מנת להוסיף את הוקטור לכל שורה במטריצה:
import numpy as np
# We will add the vector v to each row of the matrix x,
# storing the result in the matrix y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x) # Create an empty matrix with the same shape as x
# Add the vector v to each row of the matrix x with an explicit loop
for i in range(4):
y[i, :] = x[i, :] + v
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
Broadcasting חוסך את הצורך ביצירת עותקים ופשוט כותבים:
import numpy as np
# We will add the vector v to each row of the matrix x,
# storing the result in the matrix y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v # Add v to each row of x using broadcasting
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
למה זה עובד? כי NumPy “מרחיבה” את v לצורה (4,3) באופן לוגי (ללא הקצאת זיכרון מיותרת).
מצורפת תמונה להמחשה של broadcasting:

כללי ה־broadcasting#
כדי שהפעולה תצליח, חייבים להתקיים הכללים הבאים:
השוואת דרגות (rank) המערכים
אם למערכים יש מספר ממדים שונה, מוסיפים 1 בתחילת ה־shape של המערך הקטן יותר, עד שאורכי הצורות שווים. למשל:
(4, 3) ← מערך דו־ממדי
(4) ← מערך חד־ממדי
⇒ (4, 3)
(4, 1) ← נוספה 1 בתחילת ה־shape
תאימות ממדית
בממד מסוים שתי הצורות נחשבות תואמות אם הגודל בממד הזה שווה בשני המערכים, או אם לאחד מהם יש גודל 1 בממד הזה.
דוגמה תואמת:
(3, 1, 5)
(3, 4, 5) ← בממד האמצעי: 1 מול 4 מתאים
דוגמה לא תואמת:
(3, 2)
(3, 4) ← בממד השני: 2 מול 4 לא מתאים
בדיקת כל הממדים
Broadcasting אפשרי רק אם יש תאימות בכל הממדים לפי סעיף 2.
קביעת הצורה הסופית
הצורה של התוצאה היא ה־shape שמתקבל מלקיחת המקסימום רכיב־לרכיב בין שני ה־shape-ים. לדוגמה:
(3, 1, 5)
(1, 4, 5)
⇒ (3, 4, 5)
שכפול לוגי של ממדים עם 1
בממד שבו למערך אחד יש גודל 1 ולשני יש גודל גדול יותר, המערך עם ה־1 “משוכפל” לאורך הממד הזה באופן לוגי בלבד, ללא הקצאת זיכרון נוספת. לדוגמה, מערך A בגודל (2, 3) ומערך B בגודל (3) יכולים להשתלב כי NumPy מתייחסת אל B כאילו צורתו היא (1, 3), ואז משכפלת אותו לאורך השורות.
דוגמאות נוספות#
כפל חיצוני (Outer Product) באמצעות Broadcasting#
כדי לחשב כפל חיצוני בין שני וקטורים, נרצה להפוך את הווקטור הראשון לעמודה ולהכפיל איבר־איבר בווקטור השני. התוצאה היא מטריצה שכל איבר בה הוא מכפלת איבר מהווקטור הראשון באיבר מתאים מהווקטור השני.
import numpy as np
v = np.array([1, 2, 3]) # shape (3,)
w = np.array([4, 5]) # shape (2,)
# Make v a column vector, then broadcast against w
outer_vw = np.reshape(v, (3, 1)) * w # (3,1) * (2,) -> (3,2)
print(outer_vw)
# Alternative, more concise forms:
outer_vw2 = v[:, None] * w # (3,1) * (2,) -> (3,2)
print(outer_vw2)
# Using dedicated function for clarity:
outer_vw3 = np.outer(v, w) # outer product directly
print(outer_vw3)
[[ 4 5]
[ 8 10]
[12 15]]
[[ 4 5]
[ 8 10]
[12 15]]
[[ 4 5]
[ 8 10]
[12 15]]
הוספת וקטור לכל שורה (Row-wise)#
כאשר ממד הווקטור מתאים למספר העמודות במטריצה, ניתן להוסיף אותו לכל שורה באמצעות Broadcasting.
x = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2,3)
row_added = x + v # (2,3) + (3,) -> (2,3)
print(row_added)
# Explicit broadcasting with an added row axis:
row_added2 = x + v[None, :] # (2,3) + (1,3) -> (2,3)
print(row_added2)
[[2 4 6]
[5 7 9]]
[[2 4 6]
[5 7 9]]
הוספת וקטור לכל עמודה (Column-wise)#
ניתן להוסיף וקטור באורך מספר השורות לכל עמודה. אפשר לעשות זאת ע”י טרנספוזיציה או ע”י שינוי צורה מפורש של הווקטור לעמודה.
# Using transpose trick
col_added = (x.T + w).T # (3,2)->(3,2), then back to (2,3)
print(col_added)
# More direct and readable:
col_added2 = x + w[:, None] # (2,3) + (2,1) -> (2,3)
print(col_added2)
[[ 5 6 7]
[ 9 10 11]]
[[ 5 6 7]
[ 9 10 11]]
כפל בסקלר (Scalar) באמצעות Broadcasting#
סקלר מתנהג כמו מערך ריק ממדים ומתרחב לצורת המטריצה.
scaled = x * 2
print(scaled)
scaled_div = x / 2
print(scaled_div)
shifted = x + 3
print(shifted)
[[ 2 4 6]
[ 8 10 12]]
[[0.5 1. 1.5]
[2. 2.5 3. ]]
[[4 5 6]
[7 8 9]]
דוגמאות נוספות של Broadcasting שימושי#
הכפלת כל עמודה במשקל שונה (למשל לנרמול).
# 1) Column-wise scaling
weights = np.array([1.0, 10.0, 100.0]) # shape (3,)
scaled_cols = x * weights # (2,3) * (3,) -> (2,3)
print(scaled_cols)
[[ 1. 20. 300.]
[ 4. 50. 600.]]
החסרת ממוצע עמודות (Centering) - פעולת הכנה נפוצה לניתוח נתונים.
# 2) Centering by column means
col_means = x.mean(axis=0, keepdims=True) # shape (1,3)
x_centered = x - col_means # (2,3) - (1,3) -> (2,3)
print(x_centered)
[[-1.5 -1.5 -1.5]
[ 1.5 1.5 1.5]]
נרמול שורות לאורך יחידה - שימושי בווקטורים/דגימות.
# 3) Row normalization to unit length
row_norms = np.linalg.norm(x, axis=1, keepdims=True) # shape (2,1)
x_row_unit = x / row_norms # (2,3)/(2,1) -> (2,3)
print(x_row_unit)
[[0.26726124 0.53452248 0.80178373]
[0.45584231 0.56980288 0.68376346]]
כפל חיצוני בין וקטורים באורכים אחרים להמחשת הכללה.
# 4) Another outer product example with different sizes
p = np.array([2, 0, -1, 4]) # shape (4,)
q = np.array([-3, 5, 2]) # shape (3,)
outer_pq = p[:, None] * q # (4,1) * (3,) -> (4,3)
print(outer_pq)
[[ -6 10 4]
[ 0 0 0]
[ 3 -5 -2]
[-12 20 8]]
בדרך כלל broadcasting הופך את הקוד לקצר ומהיר יותר - מומלץ להשתמש בו כשאפשר.