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:

MetrikRumusKepekaannya
MAErata-rata |aktual - prediksi|linear; semua kesalahan berbobot sama
RMSEakar 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.

python Evaluasi model vs naive (src/evaluasi.py)
"""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:

text
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.13

Mari kita telaah kode di atas baris per baris:

  1. 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.
  2. Baris 26-31 (prediksi_batch): seluruh 40 jendela uji dilewatkan sekali dalam mode torch.no_grad(); squeeze(1) mengubah (40, 1) menjadi (40,) agar sebanding dengan target.
  3. Baris 45-48 (denormalisasi): prediksi, target, dan naive dikalikan sigma lalu ditambah mu sebelum metrik dihitung — membandingkan dalam skala ternormalisasi membuat angka sulit ditafsirkan.
  4. 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.