news 2026/9/11 12:55:08

ML-For-Beginners 强化学习实战:为“彼得与狼“引入能量与疲劳机制,构建更真实的 Q-Learning 世界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ML-For-Beginners 强化学习实战:为“彼得与狼“引入能量与疲劳机制,构建更真实的 Q-Learning 世界

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 章"强化学习与 Q-Learning"的课后作业展开,讲解如何把"彼得与狼"(Peter and the Wolf)这个玩具环境改造成一个包含**能量(energy)疲劳(fatigue)**状态的更真实世界,并相应地重设计奖励函数、重写随机游走基线、重新训练 Q-Learning 策略,最后对比两者在"胜/负局数"上的差异。读完本文,你将掌握如何扩展强化学习中的状态空间表示、如何把领域规则翻译成奖励函数、如何评估与调优 Q-Learning 训练过程,并可直接在课程自带的 notebook.ipynb 与 rlboard.py 基础上动手复现。

任务背景:从"理想世界"到"真实世界"

在课程主体 8-Reinforcement/1-QLearning/README.md 中,彼得可以在一个width x height的方形棋盘上自由移动而几乎不会疲惫或饥饿,他的目标只是找到苹果,同时避开水和狼。课程用Q-Learning让彼得学会一条高效路径,把随机游走时 30~40 步的平均路径缩短到 3~6 步。

而本作业要求在此基础上更进一步:让世界"更真实"。彼得不光要走到苹果旁,还必须照顾自己的体力状态,最终找到并击杀狼。这一改动会带来三方面的连锁反应:

  1. 状态不再只是"人在哪":状态需要同时包含棋盘位置、能量与疲劳三个维度;
  2. 奖励函数需要重写:把 5 条新规则翻译成数值信号;
  3. 训练成本显著上升:因为"与狼交战并获胜"是稀有机会,作业明确提醒需要更多 epoch 和更长的训练时间。

世界规则:五条新规则与胜负判定

作业 assignment.md(希腊语版见 translations/el/8-Reinforcement/1-QLearning/assignment.md)为这个世界定义了五条规则:

  1. 从一处移动到另一处,彼得会损失能量、积累疲劳
  2. 吃苹果可以恢复能量
  3. 在树或草地(绿色格子)下休息可以消除疲劳,即走进带有树/草的棋盘位置;
  4. 彼得需要找到并击杀狼
  5. 击杀狼需要满足一定的能量与疲劳水平,否则会输掉战斗。

不难发现,规则的语义与课程棋盘上的格子类型一一对应。在 rlboard.py 中,每种格子由一个枚举常量表示(见Board.Cellrlboard.py第 43-49 行):

class Board: class Cell: empty = 0 water = 1 wolf = 2 tree = 3 apple = 4
  • empty(空地):彼得可以行走;
  • water(水):不可行走,落入即"溺水";
  • wolf(狼):危险目标,本作业中需要"击杀";
  • tree(树/草):休息场所,消除疲劳;
  • apple(苹果):进食场所,恢复能量。

官方解答 solution/assignment-solution.ipynb 给出的胜败判定非常直观:当能量大于疲劳时彼得能赢下战斗,否则失败。这一判定将被同时用于"随机游走模拟"和"奖励函数"两个环节。

状态空间设计:三种可选的表示方案

原版课程中,Q-Learning 的状态就是"彼得的棋盘位置",因此 Q 表是一个width x height x len(actions)的 numpy 数组,每个格子记录四种动作(上/下/左/右)的"吸引力":

Q = np.ones((width,height,len(actions)),dtype=np.float)*1.0/len(actions)

引入能量与疲劳后,状态的含义变了。作业提示了三种实现路线:

  1. 用元组表示状态:形如(Board, energy, fatigue)的三元组;
  2. 为状态定义专门的类:可以继承自Board,把能量/疲劳封装进对象;
  3. 直接修改Board类本身:把能量/疲劳字段加进 rlboard.py 的Board

官方解答选择了方案 2——新建一个独立的state类(见assignment-solution.ipynb第 104-134 行):

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

