[RL]DQN、DDQN、DuelingDQN原理、代码实现

一、DQN

DQN原理DQN

第一次提出的论文Playing Atari with Deep Reinforcement Learning
作者提出了用深度学习模型来拟合价值函数,结合Q-Learning进行训练的深度强化学习算法DQN,并且在Atari 2600上进行了验证。
在这里插入图片描述

在2015的Human-level control through deep reinforcement learning加入Target Network
在这里插入图片描述

一般DQN算法完整流程:

在这里插入图片描述

车杆环境实现DQN代码:

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import gym
BATCH_SIZE=32
LR=0.01
EPSILON=0.9
GAMMA=0.9
TARGET_REPLACE_ITER=100
MEMORY_CAPACITY=2000
env=gym.make("CartPole-v1",render_mode="human") 
N_ACTIONS=env.action_space.n
N_STATES=env.observation_space.shape[0]

拟合 Q ( s , a ) Q(s,a) Q(s,a) Q Q Q网络

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.fc1=nn.Linear(N_STATES,50)
        self.fc1.weight.data.normal_(0,0.1)
        self.out=nn.Linear(50,N_ACTIONS)
        self.out.weight.data.normal_(0,0.1)

    def forward(self,x):
        x=F.relu(self.fc1(x))
        actions_value=self.out(x)
        return actions_value

定义DQN算法:

class DQN(object):
    def __init__(self):
        self.eval_net,self.target_net=Net(),Net()
        self.learn_step_counter=0
        self.memory_counter=0
        self.memory=np.zeros((MEMORY_CAPACITY,N_STATES*2+2))
        self.optimizer=torch.optim.Adam(self.eval_net.parameters(),lr=LR)
        self.loss_func=nn.MSELoss()

    def choose_action(self,x):
        x=torch.unsqueeze(torch.FloatTensor(x),0)
        if np.random.uniform()<EPSILON:#greedy
            actions_value=self.eval_net.forward(x)
            action=torch.max(actions_value,1)[1].data.numpy()
            action=action[0]

        else:#随机
            action=np.random.randint(0,N_ACTIONS)
        return action

    def store_transition(self,s,a,r,s_):
        transition=np.hstack((s,[a,r],s_))
        index=self.memory_counter%MEMORY_CAPACITY
        self.memory[index,:]=transition
        self.memory_counter+=1

    def learn(self):
        if 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_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:])

        q_eval=self.eval_net(b_s).gather(1,b_a)
        q_next=self.target_net(b_s_).detach()
        q_target=b_r+GAMMA*q_next.max(1)[0].view(BATCH_SIZE,1)
        loss=self.loss_func(q_eval,q_target)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
dqn=DQN()
for i in range(400):
    print('<<<<<<<Episode:%s'%i)
    s=env.reset()
    episode_reward_sum=0

    while True:
        a=dqn.choose_action(s)
        s_,r,done,info=env.step(a)
        x,x_dot,theta,theta_dot=s_
        r1=(env.x_threshold-abs(x))/env.x_threshold-0.8
        r2=(env.theta_threshold_radians-abs(theta))/env.theta_threshold_radians-0.5
        new_r=r1+r2

        dqn.store_transition(s,a,new_r,s_)
        episode_reward_sum+=new_r

        s=s_

        if dqn.memory_counter>MEMORY_CAPACITY:
            dqn.learn()

        if done:
            print('episode%s---reward_sum:%s'%(i,round(episode_reward_sum,2)))
            break

二、DDQN

论文Deep Reinforcement Learning with Double Q-learning
高估问题(overestimation) 一直是强化学习中存在的问题,但是一直以来其来源有多种说法,一是归因于不灵活的函数近似(Thrun和Schwartz, 1993),一是来自噪声(van Hasselt, 2010, 2011)。通过实验也表明,高估问题的普遍性相较之前认识的要普遍的多,需要找到方法缓解其影响。

