ML-For-Beginners 实战:为"彼得与狼"Q-Learning 环境扩展能量与疲劳状态,让世界更真实
【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners
在 ML-For-Beginners 的强化学习章节(8-Reinforcement/1-QLearning)中,基础版"彼得与狼"环境允许智能体(Peter)几乎无代价地移动:既不饿也不累。本节作业(Assignment:A More Realistic World)要求学习者基于原版 notebook.ipynb 重写游戏规则,为状态引入**能量(energy)与疲劳(fatigue)**两个维度,并据此重构奖励函数与胜负判定,让 Q-Learning 学习到一套能够真正"战胜狼"的最优策略。完成本文档后,你将掌握:如何把连续生理状态引入离散网格强化学习、如何重设计奖励函数驱动稀疏目标(猎狼成功是稀有事件)的学习、以及如何用"随机行走 vs Q-Learning"对照实验公平评价算法效果。
一、任务背景:从"取苹果"升级为"猎狼求生"
在原版课程 README.md 的设定中,Peter 的目标是在一张8×8棋盘上找到苹果,同时避开狼与水。他每走一步要么胜利到达苹果,要么失败落水或被狼吃掉,移动本身没有"体力成本",因此状态仅是位置,Q-Table 也只需覆盖width × height × len(actions)个格子。
而作业A More Realistic World(捷克语翻译版见 translations/cs/8-Reinforcement/1-QLearning/assignment.md,英文原文见 8-Reinforcement/1-QLearning/assignment.md)提出了 5 条新规则,彻底改变了游戏难度与目标:
| 规则编号 | 规则内容 | 引入的系统性影响 |
|---|---|---|
| 1 | 每从一个位置移动到另一个位置,Peter消耗能量并累积疲劳 | 移动不再"免费",最短路径不一定是最优路径 |
| 2 | Peter 可以通过吃苹果获得更多能量 | 苹果从"终点目标"降级为"补给资源",价值语义发生变化 |
| 3 | Peter 可以通过在树下或草地上休息(即走进树/草的绿色格子)消除疲劳 | 绿色格子的语义从"装饰"变为可交互的"恢复区" |
| 4 | Peter 需要找到并杀死狼 | 游戏终止条件与成功定义整体重写 |
| 5 | 杀死狼需要足够的能量与足够低的疲劳,否则战斗失败 | 引入"战前状态门槛",成败取决于到达狼格时的瞬时体力 |
对比原版可见:狼不再是单纯需要避开的"死亡格",而是可在满足条件时被击败的最终目标;苹果与绿色格子的作用也从"目标/普通地形"变成了维持续航的"资源"。这套设计迫使学习者必须重新思考状态表示与奖励函数,正是本节作业的训练重点。
二、状态表示升级:把能量与疲劳"装进"环境
作业注释(assignment 原文)明确指出了实现路线:状态变得更复杂,除人的位置外还要包含疲劳与能量水平。并给出了三种可选表示法:
- 用元组
(Board, energy, fatigue)表示状态; - 定义状态类(可从
Board派生); - 直接修改 rlboard.py 中的原始
Board类。
2.1 从源码看原版Board的可扩展性
先看Board底层结构(rlboard.py):
class Board: class Cell: empty = 0 water = 1 wolf = 2 tree = 3 apple = 4Cell枚举里tree、apple、wolf、water已全部存在,说明新规则2、3可以直接基于现有地形语义实现,无需新增格子类型。原版还提供了几个关键原语,支撑"移动消耗 + 触地判定"的重构:
at(pos=None):返回当前位置或指定位置的格子类型(rlboard.py);move_pos(pos, dpos)与move(dpos, check_correctness=True):先算目标位置、再执行位移,其中move支持在check_correctness=False时越界移动以终止回合(rlboard.py);random_start():把 Peter 放到一个empty格作为回合起点(rlboard.py)。
2.2 参考答案的状态封装:一个轻量state类
仓库中的参考解法 solution/assignment-solution.ipynb 选择了方案二:独立状态类,把能量与疲劳的全部演进逻辑收敛在一个对象里,值得借鉴:
class state: def __init__(self, board, energy=10, fatigue=0, init=True): self.board = board self.energy = energy self.fatigue = fatigue self.dead = False if init: self.board.random_start() self.update() def at(self): return self.board.at() def update(self): if self.at() == Board.Cell.water: self.dead = True return if self.at() == Board.Cell.tree: self.fatigue = 0 if self.at() == Board.Cell.apple: self.energy = 10 def move(self, a): self.board.move(a) self.energy -= 1 self.fatigue += 1 self.update() def is_winning(self): return self.energy > self.fatigue这个类逐条落实了新规则:
move(a)每步统一energy -= 1、fatigue += 1——对应规则 1;update()检测到apple时把能量拉回上限10——对应规则 2;update()检测到tree时把疲劳清零——对应规则 3,这也是作业所说的"绿色格子可休息";is_winning()用energy > fatigue判定是否有资格猎狼——对应规则 5的"能量水平门槛"。
该解法的精巧之处在于:棋盘Board本身未做任何修改,仍通过组合方式被state持有,Q-Learning 的格子索引逻辑(x, y = s.board.human)得以沿用原版结构。
三、胜负判定与随机行走基线:一切评价的锚点
作业要求"在解决方案中保留随机行走策略代码,并在结尾比较你的算法与随机行走的结果(以赢、输场次计)"——因此随机行走必须被重构为兼容新规则的基线。参考答案的行走逻辑如下:
def random_policy(state): return random.choice(list(actions)) def walk(board, policy): n = 0 # number of steps s = state(board) while True: if s.at() == Board.Cell.wolf: if s.is_winning(): return n # success! else: return -n # failure! if s.at() == Board.Cell.water: return 0 # died a = actions[policy(m)] s.move(a) n += 1与原版walk(见 README.md 的 Random walk 一节,用return -1表示被狼吃/落水)相比,新逻辑有两个关键变化:
- 遇见狼不再等于失败:进入狼格时先调用
is_winning()判定战斗胜负。胜利返回步数n,能量不足则返回-n(失败); - 落水单独计为
0,与被狼打败的负值区分开,便于统计三类结局:胜、败于狼、死于水。
为输出可比的对照统计,可继续沿用课程里的print_statistics(policy)思路(跑 100 局统计平均路径与失败次数),把返回值分成胜利次数与失败次数两个口径即可。
四、奖励函数重设计:用"体力差"驱动全过程学习
4.1 原版奖励的局限
原版课程给出了极简奖励函数(代码块 5,见 README.md):
move_reward = -0.1 goal_reward = 10 end_reward = -10 def reward(m, pos=None): pos = pos or m.human if not m.is_valid(pos): return end_reward x = m.at(pos) if x == Board.Cell.water or x == Board.Cell.wolf: return end_reward if x == Board.Cell.apple: return goal_reward return move_reward在该框架下,狼必然是end_reward=-10的绝对禁区。但在新规则中狼格可能是"通关点",因此奖励函数必须重写。
4.2 新奖励:即时反馈 + 终局胜负
参考答案给出的新奖励函数设计得相当精炼:
def reward(s): r = s.energy - s.fatigue if s.at() == Board.Cell.wolf: return 100 if s.is_winning() else -100 if s.at() == Board.Cell.water: return -100 return r它的核心思想是"奖励函数要能被分解成即时收益与终局收益两层":
- 逐格即时项
energy - fatigue:把状态自带的生理信息直接转成奖励数值。能量高、疲劳低时分数为正,鼓励 Peter 先去补给再推进;随着能量耗尽、疲劳累积,分数转负,自然抑制无意义的原地兜圈,等价于一种"自带步数惩罚"的连续化版本——不再需要手工设定move_reward=-0.1,体力差本身就是自适应步数成本。 - 终局胜负项
±100:胜利给+100、被狼反杀或落水给-100,用一个远大于即时项的绝对量级保证"赢下整局"的长期回报优先于任何单步即时收益。这正是课程 README.md 反复强调的核心洞察:多数情况下游戏只在结束时给出实质性奖励,算法必须能回溯记住那些通向正奖励的"好步骤"——这正是 Q-Table 与 Bellman 公式存在的意义。
对自研方案而言,只要保证:(a) 每个非终局状态都有连续可学习的即时反馈;(b) 终局奖励在量级上显著压倒即时项,使"猎狼成功"这种稀有事件能被 Q 值逐步扩散传播,即符合本作业对"奖励函数完整定义"的验收要求(对应评分表中"Needs Improvement:奖励函数未完整定义"的规避点)。
五、Q-Learning 主循环改造:状态变复杂,学习不变
课程核心算法 Q-Learning 依然适用,唯一变化是每个训练步都要经state封装读写能量与疲劳。以下给出参考答案在 solution/assignment-solution.ipynb 中的完整训练循环:
from IPython.display import clear_output lpath = [] for epoch in range(10000): clear_output(wait=True) print(f"Epoch = {epoch}", end='') # Pick initial point s = state(m) # Start travelling n = 0 cum_reward = 0 while True: x, y = s.board.human v = probs(Q[x, y]) while True: a = random.choices(list(actions), weights=v)[0] dpos = actions[a] if s.board.is_valid(s.board.move_pos(s.board.human, dpos)): break s.move(dpos) r = reward(s) if abs(r) == 100: # end of game lpath.append(n) break alpha = np.exp(-n / 3000) gamma = 0.5 ai = action_idx[a] Q[x, y, ai] = (1 - alpha) * Q[x, y, ai] + alpha * (r + gamma * Q[x + dpos[0], y + dpos[1]].max()) n += 1对照原版训练循环(README.md 的 Python implementation 一节),可见改动高度局部化:
| 要素 | 原版 | 新规则版 | 原因 |
|---|---|---|---|
| Q-Table 结构 | np.ones((width, height, len(actions))) * 1/len(actions) | 完全不变 | 状态新增维度未展开进 Q-Table,仍按物理坐标索引;能量/疲劳体现在"该状态是否可达/何时终止" |
| 动作选择 | 直接m.move(dpos, check_correctness=False) | 先校验is_valid再s.move(dpos) | 新规则下越界应被阻止而非用于终结回合,终局只由狼/水触发 |
| 回合结束判定 | r == end_reward或累计奖励过小 | abs(r) == 100 | 终局奖励正负都是100,统一判绝对值 |
| 学习率衰减 | alpha = np.exp(-n / 10e5)(即 /100000) | alpha = np.exp(-n / 3000) | 单回合更长、信息更密集,衰减加快以稳定收敛 |
| 回合数 epoch | 5000 | 10000 | 见下方"稀有事件"分析 |
| 折扣因子 γ | 0.5 | 0.5 | 可保持不变 |
5.1 为什么必须加大 epoch 并调学习率:猎狼是稀有事件
作业注释特别强调:"游戏成功(与狼战斗并取胜)是稀有事件,你可能需要多得多(much longer)的训练时间。"结合新世界进一步解读:
- 原版里"找到苹果"的成功率尚可,5000 个 epoch 足够让奖励沿路径传播;新世界要求 Peter 先积累足够能量、清空疲劳,再恰好走到狼格并以
energy > fatigue收场,是一条"补给 → 赶路 → 挑战"的长链条,单次成功所需步数大幅上升; - 终端奖励 +100 只在回合真正胜利时出现一次,若训练轮数不足,Q-Table 中多数格子仍未收到可用的传播信号;
- 因此除调大 epoch 外,通常还需配合学习率衰减曲线(让后期小幅微调 Q 值,避免如课程 README.md "Investigating the learning process"一节所描述的训练后期路径长度陡增的"Q 值被新样本覆盖污染"现象)与回合上限约束。
课程末尾的挑战任务(给walk限长、禁止回退)在此同样适用,可作为防止死循环的辅助手段。
六、策略评估与作业验收:用"胜/负场次"说话
训练收敛后,用课程标准的两种 Q-Table 策略做评测:
def qpolicy(m): x, y = m.human v = probs(Q[x, y]) a = random.choices(list(actions), weights=v)[0] return aqpolicy即"按 Q-Table 值成正比的概率采样动作",是训练期探索与利用平衡策略的推理版。最后调用print_statistics(random_policy)与print_statistics(qpolicy)各跑 100 局,统计胜负场次即可完成作业要求的对照。
6.1 作业评分表(Rubric)解读
assignment 提供的评分标准直接定义了"优秀"的边界:
| 等级 | 判定条件 | 对学习者的自查点 |
|---|---|---|
| 优秀(Exemplary) | 提交含新世界规则定义、Q-Learning 算法与文字解释的 notebook,且 Q-Learning显著优于随机行走 | 是否写了规则/奖励设计的文字说明?胜负场次差异是否肉眼可见?代码是否结构清晰? |
| 合格(Adequate) | Q-Learning 已实现并有改善但不显著;或 notebook 文档化差、代码结构不佳 | 确认了 Q 值已更新但提升幅度小?补足注释与模块划分 |
| 需改进(Needs Improvement) | 只尝试重定义了世界规则,但 Q-Learning 不工作,或奖励函数未完整定义 | 检查回合是否永远不结束、reward是否覆盖所有格子类型(狼/水/树/苹果/普通地面) |
结论核验的标准简单而硬核:同一个print_statistics,把随机行走与 Q-Learning 的胜场数、负场数并排输出。若 Q-Learning 在"猎狼成功次数"上明显压过随机行走,即完成作业核心目标。
七、实验建议与避坑清单
基于上述源码与参考实现,实际动手时建议按以下顺序迭代:
- 先冻结基线:不改任何学习代码,先让随机行走在"新规则 walk + 新 reward"下能稳定输出统计口径(胜/败于狼/死于水),保证后续对照可信;
- 再改状态与奖励:推荐先照抄
state类封装能量/疲劳逻辑,把update()的树/苹果/水分支写对,再用energy - fatigue做即时项、±100 做终局项; - 最后调参训练:以
epoch(参考实现用 10000)、alpha衰减分母(参考实现用 3000)、gamma(课程用 0.5)为主轴做小规模网格尝试,每轮训练后立刻跑qpolicy统计看趋势; - 验证"奖励完整定义":显式检查
reward(s)是否覆盖wolf / water / apple / tree / 普通地面五种情形——这是评分表"Needs Improvement"档位最典型的丢分点; - 保留解释性输出:
m.plot(Q)把学习后的策略画回棋盘,配合plt.plot(lpath)观察每回合步数随 epoch 的变化曲线(成功步数应逐渐下探),可显著提升 rubric 中"文字解释"维度的得分。
小结
A More Realistic World是本课程的经典进阶作业:它在不更换算法框架的前提下,迫使你重建状态语义、重写奖励函数并重定义"成功",从而真正理解状态设计、奖励塑形(reward shaping)与稀疏回报这三个 RL 工程的核心命题。仓库中的 solution/assignment-solution.ipynb 提供了一套可直接对照的完整参考实现,而8-Reinforcement/2-Gym课程则承接本作业的结论,进一步把相同思想迁移到 Gym 环境中处理连续状态空间——完整学习路径可沿8-Reinforcement/1-QLearning/README.md的 Navigation 与课后资源继续深入。
【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考