本文主要是介绍【强化学习】11 —— Double DQN算法与Dueling DQN算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- Q-learning中的过高估计
- Double DQN
- Double DQN代码实践
- Pendulum环境
- 代码
- 结果
- Dueling DQN
- Dueling DQN网络结构
- Dueling DQN优点
- Dueling DQN 代码实践
- 结果
- 参考
Q-learning中的过高估计
普通的 DQN 算法通常会导致对值的过高估计(overestimation)。传统 DQN 优化的 TD 误差目标为 r + γ max a ′ Q ω − ( s ′ , a ′ ) r+\gamma\max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right) r+γa′maxQω−(s′,a′)
其中 max a ′ Q ω − ( s ′ , a ′ ) \max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right) maxa′Qω−(s′,a′)可以变为这种形式 Q ω − ( s ′ , arg max a ′ Q ω − ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega^-}\left(s',a'\right)\right) Qω−(s′,arga′maxQω−(s′,a′))即 m a x max max操作实际可以被拆解为两部分:首先选取状态 s ′ s' s′下的最优动作 a ∗ = arg max a ′ Q ω − ( s ′ , a ′ ) a^*=\arg\max_{a^{\prime}}Q_{\omega^-}\left(s^{\prime},a^{\prime}\right) a∗=argmaxa′Qω−(s′,a′),接着计算该动作对应的价值 Q ω − ( s ′ , a ∗ ) Q_{\omega^{-}}\left(s^{\prime},a^{*}\right) Qω−(s′,a∗)。但 m a x max max 操作使得 𝑄 函数的值越来越大,甚至高于真实值。原因如下:假设有随机变量 X 1 X_1 X1, X 2 X_2 X2,取 m a x max max之后的期望大于等于先取期望再取 m a x max max操作, E [ max ( X 1 , X 2 ) ] ≥ max ( E [ X 1 ] , E [ X 2 ] ) \mathbb{E}[\max(X_1,X_2)]\geq\max(\mathbb{E}[X_1],\mathbb{E}[X_2]) E[max(X1,X2)]≥max(E[X1],E[X2])𝑄 函数的值被视作在状态 𝑠′,动作 𝑎′ 下的回报期望值。于是有 max a ′ ∈ A Q θ ′ ( s t + 1 , a ′ ) = Q θ ′ ( s t + 1 , arg max a ′ Q θ ′ ( s t + 1 , a ′ ) ) = E [ R ∣ s t + 1 , arg max a ′ Q θ ′ ( s t + 1 , a ′ ) , θ ′ ] ≥ max ( E [ R ∣ s t + 1 , a 1 , θ ′ ] , E [ R ∣ s t + 1 , a 2 , θ ′ ] , ⋯ ) , a i ∈ A \begin{aligned} \max_{a'\in A}Q_{\theta'}(s_{t+1},a')& =Q_{\theta^\prime}(s_{t+1},\arg\max_{a^\prime}Q_{\theta^\prime}(s_{t+1},a^\prime)) \\ &=\mathbb{E}[R|s_{t+1},\arg\max_{a^{\prime}}Q_{\theta^{\prime}}(s_{t+1},a^{\prime}),\theta^{\prime}] \\ &\geq\max(\mathbb{E}[R|s_{t+1},a_1,\theta^{\prime}],\mathbb{E}[R|s_{t+1},a_2,\theta^{\prime}],\cdots),a_i\in A \end{aligned} a′∈AmaxQθ′(st+1,a′)=Qθ′(st+1,arga′maxQθ′(st+1,a′))=E[R∣st+1,arga′maxQθ′(st+1,a′),θ′]≥max(E[R∣st+1,a1,θ′],E[R∣st+1,a2,θ′],⋯),ai∈A
举个例子:
如图所示,x轴为状态,有10个候选行动;左列紫线是真实价值函数,绿点是训练数据点,绿线是拟合的价值函数。中间黑色虚线是取最大值后的估计。可以看到由于数据点的分布,使得拟合出的价值函数无法与真实的价值函数吻合,甚至在部分区域产生了明显的偏差。’
此外,Q函数的过高估计程度随着候选行动数量增大变得更严重.下图中𝑄′函数是另一组独立训练的价值函数。可以看到红色部分的误差明显大于蓝色。
Double DQN
为了解决这一问题,Double DQN 算法提出利用两个独立训练的神经网络估算 max a ′ Q ∗ ( s ′ , a ′ ) \max_{a^{\prime}}Q_{*}\left(s^{\prime},a^{\prime}\right) maxa′Q∗(s′,a′)。具体做法是将原有的 Q ω − ( s ′ , arg max a ′ Q ω − ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega^-}\left(s',a'\right)\right) Qω−(s′,argmaxa′Qω−(s′,a′))更改为 Q ω − ( s ′ , arg max a ′ Q ω ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega}\left(s',a'\right)\right) Qω−(s′,argmaxa′Qω(s′,a′)),即利用一套神经网络 Q ω Q_{\omega} Qω的输出选取价值最大的动作,但在使用该动作的价值时,用另一套神经网络 Q ω − Q_{\omega^-} Qω−计算该动作的价值。这样,即使其中一套神经网络的某个动作存在比较严重的过高估计问题,由于另一套神经网络的存在,这个动作最终使用的值不会存在很大的过高估计问题。
在传统的 DQN 算法中,本来就存在两套 Q Q Q函数的神经网络——目标网络和训练网络,只不过 Q ω − ( s ′ , arg max a ′ Q ω − ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega^-}\left(s',a'\right)\right) Qω−(s′,argmaxa′Qω−(s′,a′))的计算只用到了其中的目标网络,那么我们恰好可以直接将训练网络作为 Double DQN 算法中的第一套神经网络来选取动作,将目标网络作为第二套神经网络计算值,这便是 Double DQN 的主要思想。由于在 DQN 算法中将训练网络的参数记为 ω \omega ω,将目标网络的参数记为 ω − \omega^- ω−,这与本节中 Double DQN 的两套神经网络的参数是统一的,因此,我们可以直接写出如下 Double DQN 的优化目标: r + γ Q ω − ( s ′ , arg max a ′ Q ω ( s ′ , a ′ ) ) r+\gamma Q_{\omega^-}\left(s^{\prime},\arg\max_{a^{\prime}}Q_{\omega}\left(s^{\prime},a^{\prime}\right)\right) r+γQω−(s′,arga′maxQω(s′,a′))
Double DQN代码实践
Pendulum环境
Pendulum环境文档
本节采用的环境是倒立摆(Inverted Pendulum),该环境下有一个处于随机位置的倒立摆。环境的状态包括倒立摆角度的正弦值 s i n θ sin\theta sinθ,余弦值 c o s θ cos\theta cosθ,角速度 θ ˙ \dot\theta θ˙;动作为对倒立摆施加的力矩 τ \tau τ.每一步都会根据当前倒立摆的状态的好坏给予智能体不同的奖励,该环境的奖励函数为 r = − ( t h e t a 2 + 0.1 ∗ t h e t a d t 2 + 0.001 ∗ t o r q u e 2 ) r = -(theta^2 + 0.1 * theta_dt^2 + 0.001 * torque^2) r=−(theta2+0.1∗thetadt2+0.001∗torque2).倒立摆向上保持直立不动时奖励为 0,倒立摆在其他位置时奖励为负数(最大回报为零,倒立摆是垂直的,速度为零,没有施加扭矩)。环境本身没有终止状态,运行 200 步后游戏自动结束。
Pendulum环境的状态空间
标号 | 名称 | 最小值 | 最大值 |
---|---|---|---|
0 | x = cos(theta) | -1.0 | 1.0 |
1 | y = sin(theta) | -1.0 | 1.0 |
2 | Angular Velocity | -8.0 | 8.0 |
Pendulum环境的动作空间
标号 | 名称 | 最小值 | 最大值 |
---|---|---|---|
0 | 力矩 | -2.0 | 2.0 |
力矩大小是在 [ − 2 , 2 ] [-2,2] [−2,2]范围内的连续值。为了能够应用 DQN,我们需要采用离散化动作的技巧。例如,下面的代码将连续的动作空间离散为 11 个动作。动作 [ 0 , 1 , 2 , … , 9 , 10 ] [0,1,2,\dots,9,10] [0,1,2,…,9,10]分别代表力矩为 [ − 2 , − 1.6 , − 1.2 , … , 1.2 , 1.6 , 2.0 ] [-2,-1.6,-1.2,\dots,1.2,1.6,2.0] [−2,−1.6,−1.2,…,1.2,1.6,2.0]。
代码
PS:
torch.max()[0], 只返回最大值的每个数
troch.max()[1], 只返回最大值的每个索引
class DQN:''' DQN算法 '''def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,epsilon, target_update_rate, device, numOfEpisodes, env,buffer_size, minimal_size, batch_size, DQNtype="DQN"):self.action_dim = action_dim# Q网络self.q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)# 目标网络self.target_q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)# 使用Adam优化器self.optimizer = torch.optim.Adam(self.q_net.parameters(),lr=learning_rate)self.gamma = gammaself.epsilon = epsilon# 目标网络更新频率self.target_update_rate = target_update_rate# 计数器,记录更新次数self.count = 0self.device = deviceself.numOfEpisodes = numOfEpisodesself.env = envself.buffer_size = buffer_sizeself.minimal_size = minimal_sizeself.batch_size = batch_sizeself.DQNtype = DQNtype# Choose A from S using policy derived from Q (e.g., epsilon-greedy)def ChooseAction(self, state):if np.random.random() < self.epsilon:action = np.random.randint(self.action_dim)else:state = torch.tensor(np.array([state]), dtype=torch.float).to(self.device)action = self.q_net(state).argmax().item()return actiondef Update(self, transition_dict):states = torch.tensor(transition_dict['states'],dtype=torch.float).to(self.device)actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)rewards = torch.tensor(transition_dict['rewards'],dtype=torch.float).view(-1, 1).to(self.device)next_states = torch.tensor(transition_dict['next_states'],dtype=torch.float).to(self.device)terminateds = torch.tensor(transition_dict['terminateds'],dtype=torch.float).view(-1, 1).to(self.device)truncateds = torch.tensor(transition_dict['truncateds'],dtype=torch.float).view(-1, 1).to(self.device)#Q值q_values = self.q_net(states).gather(1, actions)# 下个状态的最大Q值if self.DQNtype == "DoubleDQN":max_action = self.q_net(next_states).max(1)[1].view(-1, 1)max_next_q_values = self.target_q_net(next_states).gather(1, max_action)else: # DQN的情况max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)# TD误差目标q_targets = rewards + self.gamma * max_next_q_values * (1 - terminateds + truncateds)# 均方误差损失函数dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))# PyTorch中默认梯度会累积,这里需要显式将梯度置为0self.optimizer.zero_grad()# 反向传播更新参数dqn_loss.backward()self.optimizer.step()if self.count % self.target_update_rate == 0:self.target_q_net.load_state_dict(self.q_net.state_dict()) # 更新目标网络self.count += 1def max_q_value(self, state):state = torch.tensor(np.array([state]), dtype=torch.float).to(self.device)return self.q_net(state).max().item()# 离散动作转回连续的函数def dis_to_con(self, discrete_action, env, action_dim):action_lowbound = env.action_space.low[0]actoin_upbound = env.action_space.high[0]return action_lowbound + (discrete_action / (action_dim - 1)) \* (actoin_upbound - action_lowbound)def DQNtrain(self):replay_buffer = util.ReplayBuffer(self.buffer_size)returnList = []max_q_valueList = []max_q_value = 0for i in range(10):with tqdm(total=int(self.numOfEpisodes / 10), desc='Iteration %d' % i) as pbar:for episode in range(int(self.numOfEpisodes / 10)):# initialize statestate, info = self.env.reset()terminated = Falsetruncated = FalseepisodeReward = 0# Loop for each step of episode:while (not terminated) or (not truncated):action = self.ChooseAction(state)# 平滑处理max_q_value = self.max_q_value(state) * 0.005 + max_q_value * 0.995max_q_valueList.append(max_q_value)action_continuous = self.dis_to_con(action, self.env, self.action_dim)next_state, reward, terminated, truncated, info = self.env.step([action_continuous])replay_buffer.add(state, action, reward, next_state, terminated, truncated)if terminated or truncated:breakstate = next_stateepisodeReward += reward# 当buffer数据的数量超过一定值后,才进行Q网络训练if replay_buffer.size() > self.minimal_size:b_s, b_a, b_r, b_ns, b_te, b_tr = replay_buffer.sample(self.batch_size)transition_dict = {'states': b_s,'actions': b_a,'next_states': b_ns,'rewards': b_r,'terminateds': b_te,'truncateds': b_tr}self.Update(transition_dict)returnList.append(episodeReward)if (episode + 1) % 10 == 0: # 每10条序列打印一下这10条序列的平均回报pbar.set_postfix({'episode':'%d' % (self.numOfEpisodes / 10 * i + episode + 1),'return':'%.3f' % np.mean(returnList[-10:])})pbar.update(1)return returnList, max_q_valueList
结果
如图所示,DQN相比DoubleDQN能够获得更多的回报。
如图所示,DQN算法会产生过高估计(超越黄色虚线0),甚至部分Q值超过了10。而DoubleDQN算法则能有效缓解过高估计这一问题。
Dueling DQN
Dueling DQN 是 DQN 另一种的改进算法,它在传统 DQN 的基础上只进行了微小的改动,但却能大幅提升 DQN 的表现。在强化学习中,我们将状态动作价值函数 Q Q Q减去状态价值函数 V V V的结果定义为优势函数 A A A,即 A ( s , a ) = Q ( s , a ) − V ( s ) A(s,a)=Q(s,a)-V(s) A(s,a)=Q(s,a)−V(s).在同一个状态下,所有动作的优势值之和为 0,因为所有动作的动作价值的期望就是这个状态的状态价值。据此,在 Dueling DQN 中,Q 网络被建模为: Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) Q_{\eta,\alpha,\beta}(s,a)=V_{\eta,\alpha}(s)+A_{\eta,\beta}(s,a) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)
其中, V η , α ( s ) V_{\eta,\alpha}(s) Vη,α(s)为状态价值函数,而 A η , β ( s , a ) A_{\eta,\beta}(s,a) Aη,β(s,a)则为该状态下采取不同动作的优势函数,表示采取不同动作的差异性; η \eta η是状态价值函数和优势函数共享的网络参数,一般用在神经网络中,用来提取特征的前几层;而 α , β \alpha,\beta α,β分别为状态价值函数和优势函数的参数。
不同的Advantage聚合形式:
- Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) − max a ′ A η , β ( s , a ′ ) Q_{\eta,\alpha,\beta}(s,a)=V_{\eta,\alpha}(s)+A_{\eta,\beta}(s,a)-\max_{a'}A_{\eta,\beta}\left(s,a'\right) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)−a′maxAη,β(s,a′)
- Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) − 1 ∣ A ∣ ∑ a ′ A η , β ( s , a ′ ) Q_{\eta,\alpha,\beta}(s,a)=V_{\eta,\alpha}(s)+A_{\eta,\beta}(s,a)-\frac{1}{|\mathcal{A}|}\sum_{a'}A_{\eta,\beta}\left(s,a'\right) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)−∣A∣1a′∑Aη,β(s,a′)
Dueling DQN网络结构
Dueling DQN优点
- 能够处理与动作关联较小的状态
- Dueling DQN 能更高效学习状态价值函数。每一次更新时, V V V函数都会被更新,这也会影响到其他动作的 Q Q Q值。而传统的 DQN 只会更新某个动作的 Q Q Q值,其他动作的 Q Q Q值就不会更新。因此,Dueling DQN 能够更加频繁、准确地学习状态价值函数。
Dueling DQN 代码实践
class VAnet(torch.nn.Module):''' 只有一层隐藏层的A网络和V网络 '''def __init__(self, state_dim, hidden_dim, action_dim):super(VAnet, self).__init__()# 共享网络部分self.fc1 = torch.nn.Linear(state_dim, hidden_dim)self.fc_A = torch.nn.Linear(hidden_dim, action_dim)self.fc_V = torch.nn.Linear(hidden_dim, 1)def forward(self, x):A = self.fc_A(F.relu(self.fc1(x)))V = self.fc_V(F.relu((self.fc1(x))))Q = A + V - A.mean(1).view(-1, 1)return Qclass DQN:''' DQN算法 '''def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,epsilon, target_update_rate, device, numOfEpisodes, env,buffer_size, minimal_size, batch_size, DQNtype="DQN"):self.action_dim = action_dimif DQNtype == "DuelingDQN":self.q_net = VAnet(state_dim, hidden_dim, self.action_dim).to(device)self.target_q_net = VAnet(state_dim, hidden_dim, self.action_dim).to(device)else:# Q网络self.q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)# 目标网络self.target_q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)# 使用Adam优化器self.optimizer = torch.optim.Adam(self.q_net.parameters(),lr=learning_rate)self.gamma = gammaself.epsilon = epsilon# 目标网络更新频率self.target_update_rate = target_update_rate# 计数器,记录更新次数self.count = 0self.device = deviceself.numOfEpisodes = numOfEpisodesself.env = envself.buffer_size = buffer_sizeself.minimal_size = minimal_sizeself.batch_size = batch_sizeself.DQNtype = DQNtype
结果
同样,DQN相比Dueling DQN能够获得更多的回报。
Dueling DQN可以缓解过高估计这一问题。
参考
[1] 伯禹AI
[2] https://www.davidsilver.uk/teaching/
[3] 动手学强化学习
[4] Reinforcement Learning
这篇关于【强化学习】11 —— Double DQN算法与Dueling DQN算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!