因为神经网络等估计方法存在误差,导致估计出的在某一固定状态 s s s下,不同动作的动作价值函数 Q ( A t = a i , S t = s ) Q(A_t=a_i, S_t=s) Q(At=ai,St=s)相对真实值 Q ∗ ( A t = a i , S t = s ) Q_*(A_t=a_i, S_t=s) Q(At=ai,St=s)估计误差。假设估计噪声为高斯,无偏均值为0,误差可正可负,到此并无大碍,但是接下来的更新时的操作计算TD-target的时候,使用了max操作,这导致在这一步的误差始终为正的,造成正的偏差,这就是导致高估的原因。
在这里插入图片描述
上图表示在实现上,Q 值往往是被高估的。红色锯齿状的一直在变的线表示 Q 函数对不同的状态估计的平均 Q 值。DQN预估出来的值远比真实值大,且大很多,在每一个游戏中都是这样。

蓝色的锯齿状的线是 DDQN 的 Q 网络所估测出来的 Q 值,蓝色的无锯齿状的线是真正的Q 值,它们是比较接近的。我们用 DDQN 得出的真正的 Q 值在都是比原来的深度 Q 网络高的,代表 DDQN 学习出来的策略比较强,所以实际上得到的奖励是比较大的。虽然一般的深度 Q 网络的 Q 网络高估了自己会得到的奖励,但实际上它得到的奖励是比较低的。

因为实际在训练的时候我们要让左式与右式(目标)越接近越好。但目标的值
很容易被设得太高,因为在计算目标的时候,我们实际上在做的,是看哪一个 a a a可以得到最大的 Q Q Q 值,就把它加上去变成目标。
Q ( s t , a t ) ⟷ r t + max ⁡ a Q ( s t + 1 , a ) \begin{aligned}Q\left(s_t,a_t\right)\longleftrightarrow r_t+\max_aQ\left(s_{t+1},a\right)\end{aligned} Q(st,at)rt+amaxQ(st+1,a)
假设我们现在有 4 个动作,本来它们得到的 Q Q Q 值都是差不多的,它们得到的奖励也是差不多的。但是在估计的时候,网络是有误差的。如图所示,假设是第一个动作被高估了,绿色代表是被高估的量,智能体就会选这个动作,就会选这个高估的 Q Q Q 值来加上 r t r_t rt 来当作目标。如果第四个动作被高估了,智能体就会选第四个动作来加上 r t r_t rt 当作目标。所以智能体总是会选那个 Q Q Q值被高估的动作,总是会选奖励被高估的动作的 Q Q Q 值当作最大的结果去加上 r t r_t rt 当作目标,所以目标值总是太大。在这里插入图片描述

解决办法:

在 DDQN 里面,选动作的 Q Q Q 函数与计算值的 Q Q Q 函数不是同一个。在原来的DQN里面,我们穷举所有的 a a a,把每一个 a a a 都代入 Q Q Q 函数,看哪一个 a a a 可以得到的 Q Q Q 值最高,就把那个 Q Q Q 值加上 r t r_t rt
在 DDQN 里面有两个 Q Q Q 网络,第一个 Q Q Q 网络 Q Q Q 决定哪一个动作的 Q Q Q 值最大(我们把所有的 a a a 代入 Q Q Q 函数中,看看哪一个 a a a Q Q Q 值最大)。我们决定动作以后, Q Q Q 值是用 Q ′ Q′ Q 算出来的。
如式所示,假设我们有两个 Q Q Q 函数—— Q Q Q Q ′ Q′ Q,如果 Q Q Q 高估了它选出来的动作 a a a,只要 Q ′ Q′ Q没有高估动作 a a a 的值,算出来的就还是正常的值。假设 Q ′ Q′ Q 高估了某一个动作的值,也是没问题的,因为只要 Q Q Q 不选这个动作就可以,这就是 DDQN 神奇的地方。
Q ( s t , a t ) ⟷ r t + Q ′ ( s t + 1 , arg ⁡ max ⁡ a Q ( s t + 1 , a ) ) Q\left(s_{t},a_{t}\right)\longleftrightarrow r_{t}+Q^{\prime}\left(s_{t+1},\arg\max_{a}Q\left(s_{t+1},a\right)\right) Q(st,at)rt+Q(st+1,argamaxQ(st+1,a))

