Angka loss dari pelatihan tidak berarti apa-apa bagi pengguna bisnis; yang mereka pahami adalah satuan asli deret. Bagian penutup proyek ini mengevaluasi model pada segmen uji — 40 jendela terakhir yang tidak pernah menyentuh pelatihan, validasi, maupun normalisasi — dengan dua metrik berbahasa manusia: MAE dan RMSE. Pembandingnya bukan model lain yang lebih besar, melainkan baseline naive yang memprediksi “besok sama seperti hari ini”: nilai berikutnya sama dengan nilai terakhir jendela. Baseline ini menetapkan lantai kualitas; model yang gagal mengalahkannya sebaiknya tidak dipakai.
Metrik dan Baseline #
Kedua metrik dihitung setelah prediksi dikembalikan ke satuan asli lewat sigma dan mu:
| Metrik | Rumus | Kepekaannya |
|---|---|---|
| MAE | rata-rata |aktual - prediksi| | linear; semua kesalahan berbobot sama |
| RMSE | akar rata-rata (aktual - prediksi)² | menghukum kesalahan besar lebih berat |
Baseline naive dipilih karena gratis, tanpa pelatihan, dan sering menang pada deret yang bergerak perlahan. Kalau LSTM hanya unggul tipis, keputusan memakai model jadi layak dipertanyakan.
"""Evaluasi prediksi deret waktu: MAE, RMSE, dan pembanding naive.
Evaluasi dilakukan pada segmen uji (40 jendela terakhir) dalam satuan
asli deret. Dua metrik dihitung untuk dua pendekat:
- Model LSTM hasil latih.py (dengan early stopping).
- Baseline naive: prediksi = nilai terakhir pada jendela masukan.
"""
import numpy as np
import torch
from latih import latih_model
from model import ModelDeret, hitung_parameter
def mae(asli, prediksi):
"""Mean Absolute Error."""
return float(np.mean(np.abs(asli - prediksi)))
def rmse(asli, prediksi):
"""Root Mean Squared Error."""
return float(np.sqrt(np.mean((asli - prediksi) ** 2)))
def prediksi_batch(model, X):
"""Jalankan model pada array jendela; kembalikan prediksi (N,)."""
with torch.no_grad():
tensor = torch.tensor(X, dtype=torch.float32)
keluaran = model(tensor).squeeze(1).numpy()
return keluaran
if __name__ == "__main__":
model, data = latih_model(cetak=False)
mu, sigma = data["mu"], data["sigma"]
X_uji = data["X_uji"]
asli_z = data["y_uji"]
# prediksi kedua pendekat dalam skala ternormalisasi
pred_model_z = prediksi_batch(model, X_uji)
pred_naive_z = X_uji[:, -1, 0]
# kembalikan ke satuan asli sebelum menghitung metrik
asli = asli_z * sigma + mu
pred_model = pred_model_z * sigma + mu
pred_naive = pred_naive_z * sigma + mu
hasil = {
"Model LSTM": (mae(asli, pred_model), rmse(asli, pred_model)),
"Naive (nilai terakhir)": (mae(asli, pred_naive), rmse(asli, pred_naive)),
}
print(f"evaluasi segmen uji: {len(asli)} jendela "
f"(W={data['panjang_window']}, parameter {hitung_parameter(model)})")
print(f"{'Pendekat':22s} {'MAE':>8s} {'RMSE':>8s}")
for nama, (m, r) in hasil.items():
print(f"{nama:22s} {m:8.3f} {r:8.3f}")
pengurangan_mae = 100 * (hasil['Naive (nilai terakhir)'][0] - hasil['Model LSTM'][0]) \
/ hasil['Naive (nilai terakhir)'][0]
pengurangan_rmse = 100 * (hasil['Naive (nilai terakhir)'][1] - hasil['Model LSTM'][1]) \
/ hasil['Naive (nilai terakhir)'][1]
print(f"\nperbaikan LSTM atas naive: MAE -{pengurangan_mae:.1f}% | "
f"RMSE -{pengurangan_rmse:.1f}%")
print("\nperbandingan 5 jendela uji pertama:")
print(f"{'aktual':>8s} {'LSTM':>8s} {'naive':>8s}")
for i in range(5):
print(f"{asli[i]:8.2f} {pred_model[i]:8.2f} {pred_naive[i]:8.2f}")Skrip di atas menghasilkan keluaran berikut saat dijalankan di mesin uji (CPU, PyTorch 2.14):
Output yang Dihasilkan:
evaluasi segmen uji: 40 jendela (W=20, parameter 17217)
Pendekat MAE RMSE
Model LSTM 2.637 3.134
Naive (nilai terakhir) 3.326 4.235
perbaikan LSTM atas naive: MAE -20.7% | RMSE -26.0%
perbandingan 5 jendela uji pertama:
aktual LSTM naive
141.29 142.96 134.60
149.07 148.11 141.29
155.53 152.12 149.07
154.13 153.88 155.53
155.94 152.73 154.13Mari kita telaah kode di atas baris per baris:
- Baris 16-23 (
mae,rmse): dua rumus dihitung langsung dari array NumPy; keduanya menerima nilai dalam satuan asli sehingga hasilnya langsung bermakna bagi pembaca laporan. - Baris 26-31 (
prediksi_batch): seluruh 40 jendela uji dilewatkan sekali dalam modetorch.no_grad();squeeze(1)mengubah(40, 1)menjadi(40,)agar sebanding dengan target. - Baris 45-48 (denormalisasi): prediksi, target, dan naive dikalikan
sigmalalu ditambahmusebelum metrik dihitung — membandingkan dalam skala ternormalisasi membuat angka sulit ditafsirkan. - Baris 61-66 (persentase perbaikan): selisih MAE dan RMSE model terhadap naive dibagi metrik naive, pola pelaporan standar regresi dari chapter Metrik Regresi .
Membaca Tabel Hasil #
Hasil pada segmen uji cukup tegas: LSTM mencatat MAE 2.637 dan RMSE 3.134, sedangkan naive mencatat MAE 3.326 dan RMSE 4.235 — perbaikan 20,7 persen untuk MAE dan 26,0 persen untuk RMSE. RMSE yang jatuh lebih dalam daripada MAE menunjukkan keunggulan terbesar model justru pada kesalahan besar: naive terlambat mengikuti puncak dan lembah musiman (perhatikan baris pertama tabel contoh — aktual 141,29, naive 134,60, LSTM 142,96), sementara LSTM membaca fase siklus dari 20 nilai masukan dan memprediksi belokan arah.
Nilai MAE 2.637 juga layak dibandingkan dengan derau pembangkit: deviasi 2.0 per titik berarti prediksi sempurna pun menyisakan galat sekitar 2 satuan. Model berjarak 0.6 satuan dari batas teoretis tersebut — sisa jarak itu adalah biaya pola yang belum sepenuhnya dipelajari.
Prediksi deret waktu tuntas dari data sampai evaluasi yang jujur: LSTM mengalahkan baseline dengan margin terukur, pembagian temporal membuktikan kualitasnya lewat eksperimen kebocoran, dan seluruh angka laporan dapat direproduksi dengan satu perintah. Isi laporan/LAPORAN.md dengan hasil eksekusi Anda. Proyek berikutnya melompat ke pertanyaan praktis lain: apa yang terjadi ketika data baru sangat sedikit — dan bagaimana model pretrained menyelamatkannya.