Sejauh ini kita memakai fungsi berbeda untuk tiap kebutuhan: np.dot untuk dot product, np.outer untuk outer product, M.T untuk transpose, @ untuk perkalian matriks, dan sum(axis=...) untuk penjumlahan per axis. Padahal semuanya satu keluarga: kalikan elemen pada sumbu tertentu, lalu jumlahkan hasilnya. np.einsum (Einstein summation) menyatukan semuanya dalam satu bahasa kecil bernama notasi subscript — satu sintaks untuk banyak operasi.

Membaca Notasi Subscript #

Aturannya empat dan seluruh contoh bab ini mengikutinya:

  1. Setiap operand diberi subscript — daftar huruf, satu huruf per sumbu — dipisah koma: np.einsum("i,i->", a, b) berarti operand pertama berlabel i dan operand kedua juga i.
  2. Setelah panah -> tertulis subscript output: susunan sumbu pada hasil.
  3. Huruf yang muncul di input tetapi tidak di output mengalami kontraksi: sepanjang sumbu itu elemen dikalikan lalu dijumlahkan.
  4. Huruf yang sama pada dua operand memaksa perkalian elemen-demi-elemen pada sumbu berlabel itu.

Dua contoh pembuka pada vektor:

python
import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.einsum("i,i->", a, b))
print(np.array_equal(np.einsum("i,i->", a, b), np.dot(a, b)))
print(np.einsum("i->", a))
print(np.array_equal(np.einsum("i->", a), a.sum()))

Output yang Dihasilkan:

text
32
True
6
True

Bedah baris demi baris:

  1. Baris 5 (np.einsum("i,i->", a, b)): huruf i sama pada kedua operand → a[k]·b[k] untuk setiap k; i tidak ada di kanan panah → seluruh hasil dijumlahkan. Nilainya 32, sama dengan np.dot(a, b) (Chapter Dot Product).
  2. Baris 6 (np.array_equal(...)): True — einsum dan np.dot menghasilkan nilai yang identik.
  3. Baris 7 (np.einsum("i->", a)): tanpa operand kedua; i dibuang di output sehingga cukup penjumlahan total — setara a.sum().
  4. Baris 8: True.

Satu Operand: Transpose dan Penjumlahan per Baris #

Subscript satu operand cukup untuk operasi penyusunan ulang dan penjumlahan:

python
import numpy as np

M = np.array([[1, 2, 3],
              [4, 5, 6]])
print(np.einsum("ij->ji", M))
print(np.array_equal(np.einsum("ij->ji", M), M.T))
print(np.einsum("ij->i", M))
print(np.array_equal(np.einsum("ij->i", M), M.sum(axis=1)))

Output yang Dihasilkan:

text
[[1 4]
 [2 5]
 [3 6]]
True
[ 6 15]
True

Bedah baris demi baris:

  1. Baris 5 (np.einsum("ij->ji", M)): sumbu output dibaca terbalik — M[i, j] ditaruh pada posisi (j, i). Itu definisi transpose; hasilnya identik dengan M.T (Chapter transpose).
  2. Baris 6: True menegaskan kesamaan nilai dengan M.T.
  3. Baris 7 (np.einsum("ij->i", M)): huruf j hilang di output → sepanjang j elemen dijumlahkan → jumlah per baris, identik dengan M.sum(axis=1) (Chapter Operasi Berdasarkan Axis).
  4. Baris 8: True.

Dua Operand: Outer Product dan Perkalian Matriks #

Dengan dua operand, notasi subscript bahkan menggantikan perkalian matriks:

python
import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.einsum("i,j->ij", a, b))
print(np.array_equal(np.einsum("i,j->ij", a, b), np.outer(a, b)))

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

Output yang Dihasilkan:

text
[[ 4  5  6]
 [ 8 10 12]
 [12 15 18]]
True
[[19 22]
 [43 50]]
True

Bedah baris demi baris:

  1. Baris 5 (np.einsum("i,j->ij", a, b)): i dan j berbeda — tidak ada kontraksi; setiap pasangan a[i]·b[j] dipertahankan pada grid (3, 3) → outer product (Chapter Inner dan Outer Product).
  2. Baris 6: True — identik dengan np.outer(a, b).
  3. Baris 12 (np.einsum("ij,jk->ik", A, B)): j muncul di kedua operand dan hilang di output → A[i, j]·B[j, k] dijumlahkan sepanjang j. Itu persis definisi C[i, k] = Σj A[i, j]·B[j, k], yaitu A @ B (Chapter Matrix Multiplication).
  4. Baris 13: True.

Tabel Ringkasan Notasi #

Tabel berikut merangkum notasi yang dibahas beserta padanan eksplisitnya:

NotasiOperasiSetara dengan
"i,i->"dot productnp.dot(a, b)
"i->"penjumlahan totala.sum()
"i,i->i"perkalian elemena * b
"i,j->ij"outer productnp.outer(a, b)
"ij->ji"transposeM.T
"ij->i"jumlah per barisM.sum(axis=1)
"ii->i"diagonalM.diagonal()
"ij,jk->ik"perkalian matriksA @ B

Notasi "ii->i", "i,i->i", dan "ij->" belum kita uji — blok berikut melengkapinya:

python
import numpy as np

M = np.array([[1, 2],
              [3, 4]])
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.einsum("ii->i", M))
print(np.array_equal(np.einsum("ii->i", M), M.diagonal()))
print(np.einsum("i,i->i", a, b))
print(np.array_equal(np.einsum("i,i->i", a, b), a * b))
print(np.einsum("ij->", M))

Output yang Dihasilkan:

text
[1 4]
True
[ 4 10 18]
True
10

Bedah baris demi baris:

  1. Baris 7 (np.einsum("ii->i", M)): huruf yang sama muncul dua kali pada satu operand → ambil elemen-diagonal M[0,0] dan M[1,1] → [1 4], identik dengan M.diagonal() (Chapter Diagonal dan Trace).
  2. Baris 9 (np.einsum("i,i->i", a, b)): i sama di kedua operand tetapi tetap tertulis di output → perkalian elemen-demi-elemen tanpa penjumlahan, identik dengan a * b.
  3. Baris 11 (np.einsum("ij->", M)): kedua sumbu dikontraksi → penjumlahan seluruh elemen, identik dengan M.sum().