强化学习2——多臂老虎机(下)

167 阅读1分钟

关于探索与利用的决策与平衡

探索exploration:尝试拉动更多可能的拉杆,尽管这个拉杆不一定会获得最大的奖励,但是可以获取所有拉杆的获奖概率,知道哪根杠杆具有最大的获奖概率。

利用exploitation:拉动已知期望奖励最大的拉杆,但该信息仅来自于有限的交互观测,所以当前的最优拉杆不一定是全局最优。

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

贪心算法

ϵGreedy\epsilon -Greedy 算法,每次以概率 1ϵ1-\epsilon 选择以往经验中期望奖励估值最大的杠杆(对应利用),以概率ϵ\epsilon 随机选择一根杠杆(探索),则公式表达为:

at={argmaxaAQ^(a),采样概率:1ϵ从 A中随机选择,采样概率:ϵa_t=\begin{cases}\arg\max_{a\in\mathcal{A}}\hat{Q}(a),&{采样概率:1-\epsilon }\\\text{从 }\mathcal{A}\text{中随机选择},&{采样概率:\epsilon}\end{cases}

随着探索次数的增多,我们对各杠杆的获奖概率估计的越来越准,则可降低在探索上的投入,则可以降低ϵ\epsilon 的大小,因此可以将ϵ\epsilon 与时间绑定,随时间衰减,但其不会降低到零。

线性贪婪算法

class EpsilonGreedy(Solver):
    # ϵ-greedy算法
    def __init__(self,bandit,epsilon=0.01,initProb=1.0):
        super(EpsilonGreedy,self).__init__(bandit)
        self.epsilon=epsilon
        self.estimates=np.array([initProb]*self.bandit.K) #初始化每个拉杆的估计概率
    def runOneStep(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.0/(self.counts[k]+1)*(r-self.estimates[k]) #更新估计概率
        return k

def plotResults(solvers,solverNames):
    # 输入solvers是一个列表,列表中的每个元素是一种特定的策略(存储的数据也是以列表的形式)
    # 而solver_names也是一个列表,存储每个策略的名称
    for idx,solver in enumerate(solvers):
        timeList=range(len(solver.regrets))
        plt.plot(timeList,solver.regrets,label=solverNames[idx])
    plt.xlabel('Time')
    plt.ylabel('Cumulative regret')  
    plt.title('%d armed bandit'% solvers[0].bandit.K)
    plt.legend()
    plt.show()

np.random.seed(0)
K=10
bandit10Arm=BernoullBandit(K)
epsilons = [1e-4, 0.01, 0.1, 0.25, 0.5]
epsilon_greedy_solver_list = [
    EpsilonGreedy(bandit10Arm, epsilon=e) for e in epsilons
]
epsilon_greedy_solver_names = ["epsilon={}".format(e) for e in epsilons]
for solver in epsilon_greedy_solver_list:
    solver.run(5000)
plotResults(epsilon_greedy_solver_list, epsilon_greedy_solver_names)

image.png

随时间改变探索的概率

可以采用 ϵ\epsilon 随时间衰减,可以采用反比例衰减,为 ϵt=1t\epsilon _t=\frac{1}{t}

上置信界算法

引入不确定度 U(a)U(a) 的概念,当一根拉杆被拉动的次数比较少时,他的奖励概率的不确定性更高,更越有探索的价值。

上置信界UCB算法用到了霍夫丁不等式X1,...,XnX_1,...,X_n 为n个独立同分布的随机变量,取值范围为[0,1] ,经验期望为 xn=1nj=1nXj\overline x_n=\frac{1}{n} \sum _{j=1}^{n}X_j ,则有:

P(E[X]xn+u)e2nu2P(E[X]\geq\overline x_n +u ) \leq e^{-2nu^2}

将动作a的期望奖励估值 Q^t(a)\hat Q_t(a) 带入 xt\overline x_t (同为期望,则可以获得下面的不等式) ,并将 u=U^t(a)u=\hat U_t(a) 代表不确定度,可以得到

P{Qt(a)Q^t(a)+U^t(a)}p=e2Nt(a)Ut(a)2P\{Q_t(a) \geq \hat Q_t(a)+\hat U_t(a)\} \leq p=e^{-2N_t(a)U_t(a)^2}

那么会以1-p的概率发生 Qt(a)Q^t(a)+U^t(a)Q_t(a) \leq \hat Q_t(a)+\hat U_t(a)Q^t(a)+U^t(a)\hat Q_t(a)+\hat U_t(a) 则为期望奖励上线,此时,上置信界算法便选取期望奖励上界最大的动作 a=argmax[Q^t(a)+U^t(a)]a=argmax[\hat Q_t(a)+\hat U_t(a)] ,可以根据 p=e2Nt(a)Ut(a)2p=e^{-2N_t(a)U_t(a)^2} 计算得到:

U^t(a)=logp2Nt(a).\hat{U}_t(a)=\sqrt{\frac{-\log p}{2N_t(a)}}.

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

p=1tp=\frac{1}{t} ,并设置不确定性的比重 a=argmaxaAQ^(a)+cU^(a)a=\arg\max_{a\in\mathcal{A}}\hat{Q}(a)+c\cdot\hat{U}(a) ,同时,为了避免不确定度出现分母为0的情况,在分母+1。

class UCB(Solver):
    """ 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 runOneStep(self):
        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
  
np.random.seed(1)
coef = 1  # 控制不确定性比重的系数
K=10
bandit10Arm=BernoullBandit(K)
UCBSolver = UCB(bandit10Arm, coef)
UCBSolver.run(5000)
print('上置信界算法的累积懊悔为:', UCBSolver.regret)
plotResults([UCBSolver], ["UCB"])

image.png