Actor-critic mengganti return kumulatif REINFORCE dengan estimasi nilai dari jaringan critic, sehingga pembaruan dapat dilakukan setiap langkah alih-alih menunggu episode selesai. Topik ini menjelaskan pemisahan actor dan critic, menurunkan target TD(0) yang dipakai critic, lalu menjalankan actor-critic pada CartPole-v1 selama 500 langkah. Pembaca akan melihat contoh delta beberapa langkah awal yang mendekati satu, norma gradien rata-rata, dan return rata-rata episode terakhir yang masih rendah karena pelatihan sangat singkat.
Pemisahan actor dan critic#
Pada REINFORCE, bobot pembaruan adalah return yang baru tersedia setelah episode selesai. Actor-critic mengganti dengan estimasi nilai dari jaringan critic berparameter , sehingga actor dapat diperbarui setiap langkah. Pembaruan actor memakai advantage satu langkah , yang sering disebut delta TD. Karena advantage hanya bergantung pada satu transisi, pembaruan tidak menunggu episode selesai, sehingga umpan balik lebih cepat dan varians lebih rendah dibanding REINFORCE.
Critic sendiri dilatih untuk meminimalkan kesalahan antara dan target TD , yang merupakan target TD(0) yang diperkenalkan pada topik temporal-difference sebelumnya. Pasangan actor-critic menjadi on-policy, sebab keduanya memakai data dari kebijakan saat ini. Kompromi praktisnya adalah biaya: critic menambah jaringan dan pembaruan terpisah, namun mengganti varians besar return kumulatif dengan varians satu transisi yang lebih kecil. Konsekuensi lain, critic yang buruk menyesatkan actor, sehingga kualitas critic menjadi penentu mutu pelatihan.
Actor-critic pada CartPole-v1#
Berkas actor_critic.py berikut menjalankan actor-critic penuh selama 500 langkah interaksi, dengan actor dan critic masing-masing satu lapis tersembunyi berukuran 32, , dan learning rate . Actor diperbarui dengan , sedangkan critic diperbarui dengan kuadrat delta. Laporan akhir mencakup jumlah episode, return rata-rata lima episode terakhir, contoh delta lima langkah awal, dan norma gradien rata-rata actor.
import warnings; warnings.filterwarnings("ignore")
import numpy as np, torch, random
np.random.seed(0); torch.manual_seed(0); random.seed(0)
import gymnasium as gym
GAMMA = 0.99
LR = 1e-2
MAX_STEPS = 500
class Actor(torch.nn.Module):
def __init__(self, obs_dim, n_aksi, hidden=32):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(obs_dim, hidden),
torch.nn.Tanh(),
torch.nn.Linear(hidden, n_aksi),
)
def forward(self, x):
return self.net(x)
class Critic(torch.nn.Module):
def __init__(self, obs_dim, hidden=32):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(obs_dim, hidden),
torch.nn.Tanh(),
torch.nn.Linear(hidden, 1),
)
def forward(self, x):
return self.net(x)
env = gym.make("CartPole-v1")
obs, info = env.reset(seed=0)
env.action_space.seed(0)
env.observation_space.seed(0)
actor = Actor(4, 2)
critic = Critic(4)
opt_a = torch.optim.Adam(actor.parameters(), lr=LR)
opt_c = torch.optim.Adam(critic.parameters(), lr=LR)
step_count = 0
episode_returns = []
sample_deltas = []
grad_norms = []
s = obs
ep_return = 0.0
while step_count < MAX_STEPS:
x = torch.tensor(s, dtype=torch.float32).unsqueeze(0)
logits = actor(x)
dist = torch.distributions.Categorical(logits=logits)
a = dist.sample()
logp = dist.log_prob(a)
v = critic(x).squeeze(-1)
s_next, r, term, trunc, _ = env.step(int(a.item()))
done = term or trunc
x_next = torch.tensor(s_next, dtype=torch.float32).unsqueeze(0)
with torch.no_grad():
v_next = critic(x_next).squeeze(-1)
v_next_val = 0.0 if term else float(v_next.item())
delta = float(r + GAMMA * v_next_val - float(v.item()))
target = torch.tensor(r + GAMMA * v_next_val, dtype=torch.float32)
loss_c = (v - target).pow(2)
opt_c.zero_grad()
loss_c.backward()
opt_c.step()
loss_a = -(logp * delta)
opt_a.zero_grad()
loss_a.backward()
gn = 0.0
for p in actor.parameters():
if p.grad is not None:
gn += float(p.grad.pow(2).sum())
grad_norms.append(gn ** 0.5)
if step_count < 5:
sample_deltas.append(delta)
opt_a.step()
ep_return += r
step_count += 1
if done:
episode_returns.append(ep_return)
ep_return = 0.0
s, _ = env.reset()
else:
s = s_next
last_eps = episode_returns[-5:] if len(episode_returns) >= 5 else episode_returns
avg_ret = float(np.mean(last_eps)) if last_eps else 0.0
avg_gn = float(np.mean(grad_norms))
print(f"steps = {step_count}")
print(f"jumlah episode = {len(episode_returns)}")
print(f"avg return 5 ep terakhir = {avg_ret:.4f}")
print(f"contoh delta(5 awal) = " + " ".join(f"{d:.4f}" for d in sample_deltas))
print(f"norm gradien rata-rata = {avg_gn:.4f}")
env.close()steps = 500
jumlah episode = 22
avg return 5 ep terakhir = 18.2000
contoh delta(5 awal) = 0.9959 0.9868 0.9994 0.9935 0.9867
norm gradien rata-rata = 1.9610Membaca keluaran awal pelatihan#
Lima ratus langkah menghasilkan 22 episode, dengan return rata-rata lima episode terakhir 18,2000. Angka ini masih rendah dibanding batas atas 500 langkah CartPole-v1, yang menggambarkan keadaan awal pelatihan dengan learning rate selama 500 langkah. Contoh delta lima langkah awal berkisar 0,99, yang konsisten dengan reward 1,0 per langkah dan estimasi nilai mendekati nol pada awal pelatihan, sehingga .
Norma gradien rata-rata 1,9610 jauh lebih kecil daripada norma gradien REINFORCE yang mencapai ribuan pada topik sebelumnya. Penurunan ini adalah inti manfaat actor-critic: dengan mengganti return kumulatif berskala besar dengan advantage satu langkah berskala kecil, pembaruan menjadi lebih stabil. Bila langkah pelatihan ditingkatkan ke beberapa ribu, return biasanya naik ke puluhan dan kebijakan mulai menyeimbangkan tiang, namun 500 langkah dipilih agar keluaran dapat direproduksi dengan cepat.
Kesalahan umum#
Memakai return kumulatif sebagai pengganti delta. Penyebabnya, critic diabaikan dan actor tetap memakai seperti REINFORCE. Akibatnya, manfaat actor-critic hilang dan varians kembali besar, sekaligus critic tidak digunakan. Perbaikannya, hitung advantage satu langkah dan pakai sebagai bobot pembaruan actor.
Memperbarui critic dengan target yang sama yang dipakai actor. Penyebabnya, gradien critic ikut mengalir ke actor atau sebaliknya, sehingga kedua jaringan saling memengaruhi. Akibatnya, pelatihan tidak stabil karena target dan prediksi saling bergeser. Perbaikannya, putuskan gradien antara actor dan critic dengan memakai with torch.no_grad() saat menghitung nilai critic untuk advantage.
Ringkasan#
- Actor-critic mengganti return kumulatif REINFORCE dengan estimasi nilai dari critic, sehingga actor diperbarui setiap langkah dengan advantage .
- Critic dilatih dengan target TD(0) yang diperkenalkan pada topik temporal-difference, sehingga pasangan actor-critic bersifat on-policy dan varians pembaruan jauh lebih rendah.
- Pada CartPole-v1 selama 500 langkah, return rata-rata lima episode terakhir 18,2000 dengan norma gradien 1,9610, jauh lebih kecil daripada ribuan pada REINFORCE di topik sebelumnya.
Referensi#
- Actor-Critic Algorithms — OpenAI Spinning Up — Penjelasan struktur actor-critic dan peran advantage satu langkah.
- Reinforcement Learning: An Introduction, ch. 13 — Sutton & Barto — Bab “Policy Gradient Methods” yang memperkenalkan actor-critic dengan critic nilai state.