Bab-bab sebelumnya memakai satu bentuk kontraksi yang tetap: @ menjumlahkan perkalian sepanjang sumbu terakhir operand kiri dan sumbu pertama operand kanan. np.tensordot menggeneralisasi aturan itu — kita yang memilih sumbu mana yang dikalikan lalu dijumlahkan (kontraksi), termasuk dua sumbu sekaligus atau tidak ada sama sekali. Bergerak ke arah sebaliknya, np.kron (produk Kronecker) tidak mengurangi dimensi: setiap elemen a[i, j] menjadi satu blok utuh berisi a[i, j] * b.

Dua fungsi ini merangkum dua pola besar array multidimensi: kontraksi (meringkas sumbu) dan replikasi blok (mengulang pola).

Tiga Tingkat Kontraksi: axes 0, 1, dan 2 #

Argumen axes mengatur pemilihan sumbu: bilangan N memasangkan N sumbu terakhir a dengan N sumbu pertama b; tuple (axis_a, axis_b) memasangkan sumbu secara eksplisit. Tiga tingkat yang paling sering dipakai:

axesKontraksiSetara dengan
0tanpa kontraksi — semua pasangan elemen dipertahankannp.outer(a, b)
1satu sumbu: terakhir a, pertama bA @ B
2dua sumbu sekaligusnp.sum(A * B) untuk 2-D

Kita uji ketiganya:

python
import numpy as np

a = np.array([1, 2])
b = np.array([3, 4])
A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5, 6],
              [7, 8]])
print(np.tensordot(a, b, axes=0))
print(np.array_equal(np.tensordot(a, b, axes=0), np.outer(a, b)))
print(np.tensordot(A, B, axes=1))
print(np.array_equal(np.tensordot(A, B, axes=1), A @ B))
print(np.tensordot(A, B, axes=2))
print(np.array_equal(np.tensordot(A, B, axes=2), np.sum(A * B)))

Output yang Dihasilkan:

text
[[3 4]
 [6 8]]
True
[[19 22]
 [43 50]]
True
70
True

Bedah baris demi baris:

  1. Baris 9-10 (axes=0): tanpa kontraksi; setiap pasangan a[i]·b[j] ditaruh pada grid (2, 2) → [[3, 4], [6, 8]], persis np.outer dari Inner dan Outer Product.
  2. Baris 11-12 (axes=1): sumbu terakhir A (panjang 2) dipasangkan dengan sumbu pertama B → [[19, 22], [43, 50]], identik dengan A @ B — inti Dot Product dan perkalian matriks.
  3. Baris 13-14 (axes=2): kedua sumbu dikontraksi → 1·5 + 2·6 + 3·7 + 4·8 = 70, sama dengan np.sum(A * B); bentuk eksplisitnya axes=((0, 1), (0, 1)). Nilainya berguna sebagai skor kemiripan dua matriks.

Batch Perkalian Matriks sebagai tensordot #

Chapter Operasi Matriks Batch menunjukkan batch @ M: operand 2-D di-broadcast ke seluruh tumpukan. Tensordot menyatakan operasi yang sama tanpa aturan broadcast — cukup sebut sumbu yang dikontraksi. Kasusnya: dua gudang dengan matriks pengiriman 2 kanal x 3 produk, dikalikan matriks ongkos kemas 3 produk x 2 jenis pengiriman:

python
import numpy as np

batch = np.array([[[1, 2, 3],
                   [4, 5, 6]],
                  [[7, 8, 9],
                   [10, 11, 12]]])
M = np.array([[1, 0],
              [0, 1],
              [1, 1]])
hasil = np.tensordot(batch, M, axes=(2, 0))
print(hasil.shape)
print(np.array_equal(hasil, batch @ M))
print(hasil)

Output yang Dihasilkan:

text
(2, 2, 2)
True
[[[ 4  5]
  [10 11]]

 [[16 17]
  [22 23]]]

Bedah baris demi baris:

  1. Baris 10 (axes=(2, 0)): sumbu 2 batch (produk, panjang 3) dipasangkan dengan sumbu 0 M (juga produk); sumbu tersisa (gudang, kanal, jenis pengiriman) menata hasil (2, 2, 2).
  2. Baris 11-12: hasilnya identik dengan batch @ M; axes=1 memberi hasil yang sama karena sumbu terakhir batch adalah pasangan itu.
  3. Baris 13: anggota pertama [[4, 5], [10, 11]] adalah batch[0] @ M; baris [1, 2, 3] dikalikan kolom-kolom M.

np.kron: Satu Elemen, Satu Blok #

np.kron(a, b) menata hasil sebagai blok: blok pada posisi (i, j) berisi seluruh matriks b yang dikalikan a[i, j]; untuk a berordo (p, q) dan b berordo (r, s), hasil berordo (p·r, q·s). Kita bedah satu contoh:

