强化学习知识总结(二):多臂老虎机(MAB)问题

问题描述

有一个拥有K根拉杆的老虎机,拉动每一根拉杆都对应一个关于奖励的概率分布R。我们每拉动其中一根拉杆,就可以从该拉杆对应的奖励概率分布中获得一个奖励r。我们在各根拉杆的奖励概率分布未知的情况下,从头开始尝试,目标是在操作T次拉杆后获得尽可能高的累计奖励

形象化描述

Target: 最大化一段时间步T内累积的激励

max\sum_{t=1} ^{T}r_t,r_t \sim R(\cdot|a_t )     其中a_t 表示在第t时间步拉动某一拉杆的动作,r_t表示动作a_t 获得的奖励

累积懊悔

懊悔:拉动当前拉杆的动作a与最优拉杆的期望奖励差

R(a)=Q^{\ast}-Q(a), Q^{\ast}=max_{a\epsilon A }Q(a), Q(a)=E_{r\sim R(\cdot |a)}[r]

累积懊悔:

\sigma _{R}=\sum_{t=1}^{T}R(a_{t})

MAB问题的目标为最大化累积奖励,等价于最小化累积懊悔

估计期望奖励

为了知道拉动哪一根拉杆能获得更高的奖励,我们需要估计拉动这根拉杆的期望奖励。由于只拉动一次拉杆获得的奖励存在随机性,所以需要多次拉动一根拉杆,然后计算奖励期望

增量式更新:时间复杂度为O(1)

Q_{k}=\frac{1}{k}\sum_{i=1}^{k}r_i \\ = \frac{1}{k}(r_k+\sum_{i=1}^{k-1}r_i) \\ = \frac{1}{k}(r_k+(k-1)Q_{k-1})) \\ =Q_{k-1}+\frac{1}{k}[r_k-Q_{k-1}]

因此有:\hat{Q}(a_t)=\hat{Q}(a_t)+\frac{1}{N(a_t)}[r_t-\hat{Q}(a_t)]  其中Q表示期望奖励,N表示计数器

策略设计

MAB问题的求解流程:根据策略选择动作,根据动作获取奖励,更新期望奖励估值,更新累积懊悔和计数

探索:尝试拉动更多的拉杆,尽可能的摸清楚所有拉杆的获奖情况

利用:拉动已知期望奖励最大的那根拉杆,但由于已知的信息仅仅辣子有限次的交互观察,所以当前的最优拉杆不一定是全局最优的

设计策略时需要平衡探索和利用的次数,使得累计奖励最大化

(1)\epsilon-贪婪算法

完全贪婪算法的思想是在每一时刻采取期望奖励最大的动作(拉动拉杆),即纯粹的“利用”,没有“探索”。\epsilon-贪婪算法实在完全贪婪算法的基础上添加了噪声,每次以概率1-\epsilon选择以往经验中期望奖励估值最大的那根拉杆(action:利用),以概率\epsilon随机选择一根拉杆(action:探索)

a_t=\left\{\begin{matrix} argmax_{a\in A}\hat{Q}(a) ,P=1-\epsilon \\ random \quad choice \quad from \quad A ,P=\epsilon\end{matrix}\right.

随着探索次数的不断增加,我们队各个动作的奖励估计会越来越准,此时不必画大力气进行探索,所以可以令\epsilon随时间衰减(注意:不要在有限步数衰减到0)

(2)上置信界算法

引入不确定性度量U(a),它会随着一个动作被尝试次数的增加而减小。(一根拉杆的不确定性越大,它就越有探索的价值,因为探索之后我们可能发现它的期望奖励很大)

如何估计不确定性:

霍夫丁不等式:令X_1,X_2,...,X_n为n个独立同分布的随机变量,取值范围为[0,1],其经验期望为\bar{x_n}=\frac{1}{n}\sum_{j=1}^{n}X_j,则有P(E[x]\geqslant \bar{x_i}+u)\leqslant e^{-2nu^2}

\hat{Q}(a_t)代入\bar{x_t},不等式中的参数u=\hat{U}(a_t)代表不确定性度量。给定一个概率p=e^{-2N(a_t)U(a_t)^2},根据上述不等式,Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t)至少以概率1-p成立。当p很小时,Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t)就以很大的概率成立,\hat{Q}(a_t)+\hat{U}(a_t)即为期望奖励上界。此时,上置信界算法便选取期望奖励上界最大的动作,即a_t=argmax_{a\in A}[\hat{Q}(a)+\hat{U}(a)]。根据公式p=e^{-2N(a_t)U(a_t)^2},解得\hat{U}(a_t)=\sqrt{\frac{-log p}{2N(a_t)}}。因此,设定一个概率p后,就可以计算相应的不确定性度量\hat{U}(a_t)了。

UCB算法的思想是在每次选择拉杆前,先估计拉动每根拉杆的期望奖励上界,使得拉动每根拉杆的期望奖励只有一个较小的概率p超过这个上界,接着选出期望奖励上界最大的拉杆,从而选择最有可能获得最大期望奖励的拉杆。 

在编写代码时,设置p=\frac{1}{t},并且为了避免\hat{U}(a_t)分母为0,\hat{U}(a_t)=\sqrt{\frac{log t}{2N(a_t)+1}}(在分母上+1)。同时还设置了一个权重系数coef来控制不确定性比重,此时a_t=argmax_{a\in A}[\hat{Q}(a)+coef\cdot \hat{U}(a)]

