""" 实现一个拉杆数为10的多臂老虎机,其中拉动每根拉杆的奖励服从伯努利分布(Bernoulli distribution),
即每次拉动拉杆有p的概率获得的奖励为1,有1-p的概率获得奖励为0。奖励为1表示获奖,奖励为0表示没有获奖 """
import numpy as np
import matplotlib.pyplot as plt
class Bernoullibandit:
""" 伯努利多臂老虎机,输入K表示拉杆个数 """
def __init__(self,K):
# 随机生成K个0~1的数,作为拉动每根拉杆的获奖概率
self.probs=np.random.uniform(size=K)
# 获奖概率最大的拉杆
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
class Solver:
""" Slover基础类求解方案(根据动作获取奖励,更新奖励估值,更新累计懊悔和计数);在下面MAB算法框架中,根据策略选择动作,根据
动作获取奖励和更新奖励期望估值放在run_one_step()函数中,由每个继承类Solver类的策略具体实现,而更新累计懊悔和计数则直接放在主循环run()函数中 """
""" 多臂老虎机算法基本框架 """
def __init__(self,bandit) -> None:
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_step为总运行次数
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):
""" epsilon贪婪算法,继承Solver类 """
def __init__(self, bandit,epsilon=0.01,init_prob=1.0) -> None:
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-1)#随机选择一个拉杆
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
def plot_results(solvers,solver_names):
""" 生成累计懊悔随时间变化的图像,输入solvers为一个列表,列表中每一个元素是一种特定的策略 """
""" 而solver_names也是一个列表,存储每个策略的名称 """
for idx,solver in enumerate(solvers): #for i ,value in enumerate(a,start)表示从列表start下标开始遍历分别将列表元素下表和值赋值给i和value
time_list=range(len(solver.regrets))
plt.plot(time_list,solver.regrets,label=solver_names[idx])
plt.xlabel('Time steps')
plt.ylabel('Cumulative regrets')
plt.legend()#在图表中添加图例,以标识各个数据系列
plt.show()
#设置随机种子,使得实验可重复性
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))
a=[['1'for _ in range(10)]for _ in range(10)]
print(a)
""" epsilon-贪婪算法策略<main_start> """
np.random.seed(1)
epsilon_greedy_solver=EpsilonGreedy(bandit_10_arm,epsilon=0.01)
epsilon_greedy_solver.run(5000)
print('epsilon-贪婪算法的累计懊悔为%f'%epsilon_greedy_solver.regret)
plot_results([epsilon_greedy_solver],['EpsilonGreedy'])
""" epsilon-贪婪算法策略<main_end> """
""" 多epsilon取值的epsilon-贪婪算法策略<main_start> """
np.random.seed(0)
epsilons=[1e-4,0.01,0.1,0.25,0.5]
epsilon_greedy_solver_list=[EpsilonGreedy(bandit_10_arm,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)
plot_results(epsilon_greedy_solver_list,epsilon_greedy_solver_names)
""" 多epsilon取值的epsilon-贪婪算法策略<main_end> """
""" epsilon随时间衰减的epsilon-贪婪算法,采用反比例衰减形式<main_start> """
class DecayingEpsilonGreedy(Solver):
""" epsilon随时间衰减的epsilon-贪婪算法,继承Solver类 """
def __init__(self, bandit,init_prob=1.0) -> None:
super(DecayingEpsilonGreedy,self).__init__(bandit)
self.estimates=np.array([init_prob]*self.bandit.K)
self.total_count=0
self.epsilon=0
def run_one_step(self):
self.total_count+=1
self.epsilon=1/(self.total_count)
if np.random.random()<self.epsilon:
k=np.random.randint(0,self.bandit.K-1)
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
np.random.seed(1)
decaying_epsilon_greedy_solver=DecayingEpsilonGreedy(bandit_10_arm)
decaying_epsilon_greedy_solver.run(5000)
print("epsilon值衰减的贪婪算法的累计懊悔为{}".format(decaying_epsilon_greedy_solver.regret))
plot_results([decaying_epsilon_greedy_solver],["DecayingEpsilonGreedy"])
""" epsilon随时间衰减的epsilon-贪婪算法,采用反比例衰减形式<main_end> """
""" 上置信界算法 """
class UCB(Solver):
""" UCB算法,继承Solver类 """
def __init__(self, bandit,coef,init_prob=1.0) -> None:
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):
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)
ucb_solver=UCB(bandit=bandit_10_arm,coef=1.0)
ucb_solver.run(5000)
print("上置信界算法的累计懊悔为{}".format(ucb_solver.regret))
plot_results([ucb_solver],["UCB"])
""" 上置信界算法 """
""" 汤普森采样算法 """
""" 汤普森采样算法即Thompson sampling算法,是一种概率采样算法 ,它的核心思想是利用多个Beta分布来描述每个拉杆的获奖概率,是一种计算所有拉杆的最高获奖概率的蒙特卡洛采样算法 """""""""
""" 该拉杆的获奖概率服从参数为(m1+1,m2+1)的Beta分布"""
""" 其中m1为该拉杆已经获奖的次数,m2为该拉杆未获奖的次数"""
class ThompsonSampling(Solver):
def __init__(self, bandit) -> None:
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) #按照Beata分布采样一组奖励样本
k=np.argmax(samples) #选出采样奖励最大的拉杆
r=self.bandit.step(k)
self._a[k]+=r #更新Beata分布的第一个参数
self._b[k]+=1-r #更新Beata分布的第二个参数
return k
np.random.seed(1)
thompsonsampling=ThompsonSampling(bandit=bandit_10_arm)
thompsonsampling.run(5000)
print("汤普森采样算法的累计懊悔为{}".format(thompsonsampling.regret))
plot_results([thompsonsampling],["ThompsonSampling"])
""" 汤普森采样算法 """
""" 小结 """
""" 探索与利用是与环境做交互学习的重要问题,是强化学习试错法中的必备技术,而多臂老虎机问题是研究探索与利用理论的最佳环境,了解多臂老虎机的探索与利用问题
对接下来我们的学习强化学习环境探索有很重要的帮助,对于多臂老虎机各种算法的累计懊悔理论分析,epsilon-贪婪算法、上置信算法、汤普森采样算法在多臂老虎机问题中十分常见,其中
上置信算法和汤普森采样算法均能保证对数的渐进最优累计懊悔。多臂老虎机问题与强化学习一大区别在于其与环境的交互不会改变环境,多臂老虎机的每次交互的结果都和以往的动作无关,
可以看成是无状态的强化学习,即stateless reinforcement learning """
""" 小结 """
强化学习学习程序笔记记录一(多臂老虎机问题)
于 2024-07-23 23:47:11 首次发布