Bayangkan tiga struk belanja dari toko yang sama. Struk pertama: 2 kg gula dan 1 kg kopi, total Rp35000. Struk kedua: 1 kg gula dan 3 kg kopi, total Rp60000. Struk ketiga: 3 kg gula dan 2 kg kopi, total Rp70000. Dua harga yang tidak diketahui, tetapi tiga persamaan — sistem overdetermined seperti ini hampir selalu tidak punya solusi eksak, karena tiga syarat harus dipenuhi dua angka sekaligus.

Least squares menawarkan kompromi yang terukur: cari harga yang membuat jumlah kuadrat selisih (galat) sekecil mungkin. Metode ini bukan sekadar perbaikan: metode inilah fondasi regresi linear. NumPy menyediakannya lewat np.linalg.lstsq, dan bab ini membedah keempat nilai kembaliannya, makna residuals, serta kaitannya dengan pemodelan linear.

Sistem Overdetermined dan np.linalg.lstsq #

Kita terjemahkan cerita struk belanja menjadi matriks A (3x2) dan vektor b (3 elemen), lalu panggil lstsq:

python
import numpy as np

A = np.array([[2.0, 1.0],
              [1.0, 3.0],
              [3.0, 2.0]])
b = np.array([35000.0, 60000.0, 70000.0])
x, residuals, rank, sv = np.linalg.lstsq(A, b, rcond=None)
print(x)
print(residuals)
print(A @ x - b)
print(rank)
print(sv)

Output yang Dihasilkan:

text
[11400. 16400.]
[26999999.99999998]
[ 4200.   600. -3000.]
2
[5.         1.73205081]

Bedah baris demi baris:

  1. Baris 3-5 (A): setiap baris adalah satu struk — [jumlah gula, jumlah kopi].
  2. Baris 6 (b): total belanja tiap struk.
  3. Baris 7: lstsq mengembalikan empat nilai sekaligus; rcond=None memakai ambang default dan meniadakan peringatan versi lama.
  4. Baris 8 (x): solusi kompromi [11400. 16400.] — dugaan harga gula Rp11400 dan kopi Rp16400. Harga ini tidak memenuhi ketiga struk, dan memang tidak dijanjikan demikian.
  5. Baris 9 (residuals): jumlah kuadrat galat (SSE). Nilai matematisnya 27000000; digit belakang yang sedikit menyimpang adalah jejak aritmetika float — tema yang ditutup pada Chapter Stabilitas Numerik.
  6. Baris 10 (A @ x - b): galat tiap struk: +4200, +600, -3000. Kuadratkan dan jumlahkan, hasilnya persis nilai di residuals.
  7. Baris 11 (rank): rank A adalah 2 — kedua kolom bebas, sehingga solusi kompromi tunggal.
  8. Baris 12 (sv): nilai singular A yang terurut menurun, pratinjau bahwa lstsq bekerja di atas SVD.
Nilai kembaliBentukArti
x(n,)solusi least squares
residuals(1,)jumlah kuadrat galat (SSE)
rankskalarrank matriks A
sv(n,)nilai singular A terurut menurun

Residuals: Penanda Ada Tidaknya Solusi Eksak #

Bentuk array residuals membawa informasi yang mudah terlewat: sistem punya solusi eksak atau tidak. Kita bandingkan dengan sistem yang konsisten — cukup dua struk pertama, dua persamaan untuk dua harga:

python
import numpy as np

A2 = np.array([[2.0, 1.0],
               [1.0, 3.0]])
b2 = np.array([35000.0, 60000.0])
x2, res2, rank2, sv2 = np.linalg.lstsq(A2, b2, rcond=None)
print(x2)
print(res2.shape)
print(res2)

Output yang Dihasilkan:

text
[ 9000. 17000.]
(0,)
[]
  1. Baris 3-4 (A2): hanya dua baris pertama dari A sebelumnya — sistem persegi 2x2.
  2. Baris 6: lstsq tetap bisa dipakai; ia menyelesaikan sistem persis maupun overdetermined.
  3. Baris 7 (x2): solusi [9000. 17000.] memenuhi kedua persamaan persis: 2·9000 + 17000 = 35000 dan 9000 + 3·17000 = 60000.
  4. Baris 8 (res2.shape): (0,) — array residuals kosong.
  5. Baris 9 (res2): [], bukan 0.0. Ketika solusi eksak ada, lstsq sama sekali tidak menghitung galat.

Regresi Linear sebagai Least Squares #

Model linear y = ax + b pada setiap titik data menghasilkan satu persamaan: a·x_i + b = y_i. Susun semua persamaan itu menjadi sistem X @ [a, b] = y, dan regresi linear berubah menjadi least squares biasa:

python
import numpy as np

x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y = np.array([2.2, 3.8, 6.1, 7.9, 10.0])
X = np.column_stack([x, np.ones_like(x)])
print(X)
a, b = np.linalg.lstsq(X, y, rcond=None)[0]
print(a, b)
x_baru = np.array([6.0, 7.0])
prediksi = a * x_baru + b
print(prediksi)

Output yang Dihasilkan:

text
[[1. 1.]
 [2. 1.]
 [3. 1.]
 [4. 1.]
 [5. 1.]]
1.97 0.08999999999999965
[11.91 13.88]
  1. Baris 3-4: data pengamatan — x jumlah poster promosi, y penjualan dalam juta rupiah, lima hari.
  2. Baris 5 (X = np.column_stack(...)): susun design matrix: kolom pertama berisi x, kolom kedua berisi konstanta 1. Kolom satu inilah yang membawa intersep b.
  3. Baris 6 (print(X)): bentuk (5, 2) — lima persamaan, dua parameter, persis pola overdetermined di awal bab.
  4. Baris 7: X @ [a, b] menghasilkan a·x + b·1 untuk setiap baris; lstsq mencari pasangan [a, b] terbaik, dan [0] mengambil elemen pertama tuple hasil.
  5. Baris 8: kemiringan a = 1.97 dan intersep b ≈ 0.09. Model terpasang: penjualan ≈ 0.09 juta rupiah + 1.97 juta rupiah per poster.
  6. Baris 10-11: prediksi untuk x baru dihitung langsung dari rumus model: a * x_baru + b menghasilkan [11.91 13.88].

Pola design matrix ini meluas bebas: tambah satu kolom untuk setiap fitur, dan lstsq tetap mengembalikan satu koefisien per kolom. Berbeda dengan jalan pintas inv(X.T @ X) @ X.T @ y, fungsi lstsq menghitung lewat SVD sehingga tetap stabil pada data yang kurang kondisi baik — perbandingan dua jalur perhitungan semacam ini dibahas tuntas pada Chapter Decompositions dan Stabilitas Numerik.