Di balik hampir semua algoritma machine learning klasik terdapat operasi array: perkalian matriks, rata-rata, dan pengurangan yang bekerja per elemen. Itulah sebabnya scikit-learn dibangun di atas NumPy — dan itulah alasan kita bisa membongkar mekanisme pelatihan model tanpa pustaka model apa pun, hanya dengan array.
Sebagai studi kasus, kita memprediksi gaji (juta rupiah) dari pengalaman kerja (tahun) menggunakan sepuluh data fiktif. Empat langkah yang kita tempuh:
- Menstandardisasi fitur dengan z-score.
- Melatih regresi linear dengan gradient descent murni operasi array.
- Menghitung solusi tertutup dengan
np.linalg.lstsq. - Membandingkan hasilnya dengan
LinearRegressionscikit-learn.
Menyiapkan Data dan Standardisasi z-score #
Fitur dengan skala besar membuat gradient descent bergerak lambat dan berayun. Standardisasi z-score memusatkan fitur ke rata-rata 0 dengan deviasi standar 1, sehingga langkah pembaruan model menjadi stabil:
import numpy as np
pengalaman = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
gaji = np.array([5.2, 6.1, 6.8, 7.9, 8.6, 9.5, 10.2, 11.1, 12.0, 12.9])
print("rata-rata lama:", pengalaman.mean(), "deviasi lama:", pengalaman.std())
x = (pengalaman - pengalaman.mean()) / pengalaman.std()
print("rata-rata baru:", x.mean())
print("deviasi baru :", x.std())
print(np.round(x, 4))Output yang Dihasilkan:
rata-rata lama: 5.5 deviasi lama: 2.8722813232690143
rata-rata baru: -6.661338147750939e-17
deviasi baru : 1.0
[-1.5667 -1.2185 -0.8704 -0.5222 -0.1741 0.1741 0.5222 0.8704 1.2185
1.5667]Mari kita bedah:
- Baris 6 (
(pengalaman - mean) / std): dua operasi vectorization menjalankan rumus z-score(nilai - rata-rata) / deviasiuntuk seluruh kolom sekaligus. - Baris 7 (
x.mean()): rata-rata baru tampak-6.66e-17, bukan tepat0.0— sisa presisi floating-point yang wajar. Uji dengannp.isclose(x.mean(), 0.0)yang mengembalikanTrue(lihat Chapter Floating Point Comparison). - Baris 8-9: deviasi baru tepat
1.0, dan z-score tiap nilai tercetak teratur — fitur kini berada pada skala yang ramah untuk pelatihan.
Melatih Model dengan Gradient Descent #
Model kita sederhana: prediksi = w * x + b, dengan w (bobot) dan b (bias) sebagai dua angka yang harus ditemukan. Kualitas model diukur MSE — rata-rata kuadrat selisih prediksi dan nilai asli. Gradient descent mengulang dua langkah: hitung gradien MSE terhadap w dan b, lalu kurangi keduanya dengan learning_rate dikali gradien:
import numpy as np
pengalaman = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
gaji = np.array([5.2, 6.1, 6.8, 7.9, 8.6, 9.5, 10.2, 11.1, 12.0, 12.9])
x = (pengalaman - pengalaman.mean()) / pengalaman.std()
w, b, lr = 0.0, 0.0, 0.05
for epoch in range(1, 501):
error = w * x + b - gaji
w -= lr * 2 * np.mean(error * x)
b -= lr * 2 * np.mean(error)
if epoch in (1, 10, 50, 100, 500):
mse = np.mean((w * x + b - gaji) ** 2)
print(f"epoch {epoch:3d} -> MSE = {mse:.4f}")
print(f"model: gaji = {w:.4f} * x + {b:.4f}")Output yang Dihasilkan:
epoch 1 -> MSE = 70.8574
epoch 10 -> MSE = 10.6397
epoch 50 -> MSE = 0.0075
epoch 100 -> MSE = 0.0052
epoch 500 -> MSE = 0.0052
model: gaji = 2.4353 * x + 9.0300Penjelasan tiap baris kunci:
- Baris 8 (
error = w * x + b - gaji): seluruh sepuluh selisih prediksi dihitung sekaligus lewat vectorization — inilah loop yang biasanya ditulis per baris data. - Baris 9-10 (pembaruan
wdanb):np.mean(error * x)adalah turunan MSE terhadapw, dannp.mean(error)turunannya terhadapb; kedua parameter bergerak melawan arah gradien sebesarlrper langkah. - Baris 11-13: MSE dicetak pada beberapa epoch terpilih. Angkanya turun dari 70.86 menuju 0.0052 lalu mendatar — tanda model telah konvergen.
- Baris 14: model akhir
gaji = 2.4353 * x + 9.03;b = 9.03berarti prediksi untuk pengalaman rata-rata (z-score 0) adalah 9,03 juta rupiah, persis rata-rata gaji data kita.
Solusi Tertutup dengan np.linalg.lstsq #
Regresi linear sebenarnya punya jawaban analitik yang tidak butuh loop. Susun matriks desain berisi [x, 1] per baris, lalu serahkan ke np.linalg.lstsq — solver least squares yang lebih stabil daripada menyusun normal equation secara manual (dibahas penuh pada Chapter Least Squares):
import numpy as np
pengalaman = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
gaji = np.array([5.2, 6.1, 6.8, 7.9, 8.6, 9.5, 10.2, 11.1, 12.0, 12.9])
x = (pengalaman - pengalaman.mean()) / pengalaman.std()
A = np.column_stack([x, np.ones(x.size)])
koef, _, _, _ = np.linalg.lstsq(A, gaji, rcond=None)
print("bentuk A:", A.shape)
print("koefisien:", koef.round(4))
pred = A @ koef
print("MAE :", round(np.mean(np.abs(pred - gaji)), 4))Output yang Dihasilkan:
bentuk A: (10, 2)
koefisien: [2.4353 9.03 ]
MAE : 0.0565Yang perlu diperhatikan:
- Baris 6 (
np.column_stack): kolom z-score dan kolom angka satu disusun berdampingan menjadi matriks(10, 2); kolom satu inilah yang dianggapb. - Baris 7 (
np.linalg.lstsq): mengembalikan koefisien yang meminimalkan jumlah kuadrat error;rcond=Nonemenonaktifkan peringatan versi lama. - Baris 9: hasil
[2.4353, 9.03]cocok dengan gradient descent pada empat desimal — dua jalan berbeda menuju model yang sama. - Baris 10-11: rata-rata selisih mutlak (MAE) hanya 0,0565 juta rupiah, sekitar 56 ribu rupiah per prediksi.
Membandingkan dengan scikit-learn #
LinearRegression dari scikit-learn menyelesaikan persoalan yang sama lewat solver yang serupa. Perbedaannya ada pada antarmuka, bukan pada angkanya:
import numpy as np
from sklearn.linear_model import LinearRegression
pengalaman = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
gaji = np.array([5.2, 6.1, 6.8, 7.9, 8.6, 9.5, 10.2, 11.1, 12.0, 12.9])
x = (pengalaman - pengalaman.mean()) / pengalaman.std()
model = LinearRegression().fit(x.reshape(-1, 1), gaji)
print("sklearn coef :", round(model.coef_[0], 4))
print("sklearn intercept:", round(model.intercept_, 4))
A = np.column_stack([x, np.ones(x.size)])
koef, _, _, _ = np.linalg.lstsq(A, gaji, rcond=None)
print("identik dgn lstsq:", np.allclose(koef, [model.coef_[0], model.intercept_]))Output yang Dihasilkan:
sklearn coef : 2.4353
sklearn intercept: 9.03
identik dgn lstsq: True- Baris 7 (
fit(x.reshape(-1, 1), gaji)): scikit-learn menuntut fitur berbentuk(n_sampel, n_fitur), maka array satu dimensi di-reshapemenjadi(10, 1). - Baris 8-9:
coef_danintercept_adalahwdanbversi scikit-learn — nilainya sama dengan hasil NumPy kita. - Baris 12 (
np.allclose(...)):Truemengonfirmasi kedua solver mencapai solusi yang identik hingga toleransi floating-point.
| Aspek | Versi NumPy | scikit-learn |
|---|---|---|
| Melatih | loop pembaruan w, b | model.fit(X, y) |
| Prediksi | w * x + b | model.predict(X) |
| Parameter | array biasa, bisa diinspeksi | atribut coef_, intercept_ |
| Cocok untuk | memahami mekanisme | produksi dan eksperimen cepat |
Praktik Baik dan Jebakan #
Pengalaman dari latihan ini menyangkut tiga hal:
- Standardisasi dulu, latih kemudian. Tanpa z-score, fitur berskala besar membuat langkah gradient descent harus sangat kecil agar tidak melompat melewati titik minimum.
- Perhatikan bentuk array saat memakai pustaka lain. NumPy senang dengan vektor satu dimensi; scikit-learn menuntut dua dimensi — pesan error
reshapeakan sering kita temui (lihat Chapter NumPy dan scikit-learn). - Gunakan solver tertutup bila tersedia. Untuk regresi linear,
lstsqmemberi jawaban pasti tanpa memilihlearning_rateatau jumlah epoch; gradient descent tetap berharga sebagai dasar memahami model yang tidak punya solusi tertutup.