Ensemble Bootstrapping for Q-Learning(EBQL)【论文复现】

原文链接:Ensemble Bootstrapping for Q-Learning

EBQL理论基础

Q-Learing(QL)是一种常见的强化学习算法,由于最优贝尔曼算子中的最大化项,存在高估计偏差。这种偏差可能会导致次优行为。Double Q-Learning(DQL)通过使用两个估计器来解决这个问题,但却导致了一个低估偏差。与QL中的过高估计类似,在某些情况下,低估偏差可能会降低性能。Ensemble Bootstrapped Q-Learning(EBQL)算法是一种新的减少偏差的算法,是DQL到集成的自然扩展。从理论上讲,EBQL算法可以证明在估计一组独立随机变量的最大均值时,类EBQL算法的更新能产生较低的MSE。EBQL在ATARI游戏的深度RL变体EBQL优于其他深度QL算法的性能。

简而言之,

  • QL存在过高估计的问题,
  • DQL使用两个估计器解决了过高估计的问题,但又引入了过低估计的问题。
  • EBQL算法(本文主角),在DQL的基础上扩展,比较好的解决了过估计问题。

EBQL的前置算法就是Q-Learning和Double Q-Learning算法,关于这两个算法的细节可以看之前写的如下两篇博客:

本文不讲过多的理论,只看看这个算法的思想,最后会给出EBQL算法的代码实现。

我们知道,在机器学习中集成学习的基本思想就是将多个分类器组合,从而实现一个预测效果更好的集成分类器。Thomas G. Dietterich指出了集成算法在统计,计算和表示上的有效原因:

  • 「统计上」: 一个学习算法可以理解为在一个假设空间 H \mathcal{H} H中选找到一个最好的假设。但是,当训练样本的数据量小到不够用来精确的学习到目标假设时,学习算法可以找到很多满足训练样本的分类器。因此将多个假设集成起来可以降低选择错误分类器的风险。
  • 「计算上」:集成算法可以从多个起始点进行局部搜索,从而分散陷入局部最优的风险。
  • 「表示上」:大多时候,假设空间 H \mathcal{H} H都不能精确建模到真实的最优解,即存在近似误差,对于不同的假设条件,集成学习通过加权的形式可以扩大假设空间。

当然,集成的思想在强化学习中也有很多的应用。在EBQL原文中说到,集成在RL中最令人兴奋的应用之一是改进探索和数据收集。这些方法可以看作是汤普森采样样方法对深度RL的自然扩展。这些集成方法考虑的是一个估计器的集合如何改进学习过程,而EBQL算法关注的是如何更好地训练估计器本身。

在EBQL原文中,首先介绍了集成估计器及其优点。文中提到,集成允许在用于选择最大成分的索引和用于近似其平均值的样本之间不均匀地分割样本。作者还表明,分配更多的样本来估计平均值,可以减少各种情况下的MSE。

EBQL可以看作是通过使用 K K K个Q值函数估计器的集成,将集成估计器(Ensemble Estimator)的概念应用到QL自举阶段。与DQL类似,当更新EBQL中的第 k k k个集合成员时,我们将下一个状态动作定义为
a ^ ∗ = argmax ⁡ a Q k ( s t + 1 , a ) \hat{a}^{*}=\operatorname{argmax}_{a} Q^{k}\left(s_{t+1}, a\right) a^=argmaxaQk(st+1,a)
在EBQL中,该值是通过对其余的集合成员(Q表)进行平均而得到的,也就是:
Q E N \ k = 1 K − 1 ∑ j ∈ [ K ] \ k Q j ( s t + 1 , a ^ ∗ ) Q^{E N \backslash k}=\frac{1}{K-1} \sum_{j \in[K] \backslash k} Q^{j}\left(s_{t+1}, \hat{a}^{*}\right) QEN\k=K11j[K]\kQj(st+1,a^)
需要注意的是,每次更新所选择更新的第 k k k个集成成员是随机选择的。

同时,动作的选择是利用所有的Q表的均值取最大得到的。也即是:
a t = argmax ⁡ a [ ∑ i = 1 K Q i ( s t , a ) ] a_{t}=\operatorname{argmax}_{a}\left[\sum_{i=1}^{K} Q^{i}\left(s_{t}, a\right)\right] at=argmaxa[i=1KQi(st,a)]
同时,值得注意的时,当 K = 2 K=2 K=2时,就变为了DQL的更新方法。而 K K K值越大,对于Q值的估计就越准确。

