Ainsteinx Logo
Ainsteinx
👤
欢迎使用 Ainsteinx
登录

切换语言

货币

首页
博客
服务
第 09 课 · 基于价值的深度强化学习

DQN 与 Double DQN

让强化学习更像监督学习。

DQN 在 NFQ 的基础上加入两项技术使学习更稳定:经验回放缓冲区和目标网络。Double DQN 则解决高估问题。

CartPole 的最高分是 500。分数上下波动在深度强化学习中很常见。这段代码大约需要运行 2–5 分钟。

经验回放缓冲区存储大量经验并随机抽取小批量训练,使数据更接近 IID
目标网络复制一份网络专门计算目标值,并定期更新
DQN : y = r + γ · max_a' Q_target(s', a') DDQN : y = r + γ · Q_target(s', argmax_a' Q_online(s', a'))

Huber 损失在误差小时像 MSE,误差大时像 MAE,因此梯度不会爆炸。

要点

  • 回放缓冲区解决 IID 问题,目标网络解决目标移动问题
  • Double DQN 只改 1–2 行代码,却帮助很大
deep-rl-ep09-dqn.py
import random, collections
import numpy as np, torch, torch.nn as nn
import gymnasium as gym

env = gym.make("CartPole-v1")
n_obs, n_act = env.observation_space.shape[0], env.action_space.n
torch.manual_seed(0); random.seed(0)

def make_net():
    return nn.Sequential(nn.Linear(n_obs, 128), nn.ReLU(),
                         nn.Linear(128, 128), nn.ReLU(),
                         nn.Linear(128, n_act))       # state in -> Q ທຸກ action out

online, target = make_net(), make_net()
target.load_state_dict(online.state_dict())           # target network
opt = torch.optim.Adam(online.parameters(), lr=1e-3)
buffer = collections.deque(maxlen=50_000)             # replay buffer
gamma, batch_size, step = 0.99, 64, 0

for ep in range(400):
    s, _ = env.reset(seed=ep)
    done, ep_return = False, 0.0
    while not done:
        eps = max(0.05, 1.0 - step / 10_000)          # epsilon ຫຼຸດແບບເສັ້ນຊື່
        if random.random() < eps:
            a = env.action_space.sample()
        else:
            with torch.no_grad():
                a = int(online(torch.tensor(s)).argmax())
        s2, r, term, trunc, _ = env.step(a)
        buffer.append((s, a, r, s2, term))
        s, done, ep_return, step = s2, term or trunc, ep_return + r, step + 1

        if len(buffer) >= 1000:
            batch = random.sample(buffer, batch_size)   # ສຸ່ມ → ຂໍ້ມູນໃກ້ IID
            S, A, R, S2, T = (torch.tensor(np.array(x)) for x in zip(*batch))
            with torch.no_grad():
                a2 = online(S2).argmax(1, keepdim=True)          # Double DQN:
                q2 = target(S2).gather(1, a2).squeeze(1)         # online ເລືອກ, target ປະເມີນ
                y = R.float() + gamma * q2 * (1 - T.float())
            q = online(S).gather(1, A.long().unsqueeze(1)).squeeze(1)
            loss = nn.functional.huber_loss(q, y)                # Huber loss
            opt.zero_grad(); loss.backward(); opt.step()
        if step % 200 == 0:
            target.load_state_dict(online.state_dict())          # ອັບເດດ target ເປັນໄລຍະ
    if ep % 25 == 0:
        print(f"ເກມທີ {ep:3d}  ຄະແນນ {ep_return:5.0f}")

输出结果

ເກມທີ   0  ຄະແນນ    12
ເກມທີ  25  ຄະແນນ    20
ເກມທີ  50  ຄະແນນ    15
ເກມທີ  75  ຄະແນນ    20
ເກມທີ 100  ຄະແນນ    42
ເກມທີ 125  ຄະແນນ    12
ເກມທີ 150  ຄະແນນ   128
ເກມທີ 175  ຄະແນນ    17
ເກມທີ 200  ຄະແນນ   128
ເກມທີ 225  ຄະແນນ   108
ເກມທີ 250  ຄະແນນ   136
ເກມທີ 275  ຄະແນນ   148
ເກມທີ 300  ຄະແນນ   500
ເກມທີ 325  ຄະແນນ   500
ເກມທີ 350  ຄະແນນ    89
ເກມທີ 375  ຄະແນນ   242
  • #DeepRL
  • #ReinforcementLearning
  • #PythonສຳລັບຄົນLaos
  • #AI
  • #ບອກຕໍ່ຄວາມຮູ້