TD3与DPG、DDPG、Double DQN解析

本文详细解析了TD3(Twin Delayed DDPG)算法,对比了DPG、DDPG和DoubleDQN。TD3通过引入双Q网络和延迟更新策略,解决了高估问题,提高了连续动作空间的稳定性。此外,还介绍了DDPG的off-policy特性、目标网络和批归一化等关键改进,以及DoubleDQN如何缓解Q值高估问题。
摘要由CSDN通过智能技术生成

TD3与DPG、DDPG、Double DQN解析

0 - 前言

本文是对【上海交通-陈伟哲】的哔站TD3讲解视频做的笔记,图片均来自视频中或论文中,注意本文的阅读需要提前看一下TD3的原文

参考:

TD3参考:https://www.bilibili.com/video/BV1nE41117CR?from=search&seid=7053418203777412678

DDPG参考:https://www.jianshu.com/p/6fe18d0d8822

TD3原文:https://arxiv.org/abs/1802.09477v3

DDPG原文:https://arxiv.org/abs/1509.02971

1 - 回顾DPG

确定性策略梯度(DPG)中,critic评价(状态,动作)的值函数以及损失函数为:
在这里插入图片描述

由于使用两个critic,产生确定性连续动作的actor可以使用下面公式进行更新,即对actor的损失函数求梯度。求梯度又可以根据链式法则展开,该展开式被称为确定性策略梯度理论。需要注意的是,DPG本身是一个on-policy算法。

在这里插入图片描述

2 - 回顾DDPG

DDPG是在DPG的基础上,将AC机制与神经网络结合,DDPG的改进如下:

  • 沿用DQN的Experience replay,使得原本on-policy的DPG变为off-policy的算法
  • 同时沿用DQN的target network,能够使收敛更加稳定
  • 在Q网络上采用batch normalization
  • 在连续域动作产生上叠加噪声,使得action exploration更加有效

由于DDPG采用了类似DQN的双网络结构,而且Actor和Critic都有target-net和eval-net,也就是常说的目标网络和主网络,我们只需要训练动作估计网络(eval-actor-net)和状态估计网络(eval-critic-net)的参数,而目标网络(包括target-actor和target-critic)的参数是由前面两个网络每隔一定的时间复制过去的。

  • 先看Critic这边,Critic这边的学习过程跟DQN类似,DQN根据下面的损失函数来进行网络学习,即目标Q值和估计的Q值的平方损失:

在这里插入图片描述

上面式子中Q(S,A)是根据状态估计网络得到的,A是动作估计网络传过来的动作。而前面部分R + gamma * maxQ(S’,A’)是现实的Q值,这里不一样的是,我们计算现实的Q值,不再使用贪心算法,来选择具有最大Q值的动作A’,而是eval-actor网络得到这里的A’。

下面式子就是DDPG更新critic网络参数的公式,eval-critic的训练还是基于目标Q值(即 y i y_i yi)和估计Q值(即Q(si,ai))的平方损失估计Q值根据当前的状态S和eval-actor网络输出的动作A输入状态估计网络得到,而目标Q值根据现实的奖励R,以及将下一时刻的状态S’和target-actor网络得到的动作A’ 输入到target-actor网络 而得到的Q值的折现值加和得到(这里运用的是贝尔曼方程)。

在这里插入图片描述

  • 再来看actor这边的更新

在这里插入图片描述

这个式子看上去很吓人,但是其实理解起来很简单。假如对同一个状态,我们输出了两个不同的动作a1和a2,从状态估计网络得到了两个反馈的Q值,分别是Q1和Q2,假设Q1>Q2,即采取动作1可以得到更多的奖励,那么Policy gradient的思想就是增加a1的概率,降低a2的概率,也就是说,Actor想要尽可能的得到更大的Q值。所以Actor的损失可以简单的理解为得到的反馈Q值越大损失越小,得到的反馈Q值越小损失越大,因此只要对状态估计网络返回的Q值取个负号就好啦。

3 - 回顾Double DQN

Double DQN想解决的问题是:Q value在学习过程中经常会被高估(over-estimated)

高估会导致:Q value更新公式中贝尔曼方程每次都有取max操作,一旦某个(state,action)被高估,接下来该动作被max取中的几率会更大

