Bab terakhir section ini kembali ke pertanyaan paling praktis: bagaimana tahu agen kita benar-benar baik? Evaluasi supervised learning punya resep mapan — data uji terpisah, metrik tetap — tetapi resep itu tidak langsung terpakai di RL karena dua hal: kurva training terkontaminasi eksplorasi (chapter sebelumnya memperlihatkan return training −0.9 milik agen yang kebijakan akhirnya sempurna), dan hasil pelatihan bergantung pada urutan acak yang berbeda-beda antar keberjalan. Pada chapter ini kita susun protokol evaluasi tiga lapis — metrik yang tepat, evaluasi terpisah tanpa eksplorasi, dan pengulangan antar seed — lalu merapikan kurva belajar dengan rata-rata bergerak agar kemajuan terbaca.

Metrik Performa #

MetrikYang ditanyakanCara hitung pada demo
mean returnrata-rata hasil kebijakanmean total reward 20 episode evaluasi
success rateseberapa sering tujuan tercapaijumlah episode dengan return positif
sample efficiencyberapa cepat pengetahuan terkumpulepisode pertama saat rata-rata bergerak melewati ambang
stabilitasseberapa konsisten hasilnyasebaran mean return antar seed

Metrik pertama dan kedua saling melengkapi: mean return bisa tinggi meski sebagian episode gagal, dan success rate bisa 100% dengan jalur yang berbelang-belit. Metrik ketiga penting justru karena RL mahal — dua agen dengan performa akhir sama bisa berbeda sepuluh kali lipat dalam kebutuhan interaksi.

Evaluasi Terpisah dan Variansi Seed #

Dua prinsip jujur: putuskan eksplorasi saat mengukur, dan jangan percaya satu keberjalan. Berkas evaluasi_agen.py melatih Q-learning pada GridWorld 4x4 yang kini berbatas ketat 16 langkah per episode, lalu melakukan tiga pemeriksaan — perbandingan return training lawan evaluasi greedy, lima seed dengan anggaran sama, dan perataan kurva belajar:

python
"""Evaluasi agen RL: eval greedy terpisah, beberapa seed, dan smoothing.

Dunia GridWorld 4x4 yang sama dengan chapter sebelumnya: mulai (0,0),
target (3,3), reward -0.05 per langkah, +1 saat tiba. Batas episode
dikecilkan menjadi 16 langkah supaya anggaran interaksi ketat dan
perbedaan antar seed terlihat. Tiga bagian: (A) membandingkan return
saat training (dengan eksplorasi) melawan evaluasi greedy terpisah,
(B) melatih 5 seed dengan anggaran sama lalu menilai hasilnya,
(C) merapikan kurva belajar dengan rata-rata bergerak.
"""

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt  # noqa: E402
import numpy as np  # noqa: E402

UKURAN = 4
START, GOAL = (0, 0), (3, 3)
AKSI = [(-1, 0), (1, 0), (0, -1), (0, 1)]
MAKS_LANGKAH = 16


def step(state, aksi):
    """Satu interaksi; kembalikan (state_baru, reward, selesai)."""
    db, dk = AKSI[aksi]
    nb, nk = state[0] + db, state[1] + dk
    state_baru = (nb, nk) if (0 <= nb < UKURAN and 0 <= nk < UKURAN) else state
    selesai = state_baru == GOAL
    return state_baru, (1.0 if selesai else -0.05), selesai


def latih(seed, jumlah_episode, epsilon=0.2, alfa=0.1, gamma=0.9):
    """Q-learning epsilon-greedy; kembalikan (Q, riwayat return)."""
    rng = np.random.default_rng(seed)
    Q = np.zeros((UKURAN, UKURAN, 4))
    riwayat = []
    for _ in range(jumlah_episode):
        state, total = START, 0.0
        for _ in range(MAKS_LANGKAH):
            if rng.random() < epsilon:
                aksi = int(rng.integers(0, 4))
            else:
                aksi = int(np.argmax(Q[state[0], state[1]]))
            state_baru, reward, selesai = step(state, aksi)
            target = reward + gamma * (0.0 if selesai
                                       else Q[state_baru[0], state_baru[1]].max())
            Q[state[0], state[1], aksi] += alfa * (
                target - Q[state[0], state[1], aksi])
            total += reward
            state = state_baru
            if selesai:
                break
        riwayat.append(total)
    return Q, riwayat


