关于探索与利用的决策与平衡
探索exploration:尝试拉动更多可能的拉杆,尽管这个拉杆不一定会获得最大的奖励,但是可以获取所有拉杆的获奖概率,知道哪根杠杆具有最大的获奖概率。
利用exploitation:拉动已知期望奖励最大的拉杆,但该信息仅来自于有限的交互观测,所以当前的最优拉杆不一定是全局最优。
设计策略时就需要平衡探索和利用的次数,使得累积奖励最大化。
贪心算法
ϵ−Greedy\epsilon -Greedyϵ−Greedy 算法,每次以概率 1−ϵ1-\epsilon1−ϵ 选择以往经验中期望奖励估值最大的杠杆(对应利用),以概率ϵ\epsilonϵ 随机选择一根杠杆(探索),则公式表达为:
at={argmaxa∈AQ^(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} at={argmaxa∈AQ^(a),从 A中随机选择,采样概率:1−ϵ采样概率:ϵ
随着探索次数的增多,我们对各杠杆的获奖概率估计的越来越准,则可降低在探索上的投入,则可以降低ϵ\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)
随时间改变探索的概率
可以采用 ϵ\epsilonϵ 随时间衰减,可以采用反比例衰减,为 ϵt=1t\epsilon _t=\frac{1}{t}ϵt=t1 。
上置信界算法
引入不确定度 U(a)U(a)U(a) 的概念,当一根拉杆被拉动的次数比较少时,他的奖励概率的不确定性更高,更越有探索的价值。
上置信界UCB算法用到了霍夫丁不等式,X1,...,XnX_1,...,X_nX1,...,Xn 为n个独立同分布的随机变量,取值范围为[0,1] ,经验期望为 x‾n=1n∑j=1nXj\overline x_n=\frac{1}{n} \sum _{j=1}^{n}X_jxn=n1∑j=1nXj ,则有:
P(E[X]≥x‾n+u)≤e−2nu2
P(E[X]\geq\overline x_n +u ) \leq e^{-2nu^2}
P(E[X]≥xn+u)≤e−2nu2
将动作a的期望奖励估值 Q^t(a)\hat Q_t(a)Q^t(a) 带入 x‾t\overline x_txt (同为期望,则可以获得下面的不等式) ,并将 u=U^t(a)u=\hat U_t(a)u=U^t(a) 代表不确定度,可以得到
P{Qt(a)≥Q^t(a)+U^t(a)}≤p=e−2Nt(a)Ut(a)2
P\{Q_t(a) \geq \hat Q_t(a)+\hat U_t(a)\} \leq p=e^{-2N_t(a)U_t(a)^2}
P{Qt(a)≥Q^t(a)+U^t(a)}≤p=e−2Nt(a)Ut(a)2
那么会以1-p的概率发生 Qt(a)≤Q^t(a)+U^t(a)Q_t(a) \leq \hat Q_t(a)+\hat U_t(a)Qt(a)≤Q^t(a)+U^t(a), Q^t(a)+U^t(a)\hat Q_t(a)+\hat U_t(a)Q^t(a)+U^t(a) 则为期望奖励上线,此时,上置信界算法便选取期望奖励上界最大的动作 a=argmax[Q^t(a)+U^t(a)]a=argmax[\hat Q_t(a)+\hat U_t(a)]a=argmax[Q^t(a)+U^t(a)] ,可以根据 p=e−2Nt(a)Ut(a)2p=e^{-2N_t(a)U_t(a)^2}p=e−2Nt(a)Ut(a)2 计算得到:
U^t(a)=−logp2Nt(a).
\hat{U}_t(a)=\sqrt{\frac{-\log p}{2N_t(a)}}.
U^t(a)=2Nt(a)−logp.
更直观地说,UCB 算法在每次选择拉杆前,先估计每根拉杆的期望奖励的上界,使得拉动每根拉杆的期望奖励只有一个较小的概率超过这个上界,接着选出期望奖励上界最大的拉杆,从而选择最有可能获得最大期望奖励的拉杆。
设 p=1tp=\frac{1}{t}p=t1 ,并设置不确定性的比重 a=argmaxa∈AQ^(a)+c⋅U^(a)a=\arg\max_{a\in\mathcal{A}}\hat{Q}(a)+c\cdot\hat{U}(a)a=argmaxa∈AQ^(a)+c⋅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"])