TD3算法(Twin Delayed Deep Deterministic policy gradient)

引言

Twin Delayed Deep Deterministic policy gradient (TD3)是由Scott Fujimoto等人在Deep Deterministic Policy Gradient (DDPG)算法上改进得到的一种用于解决连续控制问题的在线(on-line)异策(off-policy)式深度强化学习算法。本质上,TD3算法就是将Double Q-Learning算法的思想融入到DDPG算法中。前面我们已经分别介绍过DDPG算法Double DQN算法的原理并进行了代码实现,有兴趣的小伙伴可以先去看一下,之后再来看本文应该就能很容易理解。本文就带领大家了解一下TD3算法的具体原理,并采用Pytorch进行实现,论文和代码的链接见下方。

论文:http://proceedings.mlr.press/v80/fujimoto18a/fujimoto18a.pdf

代码:https://github.com/indigoLovee/TD3

1 TD3算法简介

之前我们在讲Double DQN算法时就曾分析过Deep Q-Learning (DQN)算法存在高估问题,而DDPG算法是从DQN算法进化得到,因此它也存在一样的问题。为此,TD3算法就很自然地被提出,主要解决DDPG算法的高估问题。

TD3算法也是Actor-Critic (AC)框架下的一种确定性深度强化学习算法,它结合了深度确定性策略梯度算法和双重Q学习,在许多连续控制任务上都取得了不错的表现。

2 TD3算法原理

TD3算法在DDPG算法的基础上,提出了三个关键技术:

(1)双重网络 (Double network):采用两套Critic网络,计算目标值时取二者中的较小值,从而抑制网络过估计问题。

(2)目标策略平滑正则化 (Target policy smoothing regularization):计算目标值时,在下一个状态的动作上加入扰动,从而使得价值评估更准确。

(3)延迟更新 (Delayed update):Critic网络更新多次后,再更新Actor网络,从而保证Actor网络的训练更加稳定。

2.1 双重网络

TD3算法中包括六个网络,分别是Actor网络\mu \left ( \cdot \mid \theta^{\mu} \right ),Critic1网络Q_{1}\left ( \cdot \mid \theta^{Q_{1}} \right ),Critic2网络Q_{2}\left ( \cdot \mid \theta^{Q_{2}} \right ),Target Actor网络\mu^{'} \left ( \cdot \mid \theta^{\mu^{'}} \right ),Target Critic1网络Q^{'}_{1}\left ( \cdot \mid \theta^{Q^{'}_{1}} \right )​​​​​​​,Target Critic2网络Q^{'}_{2}\left ( \cdot \mid \theta^{Q^{'}_{2}} \right )。相较于DDPG算法,TD3算法多了一套Critic网络,这就是双重网络的由来。本节首先分析网络过估计的成因,然后引入双重网络,最后介绍算法的更新过程。

2.1.1 网络过估计的成因

DQN算法的高估主要来源于两个方面:自举 (Bootstrapping)和最大化,DDPG算法也是如此。之前我们在讲DDPG算法时曾强调,如果高估是均匀的,对于智能体最终的决策不会带来影响;如果是非均匀的,对于智能体最终的决策会带来显著影响。然而实际上网络的高估通常是非均匀的,这里简单分析一下原因。

在更新Critic网络时,假设从经验池中采样的数据为\left ( s, a, r, s^{'}, done \right )。首先我们会计算目标y

y=r+\gamma Q^{'}\left ( s^{'},a^{'} \mid \theta ^{Q^{'}} \right )

由于网络高估,因此

Q^{'}\left ( s^{'},a^{'} \mid \theta ^{Q^{'}} \right )\geqslant Q^{\ast }\left ( s^{'},a^{'} \right )

其中, Q^{\ast }\left ( s^{'},a^{'} \right )表示状态动作对\left ( s^{'},a^{'} \right )真实的最优状态动作价值。

接着我们会让Q\left ( s, a \mid \theta ^{Q} \right )逼近y,从而使得Q\left ( s, a \mid \theta ^{Q} \right )出现过估计,即

Q\left ( s,a \mid \theta ^{Q} \right )\geqslant Q^{\ast }\left ( s,a\right )

其中, Q^{\ast }\left ( s,a\right )表示状态动作对\left ( s,a \right )真实的最优状态动作价值。