def evaluasi_greedy(Q, jumlah_episode=20):
    """Evaluasi terpisah tanpa eksplorasi; kembalikan (mean, sukses)."""
    hasil = []
    for _ in range(jumlah_episode):
        state, total = START, 0.0
        for _ in range(MAKS_LANGKAH):
            aksi = int(np.argmax(Q[state[0], state[1]]))
            state, reward, selesai = step(state, aksi)
            total += reward
            if selesai:
                break
        hasil.append(total)
    return float(np.mean(hasil)), sum(h > 0 for h in hasil)


# ---- Bagian A: eval greedy terpisah vs return saat training ----
Q_A, riwayat_A = latih(seed=7, jumlah_episode=500)
greedy_mean, greedy_sukses = evaluasi_greedy(Q_A)
latih_akhir = float(np.mean(riwayat_A[-50:]))
latih_sukses = sum(r > 0 for r in riwayat_A[-50:])
print("Bagian A: satu agen (seed 7), 500 episode, epsilon=0.2")
print(f"  Mean return training, episode 451-500 : {latih_akhir:+.3f}")
print(f"  Mean return evaluasi greedy (20 episode): {greedy_mean:+.3f}")
print(f"  Sukses training vs evaluasi           : {latih_sukses}/50 vs "
      f"{greedy_sukses}/20")
print(f"  Selisih (greedy - training)           : {greedy_mean - latih_akhir:+.3f}"
      " <- eksplorasi menekan return training")

# ---- Bagian B: lima seed dengan anggaran ketat ----
print("\nBagian B: 5 seed, anggaran ketat 40 episode, lalu evaluasi greedy")
berhasil = 0
for seed in (7, 13, 21, 34, 42):
    Q_B, _ = latih(seed=seed, jumlah_episode=40)
    mean_b, sukses_b = evaluasi_greedy(Q_B)
    berhasil += sukses_b == 20
    print(f"  seed {seed:2d}: mean greedy {mean_b:+.2f}, "
          f"sukses {sukses_b}/20")
print(f"  Kesimpulan: {berhasil}/5 seed menghasilkan kebijakan greedy "
      "yang sampai target")

# ---- Bagian C: smoothing kurva belajar ----
rijak = np.convolve(riwayat_A, np.ones(10) / 10, mode="valid")
print("\nBagian C: return mentah vs rata-rata bergerak (seed 7, jendela 10)")
for i in range(100, 110):
    print(f"  episode {i + 1:3d}: mentah {riwayat_A[i]:+.2f}"
          f"  |  rata-rata bergerak {rijak[i - 9]:+.2f}")
lewat = next((i + 10 for i, v in enumerate(rijak) if v > 0.5), None)
print(f"  Rata-rata bergerak pertama kali > +0.50 pada episode {lewat}")

plt.figure(figsize=(7, 4.5))
plt.plot(range(1, 501), riwayat_A, alpha=0.25, linewidth=0.8,
         label="return per episode (mentah)")
plt.plot(range(10, 501), rijak, linewidth=2,
         label="rata-rata bergerak (jendela 10)")
plt.axhline(0.75, linestyle="--", color="gray", label="optimal +0.75")
plt.xlabel("Episode")
plt.ylabel("Total reward per episode")
plt.title("Kurva belajar: mentah vs smoothing")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig("kurva_belajar.png", dpi=110, bbox_inches="tight")
print("\nGrafik kurva belajar tersimpan: kurva_belajar.png")

Output yang Dihasilkan:

text
Bagian A: satu agen (seed 7), 500 episode, epsilon=0.2
  Mean return training, episode 451-500 : +0.662
  Mean return evaluasi greedy (20 episode): +0.750
  Sukses training vs evaluasi           : 50/50 vs 20/20
  Selisih (greedy - training)           : +0.088 <- eksplorasi menekan return training

Bagian B: 5 seed, anggaran ketat 40 episode, lalu evaluasi greedy
  seed  7: mean greedy -0.80, sukses 0/20
  seed 13: mean greedy +0.75, sukses 20/20
  seed 21: mean greedy +0.75, sukses 20/20
  seed 34: mean greedy -0.80, sukses 0/20
  seed 42: mean greedy -0.80, sukses 0/20
  Kesimpulan: 2/5 seed menghasilkan kebijakan greedy yang sampai target

