Perkalian matriks

Operator @, fungsi matmul, dan aturan dimensi.

Daftar isi

Perkalian matriks adalah operasi inti aljabar linear yang muncul di hampir semua perhitungan data, mulai dari transformasi koordinat, regresi, sampai lapisan model pembelajaran mesin. Topik ini membahas operator @ dan fungsi np.matmul, aturan dimensi yang mengikat keduanya, perbedaan perkalian matriks dengan perkalian per elemen, perkalian antara matriks dan vektor, serta sifat perkalian matriks yang tidak komutatif. Setelah topik ini dipahami, ekspresi @ pada kode data nyata dapat dibaca dengan benar dan error dimensi dapat didiagnosis langsung dari pesannya.

Operator @ dan np.matmul#

Python menyediakan operator @ khusus untuk perkalian matriks sejak Python 3.5, sedangkan NumPy menyediakan fungsi np.matmul yang melakukan hal yang sama. Pada array dua dimensi, keduanya setara sepenuhnya, sehingga pilihan bentuknya hanya soal keterbacaan. Bentuk operator menjadi pilihan utama karena mirip dengan notasi perkalian matriks pada buku teks, sedangkan bentuk fungsi berguna ketika operasinya perlu dikirim sebagai argumen ke fungsi lain.

Contoh berikut mengalikan matriks A berukuran (2, 3) dengan matriks B berukuran (3, 2).

python
import numpy as np

A = np.array([[1, 2, 3],
              [4, 5, 6]])
B = np.array([[7, 8],
              [9, 10],
              [11, 12]])
C = A @ B
print(C)
print(np.matmul(A, B))
print(C.shape)
text Keluaran
[[ 58  64]
 [139 154]]
[[ 58  64]
 [139 154]]
(2, 2)

Setiap elemen hasil dihitung dari satu baris A dan satu kolom B yang dikalikan berpasangan, lalu dijumlahkan. Elemen pojok kiri atas bernilai 58 karena 1×7+2×9+3×11=581 \times 7 + 2 \times 9 + 3 \times 11 = 58. Rumus umumnya dituliskan sebagai berikut.

Cij=∑k=1nAik Bkj C_{ij} = \sum_{k=1}^{n} A_{ik} \, B_{kj}

Notasi tersebut menyatakan bahwa elemen baris ke-i dan kolom ke-j pada hasil merupakan jumlah perkalian seluruh pasangan elemen dari baris ke-i pada A dan kolom ke-j pada B. Pemanggilan np.matmul pada keluaran menghasilkan nilai yang persis sama dengan operator @, sedangkan pencetakan C.shape menegaskan bentuk hasilnya, yaitu (2, 2).

Aturan dimensi perkalian matriks#

Perkalian matriks hanya sah apabila dimensi tengah kedua operand cocok, yaitu banyaknya kolom operand pertama sama dengan banyaknya baris operand kedua. Aturan ini dituliskan sebagai (m,n)@(n,p)→(m,p)(m, n) @ (n, p) \rightarrow (m, p), sehingga hasilnya membawa dimensi luar kedua operand. Diagram berikut merangkum aturan tersebut.

A(m, n)@B(n, p)=C(m, p)dimensi tengah harus sama, yaitu n
Aturan dimensi perkalian matriks (m, n) @ (n, p) menghasilkan (m, p)

Apabila aturan itu dilanggar, NumPy menghentikan operasi dengan ValueError, seperti pada percobaan mengalikan matriks (2, 3) dengan matriks (2, 2) berikut.

python
import numpy as np

A = np.array([[1, 2, 3],
              [4, 5, 6]])
B = np.array([[1, 2],
              [3, 4]])
try:
    print(A @ B)
except Exception as e:
    print(f"{type(e).__name__}: {e}")
text Keluaran
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)

Pesan errornya menunjuk operand kedua sebagai yang bermasalah pada dimensi intinya, yaitu barisnya. Angka 3 pada A adalah banyaknya kolom A, sedangkan angka 2 pada B adalah banyaknya baris B, dan keduanya tidak sama. Perbaikannya adalah menyusun ulang data agar dimensi tengahnya cocok, misalnya dengan transpose atau dengan menata ulang elemen operand, atau menerima bahwa operasi yang dimaksud memang bukan perkalian matriks.

Perkalian matriks versus perkalian per elemen#

Operator * pada array NumPy selalu berarti perkalian per elemen, sedangkan @ berarti perkalian matriks. Keduanya sah untuk matriks persegi yang sama, tetapi hasilnya berbeda total, sehingga pemilihan operator yang keliru tidak selalu memunculkan error dan justru berisiko menghasilkan angka yang salah tanpa peringatan apa pun. Contoh berikut menjalankan kedua operator pada dua matriks yang sama.

python
import numpy as np

A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5, 6],
              [7, 8]])
print(A * B)
print(A @ B)
text Keluaran
[[ 5 12]
 [21 32]]
[[19 22]
 [43 50]]

Pada perkalian per elemen, pasangan yang dikalikan adalah elemen pada posisi yang sama, sehingga elemen pojok kiri atasnya bernilai 1×5=51 \times 5 = 5. Pada perkalian matriks, pasangannya adalah baris dan kolom, sehingga elemen yang sama dihitung dari 1×5+2×7=191 \times 5 + 2 \times 7 = 19. Perbedaan kedua operasi dirangkum pada tabel berikut.

Aspek* (per elemen)@ (perkalian matriks)
Syarat bentukkedua array sebangundimensi tengah sama
Bentuk hasilsama dengan bentuk operandnya(m,p)(m, p)
Pasangan yang dihitungelemen pada posisi yang samabaris dikalikan kolom