这段实现恰好把五条规则逐一"代码化":

  • 构造与初始化:默认能量 10、疲劳 0;若init=True则在棋盘上随机选取空地作为起点(复用Board.random_start(),见 rlboard.py 第 121-126 行);
  • update():走到水里直接判死;走到树/草地清零疲劳(规则 3);吃到苹果恢复能量到 10(规则 2);
  • move():每走一步energy -= 1fatigue += 1,对应规则 1;
  • is_winning()energy > fatigue时才能战胜狼(规则 5)。

注意一个实现细节:官方解答的 Q 表仍然只以(x, y)位置为索引Q[x,y,ai]),能量/疲劳并没有被展开成额外的 Q 表维度。这意味着"能量/疲劳"通过状态机与奖励函数参与学习,而不是进入 Q 表索引。如果选择方案 1(元组状态)并希望 Q 表真正感知体力差异,就需要把 Q 表扩展成更高维数组,这也是作业留给大家的发挥空间之一。

奖励函数设计:把领域规则翻译成数值信号

原版课程 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

在更真实的世界里,这个函数必须升级为"状态驱动"。官方解答给出的新版本是:

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(连续奖励)规则 1:消耗能量、积累疲劳
击败狼(is_winning()为真)+100规则 4、5
输掉战斗(能量不足)-100规则 5
溺水-100世界规则(水中不可行走)

设计上的关键点在于:非终结状态的奖励不再是固定常数,而是energy - fatigue这个连续量。这相当于鼓励彼得"高能量、低疲劳"地行动——能量越高、越不疲劳,获得的即时奖励越大;反之则会逐步受到负向激励。它把"体力管理"内嵌进了每一步的回报里,而不是等走到终点才一次性结算。

值得一提的还有稀疏奖励问题:与狼交战的概率很低,绝大多数 episode 都在中途因溺水或能量耗尽而结束,模型几乎接触不到±100的大额奖惩。课程 README 也强调过,"在大多数情况下,我们只在游戏结束时获得实质性的奖励",算法必须依靠 Q 表把"导致最终正回报的中间步骤"的价值回溯记牢——这正是 Bellman 方程要解决的核心问题。

随机游走基线:重写模拟与胜负统计

作业明确要求:保留负责随机游走策略的代码,并在最后用它与 Q-Learning 结果做对比。因此第一步是重写walkprint_statistics(官方解答第 162-210 行):

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(m,random_policy)

注意这里与 rlboard.py 内置的Board.walk(第 174 行起)有三处重要差异:

  1. 原版walk遇到苹果即返回成功(return n),新版本则把苹果视为补给站而非终点,游戏只有三种结局:战胜狼(返回正步数)、输给狼(返回负步数)、溺水(返回 0);
  2. 每一步都通过s.move(a)同步更新能量/疲劳,而不是只更新位置;
  3. 新版本在模拟层就用is_winning()判定战斗结果。

统计函数也相应地把"胜负"拆开计数:

def print_statistics(policy): s,w,n = 0,0,0 for _ in range(100): z = walk(m,policy) if z<0: w+=1 elif z==0: n+=1 else: s+=1 print(f"Killed by wolf = {w}, won: {s} times, drown: {n} times") print_statistics(random_policy)

官方解答的一次运行输出为:

Killed by wolf = 5, won: 1 times, drown: 94 times

(由于随机性,每次运行的具体数字会不同。)这说明在纯随机策略下,彼得绝大多数时候(约 94%)会溺死在水中,几乎无法完成"找到狼并击杀"的目标——这正是 Q-Learning 需要改进的基线。

Q-Learning 训练循环:算法不变,状态更复杂

作业反复强调:学习算法本身几乎不需要改动,变的只是状态的定义方式。官方解答保留了原版课程的全部核心组件:

  • Q 表初始化(仍为width x height x 4,四个方向动作等概率初始化);
  • 概率化辅助函数probs:把 Q 值向量转换为动作概率,eps=1e-4防止全等向量除以零(见课程 README.md code block 7):
    def probs(v,eps=1e-4): v = v-v.min()+eps v = v/v.sum() return v

训练循环(官方解答第 278-312 行)与原版结构一致,但有几处针对新世界的调整:

lpath = [] for epoch in range(10000): s = state(m) 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

