Dekomposisi eigen pada Chapter Eigenvalue dan Eigenvector hanya berlaku untuk matriks persegi. Singular Value Decomposition (SVD) jauh lebih umum: setiap matriks, persegi maupun tidak, dapat difaktorkan menjadi perkalian tiga matriks dengan bentuk A = U Σ Vh, di mana U dan V orthogonal dan Σ diagonal berisi nilai singular. Karena berlaku universal, SVD menjadi mesin di balik banyak alat: perhitungan rank, pseudo-inverse, kompresi data, sampai analisis komponen utama.

Di NumPy, satu panggilan np.linalg.svd(A) mengembalikan ketiga komponen tersebut. Bab ini membedah anatomi hasilnya, membuktikan bahwa A dapat direkonstruksi dari potongan-potongannya, lalu menutup dengan ide kompresi: mengapa membuang nilai singular kecil hampir tidak mengubah matriks. Sebagai penghubung dengan section Operasi Matriks, kita tetap memakai matriks A = [[1, 2], [3, 4]].

Anatomi Hasil np.linalg.svd #

np.linalg.svd mengembalikan tuple berisi tiga array. Ada dua konvensi NumPy yang perlu kita kenali sejak awal: matriks V dikembalikan dalam keadaan sudah di-transpose (namanya Vh), dan Σ tidak dikembalikan sebagai matriks, melainkan sebagai array 1-D berisi diagonalnya yang terurut menurun.

python
import numpy as np

A = np.array([[1, 2],
              [3, 4]])
U, S, Vh = np.linalg.svd(A)
print(U)
print(S)
print(Vh)
print(U.shape, S.shape, Vh.shape)

Output yang Dihasilkan:

text
[[-0.40455358 -0.9145143 ]
 [-0.9145143   0.40455358]]
[5.4649857  0.36596619]
[[-0.57604844 -0.81741556]
 [ 0.81741556 -0.57604844]]
(2, 2) (2,) (2, 2)

Bedah baris demi baris:

  1. Baris 3-4 (A = np.array(...)): matriks 2x2 yang sama dengan contoh pada section Operasi Matriks, sehingga angkanya mudah dilacak.
  2. Baris 5 (U, S, Vh = np.linalg.svd(A)): fungsi mengembalikan tuple (U, S, Vh); tiga variabel menampung hasilnya sekaligus.
  3. Baris 6 (print(U)): U berordo (2, 2) dengan kolom-kolom orthonormal — panjang tiap kolom 1 dan saling tegak lurus.
  4. Baris 7 (print(S)): S adalah array 1-D, bukan matriks: [5.4649857 0.36596619]. Nilainya terurut menurun; nilai singular terbesar selalu berada di indeks 0.
  5. Baris 8 (print(Vh)): Vh adalah V yang sudah di-transpose, sehingga baris-barisnya orthonormal.
  6. Baris 9: bentuk (2, 2) (2,) (2, 2) menegaskan bahwa S memang 1-D.

Tabel berikut merangkum peran ketiga hasil terhadap bentuk A = U Σ Vh:

HasilBentukIsiPeran dalam A = U Σ Vh
U(m, m)kolom orthonormalarah-arah di ruang kolom A
S(k,)nilai singular menurunbesaran peregangan tiap arah
Vh(n, n)baris orthonormalarah-arah di ruang baris A

Rekonstruksi A dari U, S, dan Vh #

Uji paling meyakinkan bagi sebuah dekomposisi adalah mengalikan kembali potongan-potongannya. Perkalian U @ diag(S) @ Vh harus menghasilkan A — persis secara matematis, dan nyaris persis dalam aritmetika float.

python
import numpy as np

A = np.array([[1, 2],
              [3, 4]])
U, S, Vh = np.linalg.svd(A)
print(np.diag(S))
rekon = U @ np.diag(S) @ Vh
print(rekon)
print(np.allclose(rekon, A))
tol = S.max() * max(A.shape) * np.finfo(S.dtype).eps
print(tol)
print((S > tol).sum())
print(np.linalg.matrix_rank(A))