每次采样状态动作对\left ( s,a \right )来对Critic网络进行更新时,就会让网络高估\left ( s,a \right )的状态动作价值,而\left ( s,a \right )在经验池中的频率显然是不均匀的。如果出现的频率越高,那么高估就越严重。因此,网络的高估是非均匀的,而非均匀的高估对智能体的决策有害,因此我们需要避免网络高估。

2.1.2 双重网络的引入

DDPG算法采用目标网络解决了自举问题,有兴趣的小伙伴可以看一下我的那篇博文,里面详细分析了自举问题带来的危害,以及目标网络是如何解决自举问题的,这里就不再赘述了。但是,除了自举以外,最大化也是造成过估计的重要原因,因此要想彻底解决网络过估计,还需要解决最大化问题。这里简单分析一下为什么最大化会造成网络过估计。

假设x_{1},x_{2},\cdots ,x_{n}为观测到的真实值,在其中加入均值为0的随机噪声,得到Q_{1},Q_{2},\cdots,Q_{n}。由于噪声的均值为0,因此满足

E\left [ mean_{i}\left ( Q_{i} \right ) \right ]=mean_{i}\left ( x_{i} \right )

 但是随机噪声会让最大值变大,即

E\left [ max_{i}\left ( Q_{i} \right ) \right ]\geqslant max_{i}\left ( x_{i} \right )

同理, 随机噪声会让最小值变小,即

E\left [ min_{i}\left ( Q_{i} \right ) \right ]\leqslant min_{i}\left ( x_{i} \right )

 这三个公式都可以被证明出来,这里就不给大家证明了。

回到DQN算法的高估问题,假设每个状态动作对的真实状态动作价值为x\left ( s,a_{1} \right ),\cdots,x\left ( s,a_{n} \right )。Q网络的估计会存在一定噪声,不妨假设是无偏估计,那么估计出的状态动作价值为Q\left ( s,a_{1} \mid \theta^{Q} \right ), \cdots, Q\left ( s,a_{n} \mid \theta^{Q} \right )。由于噪声的均值为0,因此满足

mean_{a}\left ( x\left ( s,a \right ) \right )=mean_{a}\left ( Q\left ( s,a\mid \theta^{Q} \right ) \right )

 q=max_{a}Q\left ( s,a \mid \theta^{Q} \right )是一种典型的高估,即

q\geqslant max_{a}\left ( x\left ( s,a \right ) \right )

我们在计算目标值y时,会执行q_{t+1}=max_{a}Q\left ( s_{t+1}, a \mid \theta^{Q} \right ),由于q_{t+1}高估,因此目标值

y=r+\gamma q_{t+1} 也会高估。网络更新时我们会将Q\left ( s,a\mid \theta^{Q} \right )逼近y,由于y高估,因此Q\left ( s,a\mid \theta^{Q} \right )就会出现高估。

总结起来就是,最大化操作会使得网络的估计值大于真实值,从而造成网络过估计。

双重网络是解决最大化问题的有效方法。在TD3算法中,作者引入了两套相同网络架构的Critic网络。计算目标值时,会利用二者间的较小值来估计下一个状态动作对\left ( s^{'},a^{'} \right )的状态动作价值,即

y=r+\gamma min_{i=1,2}Q_{i}^{'}\left ( s^{'},a^{'} \mid \theta_{i}^{Q^{'}} \right )

从而可以有效避免最大化问题带来的高估。这时可能会有小伙伴比较疑惑,取两个网络之间的较小值会不会不太稳妥?如果用多个网络,然后取它们中的最小值会不会更好呢?其实有实验证明采用两个网络就可以了,多个网络不会带来明显的性能提升。

2.2 目标策略平滑正则化

确定性策略存在一个问题:它会过度拟合以缩小价值估计中的峰值。当更新Critic网络时,使用确定性策略的学习目标极易受到函数逼近误差的影响,从而导致目标估计的方差大,估计值不准确。这种诱导方差可以通过正则化来减少,因此作者模仿SARSA的学习更新,引入了一种深度价值学习的正则化策略——目标策略平滑。

这种方法主要强调:类似的行动应该具有类似的价值。虽然函数近似隐式地实现了这一点,但可以通过修改训练过程显示地强调类似动作之间的关系。具体的实现是利用目标动作周围的区域来计算目标值,从而有利于平滑估计值