EBQL的算法流程如下:
image.png

作者在原文中,做了关于 K K K值的实验,实验结果如下:
image.png
这也证实了 K K K越大,对Q值的估计越准确。

EBQL代码实现

EBQL的代码非常简单,在DQL的基础上,做一点改动就可以了。

class EBQL:
    def __init__(self, K=5):
        self.env = gym.make('Pendulum-v0')

        self.env.seed(0)
        np.random.seed(0)

        self.K = K  # 利用K个Q表来学习
        self.gamma = 0.9  # decrease rate
        self.num_episodes = 2000  # number of episodes
        self.epsilon = 0.95

        # 由于Pendulum-v0环境的动作空间和状态空间都是连续的,所以需要离散化动作和状态
        self.theta_num = 30
        self.thetaDot_num = 50
        self.obs_n = self.thetaDot_num * self.theta_num
        self.act_n = 5

        self.q_table = []
        for _ in range(self.K):  # 创建K个Q表
            self.q_table.append(
                # np.random.uniform(low=0, high=1, size=(self.obs_n, self.act_n)) * 2
                np.ones((self.obs_n, self.act_n))
            )

        self.num_visits = np.zeros((self.obs_n, self.act_n))  # 定义访问次数,用来实时更新学习率alpha
        self.alpha_table = np.ones((self.obs_n, self.act_n))

    def bins(self, clip_min, clip_max, num):
        """分箱处理函数,把[clip_min,clip_max]区间平均分为num段,位于i段区间的特征值x会被离散化为i"""
        return np.linspace(clip_min, clip_max, num + 1)[1:-1]

    def digitize_state(self, observation):
        """get the discrete state in total 1296 states"""
        cosTheta, sinTheta, thetaDot = observation
        theta = math.acos(cosTheta)
        if sinTheta < 0:
            theta *= -1

        # 分别对各个连续特征值进行离散化(分箱处理)
        digitized = [np.digitize(theta, bins=self.bins(-math.pi, math.pi, self.theta_num)),
                     np.digitize(thetaDot, bins=self.bins(-8.0, 8.0, self.thetaDot_num))]
        return digitized[0] + self.theta_num * digitized[1]

    def select_action(self, observation):
        state = self.digitize_state(observation)
        if np.random.uniform(0, 1) < self.epsilon:
            # 利用所有Q表的均值进行动作的选择
            action_values = np.zeros_like(self.q_table[0][state, :])
            for i in range(self.K):
                action_values += self.q_table[i][state, :]

            action_Q_values = action_values / self.K
            action = np.random.choice(np.where(action_Q_values == action_Q_values.max())[0])
        else:
            action = np.random.choice(self.act_n)
        return action

    def update(self, observation, action, reward, observation_next, done):
        state = self.digitize_state(observation)
        state_next = self.digitize_state(observation_next)

        kt = np.random.randint(0, self.K)  # 随机选择一个Q表进行更新

        if done:
            target_Q = reward
        else:
            action_next_Q_values = self.q_table[kt][state_next, :]
            max_Q_action = np.random.choice(
                np.where(action_next_Q_values == action_next_Q_values.max())[0])
            action_values = np.zeros(self.q_table[0][state_next, :].shape)
            for i in range(self.K):
                if i != kt:
                    action_values += self.q_table[i][state_next, :]
            action_values = action_values / (self.K - 1)
            target_Q = reward + self.gamma * action_values[max_Q_action]
        
        # 根据访问次数实时更新学习率
        self.num_visits[state, action] += 1
        self.alpha_table[state, action] = 1 / ((self.num_visits[state, action]) ** 0.05)

        self.q_table[kt][state, action] = (
            1 - self.alpha_table[state, action]) * self.q_table[kt][state_next, action] + self.alpha_table[state, action] * target_Q

代码运行结果如下:(K=5)
image.png

我们随后测试了不同的 K K K值对EBQL算法性能的影响:
image.png
可以发现,在相同步数限制下,保持其他所有参数相同,只改变 K K K的值。随着 K K K的值增加,EBQL算法收敛得越慢。这也很好理解,毕竟要学习得Q表越多,就学得越慢(毕竟每次只随机挑选一个Q表进行学习!)。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlphaGuaGua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值