Bagian C: return mentah vs rata-rata bergerak (seed 7, jendela 10)
  episode 101: mentah +0.65  |  rata-rata bergerak +0.67
  episode 102: mentah +0.75  |  rata-rata bergerak +0.68
  episode 103: mentah +0.50  |  rata-rata bergerak +0.66
  episode 104: mentah +0.70  |  rata-rata bergerak +0.67
  episode 105: mentah +0.75  |  rata-rata bergerak +0.67
  episode 106: mentah +0.65  |  rata-rata bergerak +0.66
  episode 107: mentah +0.65  |  rata-rata bergerak +0.67
  episode 108: mentah +0.55  |  rata-rata bergerak +0.66
  episode 109: mentah +0.75  |  rata-rata bergerak +0.66
  episode 110: mentah +0.60  |  rata-rata bergerak +0.66
  Rata-rata bergerak pertama kali > +0.50 pada episode 39

Grafik kurva belajar tersimpan: kurva_belajar.png

Bagian A mengukur selisih dua protokol pada agen yang sama (seed 7): mean return training episode 451-500 sebesar +0.662, sedangkan evaluasi greedy +0.750 — selisih +0.088 adalah biaya eksplorasi epsilon 0.2 yang mengenakan aksi acak pada kurva training; kebijakan sejatinya sudah optimal. Bagian B adalah peringatan yang lebih serius: dengan anggaran ketat 40 episode, hanya 2/5 seed (13 dan 21) yang menghasilkan kebijakan greedy sampai target — tiga seed lainnya berhenti di −0.80. Ketiga agen gagal itu tidak berbeda algoritma, hyperparameter, ataupun kode; yang berbeda hanya urutan angka acak. Melaporkan hasil satu seed sama saja menampilkan undian. Bagian C merapikan kurva: return mentah per episode berayun +0.50 hingga +0.75, sedangkan rata-rata bergerak jendela 10 (fungsi np.convolve ) bergerak stabil di sekitar +0.66, dan ambang +0.50 pertama kali terlampaui pada episode 39 — ukuran sample efficiency yang jelas. Mari kita telaah kode di atas baris per baris:

  1. Baris 12-17: backend Agg dipilih sebelum pyplot agar grafik tersimpan tanpa jendela tampilan — pola yang sama dengan chapter Gradient Descent .
  2. Baris 22: MAKS_LANGKAH = 16 — anggaran ketat per episode; jarak terpendek dunia ini enam langkah, sehingga ruang untuk kekeliruan kecil.
  3. Baris 34-56 (latih): Q-learning epsilon-greedy parameterized oleh seed; seluruh ketidakpastian mengalir lewat np.random.default_rng(seed).
  4. Baris 59-71 (evaluasi_greedy): protokol evaluasi murni — tanpa epsilon, tanpa pembaruan Q, kebijakan greedy dijalankan 20 episode; inilah pemisahan “belajar” dari “menilai”.
  5. Baris 74-85: Bagian A — satu agen, dua cara membaca: return 50 episode training terakhir lawan hasil evaluasi greedy.
  6. Baris 87-97: Bagian B — lima seed menempuh anggaran identik 40 episode lalu dievaluasi; ringkasan 2/5 mengukur stabilitas, bukan keberuntungan tunggal.
  7. Baris 100-106: Bagian C — rata-rata bergerak dihitung lewat konvolusi dengan jendela sepuluh angka 1/10, lalu episode pertama yang melampaui ambang +0.50 dicari.
  8. Baris 108-120: kurva mentah digambar transparan tipis, kurva rata-rata bergerak digambar tebal, dan garis optimal +0.75 menjadi acuan visual; matplotlib pyplot menyimpannya ke kurva_belajar.png.

Kurva belajar agen seed 7: return mentah per episode tampak berderik, sedangkan rata-rata bergerak jendela 10 naik stabil menuju garis optimal +0.75

Penutup Section #

Kita menutup section ini dengan rantai yang utuh: desain reward membentuk perilaku, MDP memberi kerangka hitung, value function dan policy gradient menjadi dua jalur belajar, jaringan memperluas keduanya lewat DQN dan actor-critic, eksplorasi menjaga pengetahuan tetap bertumbuh, dan evaluasi menjaga kita tetap jujur. Seluruhnya dilatih pada dunia mini yang setiap angkanya bisa diverifikasi — pola yang sama ketika dunia membesar: koordinat robot menggantikan sel papan, citra menggantikan one-hot, dan reward menjadi sinyal tugas nyata. Deep learning berikutnya berpindah pokok bahasan: menggabungkan lebih dari satu jenis data — teks, citra, dan audio — dalam satu model, dimulai dari chapter Pengenalan Multimodal Learning di section berikutnya.