y=r+E\left [ Q^{'}\left ( s^{'}, \mu^{'}\left ( s^{'} \mid \theta^{'} \right ) +\epsilon \mid \theta^{Q^{'}} \right ) \right ]

在实际操作时,我们可以通过向目标动作中添加少量随机噪声,并在小批量中求平均值,来近似动作的期望。因此,上式可以修改为

y=r+\gamma Q^{'}\left ( s^{'}, \mu^{'}\left ( s^{'} \mid \theta^{'} \right ) + \epsilon \mid \theta^{Q^{'}} \right )

\epsilon \sim clip\left ( N\left ( 0,\sigma \right ),-c,c \right )

其中,我们添加的噪声是服从正态分布的,并且对采样的噪声做了裁剪,以保持目标接近原始动作。直观的说,采用这种方法得出的策略往往更加安全,因为它们为抵抗干扰的动作提供了更高的价值。说了这么多可能不是特别容易理解,不妨来看两张图。

假设上图为Critic网络估计的Q值曲面。这里我们直接采用Q\left ( s^{'},a^{'} \right )来估计Q\left ( s,a \right ),因此方差会很大,不利于网络训练。

这次我们采用状态动作对\left ( s^{'},a^{'} \right )的邻域来估计Q\left ( s,a \right ),从而可以极大地降低方差,提高目标值估计的准确性,保证网络训练过程的稳定。

2.3 延迟更新

这里的延迟更新指的是Actor网络的延迟更新,即Critic网络更新多次之后再对Actor网络进行更新。这个想法其实是非常直观的,因为Actor网络是通过最大化累积期望回报来更新的,它需要利用Critic网络来进行评估。如果Critic网络非常不稳定,那么Actor网络自然也会出现震荡。

因此,我们可以让Critic网络的更新频率高于Actor网络,即等待Critic网络更加稳定之后再来帮助Actor网络更新。

3 TD3算法更新过程

