强化学习(三):DQN、Nature DQN、Double DQN,附源码解读

强化学习(三):DQN、Nature DQN、Double DQN,附源码解读

这不最近在学莫烦的强化学习嘛,有一点好处就是他讲的课虽然有一些一知半解,但是在网上再查查博客结合一下他的代码就能有比较清晰的理解了。

这篇文章就介绍一下我对DQN以及其改进算法的理解和莫烦python代码的部分解读。

1 DQN

传统的强化学习存在当状态过多维度爆炸的问题,如果全用表格来存储它们,恐怕计算机内存会不够,而且每次在这么大的表格中搜索对应的状态也是一件很耗时的事,采用机器学习中的神经网络进行替代能够很好解决这个问题。这就是DQN的由来,使用神经网络来表示Q函数,每层网络的权重就是对应的值函数,输入state,输出每个action对应的Q值。

DQN对于Q-learning的修改主要有两部分:

一个是利用深度卷积神经网络逼近值函数,说白了就是用卷积层去替换Q值的计算,常规Q-learning是输入state返回Q值,而DQN是输入state通过网络输出Q值;

另一个是DQN利用经验回放训练强化学习中的学习过程:因为通过强化学习采集的数据之间存在关联性,利用这些数据进行顺序训练神经网络可能会表现的不稳定,通过经验回放来打破数据间的关联性。后面的代码解读会讲这一部分怎么做的。

这是DQN的算法流程:

在这里插入图片描述
2 Nature DQN

在DQN算法中,在计算目标值和计算当前值用的是同一个Q网络(这一部分不知道是我的理解有误还是莫烦大佬没讲明白,在视频里讲的DQN就是用了两个Q网络,但实际上应该是Nature DQN才开始用的两个Q网络),而Nature DQN用了两个Q网络,这么做的意义是因为如果用Q网络的输出来更新Q网络的参数,依赖性太强,不利于算法的收敛,因此提出使用两个Q网络。

一个Q网络用于选择动作并更新参数,另一个Q网络(称之为Q’)用于计算目标值,Q’网络不会进行迭代更新,而是隔一段时间从Q网络中复制参数,所以两个Q网络的结构是一致的。

Nature DQN相比DQN的算法改进主要在于最后目标值的计算:

在这里插入图片描述
3 Double DQN

Double DQN算法是用于解决DQN和Nature DQN算法中出现的过估计的问题,过估计的意思是值估计的值函数值比真实的值函数值要打,原因在于Q-learning中的最大化操作,就没错,就是那个max,使得最终得到的算法模型有很大的偏差。DDQN(Double DQN)通过解耦目标Q值动作的选择和目标Q值的计算这两步来达到解决过估计的问题。

下面一幅图是Double DQN和Nature DQN算法上的区别:

在这里插入图片描述
源码解读

解读的源码时莫烦大佬的Double DQN,毕竟这三种算法很相似,所以直接挑最复杂的来。

首先是run_Pendulum.py:

env = gym.make('Pendulum-v0')
env = env.unwrapped

第一行的目的是从gym中得到Pendulum模型,这个没啥,主要是第二行,因为我们得到的模型env我们是得不到它的参数的,是封装好的,需要运行第二行命令才可以得到封装好的模型的内部参数;

f_action = (action-(ACTION_SPACE-1)/2)/((ACTION_SPACE-1)/4)

将连续动作离散化,离散化后的动作共有11个,将动作的值遍布到-2~2之间,也就是-2、-1.6、-1.2、…、1.2、1.6、2.0一共11个动作,后面会计算每一个动作对应的Q值;

主函数都没啥,主要是RL_brain.py:

首先是初始化,有一个n_features,它是3,它表示的是状态,只不过是以向量形式表示的,前面两章强化学习的内容的状态其实就是一个坐标,而本篇文章的模型不一样,所以它的状态表示也不一样,而这里的状态向量共有三个数据,分别是theta的余弦值、正弦值以及theta的导数theta_dot,这个可以通过查看模型的代码Pendulum.py查到;

