Pekerjaan nyata jarang berhenti pada satu perkalian matriks. Sering kali kita menghadapi tumpukan transformasi kecil — satu matriks per cabang, per gambar, atau per langkah waktu — dan seluruhnya harus dikalikan. Menulis loop for bisa saja, tetapi NumPy menyediakan jalan yang lebih ringkas: np.matmul (dan operator @) memperlakukan array berdimensi lebih dari dua sebagai batch, yaitu tumpukan matriks yang dikalikan sekaligus, dengan broadcasting pada sumbu batch.
Tumpukan Matriks sebagai Array Tiga Dimensi #
Aturan @ untuk input N-D (N > 2): dua sumbu terakhir adalah matriks, sedangkan seluruh sumbu di depannya adalah batch. Anggota batch yang seposisi dikalikan satu sama lain:
import numpy as np
batch = np.array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
print(batch.shape)
hasil = batch @ batch
print(hasil.shape)
print(hasil)Output yang Dihasilkan:
(2, 2, 2)
(2, 2, 2)
[[[ 7 10]
[ 15 22]]
[[ 67 78]
[ 91 106]]]Bedah baris demi baris:
- Baris 3-6 (
batch = np.array(...)): tumpukan dua matriks 2x2 —batch[0]adalah matriksAdanbatch[1]adalah matriksBdari contoh Chapter Matrix Multiplication. - Baris 7 (
batch.shape):(2, 2, 2)dibaca “dua matriks, masing-masing berordo 2x2”. - Baris 8-9 (
hasil = batch @ batch):@menghitungbatch[0] @ batch[0]danbatch[1] @ batch[1], lalu menumpuk keduanya; hasilnya batch baru berbentuk(2, 2, 2).
Nilainya cocok dengan perkalian manual per matriks: hasil[0] adalah [[7, 10], [15, 22]] (yaitu A @ A) dan hasil[1] adalah [[67, 78], [91, 106]] (yaitu B @ B).
Matriks Dua Dimensi di Salah Satu Sisi #
Bila salah satu operand hanya 2-D, matmul memperlakukannya sebagai satu matriks yang di-broadcast ke seluruh batch — analog dengan penambahan sumbu baru pada Chapter Pengenalan Broadcasting. Perhatikan bentuk hasilnya dengan teliti:
import numpy as np
batch = np.array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
M = np.array([[1, 0],
[0, 2]])
print((batch @ M).shape)
print(batch @ M)Output yang Dihasilkan:
(2, 2, 2)
[[[ 1 4]
[ 3 8]]
[[ 5 12]
[ 7 16]]]Bedah baris demi baris:
- Baris 9 (
(batch @ M).shape): hasilnya(2, 2, 2), bukan(2, 2)— operand 2-D tidak mengurangi jumlah anggota batch. - Baris 10 (
print(batch @ M)):Mdikalikan ke setiap anggota:batch[0] @ M = [[1, 4], [3, 8]]danbatch[1] @ M = [[5, 12], [7, 16]].
Aturan yang sama berlaku bila matriks 2-D berada di sisi kiri (M @ batch tetap (2, 2, 2)), dan bila operand kanan berupa vektor 1-D: batch @ v berbentuk (2, 2) — perkalian matriks-vektor untuk setiap anggota batch (batched matvec).
Loop vs Batch: Membuktikan Kesamaan Hasil #
Cara paling meyakinkan untuk memahami semantik batch adalah membandingkannya dengan loop eksplisit:
import numpy as np
batch = np.array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
M = np.array([[1, 0],
[0, 2]])
hasil_loop = np.stack([batch[i] @ M for i in range(batch.shape[0])])
hasil_batch = batch @ M
print(np.array_equal(hasil_loop, hasil_batch))Output yang Dihasilkan:
TrueBedah baris demi baris:
- Baris 10 (
hasil_loop = np.stack(...)): list comprehension mengalikan tiap anggota batch denganMsatu per satu, lalunp.stackmenumpuk hasil-hasil 2-D itu menjadi batch(2, 2, 2)kembali. - Baris 11 (
hasil_batch = batch @ M): versi satu baris tanpa loop Python. - Baris 12 (
np.array_equal(...)):True— kedua jalur menghasilkan nilai yang sama persis.
Studi Kasus: Banyak Transformasi Kecil #
Rotasi 2-D adalah contoh klasik: setiap sudut memiliki satu matriks rotasi, dan kita ingin memutar satu titik dengan beberapa sudut sekaligus. Kita bangun tumpukan (3, 2, 2) matriks rotasi, lalu kalikan dengan satu vektor kolom titik (2, 1):
import numpy as np
sudut = np.array([0.0, 30.0, 60.0])
rad = np.deg2rad(sudut)
c = np.cos(rad)
s = np.sin(rad)
rot = np.empty((3, 2, 2))
rot[:, 0, 0] = c
rot[:, 0, 1] = -s
rot[:, 1, 0] = s
rot[:, 1, 1] = c
titik = np.array([[2.0], [0.0]])
hasil = rot @ titik
print(hasil.shape)
print(hasil)Output yang Dihasilkan:
(3, 2, 1)
[[[2. ]
[0. ]]
[[1.73205081]
[1. ]]
[[1. ]
[1.73205081]]]Bedah baris demi baris:
- Baris 3-6: sudut 0°, 30°, dan 60° dikonversi ke radian, lalu
np.cosdannp.sindihitung per sudut —cdansmasing-masing berbentuk(3,). - Baris 8-12 (
rot):np.empty((3, 2, 2))menyiapkan tumpukan kosong; slicingrot[:, 0, 0] = cdan seterusnya mengisi pola matriks rotasi[[cos, -sin], [sin, cos]]untuk ketiga sudut sekaligus. - Baris 14 (
titik): titik(2, 0)ditulis sebagai vektor kolom berbentuk(2, 1)— konversi dari array 1-D dibahas pada Chapter Transpose Matriks. - Baris 15-19 (
rot @ titik): operand(2, 1)di-broadcast ke seluruh batch; hasil(3, 2, 1)berisi satu titik hasil per sudut:(2, 0)untuk 0°,(1.73, 1)untuk 30°, dan(1, 1.73)untuk 60°.
Tabel berikut merangkum kombinasi bentuk yang sudah kita uji:
| Operand kiri | Operand kanan | Bentuk hasil | Arti |
|---|---|---|---|
(2, 2, 2) | (2, 2, 2) | (2, 2, 2) | matriks ke-i dikalikan matriks ke-i |
(2, 2, 2) | (2, 2) | (2, 2, 2) | satu matriks untuk seluruh batch |
(2, 2, 2) | (2,) | (2, 2) | satu vektor per anggota batch |
(3, 2, 2) | (2, 1) | (3, 2, 1) | batch transformasi terhadap satu titik |