Double-DQN算法的原理简介、与DQN对比等。
参考深度Q网络进阶技巧
1. 原理简介
在DQN算法中,虽然有target_net和eval_net,但还是容易出现Q值高估的情况,原因在于训练时用通过target_net选取最优动作
a ⋆ = argmax a Q ( s t + 1 , a ; w − ) a^{\star}=\underset{a}{\operatorname{argmax}} Q\left(s_{t+1}, a ; \mathbf{w}^{-}\right) a⋆=aargmaxQ(st+1,a;w−)
并得到其Q值后,再根据
y t = r t + γ ⋅ Q ( s t + 1 , a ⋆ ; w − ) y_{t}=r_{t}+\gamma \cdot Q\left(s_{t+1}, a^{\star} ; \mathbf{w}^{-}\right) yt=rt+γ⋅Q(st+1,a⋆;w−)
算出TD-target,所以一旦高估,就会频繁被选中然后导致目标值持续较大。
而Double-DQN算法则是设计两个Q网络,一个进行动作选取,一个进行Q值计算。即通过eval_net选取最优动作
a ⋆ 2 = argmax a Q ( s t + 1 , a ; w ) a^{\star2}=\underset{a}{\operatorname{argmax}} Q\left(s_{t+1}, a ; \mathrm{w}\right) a⋆2=aargmaxQ(st+1,a;w)
随后再通过target_net计算其Q值得到TD_target目标值
y t = r t + γ ⋅ Q ( s t + 1 , a ⋆ 2 ; w − ) y_{t}=r_{t}+\gamma \cdot Q\left(s_{t+1}, a^{\star2} ; \mathbf{w}^{-}\right) yt=rt+γ⋅Q(st+1,a⋆2;w−)
毫无疑问, Q ( s t + 1 , a ⋆ 2 ; w − ) ≤ Q ( s t + 1 , a ⋆ ; w − ) Q\left(s_{t+1}, a^{\star2} ; \mathbf{w}^{-}\right) \leq Q\left(s_{t+1}, a^{\star} ; \mathbf{w}^{-}\right) Q(st+1,a⋆2;w−)≤Q(st+1,a⋆;w−).
2. 与DQN区别
进行TD算法梯度下降时,DQN算法是直接从target_net中选取最大Q值,而Double-DQN则是eval_net选取最优动作,target_net再选取该动作的Q值。
3. 代码
直接在DQN的代码上进行几行的修改即可,修改类中的learn(update)方法。
代码如下:
class DoubleDQN:
...def learn(self):# target parameter updateif self.learn_step_counter % TARGET_REPLACE_ITER == 0:self.target_net.load_state_dict(self.eval_net.state_dict())self.learn_step_counter += 1# sample batch transitionssample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)b_memory = self.memory[sample_index, :]b_s = torch.FloatTensor(b_memory[:, :N_STATES])b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES + 1].astype(int))b_r = torch.FloatTensor(b_memory[:, N_STATES + 1:N_STATES + 2])b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])######## 就这一点和DQN不一样 ########q_eval = self.eval_net(b_s).gather(1, b_a) # 相当于Q(s,a)a_next_eval = self.eval_net(b_s_) # eval_net估计下一步动作q_next = self.target_net(b_s_).detach() # target计算 Q(st+1)q_target = b_r + GAMMA * q_next.gather(1, torch.max(a_next_eval, 1)[1].unsqueeze(1)) # 根据eval_net估计的动作的最大值,找出target_net中对应的Q值,得到TD_targetloss = self.loss_func(q_eval, q_target)self.optimizer.zero_grad() # 梯度重置loss.backward() # 反向求导self.optimizer.step() # 更新模型参数
...