策略梯度与 Actor-Critic 方法
REINFORCE → VPG → A3C → GAE → A2C。
这些方法不是先学 Q 再选动作,而是直接学习策略。优点:可以得到随机策略、容易处理连续动作,而且探索就内置在策略中。
主要代码是在 CartPole 上运行的 VPG(带基线的 REINFORCE)。GAE 示例在详细内容部分。
∇J(θ) ≈ E[ ∇θ log πθ(aₜ|sₜ) · Ψₜ ]
Ψₜ = Gₜ REINFORCE(未来回报)
Ψₜ = Gₜ − V(sₜ) VPG(基线降低方差)
Ψₜ = Â(GAE) A2C / A3C / PPO
| 算法 | 核心思想 |
|---|---|
| REINFORCE | 提高带来高回报的动作的概率 |
| VPG | 学习 V(s) 作为基线以降低方差 |
| A3C | 多个 worker 并行试玩、异步更新,并使用 n 步自举 |
| GAE | 用 λ 混合所有 n 步优势 |
| A2C | 同步版 A3C:更简单,效果相近 |
GAE:用 λ 在偏差和方差之间权衡
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 = 策略;Critic = 自举的价值函数
- 基线不改变梯度的平均方向,但能大幅降低方差
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
- #ບອກຕໍ່ຄວາມຮູ້