Membangun solusi portofolio berarti mengubah rencana menjadi kode yang setiap klaimnya dapat diperiksa ulang. Topik ini mengerjakan satu proyek utuh dari daftar perencanaan, yaitu pencatat konsumsi list rumah dengan regresi kuadrat terkecil, mulai dari berkas data, solusi utama, sampai verifikasi berlapis yang membandingkan beberapa jalur perhitungan. Setiap tahap berakhir pada keluaran yang dapat direproduksi dari folder proyek dengan perintah yang sama.
Memecah proyek menjadi langkah terverifikasi#
Daftar batas keberhasilan diurutkan menjadi langkah pengerjaan, dan setiap langkah berakhir pada satu bukti yang tercetak atau tersimpan. Urutan untuk proyek ini adalah menyiapkan berkas data, menyusun model dan menyelesaikannya, menyimpan koefisien hasilnya, lalu memeriksa artefak tersebut dengan skrip verifikasi yang terpisah. Tidak ada pernyataan tentang hasil yang dibiarkan tanpa keluaran, sebab keluaran itulah yang nanti dikutip dokumen portofolio. Apabila sebuah langkah belum menghasilkan bukti, langkah itu belum selesai dan pengerjaan tidak dilanjutkan ke langkah berikutnya.
Prinsip kerjanya sederhana, yaitu folder proyek bertambah satu berkas pada setiap langkah dan tidak ada berkas yang sifatnya sementara. Data dipisahkan dari kode agar verifikasi dapat membaca ulang sumber yang sama, dan koefisien hasil disimpan ke berkas agar pemeriksaannya menyentuh artefak akhir dan bukan salinan memori. Pola ini membuat setiap langkah dapat diulang di komputer lain tanpa penjelasan tambahan. Dua topik lain dalam modul ini menggunakan proyek yang sama, satu di sisi perencanaan dan satu di sisi penulisannya, sehingga seluruh siklus terlihat pada satu studi kasus.
Data pencatatan dan model garis#
Proyek ini menggunakan data pencatatan tiga puluh hari dari satu rumah, dengan kolom hari sebagai penanda urutan, jam_ac sebagai lama AC menyala pada hari itu, dan kwh sebagai konsumsi list harian. Kolom hari tidak masuk model karena hanya menandai urutan pencatatan, sedangkan dua kolom terakhir menjadi pasangan besaran yang dijelaskan. Satuan pada berkas ini adalah jam untuk kolom jam_ac dan kWh untuk kolom kwh, dengan koma sebagai pemisah antarkolom. Berkas datanya ditempatkan di folder proyek agar dapat dibaca ulang oleh skrip mana pun.
hari,jam_ac,kwh
1,9.4,11.94
2,5.4,7.65
3,10.3,11.78
4,8.5,10.14
5,1.4,4.61
6,11.7,13.54
7,9.2,10.98
8,9.5,10.98
9,1.8,4.44
10,5.6,8.19
11,4.6,7.37
12,11.1,12.83
13,7.8,9.60
14,9.9,11.70
15,5.5,7.92
16,3.0,5.83
17,6.8,9.29
18,1.0,4.13
19,10.0,11.94
20,7.7,9.77
21,9.2,11.12
22,4.4,7.16
23,11.7,12.63
24,10.7,12.18
25,9.4,11.03
26,2.6,5.19
27,5.8,8.03
28,0.8,4.40
29,2.1,4.68
30,8.3,10.59Modelnya adalah garis , dengan jam pemakaian AC, konsumsi harian, beban dasar rumah saat AC menyala nol jam, dan tambahan konsumsi untuk setiap jam AC. Seluruh tiga puluh persamaan pencatatan disusun menjadi bentuk matriks , dengan sebagai matriks perancang berukuran tiga puluh baris dan dua kolom. Parameter dicari melalui persamaan normal (normal equations), yaitu sistem dua persamaan yang timbul ketika seluruh residual dibuat ortogonal terhadap kolom-kolom .
Solusi utama dengan persamaan normal#
Skrip utama membaca berkas data, menyusun matriks perancang dengan kolom pertama berisi satu agar intersep ikut terhitung, lalu menyelesaikan persamaan normal menggunakan np.linalg.solve. Residual dihitung sebagai selisih data terhadap garis, dan akar kuadrat tengahnya meringkas besarnya dalam satu angka. Terakhir, koefisien ditulis ke berkas hasil_model.txt supaya skrip verifikasi memeriksa artefak tersimpan dan bukan variabel yang masih berada di memori. Berkas model sengaja berisi dua baris angka saja agar isinya dapat diperiksa secara langsung.
import numpy as np
# baca data pencatatan 30 hari: kolom hari, jam_ac, dan kwh
data = np.genfromtxt("data_konsumsi.csv", delimiter=",", skip_header=1)
jam = data[:, 1]
kwh = data[:, 2]
# matriks perancang: kolom pertama berisi 1 untuk intersep, kolom kedua jam AC
X = np.column_stack([np.ones_like(jam), jam])
# persamaan normal (X^T X) w = X^T y diselesaikan sebagai sistem linear
w = np.linalg.solve(X.T @ X, X.T @ kwh)
residual = kwh - X @ w
rms = np.sqrt(np.mean(residual**2))
prediksi_8 = w[0] + w[1] * 8.0
print(f"jumlah hari data : {len(jam)}")
print(f"intersep w0 : {w[0]:.4f} kWh")
print(f"koefisien w1 : {w[1]:.4f} kWh per jam AC")
print(f"residual RMS : {rms:.4f} kWh")
print(f"prediksi 8 jam AC : {prediksi_8:.4f} kWh")
# simpan model agar dapat diperiksa ulang oleh skrip verifikasi
with open("hasil_model.txt", "w") as f:
f.write(f"{w[0]:.8f}\n{w[1]:.8f}\n")jumlah hari data : 30
intersep w0 : 3.2710 kWh
koefisien w1 : 0.8456 kWh per jam AC
residual RMS : 0.2768 kWh
prediksi 8 jam AC : 10.0355 kWhKoefisien yang diperoleh terbaca masuk akal. Nilai menyatakan beban dasar sekitar 3.27 kWh per hari saat AC tidak menyala, sedangkan menyatakan tambahan sekitar 0.85 kWh untuk setiap jam AC. Residual RMS sebesar 0.2768 kWh tergolong kecil dibanding rentang data yang berada di antara 4.13 dan 13.54 kWh, sehingga garis menjelaskan sebagian besar variasi. Prediksi untuk delapan jam AC adalah 10.0355 kWh, dan angka pada jam pemakaian inilah yang ditampilkan ulang oleh demo pada topik penulisan.
Tiga uji pada model tersimpan#
Verifikasi ditempatkan pada berkas terpisah yang membaca ulang berkas data dan berkas model dari disk. Pemisahan ini penting karena verifikasi yang berbagi variabel dengan skrip utama hanya menguji salinan memori yang sama, sehingga berkas model yang rusak tidak akan pernah diketahui. Tiga uji dijalankan, dua di antaranya membandingkan koefisien melalui jalur perhitungan yang berbeda, dan satu memeriksa properti geometris residualnya.
import numpy as np
# baca ulang data dan model tersimpan secara terpisah dari skrip utama
data = np.genfromtxt("data_konsumsi.csv", delimiter=",", skip_header=1)
jam, kwh = data[:, 1], data[:, 2]
X = np.column_stack([np.ones_like(jam), jam])
with open("hasil_model.txt") as f:
w = np.array([float(f.readline()), float(f.readline())])
# uji 1: koefisien cocok dengan np.linalg.lstsq yang menyelesaikan masalah yang sama
w_lstsq = np.linalg.lstsq(X, kwh, rcond=None)[0]
print("uji 1 koefisien vs lstsq :", np.allclose(w, w_lstsq))
# uji 2: residual ortogonal terhadap kolom X, yaitu X^T r mendekati nol
# toleransi diperlonggar karena model tersimpan dibulatkan sampai 8 tempat desimal
r = kwh - X @ w
print("uji 2 X^T r mendekati nol :", np.allclose(X.T @ r, 0.0, atol=1e-4))
print(" nilai terbesar |X^T r| :", np.max(np.abs(X.T @ r)))
# uji 3: koefisien cocok dengan np.polyfit yang memakai jalur perhitungan lain
slope, intersep = np.polyfit(jam, kwh, 1)
print("uji 3 koefisien vs polyfit :", np.allclose(w, [intersep, slope]))uji 1 koefisien vs lstsq : True
uji 2 X^T r mendekati nol : True
nilai terbesar |X^T r| : 8.816799948033216e-06
uji 3 koefisien vs polyfit : TrueUji pertama dan uji ketiga menyelesaikan masalah kuadrat terkecil yang sama melalui np.linalg.lstsq dan np.polyfit, dua fungsi yang algoritmenya berbeda dari np.linalg.solve, dan kedua uji itu mengembalikan True. Uji kedua memeriksa yang pada solusi eksak seharusnya sama dengan nol. Nilai terbesarnya tercetak dan bukan nol karena berkas model menyimpan koefisien sampai delapan tempat desimal. Pembulatannya menyisakan selisih orde pada koefisien, dan setelah dikalikan skala yang berorde ribuan, sisanya naik ke orde . Toleransi atol=1e-4 tetap jauh di atas sisanya dan jauh di bawah skala data yang berorde ratusan, sehingga uji tetap peka terhadap model yang benar-benar rusak.
Kesalahan umum#
Kesalahan berikut muncul berulang ketika proyek regresi pertama kali dikerjakan. Ketiganya justru terdeteksi oleh verifikasi, dan itulah alasan verifikasi tidak boleh dilewati meskipun kodenya terasa benar.
- Intersep menyatu dengan kemiringan sehingga prediksi nol jam tidak masuk akal. Penyebabnya adalah kolom konstanta 1 yang tidak disertakan pada matriks , dan perbaikannya menyusun kolom tersebut dengan
np.ones_likesebelum sistem diselesaikan. - Perbandingan koefisien dengan
==mengembalikanFalsepadahal kedua jalurnya benar. Penyebabnya adalah sisa aritmetika pecahan biner yang membuat dua hasil nyaris sama tidak pernah identik bit demi bit, dan perbaikannya menggunakannp.allclosedengan toleransi yang dinyatakan terbuka. - Verifikasi selalu mengembalikan
Truebahkan setelah data diubah. Penyebabnya adalah verifikasi yang membaca variabel memori dari skrip utama dan bukan berkas tersimpan, dan perbaikannya memisahkan berkas verifikasi yang membaca ulang data serta model dari disk.
Ringkasan#
- Proyek dipecah menjadi tahapan yang masing-masing berakhir pada keluaran tercetak atau berkas tersimpan, sehingga setiap klaim memiliki bukti.
- Model garis diselesaikan melalui persamaan normal dengan kolom konstanta sebagai tempat intersep.
- Verifikasi pada berkas terpisah membaca ulang artefak, lalu membandingkan tiga jalur perhitungan dan properti menggunakan
np.allclose. - Toleransi dinyatakan terbuka ketika artefak tersimpan dibulatkan, dan perbandingan
==tidak digunakan untuk bilangan pecahan.
Referensi#
- NumPy 2.1 documentation — NumPy
— Halaman
np.linalg.solve,np.linalg.lstsq,np.polyfit, dannp.allcloseyang menjadi tulang punggung solusi serta verifikasinya. - Mathematics for Machine Learning — Cambridge University Press — Bab 9 memperkenalkan regresi linear sebagai masalah aljabar linear beserta solusi persamaan normalnya.