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) # 索引
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 # 没有获得奖励
# 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))
class Solver:
def __init__(self, bandit):
self.bandit = bandit
self.counts = np.zeros(self.bandit.K) # 每根拉杆的尝试次数
self.regret = 0.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) # 更新懊悔值
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.counts[k] 是第 k 根拉杆的拉动次数
# 更新公式为:new_estimate = old_estimate + (reward - old_estimate) / (拉动次数 + 1)
self.estimates[k] += 0.0 + (r - self.estimates[k]) * 1./(self.counts[k] + 1)
return k
def plot_results(solvers, solver_names):
"""生成累积懊悔随时间变化的图像。输入solvers是一个列表,列表中的每个元素是一种特定的策略。而solver_names也是一个列表,存储每个策略的名称"""
for idx, solver in enumerate(solvers):
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.title('%d-armed bandit' % solvers[0].bandit.K)
plt.legend()
plt.show()
np.random.seed(1)
epsilon_greedy_solver = EpsilonGreedy(bandit_10_arm, epsilon=0.01)
# 运行 epsilon-贪婪算法 5000 步
epsilon_greedy_solver.run(5000)
print('epsilon-贪婪算法的累积懊悔为:', epsilon_greedy_solver.regret)
plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])
class DecayingEpsilonGreedy(Solver):
"""epsilon值随时间衰减的epsilon-贪婪算法,继承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 # 返回选择的拉杆编号
np.random.seed(1)
decaying_epsilon_greedy_solver = DecayingEpsilonGreedy(bandit_10_arm)
decaying_epsilon_greedy_solver.run(5000)
print('epsilon值衰减的贪婪算法的累积懊悔为:', decaying_epsilon_greedy_solver.regret)
plot_results([decaying_epsilon_greedy_solver], ["DecayingEpsilonGreedy"])
代码来源于b站up主 -xurunnan- 侵删