memory_size记忆库的容量是三千,一开始设置为全零,后面会进行覆盖存入数据,大小是3000×8;8的意思是s、a、r、s_,其中s是状态向量n_features,所以加起来就是8;

每一个Q网络有两个卷积层,其中第一层卷积层的卷积核维度是3×20,偏置是1×20;第二层卷积层的卷积核维度是20×11,偏置是1×11,至于这个卷积核维度大家自行理解,解释起来有点麻烦;

网络的输入是状态向量n_features,不过多解释;

看后面的store_transition函数:

    def store_transition(self, s, a, r, s_):
        if not hasattr(self, 'memory_counter'):
            self.memory_counter = 0
        transition = np.hstack((s, [a, r], s_))  #变成一维的list便于存储
        index = self.memory_counter % self.memory_size   #这一段的巧妙在于覆盖存储
        self.memory[index, :] = transition
        self.memory_counter += 1

这一段函数的巧妙之处就在于覆盖存储,先通过np.hstack函数将s、a、r、s_变成一维的8个数据,然后存入memory_size中,跟前面介绍的memory_size维度是能够匹配上的。后面有一个index的计算,这个计算的是余数,意思就是如果self.memory_counter超过3000了,则在memory_size中从头开始存储,把先前已有的数据覆盖掉,如果没超过3000,那一开始定义的memory_size是全零的,也直接覆盖掉;这一段代码好好理解,写的非常棒(反正要我写是做不到这一步的);

    def choose_action(self, observation):
        observation = observation[np.newaxis, :]   #增加一个维度的作用
        actions_value = self.sess.run(self.q_eval, feed_dict={self.s: observation})   #经过网络输出的是(1,11)的维度
        action = np.argmax(actions_value)   #找到最大值的index

        if not hasattr(self, 'q'):  # record action value it gets
            self.q = []
            self.running_q = 0
        self.running_q = self.running_q*0.99 + 0.01 * np.max(actions_value)  #为了让图更顺滑,实际上应该是下面注释那一步
        #self.running_q = np.max(actions_value)
        self.q.append(self.running_q)

        if np.random.uniform() > self.epsilon:  # choosing action
            action = np.random.randint(0, self.n_actions)  #11个动作里随机选取,否则按照Q-eval网络的输出选取Q值最大的那个
        return action

这里的选取动作一开始是随机选取的,这个是强化学习的一个特点,然后达到一定的步数之后就会开始有依据的选择,这一段代码就是最后的if语句,一开始的self.epsilon是0,后面会慢慢的增加,所以后面就无法进入if语句中,就不会随机选取动作了;至于前面的self.running_q这一行代码的目的是为了后面绘制的图像更加平滑而已,没什么别的作用,看下面两幅图就理解了:

self.running_q = self.running_q*0.99 + 0.01 * np.max(actions_value):
在这里插入图片描述
self.running_q = np.max(actions_value):

在这里插入图片描述
后面的learn函数:

        if self.learn_step_counter % self.replace_target_iter == 0:   #从3000步开始每200步更新一次
            self.sess.run(self.replace_target_op)
            print('\ntarget_params_replaced\n')   #检查是否替换target_net参数,每200步更新一次

这里就是前3000步是不会更新Q’网络的参数的,从3000步开始更新,每200步更新一次;

        if self.memory_counter > self.memory_size:
            sample_index = np.random.choice(self.memory_size, size=self.batch_size)   #随机选取batch_size个大小的数据,返回的是index
        else:
            sample_index = np.random.choice(self.memory_counter, size=self.batch_size)
        batch_memory = self.memory[sample_index, :]   #读取相应的index数据

随机选取memory_size中的数据用于后续的网络输入,随机选取就是打乱数据间的相关性,前面理论部分讲到了;

后面的代码就是Double DQN的算法了,就不具体剖析了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值