☰
DQN 算法 PyTorch 2.0 实战:CartPole-v1 环境 500 回合训练,平均奖励达 200
2026/10/15 20:07:39 网站建设 项目流程

DQN算法PyTorch 2.0实战:从零实现CartPole-v1智能控制

1. 环境准备与核心概念

在开始构建DQN模型之前,我们需要先理解几个关键概念。CartPole-v1是OpenAI Gym中的一个经典控制问题:一个小车需要在水平轨道上移动,以保持其顶部的杆子竖直不倒。这个环境的状态空间包含4个连续变量:小车位置、小车速度、杆子角度和杆子顶端速度。

安装必要库:

pip install gym torch numpy matplotlib

核心组件对比表:

组件传统Q-learningDQN
Q值表示查表法神经网络近似
状态处理离散状态连续状态空间
内存效率低(存储所有状态)高(参数共享)
泛化能力无通过神经网络泛化

提示:PyTorch 2.0的torch.compile()可以显著提升训练速度,建议在支持CUDA的设备上启用

2. 网络架构设计与实现

我们将使用PyTorch 2.0构建一个三层的全连接网络作为Q函数近似器。网络输入是4维状态向量,输出是2个动作(向左/向右)的Q值。

import torch import torch.nn as nn import torch.optim as optim class DQN(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net = nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, action_dim) ) # 使用PyTorch 2.0的编译优化 self.net = torch.compile(self.net) def forward(self, x): return self.net(x)

关键参数说明:

  • state_dim: CartPole-v1的状态维度(4)
  • action_dim: 动作空间大小(2)
  • 隐藏层使用ReLU激活函数避免梯度消失
  • 输出层直接输出Q值,不适用激活函数

3. 经验回放机制实现

经验回放是DQN稳定训练的关键技术,它通过存储和随机采样过去的经验来打破数据相关性。

from collections import deque import random class ReplayBuffer: def __init__(self, capacity): self.buffer = deque(maxlen=capacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch = random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones = zip(*batch) return ( torch.FloatTensor(states), torch.LongTensor(actions), torch.FloatTensor(rewards), torch.FloatTensor(next_states), torch.FloatTensor(dones) ) def __len__(self): return len(self.buffer)

经验回放的优势:

  1. 提高数据利用率
  2. 打破样本间的时间相关性
  3. 使训练数据分布更平稳

4. 完整训练流程实现

下面我们将所有组件整合,实现完整的DQN训练流程。这里采用ε-greedy策略平衡探索与利用,并使用目标网络稳定训练。

import gym import numpy as np import matplotlib.pyplot as plt # 超参数配置 config = { "gamma": 0.99, "lr": 1e-3, "batch_size": 64, "memory_size": 10000, "target_update": 100, "epsilon_start": 1.0, "epsilon_end": 0.01, "epsilon_decay": 500, "episodes": 500 } def train_dqn(): env = gym.make("CartPole-v1") state_dim = env.observation_space.shape[0] action_dim = env.action_space.n policy_net = DQN(state_dim, action_dim) target_net = DQN(state_dim, action_dim) target_net.load_state_dict(policy_net.state_dict()) target_net.eval() optimizer = optim.Adam(policy_net.parameters(), lr=config["lr"]) memory = ReplayBuffer(config["memory_size"]) rewards_history = [] steps_done = 0 for episode in range(config["episodes"]): state = env.reset() episode_reward = 0 while True: # ε-greedy动作选择 epsilon = config["epsilon_end"] + (config["epsilon_start"] - config["epsilon_end"]) * \ np.exp(-1. * steps_done / config["epsilon_decay"]) steps_done += 1 if np.random.rand() < epsilon: action = env.action_space.sample() else: with torch.no_grad(): action = policy_net(torch.FloatTensor(state)).argmax().item() # 执行动作 next_state, reward, done, _ = env.step(action) episode_reward += reward # 存储经验 memory.push(state, action, reward, next_state, done) state = next_state # 训练步骤 if len(memory) > config["batch_size"]: states, actions, rewards, next_states, dones = memory.sample(config["batch_size"]) # 计算当前Q值 current_q = policy_net(states).gather(1, actions.unsqueeze(1)) # 计算目标Q值 with torch.no_grad(): next_q = target_net(next_states).max(1)[0] target_q = rewards + config["gamma"] * next_q * (1 - dones) # 计算损失并更新 loss = nn.MSELoss()(current_q.squeeze(), target_q) optimizer.zero_grad() loss.backward() optimizer.step() # 更新目标网络 if steps_done % config["target_update"] == 0: target_net.load_state_dict(policy_net.state_dict()) if done: break rewards_history.append(episode_reward) print(f"Episode {episode}, Reward: {episode_reward}, Epsilon: {epsilon:.2f}") # 绘制训练曲线 plt.plot(rewards_history) plt.xlabel("Episode") plt.ylabel("Reward") plt.title("DQN Training Progress") plt.show() return policy_net

5. 高级调优技巧与可视化

当基础DQN实现能够稳定达到200分后,我们可以进一步优化模型性能。以下是几个实用的调优方向:

超参数优化建议:

  1. 学习率:尝试1e-4到1e-3之间的值
  2. 批量大小:32到128之间调整
  3. γ值:0.9到0.99之间
  4. 网络结构:增加/减少隐藏层神经元数量

奖励曲线分析:

  • 初期奖励波动大:正常现象,表示智能体在探索
  • 中期奖励快速上升:学习阶段
  • 后期稳定在200分:成功掌握策略

可视化Q值变化:

def visualize_q_values(model, state): with torch.no_grad(): q_values = model(torch.FloatTensor(state)) plt.bar(["Left", "Right"], q_values.numpy()) plt.ylabel("Q Value") plt.title("Action Q Values") plt.show() # 示例使用 trained_model = train_dqn() visualize_q_values(trained_model, [0, 0, 0, 0]) # 中立状态

6. 常见问题与解决方案

在实际实现过程中,可能会遇到以下典型问题:

训练不稳定:

  • 检查目标网络更新频率
  • 减小学习率
  • 增大经验回放缓冲区大小

奖励无法提升:

  • 调整ε衰减速度
  • 检查网络结构是否足够复杂
  • 验证奖励函数设计

收敛速度慢:

  • 尝试优先经验回放(PER)
  • 使用Double DQN减少过估计
  • 增加批量大小

7. 扩展应用与进阶方向

当CartPole-v1被攻克后,可以考虑以下进阶挑战:

  1. 更复杂环境:

    • MountainCar
    • LunarLander
    • Atari游戏
  2. 算法改进:

    # Double DQN目标值计算示例 with torch.no_grad(): next_actions = policy_net(next_states).argmax(1) next_q = target_net(next_states).gather(1, next_actions.unsqueeze(1)) target_q = rewards + config["gamma"] * next_q.squeeze() * (1 - dones)
  3. 工程优化:

    • 使用PyTorch Lightning简化训练循环
    • 添加TensorBoard日志记录
    • 实现分布式训练

在实际项目中,我发现将ε初始值设为1.0并缓慢衰减到0.01的效果最好,同时目标网络每100步更新一次能很好平衡训练稳定性与收敛速度。对于更复杂的控制问题,建议先从小型网络开始,逐步增加复杂度以避免过拟合。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询