在这里插入图片描述

看下面一张图,真实值(true value)以横线示出,能够观察到DQN估计值(DQN estimate)几乎是真实值的几倍,Double DQN在一定程度上改善了高估现象,但并没有完全消除

在这里插入图片描述

Double DQN采用的解决方式时:采用两个Q函数。

Q函数的更新过程为:每次在Q表中取出使Q值最大的a,在Q’表中取出这个a与下一状态 s t + 1 s_{t+1} st+1 对应的Q’值,与当前的奖励 r t r_t rt 一起更新当前状态 s t s_t st与当前 a t a_t at对应的Q函数

在这里插入图片描述

Double DQN防止高估的原理在于:如果Q高估了动作a,那么Q’给出一个真实值,就会解决高估的问题,相当于Q‘在修正Q的高估。如果Q’高估了a,那么就只能寄希望于Q不会高估动作a。

4 - TD3算法

TD3全名为 Twin Delayed DDPG

TD3采用了Double Q Learning的思想,在actor-critic机制下,对于两个Q函数分别求y值(即目标值,也称为现实值、真实值)。 θ 1 \theta_1 θ1 θ 2 \theta_2 θ2分别是两个critic网络的参数, ϕ \phi ϕ是对应actor网络的参数,加单引号的均表示target network

在这里插入图片描述

为了解决高估问题。对于更新y值的Q’,采用最小上限思想,即在target network中选最小的一个用来更新y,得到下面的式子作为贝尔曼方程的target value

在这里插入图片描述

TD3中Delayed的原因在于,采用下式来更新target network

在这里插入图片描述

为了使相似动作有相似输出(即平滑输出),TD3在target policy上叠加少量随机噪声,并进行mini-batches上的平均,如下:

在这里插入图片描述

完整的TD3算法如下:实际的TD3只有两个critic网络和一个actor网络

在这里插入图片描述