(3)汤普森采样算法

先假设拉动每根拉杆的奖励服从一个特定的奖励分布,然后根据拉动每根拉杆的期望奖励来进行选择。但是由于计算所有拉杆的期望奖励的代价比较高,汤普森采样算法使用采样的方式,即根据当前每个动作a的奖励概率分布进行一轮采样,得到一组各根拉杆的奖励样本,再选择样本中奖励最大的动作,可以看出,汤普森采样是一种计算所有拉杆的最高奖励概率的蒙特卡洛采样方法。

在实际情况中,通常用Beta分布对当前每个动作的奖励概率分布进行建模。具体来说,若拉杆被选择了k次,其中m1次奖励为1,m2次奖励为0,则该拉杆的奖励服从参数为(m1+1,m2+1)的Beta分布。

代码实现

import numpy as np
import matplotlib.pyplot as plt


class BernoulliBandit:  # 期望奖励满足伯努利分布的多臂老虎机问题
    def __init__(self, K):
        self.probs = np.random.uniform(size=K)  # 随机生成每根拉杆的获奖概率
        self.best_idx = np.argmax(self.probs)  # 期望奖励最大的拉杆的index
        self.best_prob = self.probs[self.best_idx]  # 期望奖励最大的拉杆的获奖概率
        self.K = K  # 拉杆的根数

    def step(self, K):
        if np.random.rand() < self.probs[K]:
            return 1
        else:
            return 0


class Solver:
    def __init__(self, bandit):
        self.bandit = bandit
        self.counts = np.zeros(self.bandit.K)
        self.regret = 0.
        self.actions = []
        self.regrets = []

    def update_regret(self, k):
        self.regret += self.bandit.best_prob - self.bandit.probs[k]  # 累计懊悔
        self.regrets.append(self.regret)

    def run_one_step(self):
        return NotImplementedError

    def run(self, num_steps):
        for _ in range(num_steps):
            k = self.run_one_step()
            self.counts[k] += 1
            self.actions.append(k)
            self.update_regret(k)


class EpsilonGreedy(Solver):
    def __init__(self, bandit, epsilon=0.01, init_prob=1.0):
        super(EpsilonGreedy, self).__init__(bandit)
        self.epsilon = epsilon
        self.estimates = np.array([init_prob] * self.bandit.K)  # 初始化期望奖励

    def run_one_step(self):
        if np.random.random() < self.epsilon:
            k = np.random.randint(0, self.bandit.K)
        else:
            k = np.argmax(self.estimates)
        r = self.bandit.step(k)
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])  # 增量式更新期望奖励
        return k


class DecayingEpsilonGreedy(Solver):
    def __init__(self, bandit, init_prob=1.0):
        super(DecayingEpsilonGreedy, self).__init__(bandit)
        self.estimates = np.array([init_prob] * self.bandit.K)
        self.total_count = 0

    def run_one_step(self):
        self.total_count += 1
        if np.random.random() < 1 / self.total_count:  # epsilon随时间衰减
            k = np.random.randint(0, self.bandit.K)
        else:
            k = np.argmax(self.estimates)
        r = self.bandit.step(k)
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])  # 增量式更新期望奖励
        return k


class UCB(Solver):
    def __init__(self, bandit, coef, init_prob=1.0):
        super(UCB, self).__init__(bandit)
        self.total_count = 0
        self.estimates = np.array([init_prob] * self.bandit.K)  # 初始化期望奖励
        self.coef = coef # 控制不确定性比重

    def run_one_step(self):  # 取p=1/t t为拉杆次数
        self.total_count += 1
        ucb = self.estimates + self.coef * np.sqrt(np.log(self.total_count) / (2 * (self.counts + 1)))  # 计算上置信界
        k = np.argmax(ucb)  # 选取期望奖励上界最大的动作
        r = self.bandit.step(k)
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])  # 增量式更新期望奖励
        return k


class ThompsonSampling(Solver):
    def __init__(self, bandit):
        super(ThompsonSampling, self).__init__(bandit)
        self._a = np.ones(self.bandit.K)  # 列表,表示每根拉杆奖励为1的次数
        self._b = np.ones(self.bandit.K)  # 列表,表示每根拉杆奖励为0的次数

    def run_one_step(self):
        samples = np.random.beta(self._a, self._b)  # 按照beta分布采样一组奖励样本
        k = np.argmax(samples)  # 选出采样奖励最大的拉杆
        r = self.bandit.step(k)

        self._a[k] += r  # 更新beta分布的第一个参数
        self._b[k] += 1 - r  # 更新beta分布的第二个参数
        return k


np.random.seed(1)
K = 10
bandit_10_arm = BernoulliBandit(K)
solver1 = DecayingEpsilonGreedy(bandit_10_arm)
coef = 1
solver2 = UCB(bandit_10_arm, coef)
solver3 = ThompsonSampling(bandit_10_arm)
num_step = 5000

solver1.run(num_step)
print("epsilon-贪婪算法的累计懊悔:", solver1.regret)
solver2.run(num_step)
print("上置信界算法的累计懊悔:", solver2.regret)
solver3.run(num_step)
print("汤普森采样算法的累计懊悔:", solver3.regret)

  • 49
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值