用Python构建贪吃蛇AI:从零理解Sarsa与Q-learning的本质差异
当我在大学第一次接触强化学习时,教授在黑板上写满了贝尔曼方程的推导过程。那些复杂的数学符号让我昏昏欲睡,直到我决定用代码把它们"敲"出来——在构建贪吃蛇AI的过程中,那些抽象概念突然变得鲜活起来。本文将带你用Python重现这个顿悟时刻,通过游戏开发理解On-Policy与Off-Policy的核心区别。
1. 环境搭建:会咬自己尾巴的Python贪吃蛇
我们先从构建游戏环境开始。使用PyGame库可以快速创建可视化界面,但为了聚焦算法本质,我推荐更轻量级的pygame-zero库。以下是环境类的核心结构:
class SnakeGame: def __init__(self, grid_size=10): self.grid_size = grid_size self.reset() def reset(self): self.snake = [(5, 5)] # 初始蛇头位置 self.direction = (0, 1) # 初始移动方向 self.food = self._generate_food() self.done = False self.score = 0 def _generate_food(self): """在非蛇身位置随机生成食物""" available = [(x, y) for x in range(self.grid_size) for y in range(self.grid_size) if (x, y) not in self.snake] return random.choice(available)关键状态特征设计(直接影响AI学习效率):
- 相对食物方向:蛇头与食物的角度关系(8个方位)
- 危险检测:蛇头前方/左右各一格是否接近墙壁或自身
- 移动方向:当前行进方向的向量表示
提示:状态设计要兼顾信息完备性和维度简洁性。过于复杂的特征会显著增加训练难度。
2. 算法实现:当Sarsa遇见Q-learning
2.1 Sarsa的保守主义哲学
Sarsa得名于其更新所需的五元组(State, Action, Reward, next State, next Action)。它的核心特点是"言行一致"——用实际执行的下一步动作来更新当前策略。这种On-Policy特性使其行为相对保守:
def sarsa_update(self, state, action, reward, next_state, next_action, alpha=0.1, gamma=0.9): current_q = self.q_table.get((state, action), 0) next_q = self.q_table.get((next_state, next_action), 0) # 关键区别:使用实际采取的next_action计算TD目标 new_q = current_q + alpha * (reward + gamma * next_q - current_q) self.q_table[(state, action)] = new_q在贪吃蛇游戏中,这种保守性表现为:
- 遇到危险区域时会提前转向
- 更倾向于保持现有安全路径
- 探索新路线时更加谨慎
2.2 Q-learning的冒险家精神
相比之下,Q-learning是典型的Off-Policy算法,它总是假设下一步会采取最优动作,而不论实际策略如何:
def q_learning_update(self, state, action, reward, next_state, alpha=0.1, gamma=0.9): current_q = self.q_table.get((state, action), 0) # 关键区别:选择最大Q值的动作,不考虑实际策略 max_next_q = max([self.q_table.get((next_state, a), 0) for a in self.possible_actions]) new_q = current_q + alpha * (reward + gamma * max_next_q - current_q) self.q_table[(state, action)] = new_q这种特性带来的行为模式:
- 更愿意冒险穿过狭窄通道
- 对短期危险的反应较慢
- 最终策略通常更激进高效
3. 对比实验:当算法在游戏中现形
我们在相同初始条件下训练两个算法各5000局,记录关键指标:
| 指标 | Sarsa | Q-learning |
|---|---|---|
| 平均存活步数 | 152±28 | 138±35 |
| 最大得分 | 48 | 52 |
| 撞墙概率 | 12% | 23% |
| 自噬概率 | 9% | 17% |
典型场景对比:
狭窄通道穿越:
- Sarsa会评估通过风险,可能选择绕路
- Q-learning常选择直接穿越,成功率约65%
食物追逐策略:
- Sarsa倾向于保持安全距离
- Q-learning常采取最短路径,即使要贴近蛇身
探索行为:
- Sarsa的ε-greedy探索更保守
- Q-learning在探索阶段更可能尝试危险动作
4. 可视化分析:策略差异的本质
通过绘制两种算法训练过程中的Q值变化曲线,我们可以发现:
def plot_learning_curve(sarsa_scores, q_scores, window=100): plt.figure(figsize=(10,6)) plt.plot(np.convolve(sarsa_scores, np.ones(window)/window, mode='valid'), label='Sarsa') plt.plot(np.convolve(q_scores, np.ones(window)/window, mode='valid'), label='Q-learning') plt.xlabel('Episode') plt.ylabel('Average Score') plt.legend()关键观察点:
- 训练初期:Q-learning得分波动更大,验证其冒险特性
- 中期阶段:Sarsa稳定性优势显现
- 收敛后期:Q-learning通常能获得更高上限分
注意:在小型离散状态空间中,表格型方法效果良好。但对于更复杂环境,需要考虑神经网络近似Q函数。
5. 进阶思考:从游戏到现实的算法选择
在实际项目中选择算法时,需要权衡以下因素:
Sarsa适用场景:
- 错误决策代价高昂(如医疗、金融)
- 需要平稳渐进的学习过程
- 环境动态性较强的场景
Q-learning优势场景:
- 允许一定风险换取更高回报
- 状态空间离散且维度适中
- 需要快速获得可行解的场景
我在开发仓库机器人路径规划系统时,就遇到过类似选择。最终对运输贵重物品的机器人采用Sarsa变体,而对普通物流机器人使用Q-learning,取得了良好平衡。