Kode deep learning jarang memproses satu angka; kode tersebut memproses sekaligus ribuan angka dalam array berbentuk berbeda-beda. Broadcasting adalah aturan NumPy yang mengizinkan operasi antar array dengan bentuk berbeda, misalnya menambahkan satu vektor bias ke seluruh baris sebuah batch. Vectorization adalah kebiasaan menulis operasi pada seluruh array sekaligus, menggantikan perulangan for Python yang lambat. Chapter ini membahas aturan broadcasting, menerapkannya untuk menormalkan satu batch, lalu mengukur perbandingan kecepatan nyata antara perulangan dan operasi vectorized.

Masalah: Perulangan yang Mahal #

Bayangkan kita perlu menambahkan vektor bias b berisi 3 angka ke setiap baris batch X berukuran 32x3. Dengan pola pemrograman dasar, kita menulis dua perulangan bersarang: luar untuk baris, dalam untuk kolom. Batch 32 baris masih terasa cepat, tetapi model nyata memproses jutaan angka per langkah dan mengulangnya ribuan kali per epoch — biaya perulangan Python menumpuk menjadi penghambat utama.

NumPy menawarkan jalan keluar: tulis X + b dan biarkan NumPy mengatur replikasi serta penjumlahan di level kode C. Mekanisme yang mengizinkan operasi tersebut bernama broadcasting.

Aturan Broadcasting #

Broadcasting bekerja dengan membandingkan bentuk kedua array dari dimensi paling kanan, seperti diilustrasikan oleh diagram berikut:

Kata kuncinya: NumPy tidak pernah menggabungkan ukuran berbeda kecuali salah satunya 1, dan replikasi hanya terjadi pada dimensi berukuran 1. Tabel berikut merangkum kasus yang akan kita uji:

OperasiBentuk ABentuk BBentuk hasilYang terjadi
skalar + vektor()(3,)(3,)Skalar direplikasi 3 kali
vektor + matriks(3,)(2, 3)(2, 3)Vektor direplikasi ke 2 baris
kolom + vektor(4, 1)(3,)(4, 3)Kedua array direplikasi saling menyilang
vektor + vektor(3,)(2,)—ValueError karena 3 tidak sama dengan 2

Empat Kasus Broadcasting #

Berkas aturan-broadcasting.py menjalankan keempat kasus pada tabel di atas, termasuk satu kasus yang sengaja gagal supaya kita mengenali bentuk pesan galatnya.

python
import numpy as np

# Kasus 1: skalar + vektor
skalar = 10.0
vektor = np.array([1.0, 2.0, 3.0])
print("skalar + vektor :", skalar + vektor)

# Kasus 2: vektor (3,) + matriks (2, 3)
matriks = np.array([[10.0, 20.0, 30.0],
                    [40.0, 50.0, 60.0]])
print("vektor + matriks:")
print(vektor + matriks)

# Kasus 3: kolom (4, 1) + baris (3,) -> hasil (4, 3)
kolom = np.arange(4).reshape(4, 1)
print("kolom + vektor :")
print(kolom + vektor)

# Kasus 4: bentuk yang tidak kompatibel
a = np.ones(3)
b = np.ones(2)
try:
    hasil = a + b
except ValueError as galat:
    print("ValueError:", galat)

Output yang Dihasilkan:

text
skalar + vektor : [11. 12. 13.]
vektor + matriks:
[[11. 22. 33.]
 [41. 52. 63.]]
kolom + vektor :
[[1. 2. 3.]
 [2. 3. 4.]
 [3. 4. 5.]
 [4. 5. 6.]]
ValueError: operands could not be broadcast together with shapes (3,) (2,) 

Mari kita telaah kode di atas baris per baris:

  1. Baris 6 (skalar + vektor): skalar diperlakukan sebagai array nol dimensi, direplikasi menjadi 3 elemen, lalu dijumlahkan — hasilnya [11, 12, 13].
  2. Baris 12 (vektor + matriks): vektor (3,) diratakan menjadi (1, 3), direplikasi menjadi 2 baris sehingga bentuknya (2, 3), lalu kedua matriks dijumlahkan elemen per elemen — setiap baris matriks menerima vektor [1, 2, 3] yang sama.
  3. Baris 17-19 (kolom + vektor): kolom berbentuk (4, 1) dan vektor (3,) diratakan menjadi (1, 3). Kedua dimensi berukuran 1 direplikasi — 1 menjadi 4 dan 1 menjadi 3 — sehingga hasilnya (4, 3): setiap sel adalah kolom[i] + vektor[j].
  4. Baris 22-27 (try/except): array (3,) dan (2,) tidak bisa direkonsiliasi karena 3 berbeda dari 2 dan tidak ada yang berukuran 1. NumPy menghentikan operasi dengan ValueError; blok except menangkapnya agar skrip berlanjut dan pesannya tercetak.

Normalisasi Batch Tanpa Perulangan #

Pola paling sering muncul di deep learning adalah normalisasi: menggeser dan mengskalakan setiap fitur agar rata-ratanya 0 dan simpangannya 1. Berkas normalisasi-batch.py melakukan standarisasi z-score pada batch 5x3 dengan dua baris inti dan tanpa satu pun perulangan.

python
import numpy as np

np.random.seed(42)

# Batch 5 contoh data dengan 3 fitur berskala sangat berbeda
X = np.random.rand(5, 3) * np.array([100.0, 1.0, 5000.0])

print("X sebelum normalisasi:")
print(X)

# Statistik per fitur (axis=0): satu angka untuk tiap kolom
rata = X.mean(axis=0)
simpangan = X.std(axis=0)
print("rata-rata  :", rata)
print("simpangan  :", simpangan)

