强化学习——day12 多臂老虎机问题MAB

在多臂老虎机(multi-armed bandit,MAB)问题(见图 2-1)中,有一个拥有 根拉杆的老虎机,拉动每一根拉杆都对应一个关于奖励的概率分布 。我们每次拉动其中一根拉杆,就可以从该拉杆对应的奖励概率分布中获得一个奖励 。我们在各根拉杆的奖励概率分布未知的情况下,从头开始尝试,目标是在操作 次拉杆后获得尽可能高的累积奖励。由于奖励的概率分布是未知的,因此我们需要在“探索拉杆的获奖概率”和“根据经验选择获奖最多的拉杆”中进行权衡。“采用怎样的操作策略才能使获得的累积奖励最高”便是多臂老虎机问题。如果是你,会怎么做呢?

图2-1 多臂老虎机
2.2.2 形式化描述
多臂老虎机问题可以表示为一个元组 ,其中:

为动作集合,其中一个动作表示拉动一个拉杆。若多臂老虎机一共有根拉杆,那动作空间就是集合,我们用 表示任意一个动作;
为奖励概率分布,拉动每一根拉杆的动作都对应一个奖励概率分布,不同拉杆的奖励分布通常是不同的。
假设每个时间步只能拉动一个拉杆,多臂老虎机的目标为最大化一段时间步内累积的奖励: 。其中表示在第时间步拉动某一拉杆的动作,表示动作获得的奖励。

2.2.3 累积懊悔
对于每一个动作,我们定义其期望奖励为。于是,至少存在一根拉杆,它的期望奖励不小于拉动其他任意一根拉杆,我们将该最优期望奖励表示为 。为了更加直观、方便地观察拉动一根拉杆的期望奖励离最优拉杆期望奖励的差距,我们引入懊悔(regret)概念。懊悔定义为拉动当前拉杆的动作与最优拉杆的期望奖励差,即 。累积懊悔(cumulative regret)即操作 次拉杆后累积的懊悔总量,对于一次完整的步决策,累积懊悔为 。MAB 问题的目标为最大化累积奖励,等价于最小化累积懊悔。

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

对于,初始化计数器 和期望奖励估值
for do
选取某根拉杆,该动作记为
得到奖励
更新计数器:
更新期望奖励估值:
end for
以上 for 循环中的第四步如此更新估值,是因为这样可以进行增量式的期望更新,公式如下。

如果将所有数求和再除以次数,其缺点是每次更新的时间复杂度和空间复杂度均为 。而采用增量式更新,时间复杂度和空间复杂度均为 。

下面我们编写代码来实现一个拉杆数为 10 的多臂老虎机。其中拉动每根拉杆的奖励服从伯努利分布(Bernoulli distribution),即每次拉下拉杆有的概率获得的奖励为 1,有的概率获得的奖励为 0。奖励为 1 代表获奖,奖励为 0 代表没有获奖。

导入需要使用的库,其中numpy是支持数组和矩阵运算的科学计算库,而matplotlib是绘图库

import numpy as np
import matplotlib.pyplot as plt

class BernoulliBandit:
“”" 伯努利多臂老虎机,输入K表示拉杆个数 “”"
def init(self, K):
self.probs = np.random.uniform(size=K) # 随机生成K个0~1的数,作为拉动每根拉杆的获奖
# 概率
self.best_idx = np.argmax(self.probs) # 获奖概率最大的拉杆
self.best_prob = self.probs[self.best_idx] # 最大的获奖概率
self.K = K

def step(self, k):
    # 当玩家选择了k号拉杆后,根据拉动该老虎机的k号拉杆获得奖励的概率返回1(获奖)或0(未
    # 获奖)
    if np.random.rand() < self.probs[k]:
        return 1
    else:
        return 0

np.random.seed(1) # 设定随机种子,使实验具有可重复性
K = 10
bandit_10_arm = BernoulliBandit(K)
print(“随机生成了一个%d臂伯努利老虎机” % K)
print(“获奖概率最大的拉杆为%d号,其获奖概率为%.4f” %
(bandit_10_arm.best_idx, bandit_10_arm.best_prob))
随机生成了一个10臂伯努利老虎机
获奖概率最大的拉杆为1号,其获奖概率为0.7203
接下来我们用一个 Solver 基础类来实现上述的多臂老虎机的求解方案。根据前文的算法流程,我们需要实现下列函数功能:根据策略选择动作、根据动作获取奖励、更新期望奖励估值、更新累积懊悔和计数。在下面的 MAB 算法基本框架中,我们将根据策略选择动作、根据动作获取奖励和更新期望奖励估值放在 run_one_step() 函数中,由每个继承 Solver 类的策略具体实现。而更新累积懊悔和计数则直接放在主循环 run() 中。

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):
    # 计算累积懊悔并保存,k为本次动作选择的拉杆的编号
    self.regret += self.bandit.best_prob - self.bandit.probs[k]
    self.regrets.append(self.regret)

def run_one_step(self):
    # 返回当前动作选择哪一根拉杆,由每个具体的策略实现
    raise NotImplementedError

def run(self, num_steps):
    # 运行一定次数,num_steps为总运行次数
    for _ in range(num_steps):
        k = self.run_one_step()
        self.counts[k] += 1
        self.actions.append(k)
        self.update_regret(k)

2.3 探索与利用的平衡
在 2.2 节的算法框架中,还没有一个策略告诉我们应该采取哪个动作,即拉动哪根拉杆,所以接下来我们将学习如何设计一个策略。例如,一个最简单的策略就是一直采取第一个动作,但这就非常依赖运气的好坏。如果运气绝佳,可能拉动的刚好是能获得最大期望奖励的拉杆,即最优拉杆;但如果运气很糟糕,获得的就有可能是最小的期望奖励。在多臂老虎机问题中,一个经典的问题就是探索与利用的平衡问题。探索(exploration)是指尝试拉动更多可能的拉杆,这根拉杆不一定会获得最大的奖励,但这种方案能够摸清楚所有拉杆的获奖情况。例如,对于一个 10 臂老虎机,我们要把所有的拉杆都拉动一下才知道哪根拉杆可能获得最大的奖励。利用(exploitation)是指拉动已知期望奖励最大的那根拉杆,由于已知的信息仅仅来自有限次的交互观测,所以当前的最优拉杆不一定是全局最优的。例如,对于一个 10 臂老虎机,我们只拉动过其中 3 根拉杆,接下来就一直拉动这 3 根拉杆中期望奖励最大的那根拉杆,但很有可能期望奖励最大的拉杆在剩下的 7 根当中,即使我们对 10 根拉杆各自都尝试了 20 次,发现 5 号拉杆的经验期望奖励是最高的,但仍然存在着微小的概率—另一根 6 号拉杆的真实期望奖励是比 5 号拉杆更高的。

于是在多臂老虎机问题中,设计策略时就需要平衡探索和利用的次数,使得累积奖励最大化。一个比较常用的思路是在开始时做比较多的探索,在对每根拉杆都有比较准确的估计后,再进行利用。目前已有一些比较经典的算法来解决这个问题,例如-贪婪算法、上置信界算法和汤普森采样算法等,我们接下来将分别介绍这几种算法。

你可能感兴趣的:(强化学习,人工智能)