Policy Gradient และ Actor-Critic
REINFORCE → VPG → A3C → GAE → A2C
แทนที่จะเรียน Q แล้วจึงเลือก action วิธีนี้เรียน policy โดยตรง ข้อดี: ได้ policy แบบสุ่ม ใช้กับ action ต่อเนื่องได้ง่าย และการสำรวจอยู่ใน policy เอง
โค้ดหลักคือ VPG (REINFORCE + baseline) บน CartPole ตัวอย่าง GAE อยู่ในส่วนรายละเอียด
∇J(θ) ≈ E[ ∇θ log πθ(aₜ|sₜ) · Ψₜ ]
Ψₜ = Gₜ REINFORCE (reward-to-go)
Ψₜ = Gₜ − V(sₜ) VPG (มี baseline ลด variance)
Ψₜ = Â(GAE) A2C / A3C / PPO
| Algorithm | แนวคิดหลัก |
|---|---|
| REINFORCE | เพิ่มโอกาสของ action ที่นำไปสู่ผลตอบแทนสูง |
| VPG | เรียน V(s) เป็น baseline เพื่อลด variance |
| A3C | หลาย worker เล่นพร้อมกัน อัปเดตแบบ asynchronous ใช้ n-step bootstrap |
| GAE | ผสม n-step advantage ทุกขนาดด้วย λ |
| A2C | A3C แบบ synchronous: ง่ายกว่าและได้ผลใกล้เคียงกัน |
GAE: ปรับระหว่าง bias และ variance ด้วย λ
import numpy as np
def gae(rewards, values, gamma=0.99, lam=0.95):
"""values ມີຍາວກວ່າ rewards 1 ຕົວ (ຄ່າ V ຂອງ state ສຸດທ້າຍ, ຖ້າຈົບເກມໃຫ້ເປັນ 0)"""
adv, running = np.zeros(len(rewards)), 0.0
for t in reversed(range(len(rewards))):
delta = rewards[t] + gamma * values[t + 1] - values[t] # TD error 1 ກ້າວ
running = delta + gamma * lam * running # ສະສົມແບບຖ່ວງນ້ຳໜັກ
adv[t] = running
return adv
r = np.array([1.0, 1.0, 1.0, 1.0])
v = np.array([3.0, 2.5, 2.0, 1.0, 0.0])
print("λ=0 (TD, bias ສູງ) :", gae(r, v, lam=0.0).round(3))
print("λ=0.95 :", gae(r, v, lam=0.95).round(3))
print("λ=1 (MC, variance ສູງ):", gae(r, v, lam=1.0).round(3))λ=0 (TD, bias ສູງ) : [ 0.475 0.48 -0.01 0. ] λ=0.95 : [ 0.918 0.471 -0.01 0. ] λ=1 (MC, variance ສູງ): [ 0.94 0.47 -0.01 0. ]
สิ่งที่ควรจำ
- Actor = policy, Critic = value function ที่ bootstrap
- Baseline ไม่เปลี่ยนทิศทาง gradient โดยเฉลี่ย แต่ลด variance ได้มาก
import numpy as np, torch, torch.nn as nn
import gymnasium as gym
env = gym.make("CartPole-v1")
torch.manual_seed(0)
policy = nn.Sequential(nn.Linear(4, 128), nn.ReLU(), nn.Linear(128, 2)) # logits
value = nn.Sequential(nn.Linear(4, 128), nn.ReLU(), nn.Linear(128, 1)) # baseline V(s)
opt_pi = torch.optim.Adam(policy.parameters(), lr=1e-3)
opt_v = torch.optim.Adam(value.parameters(), lr=1e-3)
gamma, returns_log = 0.99, []
for ep in range(600):
s, _ = env.reset(seed=ep)
states, logps, rewards, done = [], [], [], False
while not done:
st = torch.tensor(s)
dist = torch.distributions.Categorical(logits=policy(st)) # ນະໂຍບາຍແບບສຸ່ມ
a = dist.sample()
s, r, term, trunc, _ = env.step(a.item())
states.append(st); logps.append(dist.log_prob(a)); rewards.append(r)
done = term or trunc
G, rtg = 0.0, [] # reward-to-go ຂອງແຕ່ລະກ້າວ
for r in reversed(rewards):
G = r + gamma * G
rtg.insert(0, G)
rtg = torch.tensor(rtg)
V = value(torch.stack(states)).squeeze(1)
adv = (rtg - V).detach() # VPG: advantage = G - V(s)
loss_pi = -(torch.stack(logps) * adv).mean()
loss_v = ((rtg - V) ** 2).mean()
opt_pi.zero_grad(); loss_pi.backward(); opt_pi.step()
opt_v.zero_grad(); loss_v.backward(); opt_v.step()
returns_log.append(sum(rewards))
if ep % 50 == 0:
print(f"ເກມທີ {ep:3d} ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: {np.mean(returns_log[-20:]):6.1f}")
ผลลัพธ์
ເກມທີ 0 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 19.0 ເກມທີ 50 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 21.4 ເກມທີ 100 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 20.2 ເກມທີ 150 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 32.4 ເກມທີ 200 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 46.0 ເກມທີ 250 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 91.3 ເກມທີ 300 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 151.0 ເກມທີ 350 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 165.2 ເກມທີ 400 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 206.6 ເກມທີ 450 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 272.1 ເກມທີ 500 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 383.8 ເກມທີ 550 ຄະແນນສະເລ່ຍ 20 ເກມລ່າສຸດ: 272.6
- #DeepRL
- #ReinforcementLearning
- #PythonສຳລັບຄົນLaos
- #AI
- #ບອກຕໍ່ຄວາມຮູ້