别再死磕DQN了!用DDPG和TD3搞定机器人连续控制(附PyTorch实战代码)
从DQN到DDPG/TD3突破连续控制难题的强化学习实战指南在Atari游戏等离散动作环境中大放异彩的DQN算法当面对机器人控制、自动驾驶等需要连续动作输出的场景时却显得力不从心。许多从离散控制入门强化学习的开发者在转向连续控制问题时常常陷入困境——动作空间从有限的几个离散选项变成了无限可能的连续值域传统的Q-learning方法突然失效。这正是DDPG(深度确定性策略梯度)和其进化版TD3(双延迟DDPG)大显身手的领域。1. 连续控制问题的独特挑战与解决思路当我们需要控制机器人手臂的关节角度、调整无人机的螺旋桨转速或决定方向盘的精确转向角度时动作空间本质上是连续的。与离散动作空间不同这里无法简单地用softmax输出几个动作的概率分布。连续控制问题通常具有以下特征动作空间维度高一个机械臂可能有7个自由度每个自由度都需要输出一个连续值动作间存在耦合关系调整一个关节角度会影响其他关节的最佳动作奖励信号稀疏可能需要完成复杂动作序列后才能获得正奖励DQN的局限性在连续控制中尤为明显无法直接处理连续动作输出最大Q值对应的动作在连续空间中难以计算高维动作空间导致维数灾难DDPG的创新之处在于将DQN与策略梯度方法相结合通过演员-评论家(Actor-Critic)架构同时学习Actor网络直接输出确定性动作Critic网络评估动作价值这种架构既保留了DQN中价值函数学习的稳定性又具备了直接输出连续动作的能力。下面是一个简单的DDPG网络结构示意class Actor(nn.Module): def __init__(self, state_dim, action_dim, max_action): super().__init__() self.fc1 nn.Linear(state_dim, 256) self.fc2 nn.Linear(256, 256) self.fc3 nn.Linear(256, action_dim) self.max_action max_action def forward(self, state): x F.relu(self.fc1(state)) x F.relu(self.fc2(x)) return self.max_action * torch.tanh(self.fc3(x)) class Critic(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.fc1 nn.Linear(state_dim action_dim, 256) self.fc2 nn.Linear(256, 256) self.fc3 nn.Linear(256, 1) def forward(self, state, action): x torch.cat([state, action], dim1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x)2. DDPG核心技术解析与实现细节DDPG算法可以看作是在DQN基础上的四个关键创新确定性策略输出直接映射状态到具体动作双网络结构Actor生成动作Critic评估动作目标网络机制稳定训练过程经验回放提高数据利用率2.1 关键组件实现经验回放缓冲区是DDPG中的重要组件它存储了状态转移样本(s,a,r,s,d)其中d表示是否终止。实现时通常采用环形缓冲区class ReplayBuffer: def __init__(self, capacity): self.buffer collections.deque(maxlencapacity) def add(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): transitions random.sample(self.buffer, batch_size) return zip(*transitions)目标网络更新采用软更新方式而非DQN中的硬更新。这使训练过程更加稳定def soft_update(net, net_target, tau): for param, param_target in zip(net.parameters(), net_target.parameters()): param_target.data.copy_(tau*param.data (1-tau)*param_target.data)2.2 探索策略设计确定性策略的一个挑战是如何平衡探索与利用。DDPG通过在动作输出中添加噪声来实现探索class DDPG: def __init__(self, state_dim, action_dim, max_action): self.actor Actor(state_dim, action_dim, max_action) self.actor_target copy.deepcopy(self.actor) self.actor_optimizer torch.optim.Adam(self.actor.parameters(), lr1e-3) self.critic Critic(state_dim, action_dim) self.critic_target copy.deepcopy(self.critic) self.critic_optimizer torch.optim.Adam(self.critic.parameters(), lr1e-3) self.max_action max_action self.noise OUNoise(action_dim) # Ornstein-Uhlenbeck噪声 def select_action(self, state, add_noiseTrue): state torch.FloatTensor(state).unsqueeze(0) action self.actor(state).squeeze(0).detach().numpy() if add_noise: action self.noise.sample() return np.clip(action, -self.max_action, self.max_action)提示噪声类型的选择会影响探索效果。Ornstein-Uhlenbeck噪声适合物理系统而简单的高斯噪声在多数情况下也能工作良好。2.3 训练流程剖析DDPG的训练过程包含三个核心步骤Critic网络更新最小化贝尔曼误差Actor网络更新最大化Q值目标网络更新软更新参数def train(self, replay_buffer, batch_size256, gamma0.99, tau0.005): # 从缓冲区采样 state, action, reward, next_state, done replay_buffer.sample(batch_size) # 转换为张量 state torch.FloatTensor(np.array(state)) action torch.FloatTensor(np.array(action)) reward torch.FloatTensor(np.array(reward)).unsqueeze(1) next_state torch.FloatTensor(np.array(next_state)) done torch.FloatTensor(np.array(done)).unsqueeze(1) # 更新Critic next_action self.actor_target(next_state) target_Q self.critic_target(next_state, next_action) target_Q reward (1 - done) * gamma * target_Q current_Q self.critic(state, action) critic_loss F.mse_loss(current_Q, target_Q.detach()) self.critic_optimizer.zero_grad() critic_loss.backward() self.critic_optimizer.step() # 更新Actor actor_loss -self.critic(state, self.actor(state)).mean() self.actor_optimizer.zero_grad() actor_loss.backward() self.actor_optimizer.step() # 更新目标网络 soft_update(self.critic, self.critic_target, tau) soft_update(self.actor, self.actor_target, tau)3. TD3解决DDPG高估问题的三大技巧虽然DDPG在连续控制任务中表现出色但它存在一个严重问题Q值的高估倾向。这种高估会通过策略更新传播导致策略性能下降。TD3通过三个关键技术解决了这一问题3.1 截断的双Q学习TD3同时维护两个Critic网络取两者中的较小值作为目标Q值# 在TD3中Critic网络有两个 self.critic1 Critic(state_dim, action_dim) self.critic2 Critic(state_dim, action_dim) # 计算目标Q值时取最小值 target_Q1 self.critic1_target(next_state, next_action) target_Q2 self.critic2_target(next_state, next_action) target_Q torch.min(target_Q1, target_Q2) target_Q reward (1 - done) * gamma * target_Q3.2 延迟策略更新TD3以比Critic更低的频率更新Actor网络通常每两次Critic更新才更新一次Actorif self.total_it % self.policy_freq 0: # 更新Actor actor_loss -self.critic1(state, self.actor(state)).mean() self.actor_optimizer.zero_grad() actor_loss.backward() self.actor_optimizer.step() # 更新目标网络 soft_update(self.actor, self.actor_target, tau) soft_update(self.critic1, self.critic1_target, tau) soft_update(self.critic2, self.critic2_target, tau)3.3 目标策略平滑在目标动作上添加噪声防止策略过拟合noise torch.clamp(torch.randn_like(action) * self.policy_noise, -self.noise_clip, self.noise_clip) next_action self.actor_target(next_state) noise next_action torch.clamp(next_action, -self.max_action, self.max_action)这三种技术的组合使TD3在保持DDPG优点的同时显著提高了算法的稳定性和最终性能。4. 实战MuJoCo环境中的连续控制让我们以MuJoCo的HalfCheetah环境为例展示如何实现一个完整的TD3算法。这个环境要求控制一个双足机器人学会快速奔跑。4.1 环境配置首先安装必要的依赖pip install gym mujoco-py torch然后初始化环境import gym env gym.make(HalfCheetah-v3) state_dim env.observation_space.shape[0] action_dim env.action_space.shape[0] max_action float(env.action_space.high[0])4.2 超参数设置TD3的性能对超参数较为敏感以下是经过调优的参数设置参数值说明学习率3e-4Actor和Critic的学习率折扣因子0.99未来奖励的折扣系数回放缓冲区大小1e6存储的经验样本数批次大小256每次更新的样本数目标网络更新率0.005软更新系数策略噪声0.2目标策略平滑噪声噪声范围0.5噪声裁剪范围策略更新频率2每N次Critic更新更新一次Actor4.3 训练循环完整的训练流程包含以下步骤def train_td3(env, agent, max_episodes1000, max_steps1000): rewards [] for episode in range(max_episodes): state env.reset() episode_reward 0 for step in range(max_steps): # 选择动作并执行 action agent.select_action(state) next_state, reward, done, _ env.step(action) # 存储经验 replay_buffer.add(state, action, reward, next_state, done) # 更新状态 state next_state episode_reward reward # 训练 if len(replay_buffer) batch_size: agent.train(replay_buffer, batch_size) if done: break rewards.append(episode_reward) print(fEpisode {episode}: Reward {episode_reward:.1f}) # 定期保存模型 if episode % 100 0: torch.save(agent.state_dict(), ftd3_cheetah_{episode}.pth) return rewards4.4 性能优化技巧在实际应用中我们发现以下几个技巧可以显著提升TD3的性能状态归一化对输入状态进行标准化处理梯度裁剪防止Critic网络梯度爆炸探索噪声衰减随着训练逐渐减小探索噪声目标噪声调整根据训练进度调整目标策略噪声# 状态归一化示例 class Normalizer: def __init__(self, size): self.mean np.zeros(size) self.var np.ones(size) self.count 1e-4 def update(self, x): batch_mean np.mean(x, axis0) batch_var np.var(x, axis0) batch_count x.shape[0] delta batch_mean - self.mean total_count self.count batch_count self.mean delta * batch_count / total_count self.var (self.var * self.count batch_var * batch_count np.square(delta) * self.count * batch_count / total_count) / total_count self.count total_count def normalize(self, x): return (x - self.mean) / np.sqrt(self.var 1e-8)在机器人控制项目中从DQN切换到DDPG/TD3往往能带来质的飞跃。一个常见的误区是过早放弃——由于连续控制问题的复杂性算法可能需要较长时间才能显示出明显进步。保持耐心仔细调整超参数监控训练曲线你终将看到智能体掌握那些看似不可能的复杂动作。