我们动手实现的时候,有两个 Q Q Q 网络:会更新的 Q Q Q 网络和目标 Q Q Q 网络。所以在 DDQN 里面,我们会用会更新参数的 Q Q Q 网络去选动作,用目标 Q Q Q 网络(固定住的网络)计算值。

DDQN改动部分:

只需改动计算target时计算方法,用我们每次更新的Q网络去挑选最大Q值的action,挑选出action之后用Target Q网络输出下个状态此动作Q值,去计算target

        q_eval=self.eval_net(b_s).gather(1,b_a)
        q_next_eval=self.eval_net(b_s_).detach()
        q_next_target = self.target_net(b_s_).detach()
        max_action=q_next_eval.max(1)[1].view(BATCH_SIZE,1)
        q_next=q_next_target.gather(1,max_action)
        q_target=b_r+GAMMA*q_next
        loss=self.loss_func(q_eval,q_target)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

原DQN:

        q_eval=self.eval_net(b_s).gather(1,b_a)
        q_next=self.target_net(b_s_).detach()
        q_target=b_r+GAMMA*q_next.max(1)[0].view(BATCH_SIZE,1)
        loss=self.loss_func(q_eval,q_target)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

三、Dueling DQN

论文Dueling Network Architectures for Deep Reinforcement Learning

相较于原来的DQN,它唯一的差别是改变了网络的架构。 Q Q Q 网络输入状态,输出的是每一个动作的 Q Q Q 值。如图所示,原来的DQN直
接输出 Q Q Q 值,Dueling DQN不直接输出 Q Q Q 值,而是分成两条路径运算。第一条路径会输出一个标量 V ( s ) V (s) V(s),因为它与输入 s s s 是有关系的,所以称为 V ( s ) V (s) V(s)。第二条路径会输出一个向量 A ( s , a ) A(s, a) A(s,a),它的每一个动作都有一个值。我们再把 V ( s ) V (s) V(s) A ( s , a ) A(s, a) A(s,a) 加起来就可以得到 Q Q Q Q ( s , a ) Q(s, a) Q(s,a)
在这里插入图片描述
Q ( s , a ) = V ( s ) + A ( s , a ) \boldsymbol{Q}(s,\boldsymbol{a})=V(\boldsymbol{s})+\boldsymbol{A}(\boldsymbol{s},\boldsymbol{a}) Q(s,a)=V(s)+A(s,a)
假设我们在训练网络的时候,目标是希望 Q 表格中第一行第二列的值变成 4,第二行第二列的值变成 0。但是我们实际上能修改的并不是 Q 值,能修改的是 V (s) 与 A(s, a) 的值。根据网络的参数,V (s) 与 A(s, a) 的值输出以后,就直接把它们加起来,所以其实不是修改 Q 值。在学习网络的时候,假设我们希望 Q 表格中的 3 增加 1 变成 4、−1 增加 1 变成 0。最后我们在训练网络的时候,我们可能就不用修改 A(s, a) 的值,就修改 V (s) 的值,把 V (s) 的值从 0 变成 1。从 0 变成 1 有什么好处呢?本来只想修改两个值,但 Q 表格中的第三个值也被修改了:−2 变成了 −1。所以有可能我们在某一个状态下,只采样到这两个动作,没采样到第三个动作,但也可以更改第三个动作的 Q 值。这样的好处就是我们不需要把所有的状态-动作对都采样,可以用比较高效的方式去估计 Q 值。因为有时候我们更新的时候,不一定是更新 Q 表格,而是只更新了 V (s),但更新 V (s) 的时候,只要修改 V (s) 的值,Q 表格的值也会被修改。Dueling DQN是一个使用数据比较有效率的方法。
在这里插入图片描述

可能会有人认为使用Dueling DQN会有一个问题,Dueling DQN最后学习的结果可能是这样的:智能体就学到 V (s) 等于 0,A(s, a) 等于 Q,使用任何Dueling DQN就没有任何好处,就和原来的DQN一样。为了避免这个问题出现,实际上我们要给 A(s, a) 一些约束,让 A(s, a) 的更新比较麻烦,让网络倾向于使用 V (s) 来解决问题。例如,我们有不同的约束,一个最直觉的约束是必须要让 A(s, a) 的每一列的和都是 0,所以看我这边举的例子,列的和都是 0。如果这边列的和都是 0,我们就可以把 V (s) 的值想成是上面 Q 的每一列的平均值。这个平均值,加上 A(s, a) 的值才会变成是 Q 的值。