# Standarisasi seluruh batch tanpa satu pun perulangan
X_norm = (X - rata) / simpangan

print("X setelah normalisasi:")
print(X_norm)
print("rata-rata baru :", np.round(X_norm.mean(axis=0), 8))
print("simpangan baru :", np.round(X_norm.std(axis=0), 8))

Output yang Dihasilkan:

text
X sebelum normalisasi:
[[3.74540119e+01 9.50714306e-01 3.65996971e+03]
 [5.98658484e+01 1.56018640e-01 7.79972602e+02]
 [5.80836122e+00 8.66176146e-01 3.00557506e+03]
 [7.08072578e+01 2.05844943e-02 4.84954926e+03]
 [8.32442641e+01 2.12339111e-01 9.09124836e+02]]
rata-rata  : [5.14359487e+01 4.41166540e-01 2.64083829e+03]
simpangan  : [2.73329510e+01 3.87513268e-01 1.58187980e+03]
X setelah normalisasi:
[[-0.51154143  1.31491696  0.64425338]
 [ 0.30841528 -0.73584035 -1.17636352]
 [-1.66932533  1.09676143  0.23057173]
 [ 0.70871634 -1.08533586  1.39625714]
 [ 1.16373513 -0.59050218 -1.09471874]]
rata-rata baru : [-0.  0. -0.]
simpangan baru : [1. 1. 1.]

Mari kita telaah kode di atas baris per baris:

  1. Baris 7 (X = np.random.rand(5, 3) * np.array([100.0, 1.0, 5000.0])): baris ini sendiri sudah memakai broadcasting — array (5, 3) dikalikan array (3,), sehingga skala [100, 1, 5000] diterapkan ke setiap baris dan ketiga fitur sengaja dibuat berskala sangat berbeda.
  2. Baris 14-15 (X.mean(axis=0)): axis=0 menghitung statistik menuruni baris, menghasilkan vektor (3,) berisi rata-rata dan simpangan baku setiap fitur.
  3. Baris 19 (X_norm = (X - rata) / simpangan): pengurangan X - rata menyiarkan vektor (3,) ke seluruh 5 baris; pembagian oleh simpangan pun demikian. Kedua operasi menggantikan perulangan dua tingkat.
  4. Baris 24-25 (np.round(..., 8)): verifikasi menunjukkan rata-rata baru nol dan simpangan baru satu hingga 8 angka desimal. Tanda -0. adalah nol bertanda negatif — hasil pembulatan angka sangat kecil, bukan galat perhitungan.

Seberapa Cepat Vectorization #

Teori broadcasting baru terasa ketika kita melihat angka. Berkas benchmark-vectorization.py menghitung fungsi sigmoid — fungsi aktivasi yang sering dipakai model — atas satu juta nilai, versi perulangan Python versus versi vectorized NumPy.

python
import math
import time

import numpy as np

np.random.seed(42)

N = 1_000_000
x = np.random.randn(N) * 5.0

# Versi 1: perulangan Python dengan math.exp per elemen
mulai = time.perf_counter()
hasil_loop = []
for nilai in x:
    hasil_loop.append(1.0 / (1.0 + math.exp(-nilai)))
waktu_loop = time.perf_counter() - mulai

# Versi 2: operasi vectorized NumPy pada seluruh array
mulai = time.perf_counter()
hasil_numpy = 1.0 / (1.0 + np.exp(-x))
waktu_numpy = time.perf_counter() - mulai

print(f"waktu perulangan Python : {waktu_loop:.4f} detik")
print(f"waktu NumPy vectorized  : {waktu_numpy:.4f} detik")
print(f"percepatan              : {waktu_loop / waktu_numpy:.0f}x")

hasil_loop = np.array(hasil_loop)
print("selisih maksimum        :", np.max(np.abs(hasil_loop - hasil_numpy)))

Output yang Dihasilkan:

text
waktu perulangan Python : 0.1819 detik
waktu NumPy vectorized  : 0.0076 detik
percepatan              : 24x
selisih maksimum        : 2.220446049250313e-16

Mari kita telaah kode di atas baris per baris:

  1. Baris 12 (x = np.random.randn(N) * 5.0): satu juta nilai acak berdistribusi normal disiapkan dengan seed tetap; pengalian dengan skalar 5.0 juga merupakan broadcasting kasus pertama kita.
  2. Baris 15-18 (for nilai in x:): versi perulangan memanggil math.exp satu kali untuk setiap elemen, membuat objek float baru, lalu menambahkannya ke list — tiga sumber biaya Python murni.
  3. Baris 23 (hasil_numpy = 1.0 / (1.0 + np.exp(-x))): seluruh ekspresi (negasi, np.exp, penjumlahan, pembagian) dijalankan per operasi pada seluruh array oleh kode C, tanpa satu pun perulangan Python.
  4. Baris 31-32 (np.max(np.abs(...))): kedua hasil dibandingkan untuk memastikan isinya sama; selisih maksimum sebesar 2.2e-16 hanyalah pembulatan titik mengambang, jauh di bawah toleransi pembelajaran.

Angka waktu di atas diukur sekali di satu mesin, sehingga nilai pasti yang Anda dapatkan berbeda — faktor percepatan bergantung perangkat keras. Yang konsisten adalah polanya: operasi vectorized selalu berorde lebih cepat, dan selisihnya melebar pada GPU. Aturan lengkap broadcasting didokumentasikan di numpy.org/doc/stable/user/basics.broadcasting .

Broadcasting menyatukan dua array dengan bentuk berbeda; operasi matriks menyusun array menjadi perhitungan berlapis seperti forward pass sebuah layer. Chapter berikutnya membahas perkalian matriks @, transpose, sampai invers — operasi inti yang membuat neural network dapat menghitung.