python
import numpy as np

a = np.array([[1, 2],
              [3, 4]])
b = np.array([[10, 20],
              [30, 40]])
k = np.kron(a, b)
print(k.shape)
print(k)

Output yang Dihasilkan:

text
(4, 4)
[[ 10  20  20  40]
 [ 30  40  60  80]
 [ 30  60  40  80]
 [ 90 120 120 160]]

Bedah baris demi baris:

  1. Blok kiri-atas (a[0,0] = 1): 1 * b = [[10, 20], [30, 40]] — dua baris dan dua kolom pertama.
  2. Blok kanan-atas (a[0,1] = 2): 2 * b = [[20, 40], [60, 80]] — baris 1-2, kolom 3-4.
  3. Blok bawah (a[1,0] = 3, a[1,1] = 4): 3 * b dan 4 * b menempati dua blok bawah; kron menggandakan nilai (angka 40 muncul di dua blok), bukan memindahkannya.

Pola Blok Berulang: Blok Diagonal dan Desain Eksperimen #

Dua kasus nyata berikut hampir selalu dirumuskan lewat kron. Kasus 1 — blok diagonal per kategori: dua kategori produk, tiap kategori punya pola kovarians [[4, 2], [2, 4]] antara dua produknya, dan kategori berbeda dianggap bebas (kovarians nol); matriks gabungan 4x4-nya kron(np.eye(2), pola). Kasus 2 — desain eksperimen: tiga lokasi uji, dua perlakuan, pola perlakuan sama diulang di tiap lokasi → kron(np.ones((3, 1)), treatment):

python
import numpy as np

pola = np.array([[4.0, 2.0],
                 [2.0, 4.0]])
blok = np.kron(np.eye(2), pola)
print(blok)

desain = np.kron(np.ones((3, 1), dtype=int), np.array([[1, 0], [0, 1]]))
print(desain)

Output yang Dihasilkan:

text
[[4. 2. 0. 0.]
 [2. 4. 0. 0.]
 [0. 0. 4. 2.]
 [0. 0. 2. 4.]]
[[1 0]
 [0 1]
 [1 0]
 [0 1]
 [1 0]
 [0 1]]

Bedah baris demi baris:

  1. Baris 5 (np.kron(np.eye(2), pola)): elemen diagonal eye(2) bernilai 1 → blok pola utuh; elemen lain 0 → blok nol. Nol antar-kategori muncul otomatis.
  2. Baris 8 (np.ones((3, 1), dtype=int)): operand kiri (3, 1) mengulang treatment tiga kali ke bawah → desain 6x2, tiga lokasi x dua perlakuan.

Untuk k kategori, pakai kron(np.eye(k), pola); bila antar-kategori punya hubungan, operand kiri diganti matriks korelasi antar-kategori.

tensordot atau einsum #

einsum menyatakan kontraksi yang sama — tensordot menyebut nomor sumbu, einsum menyebut huruf per sumbu. Yang menarik semantiknya: tensordot menjumlahkan habis semua sumbu yang dipasangkan; einsum mempertahankan sumbu bersama sebagai pasangan sejajar — persis semantik @ berbatch:

python
import numpy as np

A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5, 6],
              [7, 8]])
print(np.array_equal(np.tensordot(A, B, axes=1), np.einsum("ij,jk->ik", A, B)))
print(np.array_equal(np.tensordot(A, B, axes=2), np.einsum("ij,ij->", A, B)))

Ab = np.array([[[1, 2, 3], [4, 5, 6]],
               [[7, 8, 9], [10, 11, 12]]])
Bb = np.array([[[1, 0], [0, 1], [1, 1]],
               [[2, 0], [0, 2], [1, 1]]])
print(np.array_equal(Ab @ Bb, np.einsum("kij,kjp->kip", Ab, Bb)))
print(np.tensordot(Ab, Bb, axes=(2, 1)).shape)

Output yang Dihasilkan:

text
True
True
True
(2, 2, 2, 2)

Bedah baris demi baris:

  1. Baris 7-8: kontraksi satu sumbu dan kontraksi ganda — tensordot dan einsum menghasilkan nilai yang identik.
  2. Baris 14 (einsum("kij,kjp->kip", ...)): huruf k ada di kedua operand dan tetap di output → matriks ke-k dikalikan matriks ke-k — identik dengan Ab @ Bb.
  3. Baris 15 (tensordot(..., axes=(2, 1))): sumbu produk dan batch ikut terkontraksi → hasil (2, 2, 2, 2): semua kombinasi anggota batch dipasangkan, bukan anggota-ke-anggota.

Dengan tensordot untuk kontraksi dan kron untuk replikasi blok, matriks besar dapat dirakit dari pola kecil yang mudah diverifikasi.