实现时,我们要给这个 A(s, a) 一个约束。假设有 3 个动作,输出的向量是
,我们在把 A(s, a) 与 V (s) 加起来之前,先进行归一化(normalization)。计算这三个动作输出均值,并将每一个动作减去均值。这样 A(s, a) 就会有比较大的约束,网络就会给它一些好处,让它倾向于去更新 V (s) 的值,这就是Dueling DQN。

Dueling DQN改动部分:

只需改动拟合Q值的网络结构

class DuelingNet(nn.Module):
    def __init__(self):
        super(DuelingNet, self).__init__()
        self.fc1 = nn.Linear(N_STATES, 50)
        self.fc1_advantage = nn.Linear(50, N_ACTIONS)
        self.fc1_value = nn.Linear(50, 1)

        self.fc1.weight.data.normal_(0, 0.1)
        self.fc1_advantage.weight.data.normal_(0, 0.1)
        self.fc1_value.weight.data.normal_(0, 0.1)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        adv = self.fc1_advantage(x)
        val = self.fc1_value(x)
        
        q_values = val + (adv - adv.mean(dim=1, keepdim=True))
        return q_values

原DQN:

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.fc1=nn.Linear(N_STATES,50)
        self.fc1.weight.data.normal_(0,0.1)
        self.out=nn.Linear(50,N_ACTIONS)
        self.out.weight.data.normal_(0,0.1)

    def forward(self,x):
        x=F.relu(self.fc1(x))
        actions_value=self.out(x)
        return actions_value

三个网络结果
在这里插入图片描述在这里插入图片描述
还在学习如何优化,欢迎提出宝贵意见!

DQNDDQN都是强化学习中的经典算法,用于解决深度强化学习中的探索-利用困境问题,其原理实现方式有一些相似之处,但也存在一些差异。下面我会分别介绍它们的原理实现和应用。 DQN(Deep Q-Network) DQN是一种基于Q-learning的深度强化学习算法,其主要思想是通过神经网络来学习Q值函数,从而实现对环境的控制。其主要的优点是可以处理大规模的离散状态和动作空间,同时还可以处理连续状态和动作空间。 DQN实现过程主要包括以下步骤: 1. 定义神经网络结构:通常采用卷积神经网络(CNN)或全连接神经网络(FCN)作为DQN的模型,神经网络的输入是状态,输出是每个动作的Q值。 2. 选择动作:根据当前状态和Q值函数,选择一个动作。 3. 执行动作:执行所选的动作,观察环境的反馈。 4. 记录经验:将当前状态、所选动作、环境反馈和下一个状态存储起来,作为经验。 5. 训练网络:从经验池中随机采样一批经验,计算损失函数并更新网络参数。损失函数通常采用均方误差(MSE)或Huber误差。 6. 更新目标网络:定期更新目标网络,目的是减少目标Q值与实际Q值之间的误差。 DQN算法的应用非常广泛,例如在游戏AI、机器人控制、自动驾驶等领域都得到了广泛的应用。 DDQN(Double Deep Q-Network) DDQNDQN的改进版,主要是为了解决DQN在处理高维状态空间时容易出现过度估计Q值的问题。DDQN采用了一种双Q学习的方式,通过使用一个网络选择动作,另一个网络评估这个动作的Q值,从而减少了过度估计。 DDQN算法实现过程与DQN类似,只是在计算Q值时,使用的是评估网络(eval network)而不是选择网络(target network),从而避免了过度估计。 DDQN算法同样具有广泛的应用场景,例如在游戏AI、机器人控制、自动驾驶等领域都得到了广泛的应用。 总结 DQNDDQN都是深度强化学习中的经典算法,其原理实现方式有一些相似之处,但也存在一些差异。DQN主要采用单个Q网络来选择动作和评估Q值,而DDQN通过使用两个网络来评估Q值,从而减少了过度估计的问题。在应用方面,这两种算法都得到了广泛的应用,例如在游戏AI、机器人控制、自动驾驶等领域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值