TD3算法的更新过程与DDPG算法的更新过程差别不大,主要区别在于目标值的计算方式(2.1.2节已经给出)。其中Actor网络通过最大化累积期望回报来更新(确定性策略梯度),Critic1和Critic2网络都是通过最小化评估值与目标值之间的误差来更新(MSE),所有的目标网络都采用软更新的方式来更新(Exponential Moving Average, EMA)。在训练阶段,我们从Replay Buffer中采样一个批次 (Batch size) 的数据,假设采样到的一条数据为\left ( s,a,r,s^{'}, done \right ),所有网络的更新过程如下。

Critic1和Critic2网络更新过程:利用Target Actor网络计算出状态s^{'}下的动作

a^{'}=\mu ^{'}\left ( s^{'} \mid \theta^{\mu^{'}} \right )

然后基于目标策略平滑正则化,再目标动作a^{'}上加入噪声

a^{'}=a^{'}+\epsilon

\epsilon \sim clip\left ( N\left ( 0,\sigma \right ),-c,c \right )

接着基于双重网络的思想,计算目标值

 y=r+\gamma min_{i=1,2}Q_{i}^{'}\left ( s^{'},a^{'} \mid \theta_{i}^{Q^{'}} \right )

最后利用梯度下降算法最小化评估值和目标值之间的误差L_{c_{i}},从而对Critic1和Critic2网络中的参数进行更新

L_{c_{i}}=\left ( Q_{i}\left ( s,a \mid \theta^{Q_{i}} \right )-y \right )^{2} \left ( i=1,2 \right )

Actor网络更新过程:(在Ctitic1和Critic2网络更新d 步之后,启动Actor网络更新) 利用Actor网络计算出状态s下的动作

a_{new}=\mu \left ( s \mid \theta^{\mu} \right )

这里需要注意:计算出动作后不需要加入噪声,因为这里是希望Actor网络能够朝着最大值方向更新,加入噪声没有任何意义。然后利用Critic1或者Critic2网络来计算状态动作对\left ( s,a_{new} \right )的评估值,这里我们假定使用Critic1网络

q_{new}=Q_{1}\left ( s,a_{new} \mid \theta^{Q_{1}} \right )

最后采用梯度上升算法最大化q_{new},从而完成对Actor网络的更新。  

注:这里我们之所以可以使用Critic1和Critic2两者中的任何一个来计算Q值,我觉得主要是因为Actor网络的目的就在于最大化累积期望回报,没有必要使用最小值。

目标网络的更新过程:采用软更新方式对目标网络进行更新。引入一个学习率(或者成为动量)\tau,将旧的目标网络参数和新的对应网络参数做加权平均,然后赋值给目标网络

\theta^{Q_{i}^{'}}=\tau \theta^{Q_{i}}+\left ( 1- \tau \right )\theta^{Q_{i}^{'}}(i=1,2)

\theta^{\mu^{'}}=\tau \theta^{\mu}+\left ( 1-\tau \right ) \theta^{\mu^{'}}

学习率(动量)\tau \in \left ( 0,1 \right ),通常取值0.005。

4 TD3算法伪代码

5 PyTorch代码实现

Replay Buffer的代码实现(buffer.py):


 
 
  1. import numpy as np
  2. class ReplayBuffer:
  3. def __init__( self, max_size, state_dim, action_dim, batch_size):
  4. self.mem_size = max_size
  5. self.batch_size = batch_size
  6. self.mem_cnt = 0
  7. self.state_memory = np.zeros((max_size, state_dim))
  8. self.action_memory = np.zeros((max_size, action_dim))
  9. self.reward_memory = np.zeros((max_size, ))
  10. self.next_state_memory = np.zeros((max_size, state_dim))
  11. self.terminal_memory = np.zeros((max_size, ), dtype=np. bool)
  12. def store_transition( self, state, action, reward, state_, done):
  13. mem_idx = self.mem_cnt % self.mem_size
  14. self.state_memory[mem_idx] = state
  15. self.action_memory[mem_idx] = action
  16. self.reward_memory[mem_idx] = reward
  17. self.next_state_memory[mem_idx] = state_
  18. self.terminal_memory[mem_idx] = done
  19. self.mem_cnt += 1
  20. def sample_buffer( self):
  21. mem_len = min(self.mem_cnt, self.mem_size)
  22. batch = np.random.choice(mem_len, self.batch_size, replace= False)
  23. states = self.state_memory[batch]
  24. actions = self.action_memory[batch]
  25. rewards = self.reward_memory[batch]
  26. states_ = self.next_state_memory[batch]
  27. terminals = self.terminal_memory[batch]
  28. return states, actions, rewards, states_, terminals
  29. def ready( self):
  30. return self.mem_cnt >= self.batch_size

Actor和Critic网络的代码实现(networks.py):


 
 
  1. import torch as T
  2. import torch.nn as nn
  3. import torch.optim as optim
  4. device = T.device( "cuda:0" if T.cuda.is_available() else "cpu")
  5. class ActorNetwork(nn.Module):
  6. def __init__( self, alpha, state_dim, action_dim, fc1_dim, fc2_dim):
  7. super(ActorNetwork, self).__init__()
  8. self.fc1 = nn.Linear(state_dim, fc1_dim)
  9. self.ln1 = nn.LayerNorm(fc1_dim)
  10. self.fc2 = nn.Linear(fc1_dim, fc2_dim)
  11. self.ln2 = nn.LayerNorm(fc2_dim)
  12. self.action = nn.Linear(fc2_dim, action_dim)
  13. self.optimizer = optim.Adam(self.parameters(), lr=alpha)
  14. self.to(device)
  15. def forward( self, state):
  16. x = T.relu(self.ln1(self.fc1(state)))
  17. x = T.relu(self.ln2(self.fc2(x)))
  18. action = T.tanh(self.action(x))
  19. return action
  20. def save_checkpoint( self, checkpoint_file):
  21. T.save(self.state_dict(), checkpoint_file, _use_new_zipfile_serialization= False)
  22. def load_checkpoint( self, checkpoint_file):
  23. self.load_state_dict(T.load(checkpoint_file))
  24. class CriticNetwork(nn.Module):
  25. def __init__( self, beta, state_dim, action_dim, fc1_dim, fc2_dim):
  26. super(CriticNetwork, self).__init__()
  27. self.fc1 = nn.Linear(state_dim+action_dim, fc1_dim)
  28. self.ln1 = nn.LayerNorm(fc1_dim)
  29. self.fc2 = nn.Linear(fc1_dim, fc2_dim)
  30. self.ln2 = nn.LayerNorm(fc2_dim)
  31. self.q = nn.Linear(fc2_dim, 1)
  32. self.optimizer = optim.Adam(self.parameters(), lr=beta)
  33. self.to(device)
  34. def forward( self, state, action):
  35. x = T.cat([state, action], dim=- 1)
  36. x = T.relu(self.ln1(self.fc1(x)))
  37. x = T.relu(self.ln2(self.fc2(x)))
  38. q = self.q(x)
  39. return q
  40. def save_checkpoint( self, checkpoint_file):
  41. T.save(self.state_dict(), checkpoint_file, _use_new_zipfile_serialization= False)
  42. def load_checkpoint( self, checkpoint_file):
  43. self.load_state_dict(T.load(checkpoint_file))

TD3算法的代码实现(TD3.py):


 
 
  1. import torch as T
  2. import torch.nn.functional as F
  3. import numpy as np
  4. from networks import ActorNetwork, CriticNetwork
  5. from buffer import ReplayBuffer
  6. device = T.device( "cuda:0" if T.cuda.is_available() else "cpu")
  7. class TD3:
  8. def __init__( self, alpha, beta, state_dim, action_dim, actor_fc1_dim, actor_fc2_dim,
  9. critic_fc1_dim, critic_fc2_dim, ckpt_dir, gamma=0.99, tau=0.005, action_noise=0.1,
  10. policy_noise=0.2, policy_noise_clip=0.5, delay_time=2, max_size=1000000,
  11. batch_size=256):
  12. self.gamma = gamma
  13. self.tau = tau
  14. self.action_noise = action_noise
  15. self.policy_noise = policy_noise
  16. self.policy_noise_clip = policy_noise_clip
  17. self.delay_time = delay_time
  18. self.update_time = 0
  19. self.checkpoint_dir = ckpt_dir
  20. self.actor = ActorNetwork(alpha=alpha, state_dim=state_dim, action_dim=action_dim,
  21. fc1_dim=actor_fc1_dim, fc2_dim=actor_fc2_dim)
  22. self.critic1 = CriticNetwork(beta=beta, state_dim=state_dim, action_dim=action_dim,
  23. fc1_dim=critic_fc1_dim, fc2_dim=critic_fc2_dim)
  24. self.critic2 = CriticNetwork(beta=beta, state_dim=state_dim, action_dim=action_dim,
  25. fc1_dim=critic_fc1_dim, fc2_dim=critic_fc2_dim)
  26. self.target_actor = ActorNetwork(alpha=alpha, state_dim=state_dim, action_dim=action_dim,
  27. fc1_dim=actor_fc1_dim, fc2_dim=actor_fc2_dim)
  28. self.target_critic1 = CriticNetwork(beta=beta, state_dim=state_dim, action_dim=action_dim,
  29. fc1_dim=critic_fc1_dim, fc2_dim=critic_fc2_dim)
  30. self.target_critic2 = CriticNetwork(beta=beta, state_dim=state_dim, action_dim=action_dim,
  31. fc1_dim=critic_fc1_dim, fc2_dim=critic_fc2_dim)
  32. self.memory = ReplayBuffer(max_size=max_size, state_dim=state_dim, action_dim=action_dim,
  33. batch_size=batch_size)
  34. self.update_network_parameters(tau= 1.0)
  35. def update_network_parameters( self, tau=None):
  36. if tau is None:
  37. tau = self.tau
  38. for actor_params, target_actor_params in zip(self.actor.parameters(),
  39. self.target_actor.parameters()):
  40. target_actor_params.data.copy_(tau * actor_params + ( 1 - tau) * target_actor_params)
  41. for critic1_params, target_critic1_params in zip(self.critic1.parameters(),
  42. self.target_critic1.parameters()):
  43. target_critic1_params.data.copy_(tau * critic1_params + ( 1 - tau) * target_critic1_params)
  44. for critic2_params, target_critic2_params in zip(self.critic2.parameters(),
  45. self.target_critic2.parameters()):
  46. target_critic2_params.data.copy_(tau * critic2_params + ( 1 - tau) * target_critic2_params)
  47. def remember( self, state, action, reward, state_, done):
  48. self.memory.store_transition(state, action, reward, state_, done)
  49. def choose_action( self, observation, train=True):
  50. self.actor. eval()
  51. state = T.tensor([observation], dtype=T. float).to(device)
  52. action = self.actor.forward(state)
  53. if train:
  54. # exploration noise
  55. noise = T.tensor(np.random.normal(loc= 0.0, scale=self.action_noise),
  56. dtype=T. float).to(device)
  57. action = T.clamp(action+noise, - 1, 1)
  58. self.actor.train()
  59. return action.squeeze().detach().cpu().numpy()
  60. def learn( self):
  61. if not self.memory.ready():
  62. return
  63. states, actions, rewards, states_, terminals = self.memory.sample_buffer()
  64. states_tensor = T.tensor(states, dtype=T. float).to(device)
  65. actions_tensor = T.tensor(actions, dtype=T. float).to(device)
  66. rewards_tensor = T.tensor(rewards, dtype=T. float).to(device)
  67. next_states_tensor = T.tensor(states_, dtype=T. float).to(device)
  68. terminals_tensor = T.tensor(terminals).to(device)
  69. with T.no_grad():
  70. next_actions_tensor = self.target_actor.forward(next_states_tensor)
  71. action_noise = T.tensor(np.random.normal(loc= 0.0, scale=self.policy_noise),
  72. dtype=T. float).to(device)
  73. # smooth noise
  74. action_noise = T.clamp(action_noise, -self.policy_noise_clip, self.policy_noise_clip)
  75. next_actions_tensor = T.clamp(next_actions_tensor+action_noise, - 1, 1)
  76. q1_ = self.target_critic1.forward(next_states_tensor, next_actions_tensor).view(- 1)
  77. q2_ = self.target_critic2.forward(next_states_tensor, next_actions_tensor).view(- 1)
  78. q1_[terminals_tensor] = 0.0
  79. q2_[terminals_tensor] = 0.0
  80. critic_val = T. min(q1_, q2_)
  81. target = rewards_tensor + self.gamma * critic_val
  82. q1 = self.critic1.forward(states_tensor, actions_tensor).view(- 1)
  83. q2 = self.critic2.forward(states_tensor, actions_tensor).view(- 1)
  84. critic1_loss = F.mse_loss(q1, target.detach())
  85. critic2_loss = F.mse_loss(q2, target.detach())
  86. critic_loss = critic1_loss + critic2_loss
  87. self.critic1.optimizer.zero_grad()
  88. self.critic2.optimizer.zero_grad()
  89. critic_loss.backward()
  90. self.critic1.optimizer.step()
  91. self.critic2.optimizer.step()
  92. self.update_time += 1
  93. if self.update_time % self.delay_time != 0:
  94. return
  95. new_actions_tensor = self.actor.forward(states_tensor)
  96. q1 = self.critic1.forward(states_tensor, new_actions_tensor)
  97. actor_loss = -T.mean(q1)
  98. self.actor.optimizer.zero_grad()
  99. actor_loss.backward()
  100. self.actor.optimizer.step()
  101. self.update_network_parameters()
  102. def save_models( self, episode):
  103. self.actor.save_checkpoint(self.checkpoint_dir + 'Actor/TD3_actor_{}.pth'. format(episode))
  104. print( 'Saving actor network successfully!')
  105. self.target_actor.save_checkpoint(self.checkpoint_dir +
  106. 'Target_actor/TD3_target_actor_{}.pth'. format(episode))
  107. print( 'Saving target_actor network successfully!')
  108. self.critic1.save_checkpoint(self.checkpoint_dir + 'Critic1/TD3_critic1_{}.pth'. format(episode))
  109. print( 'Saving critic1 network successfully!')
  110. self.target_critic1.save_checkpoint(self.checkpoint_dir +
  111. 'Target_critic1/TD3_target_critic1_{}.pth'. format(episode))
  112. print( 'Saving target critic1 network successfully!')
  113. self.critic2.save_checkpoint(self.checkpoint_dir + 'Critic2/TD3_critic2_{}.pth'. format(episode))
  114. print( 'Saving critic2 network successfully!')
  115. self.target_critic2.save_checkpoint(self.checkpoint_dir +
  116. 'Target_critic2/TD3_target_critic2_{}.pth'. format(episode))
  117. print( 'Saving target critic2 network successfully!')
  118. def load_models( self, episode):
  119. self.actor.load_checkpoint(self.checkpoint_dir + 'Actor/TD3_actor_{}.pth'. format(episode))
  120. print( 'Loading actor network successfully!')
  121. self.target_actor.load_checkpoint(self.checkpoint_dir +
  122. 'Target_actor/TD3_target_actor_{}.pth'. format(episode))
  123. print( 'Loading target_actor network successfully!')
  124. self.critic1.load_checkpoint(self.checkpoint_dir + 'Critic1/TD3_critic1_{}.pth'. format(episode))
  125. print( 'Loading critic1 network successfully!')
  126. self.target_critic1.load_checkpoint(self.checkpoint_dir +
  127. 'Target_critic1/TD3_target_critic1_{}.pth'. format(episode))
  128. print( 'Loading target critic1 network successfully!')
  129. self.critic2.load_checkpoint(self.checkpoint_dir + 'Critic2/TD3_critic2_{}.pth'. format(episode))
  130. print( 'Loading critic2 network successfully!')
  131. self.target_critic2.load_checkpoint(self.checkpoint_dir +
  132. 'Target_critic2/TD3_target_critic2_{}.pth'. format(episode))
  133. print( 'Loading target critic2 network successfully!')

算法仿真环境是gym库中的LunarLanderContinuous-v2环境,因此需要先配置好gym库。进入Aanconda中对应的Python环境中,执行下面的指令

pip install gym
 
 

但是,这样安装的gym库只包括少量的内置环境,如算法环境、简单文字游戏环境和经典控制环境,无法使用LunarLanderContinuous-v2。因此还要安装一些其他依赖项,具体可以参照这篇blog: AttributeError: module ‘gym.envs.box2d‘ has no attribute ‘LunarLander‘解决办法。如果已经配置好环境,那请忽略这一段。

训练脚本(train.py):


 
 
  1. import gym
  2. import numpy as np
  3. import argparse
  4. from TD3 import TD3
  5. from utils import create_directory, plot_learning_curve, scale_action
  6. parser = argparse.ArgumentParser()
  7. parser.add_argument( '--max_episodes', type= int, default= 1000)
  8. parser.add_argument( '--ckpt_dir', type= str, default= './checkpoints/TD3/')
  9. parser.add_argument( '--figure_file', type= str, default= './output_images/reward.png')
  10. args = parser.parse_args()
  11. def main():
  12. env = gym.make( 'LunarLanderContinuous-v2')
  13. agent = TD3(alpha= 0.0003, beta= 0.0003, state_dim=env.observation_space.shape[ 0],
  14. action_dim=env.action_space.shape[ 0], actor_fc1_dim= 400, actor_fc2_dim= 300,
  15. critic_fc1_dim= 400, critic_fc2_dim= 300, ckpt_dir=args.ckpt_dir, gamma= 0.99,
  16. tau= 0.005, action_noise= 0.1, policy_noise= 0.2, policy_noise_clip= 0.5,
  17. delay_time= 2, max_size= 1000000, batch_size= 256)
  18. create_directory(path=args.ckpt_dir, sub_path_list=[ 'Actor', 'Critic1', 'Critic2', 'Target_actor',
  19. 'Target_critic1', 'Target_critic2'])
  20. total_reward_history = []
  21. avg_reward_history = []
  22. for episode in range(args.max_episodes):
  23. total_reward = 0
  24. done = False
  25. observation = env.reset()
  26. while not done:
  27. action = agent.choose_action(observation, train= True)
  28. action_ = scale_action(action, low=env.action_space.low, high=env.action_space.high)
  29. observation_, reward, done, info = env.step(action_)
  30. agent.remember(observation, action, reward, observation_, done)
  31. agent.learn()
  32. total_reward += reward
  33. observation = observation_
  34. total_reward_history.append(total_reward)
  35. avg_reward = np.mean(total_reward_history[- 100:])
  36. avg_reward_history.append(avg_reward)
  37. print( 'Ep: {} Reward: {} AvgReward: {}'. format(episode+ 1, total_reward, avg_reward))
  38. if (episode + 1) % 200 == 0:
  39. agent.save_models(episode+ 1)
  40. episodes = [i+ 1 for i in range(args.max_episodes)]
  41. plot_learning_curve(episodes, avg_reward_history, title= 'AvgReward', ylabel= 'reward',
  42. figure_file=args.figure_file)
  43. if __name__ == '__main__':
  44. main()

训练脚本中有三个参数,max_episodes表示训练幕数,checkpoint_dir表示训练权重保存路径,figure_file表示训练结果的保存路径(其实是一张累积奖励曲线图),按照默认设置即可。

训练时还会用到画图函数和创建文件夹函数,它们都被放在utils.py脚本中:


 
 
  1. import os
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. def create_directory( path: str, sub_path_list: list):
  5. for sub_path in sub_path_list:
  6. if not os.path.exists(path + sub_path):
  7. os.makedirs(path + sub_path, exist_ok= True)
  8. print( 'Path: {} create successfully!'. format(path + sub_path))
  9. else:
  10. print( 'Path: {} is already existence!'. format(path + sub_path))
  11. def plot_learning_curve( episodes, records, title, ylabel, figure_file):
  12. plt.figure()
  13. plt.plot(episodes, records, color= 'b', linestyle= '-')
  14. plt.title(title)
  15. plt.xlabel( 'episode')
  16. plt.ylabel(ylabel)
  17. plt.show()
  18. plt.savefig(figure_file)
  19. def scale_action( action, low, high):
  20. action = np.clip(action, - 1, 1)
  21. weight = (high - low) / 2
  22. bias = (high + low) / 2
  23. action_ = action * weight + bias
  24. return action_

另外我们还提供了测试代码,主要用于测试训练效果以及观察环境的动态渲染 (test.py):


 
 
  1. import gym
  2. import imageio
  3. import argparse
  4. from TD3 import TD3
  5. from utils import scale_action
  6. parser = argparse.ArgumentParser()
  7. parser.add_argument( '--ckpt_dir', type= str, default= './checkpoints/TD3/')
  8. parser.add_argument( '--figure_file', type= str, default= './output_images/LunarLander.gif')
  9. parser.add_argument( '--fps', type= int, default= 30)
  10. parser.add_argument( '--render', type= bool, default= True)
  11. parser.add_argument( '--save_video', type= bool, default= True)
  12. args = parser.parse_args()
  13. def main():
  14. env = gym.make( 'LunarLanderContinuous-v2')
  15. agent = TD3(alpha= 0.0003, beta= 0.0003, state_dim=env.observation_space.shape[ 0],
  16. action_dim=env.action_space.shape[ 0], actor_fc1_dim= 400, actor_fc2_dim= 300,
  17. critic_fc1_dim= 400, critic_fc2_dim= 300, ckpt_dir=args.ckpt_dir, gamma= 0.99,
  18. tau= 0.005, action_noise= 0.1, policy_noise= 0.2, policy_noise_clip= 0.5,
  19. delay_time= 2, max_size= 1000000, batch_size= 256)
  20. agent.load_models( 1000)
  21. video = imageio.get_writer(args.figure_file, fps=args.fps)
  22. done = False
  23. observation = env.reset()
  24. while not done:
  25. if args.render:
  26. env.render()
  27. action = agent.choose_action(observation, train= True)
  28. action_ = scale_action(action, low=env.action_space.low, high=env.action_space.high)
  29. observation_, reward, done, info = env.step(action_)
  30. observation = observation_
  31. if args.save_video:
  32. video.append_data(env.render(mode= 'rgb_array'))
  33. if __name__ == '__main__':
  34. main()

测试脚本中包括五个参数,filename表示环境动态图的保存路径,checkpoint_dir表示加载的权重路径,save_video表示是否要保存动态图,fps表示动态图的帧率,rander表示是否开启环境渲染。大家只需要调整save_video和rander这两个参数,其余保持默认即可。

6 实验结果

通过平均奖励曲线可以看出,大概迭代到400步左右时算法趋于收敛。相较于DDPG算法,TD3算法的性能有了明显提升。

 这是测试效果图,智能体能够很好地完成降落任务,整个过程非常平稳!

7 结论

本文主要讲解了TD3算法中的相关技术细节,并进行了代码实现。TD3算法是一种有效解决连续控制问题的深度强化学习算法,也是我非常喜欢用的算法之一,希望各位小伙伴能够理解并掌握这个算法。

以上如果有出现错误的地方,欢迎各位怒斥!

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值