用Python和NumPy手把手教你实现10臂老虎机(附完整代码与可视化分析)
用Python和NumPy手把手教你实现10臂老虎机附完整代码与可视化分析在强化学习的入门阶段很多学习者都会被各种数学公式和抽象概念所困扰。今天我们将通过一个经典的10臂老虎机问题用Python代码带你直观理解强化学习的核心机制。不同于传统的理论讲解我们将从零开始编写每一行代码并通过实时可视化观察学习过程。1. 理解多臂老虎机问题多臂老虎机Multi-armed Bandit是强化学习中最基础的决策问题模型。想象你站在一个赌场里面前有10台老虎机slot machine每台机器的中奖概率各不相同。你的目标是通过有限的尝试次数找到最优的老虎机并获得最大累积奖励。这个问题的核心在于探索-利用困境Exploration-Exploitation Dilemma探索尝试不同的老虎机以收集信息利用根据已有知识选择当前表现最好的老虎机import numpy as np import matplotlib.pyplot as plt # 设置随机种子确保结果可复现 np.random.seed(42)2. 构建老虎机环境首先我们需要创建一个老虎机环境类。每个老虎机都有一个固定的中奖概率但每次拉动臂杆的结果仍然是随机的。class Bandit: def __init__(self, arms10): # 随机生成10个老虎机的中奖概率 self.true_rates np.random.rand(arms) def play(self, arm): # 模拟拉动指定老虎机臂杆的结果 rate self.true_rates[arm] return 1 if rate np.random.rand() else 0提示这里我们使用0和1作为奖励信号1表示中奖0表示未中奖。在实际应用中奖励可以是任意数值。3. 实现智能体决策逻辑智能体需要维护两个关键信息每个动作的价值估计Q值每个动作的尝试次数class Agent: def __init__(self, epsilon, action_size10): self.epsilon epsilon # 探索率 self.Q np.zeros(action_size) # 动作价值估计 self.counts np.zeros(action_size) # 动作尝试次数 def update(self, action, reward): 根据新获得的奖励更新价值估计 self.counts[action] 1 # 增量式更新Q值 self.Q[action] (reward - self.Q[action]) / self.counts[action] def get_action(self): 基于ε-greedy策略选择动作 if np.random.rand() self.epsilon: # 探索随机选择动作 return np.random.randint(len(self.Q)) # 利用选择当前估计价值最高的动作 return np.argmax(self.Q)4. 完整训练流程与可视化现在我们将环境、智能体和训练循环组合起来并实时可视化学习过程。def run_experiment(epsilon, steps1000): bandit Bandit() agent Agent(epsilon) rewards [] optimal_rates [] optimal np.argmax(bandit.true_rates) # 真实最优老虎机 for step in range(steps): action agent.get_action() reward bandit.play(action) agent.update(action, reward) # 记录数据用于可视化 rewards.append(reward) optimal_rates.append(action optimal) return rewards, optimal_rates # 运行不同探索率的实验 epsilons [0, 0.01, 0.1] results {eps: run_experiment(eps) for eps in epsilons} # 可视化结果 plt.figure(figsize(12, 8)) # 绘制累积奖励曲线 plt.subplot(2, 1, 1) for eps, (rewards, _) in results.items(): plt.plot(np.cumsum(rewards), labelfε{eps}) plt.ylabel(累积奖励) plt.xlabel(步数) plt.legend() # 绘制最优动作选择率 plt.subplot(2, 1, 2) for eps, (_, optimal_rates) in results.items(): plt.plot(np.cumsum(optimal_rates) / (np.arange(len(optimal_rates)) 1), labelfε{eps}) plt.ylabel(最优动作选择率) plt.xlabel(步数) plt.legend() plt.tight_layout() plt.show()5. 关键参数分析与调优从实验结果可以看出探索率ε对学习效果有显著影响ε值累积奖励收敛速度最优动作发现能力0最低最快差0.01中等慢中等0.1最高中等优实际应用建议初期可以设置较高的ε值如0.1-0.3以充分探索随着尝试次数增加可以逐渐降低ε值退火策略对于确定性环境最终可以将ε降至0# 退火ε-greedy策略实现 class AnnealingAgent(Agent): def __init__(self, action_size10): super().__init__(1.0, action_size) # 初始ε1.0 self.steps 0 def get_action(self): self.epsilon 1.0 / (self.steps 1) self.steps 1 return super().get_action()6. 高级改进与扩展思路基础版本实现后我们可以考虑以下改进方向置信区间上界UCB算法不仅考虑Q值还考虑动作的不确定性平衡探索和利用的更优方式class UCBAgent: def __init__(self, c2, action_size10): self.Q np.zeros(action_size) self.counts np.zeros(action_size) self.c c # 探索系数 self.total_counts 0 def get_action(self): if self.total_counts 0: return np.random.randint(len(self.Q)) # UCB计算公式 ucb_values self.Q self.c * np.sqrt(np.log(self.total_counts) / (self.counts 1e-5)) return np.argmax(ucb_values) def update(self, action, reward): self.counts[action] 1 self.total_counts 1 self.Q[action] (reward - self.Q[action]) / self.counts[action]非平稳环境处理真实场景中老虎机的中奖概率可能随时间变化可以使用加权平均替代算术平均class NonStationaryAgent(Agent): def __init__(self, epsilon0.1, alpha0.1, action_size10): super().__init__(epsilon, action_size) self.alpha alpha # 固定学习率 def update(self, action, reward): self.Q[action] self.alpha * (reward - self.Q[action])7. 实战技巧与常见问题在实现过程中可能会遇到以下典型问题及解决方案初始Q值设置全零初始化可能导致智能体过于保守解决方案使用乐观初始值Optimistic Initial Valuesclass OptimisticAgent(Agent): def __init__(self, epsilon0, initial_value5, action_size10): super().__init__(epsilon, action_size) self.Q[:] initial_value # 设置乐观初始值随机性处理确保实验可重复性解决方案固定随机种子np.random.seed(42) # 在实验开始前设置性能优化对于大规模实验使用向量化操作避免Python循环尽量使用NumPy# 向量化实现示例 def vectorized_update(Q, counts, actions, rewards): counts[actions] 1 Q[actions] (rewards - Q[actions]) / counts[actions]在完成基础实现后我通常会先测试极端参数情况如ε0和ε1来验证代码逻辑是否正确。一个常见错误是忘记在ε-greedy策略中处理探索和利用的边界条件这会导致智能体无法学习到最优策略。