Output yang Dihasilkan:

text
[[5.4649857  0.        ]
 [0.         0.36596619]]
[[1. 2.]
 [3. 4.]]
True
2.4269411832285226e-15
2
2

Mari kita telaah baris-baris barunya:

  1. Baris 6 (print(np.diag(S))): np.diag(S) menyusun matriks diagonal 2x2 dari vektor S — inilah wujud Σ yang sesungguhnya.
  2. Baris 7-8 (rekon = ...): perkalian U @ Σ @ Vh mengembalikan susunan asli [[1. 2.] [3. 4.]].
  3. Baris 9 (np.allclose(rekon, A)): True berarti hasil sama dalam toleransi default; perbedaannya hanya pada digit belakang representasi float.
  4. Baris 10 (tol = ...): ambang batas rank: nilai singular terbesar dikali dimensi terbesar dikali epsilon mesin (~2.22e-16 untuk float64). Rumus inilah yang dipakai np.linalg.matrix_rank.
  5. Baris 12-13: kedua nilai singular berada di atas tol, sehingga hitungan manual (S > tol).sum() menghasilkan 2 — sama persis dengan matrix_rank(A).

Dua nilai singular bernilai jauh dari nol berarti A membawa dua dimensi informasi bebas. Bab berikutnya, Matrix Rank, membahas gagasan ini secara khusus.

Ide Kompresi: Simpan k Nilai Singular Teratas #

Karena S terurut menurun, memotong nilai-nilai kecil di ekor hampir tidak mengubah matriks — inilah dasar kompresi berbasis SVD. Kita uji pada matriks gradien 6x6 yang entrinya G[i, j] = i + j:

python
import numpy as np

G = (np.arange(6).reshape(-1, 1) + np.arange(6).reshape(1, -1)).astype(float)
U, S, Vh = np.linalg.svd(G)
print(S)
print(np.linalg.norm(G))
for k in (1, 2):
    Gk = U[:, :k] @ np.diag(S[:k]) @ Vh[:k, :]
    err = np.linalg.norm(G - Gk)
    print(f"k={k}: error = {err:.2e}")

Output yang Dihasilkan:

text
[3.31659021e+01 3.16590212e+00 1.44782968e-15 4.38184935e-16
 1.21584919e-16 2.42274449e-17]
33.31666249791537
k=1: error = 3.17e+00
k=2: error = 1.62e-14
  1. Baris 3: susun gradien 6x6; entri bertambah satu per langkah baris maupun kolom.
  2. Baris 4: SVD penuh menghasilkan enam nilai singular.
  3. Baris 5 (print(S)): hanya dua nilai besar — 33.17 dan 3.17 — sedangkan empat sisanya berordo 1e-15 atau lebih kecil, praktis nol. Secara matematis matriks gradien ini memang ber-rank 2, dan SVD menangkapnya secara numerik.
  4. Baris 6: np.linalg.norm(G) menghitung ukuran total matriks (norm Frobenius): 33.3167. Angka ini menjadi pembanding seberapa besar error pemotongan.
  5. Baris 8 (Gk = ...): susun ulang memakai hanya k kolom pertama U, k nilai pertama S, dan k baris pertama Vh — versi terkompresi dari G.
  6. Baris 10: error diukur sebagai jarak Frobenius antara G asli dan versi terpotong.
k dipertahankanErrorMakna
13.17e+00sekitar 9,5% dari ukuran total 33.32; satu komponen belum cukup
21.62e-14praktis nol; matriks memang ber-rank 2

Dari tabel terlihat polanya: nilai singular kecil boleh dibuang karena kontribusinya sekecil galat float. Pada data nyata yang besar, pola yang sama dipakai untuk memampatkan gambar dan matriks rekomendasi — simpan sedikit nilai teratas, buang sisanya. Batas antara “kecil” dan “bermakna” inilah yang diukur secara formal oleh rank pada Chapter Matrix Rank.