TD3算法和DDPG算法的比较优缺点如下: 优点: 1. TD3算法相对于DDPG算法来说更加稳定,能够更快地收敛。 2. TD3算法引入了目标策略平滑正则化,可以减少过拟合的情况。 3. TD3算法在训练过程中使用了三个神经网络,可以更好地估计Q值函数。 缺点: 1. TD3算法相对于DDPG算法来说更加复杂,需要更多的计算资源。 2. TD3算法在某些情况下可能会出现低估Q值的情况。 3. TD3算法对于超参数的选择比较敏感,需要进行更加细致的调参。 下面是一个使用TD3算法解决连续控制问题的例子: ```python import gym import numpy as np import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.autograd import Variable # 定义Actor网络 class Actor(nn.Module): def __init__(self, state_dim, action_dim, max_action): super(Actor, self).__init__() self.layer1 = nn.Linear(state_dim,400) self.layer2 = nn.Linear(400, 300) self.layer3 = nn.Linear(300, action_dim) self.max_action = max_action def forward(self, state): x = F.relu(self.layer1(state)) x = F.relu(self.layer2(x)) x = self.max_action * torch.tanh(self.layer3(x)) return x # 定义Critic网络 class Critic(nn.Module): def __init__(self, state_dim, action_dim): super(Critic, self).__init__() self.layer1 = nn.Linear(state_dim + action_dim, 400) self.layer2 = nn.Linear(400, 300) self.layer3 = nn.Linear(300, 1) def forward(self, state, action): x = torch.cat([state, action], 1) x = F.relu(self.layer1(x)) x = F.relu(self.layer2(x)) x = self.layer3(x) return x # 定义TD3算法 class TD3(object): def __init__(self, state_dim, action_dim, max_action): self.actor = Actor(state_dim, action_dim, max_action) self.actor_target = Actor(state_dim, action_dim, max_action) self.actor_target.load_state_dict(self.actor.state_dict()) self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=0.001) self.critic1 = Critic(state_dim, action_dim) self.critic1_target = Critic(state_dim, action_dim) self.critic1_target.load_state_dict(self.critic1.state_dict()) self.critic1_optimizer = optim.Adam(self.critic1.parameters(), lr=0.001) self.critic2 = Critic(state_dim, action_dim) self.critic2_target = Critic(state_dim, action_dim) self.critic2_target.load_state_dict(self.critic2.state_dict()) self.critic2_optimizer = optim.Adam(self.critic2.parameters(), lr=0.001) self.max_action = max_action def select_action(self, state): state = torch.FloatTensor(state.reshape(1, -1)) return self.actor(state).cpu().data.numpy().flatten() def train(self, replay_buffer, iterations, batch_size=100, discount=0.99, tau=0.005, policy_noise=0.2, noise_clip=0.5, policy_freq=2): for it in range(iterations): # 从缓存中随机采样一批数据 batch_states, batch_next_states, batch_actions, batch_rewards, batch_dones = replay_buffer.sample(batch_size) state = torch.FloatTensor(batch_states) next_state = torch.FloatTensor(batch_next_states) action = torch.FloatTensor(batch_actions) reward = torch.FloatTensor(batch_rewards.reshape((batch_size, 1))) done = torch.FloatTensor(batch_dones.reshape((batch_size, 1))) # 计算目标Q值 with torch.no_grad(): noise = (torch.randn_like(action) * policy_noise).clamp(-noise_clip, noise_clip) next_action = (self.actor_target(next_state) + noise).clamp(-self.max_action, self.max_action) 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) * discount * target_Q) # 更新Critic1网络 current_Q1 = self.critic1(state, action) loss_Q1 = F.mse_loss(current_Q1, target_Q) self.critic1_optimizer.zero_grad() loss_Q1.backward() self.critic1_optimizer.step() # 更新Critic2网络 current_Q2 = self.critic2(state, action) loss_Q2 = F.mse_loss(current_Q2, target_Q) self.critic2_optimizer.zero_grad() loss_Q2.backward() self.critic2_optimizer.step() # 延迟更新Actor网络和目标网络 if it % 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() # 更新目标网络 for param, target_param in zip(self.actor.parameters(), self.actor_target.parameters()): target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data) for param, target_param in zip(self.critic1.parameters(), self.critic1_target.parameters()): target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data) for param, target_param in zip(self.critic2.parameters(), self.critic2_target.parameters()): target_param.data.copy_(tau * param.data + (1 - tau) * target_param.data) def save(self, filename): torch.save(self.actor.state_dict(), filename + "_actor") torch.save(self.critic1.state_dict(), filename + "_critic1") torch.save(self.critic2.state_dict(), filename + "_critic2") def load(self, filename): self.actor.load_state_dict(torch.load(filename + "_actor")) self.actor_target.load_state_dict(torch.load(filename + "_actor")) self.critic1.load_state_dict(torch.load(filename + "_critic1")) self.critic1_target.load_state_dict(torch.load(filename + "_critic1")) self.critic2.load_state_dict(torch.load(filename + "_critic2")) self.critic2_target.load_state_dict(torch.load(filename + "_critic2")) # 创建环境 env = gym.make('Pendulum-v0') state_dim = env.observation_space.shape[0] action_dim = env.action_space.shape[0] max_action = float(env.action_space.high[0]) # 创建TD3算法对象 td3 = TD3(state_dim, action_dim, max_action) # 定义缓存大小和训练次数 replay_buffer = ReplayBuffer() replay_buffer_size = 1000000 replay_buffer.init(replay_buffer_size, state_dim, action_dim) iterations = 100000 # 训练TD3算法 state, done = env.reset(), False episode_reward = 0 episode_timesteps = 0 episode_num = 0 for t in range(iterations): episode_timesteps += 1 # 选择动作并执行 action = td3.select_action(state) next_state, reward, done, _ = env.step(action) replay_buffer.add(state, next_state, action, reward, done) state = next_state episode_reward += reward # 如果缓存中的数据足够,就开始训练 if replay_buffer.size() > 1000: td3.train(replay_buffer, 100) # 如果一个episode结束,就输出信息 if done: print("Total Timesteps: {} Episode Num: {} Episode Timesteps: {} Reward: {}".format(t+1, episode_num+1, episode_timesteps, episode_reward)) state, done = env.reset(), False episode_reward = 0 episode_timesteps = 0 episode_num += 1 # 保存模型 td3.save("td3_pendulum") --相关问题--:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值