Broadcasting (broadcasting) adalah mekanisme NumPy yang memperluas operand berbentuk kecil secara otomatis sehingga operasi aritmetika antar array dengan bentuk berbeda tetap dapat dijalankan. Topik ini membahas dua aturan yang menentukan kompatibilitas dua bentuk, contoh penyelarasannya pada bentuk (3, 4) dengan (4,) serta (3, 1) dengan (1, 4), dan alasan dimensi yang diperluas tidak menyalin data. Setelah membacanya, bentuk hasil dari dua operand apa pun dapat ditentukan secara manual tanpa mencoba-coba.
Dua aturan penyelarasan#
NumPy menilai kompatibilitas dua bentuk dengan dua aturan yang diperiksa berurutan. Pertama, kedua bentuk diselaraskan dari dimensi terakhir ke arah kiri, dan bentuk yang lebih pendek dilapisi dengan dimensi bernilai 1 pada sisi kirinya sampai panjangnya sama. Kedua, setiap pasangan dimensi yang sejajar dinyatakan cocok apabila nilainya sama atau salah satunya bernilai 1.
Apabila seluruh pasangan cocok, bentuk hasil diambil dari nilai terbesar pada setiap pasangan. Dimensi bernilai 1 berperan sebagai dimensi yang dapat diperluas, yaitu satu-satunya nilai pada dimensi itu digunakan ulang sepanjang dimensi pasangannya. Apabila satu pasangan saja gagal, yakni kedua nilainya berbeda dan tidak ada yang bernilai 1, seluruh operasi dihentikan dengan ValueError.
Bentuk (3, 4) dan (4,) memuat kedua aturan sekaligus. Bentuk yang lebih pendek dilapisi menjadi (1, 4), kemudian pasangan dimensinya diperiksa dari kanan. Nilai 4 bertemu 4 dan dinyatakan cocok, sedangkan nilai 3 bertemu 1 dan juga cocok karena salah satunya bernilai 1. Bentuk hasilnya adalah (3, 4).
Jumlah dimensi bentuk hasil mengikuti operand yang lebih panjang. Dimensi di sisi kiri yang tidak memiliki pasangan dibiarkan apa adanya, karena hanya dimensi yang sejajar yang diperiksa. Dengan aturan ini, (2, 3, 4) dan (4,) menghasilkan (2, 3, 4) tanpa syarat tambahan apa pun selain dimensi terakhirnya yang cocok.
Contoh pada array 2D dan 1D#
Contoh berikut menambahkan array berbentuk (4,) ke array berbentuk (3, 4). Nilai pada b diperlakukan sebagai satu baris panjang 4 yang ditambahkan ke setiap baris a.
import numpy as np
a = np.ones((3, 4))
b = np.arange(4)
c = a + b
print("a:", a.shape, "b:", b.shape, "-> c:", c.shape)
print(c)a: (3, 4) b: (4,) -> c: (3, 4)
[[1. 2. 3. 4.]
[1. 2. 3. 4.]
[1. 2. 3. 4.]]Hasilnya berbentuk (3, 4) dan setiap barisnya adalah [1. 2. 3. 4.], yaitu setiap elemen b ditambahkan ke nilai a yang selalu 1. Pasangan (3, 1) dan (1, 4) memperlihatkan perluasan pada dua sisi sekaligus. Vektor kolom v diperluas sepanjang dimensi kolom menjadi empat salinan, sedangkan vektor baris w diperluas sepanjang dimensi baris menjadi tiga salinan.
Seluruh fungsi per elemen pada NumPy, yaitu seluruh ufunc (universal functions), mengikuti aturan yang sama. Penjumlahan, pengurangan, perkalian, pembagian, dan perbandingan semuanya menerima operand dengan bentuk berbeda selama penyelarasannya berhasil.
import numpy as np
v = np.array([[1], [2], [3]])
w = np.array([[10, 20, 30, 40]])
print("v:", v.shape, "w:", w.shape)
print(v + w)v: (3, 1) w: (1, 4)
[[11 21 31 41]
[12 22 32 42]
[13 23 33 43]]Fungsi np.broadcast_shapes merangkum kedua aturan tersebut tanpa perlu membuat array. Fungsi ini menerima sejumlah bentuk dan mengembalikan bentuk hasilnya, sehingga berguna untuk memeriksa rencana broadcasting terlebih dahulu sebelum array yang sesungguhnya dibuat.
import numpy as np
print(np.broadcast_shapes((3, 4), (4,)))
print(np.broadcast_shapes((3, 1), (1, 4)))
print(np.broadcast_shapes((5, 3, 2), (2,)))(3, 4)
(3, 4)
(5, 3, 2)Bentuk (5, 3, 2) bertemu (2,) juga cocok karena dimensi terakhirnya, yaitu 2 bertemu 2, sedangkan dimensi di depannya dibiarkan apa adanya.
Membaca diagram penyelarasan#
Diagram berikut mengulang proses penyelarasan (3, 4) dengan (4,) secara visual. Diagram dibaca dari atas ke bawah, dan baris keduanya memperlihatkan pelapisan dimensi 1 di sisi kiri bentuk (4,) sebelum pasangan dimensi diperiksa.
Kesalahan yang lazim adalah menyelaraskan bentuk dari kiri. Apabila (4,) dibandingkan dengan angka 3 pada (3, 4) dari sisi kiri, kesimpulan kompatibilitasnya keliru, padahal penyelarasan dari kanan menyatakan keduanya cocok. Contoh sebaliknya adalah (3,) dan (3, 4), yang memang tidak kompatibel. Penyelarasan dari kanan menemukan ketidakcocokan itu karena angka 3 pada (3,) berada di posisi dimensi terakhir dan harus dibandingkan dengan angka 4.
Dimensi yang diperluas tidak menyalin data#
Pemikiran yang wajar setelah contoh di atas adalah NumPy menyalin b menjadi array (3, 4) terlebih dahulu sebelum dijumlahkan. Hal itu tidak terjadi. Pada dimensi yang diperluas, NumPy mengatur ulang langkah memori (stride) menjadi 0, yaitu penunjuk bahwa byte yang sama dibaca ulang tanpa berpindah alamat. Fungsi np.broadcast_to memperlihatkan mekanisme ini secara eksplisit.
import numpy as np
b = np.arange(4)
m = np.broadcast_to(b, (3, 4))
print(m)
print("stride asli :", b.strides)
print("stride hasil:", m.strides)[[0 1 2 3]
[0 1 2 3]
[0 1 2 3]]
stride asli : (8,)
stride hasil: (0, 8)Stride (8,) pada b berarti berpindah 8 byte untuk satu elemen berikutnya, sesuai ukuran int64. Stride (0, 8) pada hasil berarti berpindah antarbaris tidak mengubah alamat sama sekali, sehingga ketiga baris membaca data yang sama. Array keluaran tetap merupakan array baru, tetapi operand yang diperluas tidak pernah diduplikasi.
Kontrasnya terlihat pada np.tile, yang memang menduplikasi data menjadi salinan sungguhan. Stride salinan tersebut tidak ada yang bernilai 0 karena setiap baris kini menempati alamatnya sendiri.
import numpy as np
b = np.arange(4)
salinan = np.tile(b, (3, 1))
print(salinan)
print("stride tile :", salinan.strides)[[0 1 2 3]
[0 1 2 3]
[0 1 2 3]]
stride tile : (32, 8)Stride (32, 8) berarti antarbaris berpindah 32 byte, yaitu empat elemen int64 sekaligus, karena setiap baris benar-benar tersimpan terpisah. Nilai keluarannya sama dengan np.broadcast_to, tetapi konsumsi memorinya berbeda. Untuk array besar, perbedaan inilah yang menentukan apakah sebuah operasi muat di memori.
Ringkasan#
- Broadcasting menyelaraskan dua bentuk dari dimensi terakhir, dan setiap pasangan dimensi cocok apabila nilainya sama atau salah satunya bernilai 1.
- Bentuk hasil adalah nilai terbesar dari setiap pasangan dimensi, dan
np.broadcast_shapesdapat digunakan untuk memeriksanya tanpa membuat array. - Dimensi bernilai 1 diperluas secara virtual melalui stride 0, sehingga tidak ada salinan data pada operand yang diperluas.
Referensi#
- Broadcasting — NumPy — Dokumentasi resmi aturan broadcasting beserta contoh bentuk yang cocok dan yang gagal.
- Universal functions (ufunc) — NumPy — Bagian broadcasting pada dokumentasi ufunc, mekanisme yang menjalankan operasi per elemen.