Tabel tersebut menegaskan bahwa syarat bentuk keduanya berbeda, sehingga banyak ekspresi yang sah untuk @ tidak sah untuk * dan sebaliknya. Kebiasaan yang aman adalah membaca * sebagai operasi nilai pada posisi yang sama dan menyisakan @ untuk semua perhitungan yang melibatkan baris dan kolom. Konvensi ini juga membuat kode lebih mudah diaudit, karena setiap @ yang muncul dapat langsung diasosiasikan dengan transformasi aljabar linear.

Perkalian matriks dengan vektor#

Operand pada @ tidak harus matriks keduanya. Apabila operand kedua berupa array satu dimensi, array tersebut diperlakukan sebagai vektor kolom sementara yang dimensi keduanya bernilai satu, sedangkan operand pertama yang satu dimensi diperlakukan sebagai vektor baris sementara. Hasil perkalian matriks (m,n)(m, n) dengan vektor berukuran (n,)(n,) adalah vektor berukuran (m,)(m,).

python
import numpy as np

A = np.array([[2, 1],
              [0, 3]])
v = np.array([1, 2])
w = A @ v
print(w)
print(w.shape)
text Keluaran
[4 6]
(2,)

Nilai 4 dihitung dari 2×1+1×22 \times 1 + 1 \times 2, sedangkan nilai 6 dihitung dari 0×1+3×20 \times 1 + 3 \times 2. Pola (m,n)@(n,)→(m,)(m, n) @ (n,) \rightarrow (m,) ini adalah bentuk yang paling sering muncul pada pekerjaan data, misalnya ketika banyak baris sampel dikalikan dengan satu vektor bobot berukuran (n,)(n,), sehingga setiap baris data dipetakan menjadi satu nilai keluaran. Arah sebaliknya juga sah, yaitu v @ A dengan v berukuran (n,)(n,) dan A berukuran (n,p)(n, p), yang memperlakukan v sebagai vektor baris dan menghasilkan vektor berukuran (p,)(p,).

Urutan perkalian tidak dapat ditukar#

Perkalian matriks tidak komutatif, yaitu A@BA @ B umumnya tidak sama dengan B@AB @ A meskipun keduanya sah secara dimensi. Sifat ini berbeda dari perkalian bilangan biasa yang urutannya bebas ditukar. Contoh berikut mengalikan dua matriks persegi yang sama dalam dua urutan.

python
import numpy as np

A = np.array([[1, 2],
              [0, 1]])
B = np.array([[1, 0],
              [3, 1]])
print(A @ B)
print(B @ A)
print(np.allclose(A @ B, B @ A))
text Keluaran
[[7 2]
 [3 1]]
[[1 2]
 [3 7]]
False

Kedua hasil berbeda pada elemen diagonal utamanya, yaitu 7 berbanding 1 pada posisi pertama dan 1 berbanding 7 pada posisi kedua, sedangkan elemen di luar diagonalnya bernilai sama, dan np.allclose menegaskan ketidaksamaan kedua matriks itu. Sifat ini wajar secara geometris, karena mengalikan dengan matriks berarti menerapkan transformasi, dan urutan penerapan dua transformasi memengaruhi hasil akhirnya. Konsekuensi praktisnya, urutan operand pada ekspresi @ adalah bagian dari makna kode, sehingga penataan ulang ekspresi tetap harus menjaga urutannya. Kasus istimewa yang memenuhi A@B=B@AA @ B = B @ A memang ada, misalnya ketika salah satu operandnya adalah matriks identitas, tetapi sifat itu tidak berlaku umum.

Kesalahan umum#

Kesalahan pertama adalah menggunakan * padahal yang dimaksud adalah perkalian matriks. Pada matriks persegi, kesalahan ini tidak memunculkan error karena kedua operator sah, sehingga hasil yang salah baru diketahui setelah angkanya diverifikasi. Perbaikannya adalah menjadikan @ satu-satunya operator untuk perkalian matriks dan menyisakan * untuk operasi per elemen.

Kesalahan kedua adalah memanggil @ pada list Python, bukan pada array NumPy.

python
import numpy as np

A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
try:
    print(A @ B)
except Exception as e:
    print(f"{type(e).__name__}: {e}")
text Keluaran
TypeError: unsupported operand type(s) for @: 'list' and 'list'

Operator @ tidak dikenal oleh list, sehingga Python menolaknya dengan TypeError. Perbaikannya adalah mengonversi kedua operand menjadi array dengan np.array terlebih dahulu, lalu mengalikannya.

Kesalahan ketiga adalah membaca pesan ValueError dari @ sebagai tanda data yang rusak. Pesan tersebut justru deskriptif karena menyebut dimensi yang bentrok beserta ukurannya, sehingga langkah perbaikannya adalah memeriksa .shape kedua operand, bukan mengubah data tanpa arah.

Ringkasan#

  • Operator @ dan np.matmul mengalikan matriks, yaitu setiap elemen hasil merupakan jumlah perkalian satu baris operand pertama dengan satu kolom operand kedua.
  • Aturan dimensinya adalah (m,n)@(n,p)→(m,p)(m, n) @ (n, p) \rightarrow (m, p), dan pelanggarannya memunculkan ValueError yang menyebut dimensi yang bentrok.
  • Operator * tetap berarti perkalian per elemen dan menghasilkan angka yang berbeda dari @ pada matriks yang sama.
  • Perkalian matriks tidak komutatif, sehingga urutan operand merupakan bagian dari makna ekspresi.

Referensi#

  • numpy.matmul — NumPy — Dokumentasi resmi aturan perkalian matriks, termasuk penanganan operand berdimensi satu.
  • PEP 465 — Python — Proposal yang memperkenalkan operator @ khusus untuk perkalian matriks di Python 3.5.