值得展开的几个关键点:

  • 动作选择(探索 vs 利用)random.choices(..., weights=v)按 Q 值比例采样动作。训练初期 Q 值全相等,等价于随机游走;随着学习推进,越来越倾向选择 Q 值高的动作,但仍有概率探索未知路径。这正是课程 README"Exploit vs. explore"一节的精髓。
  • 合法性检查:新版本在选动作时用is_valid循环重试,保证彼得不会走出棋盘边界,而原版训练代码是通过m.move(dpos, check_correctness=False)故意允许越界来终止 episode 的——这是新老实现的差异之一。
  • 学习率衰减alpha = np.exp(-n / 3000)。n 是当前 episode 内已走步数,3000 是衰减常数,比原版np.exp(-n / 10e5)衰减快得多。原因是新世界每局步数更多、体力约束更强,需要更快收敛;课程 README 也指出,训练后期应让 Q 表只做小幅修正,避免"写坏"已经学好的系数。
  • 折扣因子gamma = 0.5,与课程示例一致,用来权衡即时奖励与未来奖励。
  • 终止条件abs(r)==100即遇到狼(胜/负)或水,此时 episode 结束并记录路径长度。
  • Epoch 数:官方解答使用10000,是原版 5000 的两倍,且每轮训练输出进度。作业明确提示:因为"击杀狼"是稀有机会,可以预期训练时间远长于原版,必要时还需继续加大 epoch。

训练完成后,可以用m.plot(Q)把 Q 表可视化:每个空格内的线段方向表示该状态下的"首选移动方向",图中可以看出彼得学会了有目标地朝狼或苹果移动,而不是随机乱撞。

评估与对比:Q-Learning vs 随机游走

训练结束后,用与随机游走相同的print_statistics评估 Q 学习出的策略。官方解答使用"概率化策略"(按 Q 值比例采样,而非贪心取最大值),这样可以兼顾探索:

def qpolicy(m): x,y = m.human v = probs(Q[x,y]) a = random.choices(list(actions),weights=v)[0] return a print_statistics(qpolicy)

官方解答的一次运行输出为:

Killed by wolf = 1, won: 9 times, drown: 90 times

与随机游走基线(won: 1, drown: 94, killed by wolf: 5)对比可以得出两条关键结论:

  1. 溺水率有所下降:从约 94% 降到约 90%,说明 Q-Learning 学到了一定的"避水"倾向;
  2. 战胜狼的次数从 1 次提升到 9 次:获胜局数提升约 9 倍,而"被狼反杀"的次数也从 5 次降至 1 次——说明策略开始懂得"养精蓄锐后再决战",这正是奖励函数energy - fatigue±100战果奖惩共同作用的结果。

需要强调,上述数字只是某一次运行的结果,由于训练与评估都带随机性,每次复现会有波动;作业的评估标准(Rubric)要求的也是**"Q-Learning 能显著改善随机游走的结果"**这一相对结论,而不是某个绝对数值。解答还提示:如果发现彼得仍不能稳定击杀狼,可以继续尝试调整超参数。

超参数调优:让稀有机会被真正学会

作业末尾特别提醒:"你可能需要调整超参数,尤其是 epoch 数量"。结合官方解答与课程 README.md 的学习过程分析,可调的关键旋钮有:

超参数官方解答取值原版课程取值调整方向与影响
Epoch 数100005000击杀狼是稀疏事件,epoch 不足时 Q 表可能从未"见过"狼;可继续加大
学习率衰减常数3000(alpha = np.exp(-n/3000)10e5常数越小衰减越快、收敛越早,但也可能过早锁定次优策略;训练后期应只做微调
折扣因子 γ0.50.5越大越看重未来回报,适合需要"先蓄力后决战"的长期规划
每局步数上限未显式限制挑战任务建议 100可防止彼得在空旷区域无限徘徊,从而"饿死"

课程 README 对学习过程的三点观察同样适用且更具启发性:

  • 平均路径先增后减:初期一无所知时容易困在坏状态(水/狼);随着知识积累开始探索更远,路径变长;学会后路径重新变短,但探索行为仍会让路径偶尔偏离最优;
  • 路径长度可能突然跳升:这说明 Q 表系数可能在某个时刻被新值"覆盖"(overwrite)而退化,训练后期应依靠更小的学习率来抑制这种扰动;
  • 收敛质量高度依赖超参数:学习率、学习率衰减与折扣因子共同决定了训练的成功率与稳定性,超参数优化本身就是一个值得单独研究的话题。

评估标准(Rubric):怎样的答卷才算合格

作业附带的评分标准(Rubric)从三个层次给出了明确的验收口径:

标准优秀合格需改进
内容提交的 notebook 包含新世界规则的定义、Q-Learning 算法以及必要的文字说明,且 Q-Learning 相比随机游走显著提升了成绩notebook 已提交,Q-Learning 已实现并能改进结果,但提升不显著;或 notebook 文档化不足、代码结构混乱尝试了重新定义世界规则,但 Q-Learning 无法工作,或奖励函数未完整定义

对照此标准,一份完整的解答至少应包含:

  1. 新世界规则的代码化state类或等价实现);
  2. 重写后的奖励函数reward(s));
  3. 保留随机游走策略及其统计代码,作为基线;
  4. Q-Learning 训练循环与 Q 表可视化;
  5. 末尾的对比结论:用print_statistics分别跑随机策略与学习策略,给出胜负局数的定量对比。

运行环境与文件指引

要复现本作业,你需要课程目录中的两个文件:

  • 8-Reinforcement/1-QLearning/notebook.ipynb:课程主 notebook,作为解答的起点;作业要求在其基础上修改奖励函数并重跑学习算法;
  • 8-Reinforcement/1-QLearning/rlboard.py:棋盘环境模块,提供Board类、格子枚举与可视化;state类可以直接放在 notebook 中,也可以像解答那样引用它。

课程 README.md 的"Prerequisites and Setup"一节也提醒:若在云端运行 notebook,需要把rlboard.py放到与 notebook 相同的目录下(二者位于同一目录)。官方参考解答位于 solution/assignment-solution.ipynb,其中完整给出了state类、随机游走统计、新奖励函数与 10000 epoch 的训练循环,可作为对照实现与调参起点。

通过本次练习,你实际完成了一次典型的强化学习工程流程:定义领域规则 → 设计状态表示 → 设计奖励函数 → 建立随机基线 → 训练与评估 → 超参数调优。这套方法论同样适用于其他需要"资源管理 + 稀有成功事件"的现实任务,例如机器人节能导航、游戏角色的体力规划、库存与配送调度等场景。

【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 12:55:00

数组模拟双向链表:洛谷 P1160 队列安排 O(1) 插入删除详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 12:52:12

消息队列技术选型与应用实践指南

1. 消息队列的本质与核心价值消息队列&#xff08;Message Queue&#xff09;本质上是一种应用程序间的通信方式&#xff0c;它允许不同服务通过发送和接收消息来进行异步交互。这种设计模式最早可以追溯到上世纪80年代的银行交易系统&#xff0c;但直到互联网分布式架构兴起后…

作者头像 李华
网站建设 2026/9/11 12:51:11

ESP32声音感知入门:用MicroPython和ADC实现可靠声控

1. 项目概述&#xff1a;为什么“让ESP32拥有听觉”不是一句口号&#xff0c;而是可落地的感知能力升级你有没有试过让一块开发板“听见”拍手声、敲击桌面的震动&#xff0c;甚至环境噪音的起伏&#xff1f;这不是科幻电影里的桥段&#xff0c;而是用一块不到30元的ESP32就能实…

作者头像 李华
网站建设 2026/9/11 12:51:08

AutoCAD SHX字体矢量方向代码解析与应用

1. SHX形定义文件与矢量方向代码概述 SHX文件是AutoCAD等CAD软件使用的特殊字体格式&#xff0c;它采用矢量图形而非TrueType那样的轮廓曲线来描述字符。这种文件本质上是一系列"形(Shape)"的集合&#xff0c;每个形通过一组坐标点和矢量方向代码来定义几何图形。 在…

作者头像 李华
网站建设 2026/9/11 12:48:25

Linux设备驱动开发实战:从环境搭建到内核调试与性能调优

很多刚接触Linux设备驱动开发的朋友&#xff0c;第一反应都是去翻内核源码、背函数接口&#xff0c;结果看了两周还是一头雾水。我做了这么多年嵌入式Linux&#xff0c;最大的感受是&#xff1a;学驱动开发&#xff0c;三分靠写代码&#xff0c;七分靠调试和内核机制的理解。字…

作者头像 李华