
ML-For-Beginners 强化学习实战用 Q-Learning 训练 Peter 在《彼得与狼》网格世界中寻路【免费下载链接】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 Learning的三大要素、随机游走基线策略、奖励函数设计、Q 表Q-Table的构建与更新以及 Bellman 方程的推导与落地实现。读完本文你将能够基于仓库中的rlboard.py环境模块与notebook.ipynb交互式笔记本从零实现一个可运行的 Q-Learning 智能体让彼得学会在避开狼和水域的前提下以远短于随机游走的步数找到苹果。图强化学习核心概念速览Sketchnote背景为什么强化学习不同于监督学习在 ML-For-Beginners 课程中前面章节已介绍过两类经典问题监督学习分类、回归依赖带标签的数据集提供参考答案无监督学习聚类在无标签数据上发现结构。强化学习则属于第三种范式不需要标签而是让Agent智能体在Environment环境中反复实验通过Reward Function奖励函数定义的目标来学习最优行为。以电脑游戏为例要让程序学会下棋或玩《超级马里奥》我们并没有覆盖海量状态-动作对的数据集因此无法把它当作分类问题。强化学习的思路是让程序自己玩很多次并观察结果这需要两样东西环境与模拟器定义游戏规则、所有可能的状态与动作奖励函数告诉智能体每一步或每一局表现得怎么样。强化学习的关键特性在于通常在整局游戏结束时我们才知道输赢中间某个单独的动作很难立即判断好坏奖励往往在最后才给出。本课将用经典的无模型算法Q-Learning来应对这种不确定性。环境Peter 的 8×8 网格世界本课的故事背景取自俄国作曲家普罗科菲耶夫的交响童话《彼得与狼》。为简化问题Peter 的世界被建模为一个width × height的正方形棋盘本课使用 8×8 尺寸。棋盘上每格可能是五种状态之一地面Peter 和其他生物可以行走水域显然不能行走树或草地可以休息的地方苹果Peter 需要找到的食物狼危险必须避开。与棋盘交互的全部代码封装在独立的 Python 模块 rlboard.py 中。从源码看rlboard.py环境由Board类实现其内部用numpy矩阵存储各格类型Cell枚举依次为empty / water / wolf / tree / apple并通过randomize()方法以可控的随机种子生成水域、狼、树和苹果的布局。Board还提供了at()查询当前格、is_valid()判断坐标是否越界、move()/move_pos()移动与坐标计算以及plot()绘制棋盘等基础方法它们是后面所有策略与学习算法的基础设施。在 notebook.ipynb 中创建并绘制示例棋盘代码块 1from rlboard import * width, height 8,8 m Board(width,height) m.randomize(seed13) m.plot()提示如果从云端如在线 Notebook 环境运行代码需要把 rlboard.py 一并获取到与 Notebook 相同的目录因为它被 Notebook 代码直接引用。图Board.randomize(seed13)生成的示例环境蓝色为水域、绿色为树木草地、苹果为奖励点、狼需要避开动作与策略PolicyPeter 的目标是找到苹果同时避开狼和其他障碍。在任何位置他可以在四个方向中选择一个动作上U、下D、左L、右R。用字典把动作映射为坐标增量例如向右移动R对应(1,0)代码块 2actions { U : (0,-1), D : (0,1), L : (-1,0), R : (1,0) } action_idx { a : i for i,a in enumerate(actions.keys()) }由此引出两个核心概念策略Policy智能体Peter的决策方式是一个在给定状态下返回动作的函数。在本例中状态由棋盘布局加当前玩家位置共同表示强化学习的目标通过学习获得一个好的策略从而高效解决问题。作为基线我们先考虑最简单的策略——随机游走Random Walk。基线策略随机游走随机游走策略会在每一步从允许的动作中随机选择一个直到到达苹果代码块 3def random_policy(m): return random.choice(list(actions)) def walk(m,policy,start_positionNone): n 0 # number of steps # set initial position if start_position: m.human start_position else: m.random_start() while True: if m.at() Board.Cell.apple: return n # success! if m.at() in [Board.Cell.wolf, Board.Cell.water]: return -1 # eaten by wolf or drowned while True: a actions[policy(m)] new_pos m.move_pos(m.human,a) if m.is_valid(new_pos) and m.at(new_pos)!Board.Cell.water: m.move(a) # do the actual move break n1 walk(m,random_policy)walk函数返回路径长度成功找到苹果返回正数步数被狼吃掉或掉进水里返回-1。注意内部循环的逻辑策略选出的动作若导致非法位置越界或踏入水域会被拒绝直到选出合法动作才真正移动——这一点对应 rlboard.py 中Board.walk的实现思路。把实验重复 100 次并统计代码块 4def print_statistics(policy): s,w,n 0,0,0 for _ in range(100): z walk(m,policy) if z0: w1 else: s z n 1 print(fAverage path length {s/n}, eaten by wolf: {w} times) print_statistics(random_policy)结果很有说服力随机游走的平均路径长度约为 30-40 步而最近苹果的平均距离只有 5-6 步——大量步数被浪费在反复探索与兜圈子上。这清楚地说明仅仅随机碰运气远远不够我们需要让策略变聪明。设计奖励函数要比较哪个动作更好必须先明确目标。目标用奖励函数Reward Function形式化它为每个状态返回一个分数数值越高代表奖励越好代码块 5move_reward -0.1 goal_reward 10 end_reward -10 def reward(m,posNone): pos pos or m.human if not m.is_valid(pos): return end_reward x m.at(pos) if xBoard.Cell.water or x Board.Cell.wolf: return end_reward if xBoard.Cell.apple: return goal_reward return move_reward该函数体现了两个重要设计点稀疏的实质奖励大幅度的奖励10 到达苹果、-10 进入水域/遇狼只在终局状态给出普通移动每步只有 -0.1 的微小代价隐式引导正是因为大部分状态没有显著奖励算法必须记住那些最终导向正奖励的好步骤并提高其权重同时抑制导向坏结果的动作。这就是后文 Q 表需要承担的记忆职责。Q-Learning用 Q 表记录动作的好坏Q-Learning用一个称为Q 表Q-Table的函数或数据结构来定义策略它记录在给定状态下每个动作有多好。由于棋盘尺寸为width × heightQ 表可以很方便地表示为一个形状为width × height × len(actions)的 numpy 数组代码块 6Q np.ones((width,height,len(actions)),dtypenp.float)*1.0/len(actions)初始时所有 Q 值都被设为相等值 0.25这等价于随机游走策略——每个状态下所有动作一样好。把 Q 表传给plot函数可以把它可视化在棋盘上m.plot(Q)。在每格中心有一个箭头指示偏好移动方向由于初始所有方向等价显示为圆点。图初始化后的 Q 表各方向等价格内显示为圆点接下来需要运行模拟、探索环境并学习出更优的 Q 值分布让 Peter 更快找到苹果。Bellman 方程Q-Learning 的本质一旦开始移动每个动作都有对应奖励理论上可以按立即奖励最高来选下一步。但多数状态下这一步并不能直接达成找到苹果的目标所以我们无法立即判断哪个方向更好。记住重要的不是立即结果而是模拟结束时得到的最终结果。为处理这种延迟奖励需要借助动态规划的思想递归地看待问题。假设当前处于状态s要转移到下一状态s我们会获得由奖励函数定义的立即奖励r(s,a)加上某种未来奖励。如果假设 Q 表已正确反映每个动作的吸引力那么在状态s处我们会选择使Q(s,a)最大的动作a。于是状态s处能获得的最佳未来奖励为maxaQ(s,a)对状态s下所有可能动作求最大值。由此得到计算状态s、动作a的 Q 值的Bellman 公式其中 γ 是折扣因子Discount Factor它决定了我们更偏好当前奖励还是未来奖励γ 越接近 1未来奖励的权重越大越接近 0越短视。学习算法伪代码基于 Bellman 方程可以写出学习算法的伪代码用相等数值初始化 Q 表 Q覆盖所有状态与动作设置学习率 α ← 1重复多次模拟从随机位置开始重复执行在状态s选择一个动作a执行动作转移到新状态s若遇到终局条件或累计奖励过小——退出本次模拟计算新状态下的奖励r按 Bellman 方程更新 Q 函数Q(s,a)←(1-α)Q(s,a)α(rγ maxaQ(s,a))s←s更新累计奖励并减小 α可以看到α学习率控制新旧信息的混合比例α 越大新经验对 Q 值的修正越激进α 随训练逐渐衰减能让训练后期趋于稳定。探索与利用的平衡Exploit vs. Explore在上面的伪代码中步骤 2.1如何选择动作并未限定纯随机选择 随机探索环境容易频繁死亡也会访问平时不会去的地方纯贪婪利用 始终选择 Q 表中值最高最好的动作利用已知知识但会阻止探索其他状态很可能找不到最优解。最佳做法是在两者间取得平衡以与 Q 表数值成比例的概率选择动作。训练初期 Q 值全部相同这等价于随机选择随着对环境的了解加深智能体更可能沿最优路线前进同时偶尔仍会尝试未探索的路径。Python 实现5000 次 epoch 的完整训练实现学习算法前需要一个把 Q 表中任意数值转换成对应动作概率向量的辅助函数代码块 7def probs(v,eps1e-4): v v-v.min()eps v v/v.sum() return vprobs先减去最小值并加上极小的eps目的是避免初始情况下向量各分量完全相同出现除以 0 的问题然后归一化为概率分布。接着运行 5000 次实验epochs进行学习代码块 8for epoch in range(5000): # Pick initial point m.random_start() # Start travelling n0 cum_reward 0 while True: x,y m.human v probs(Q[x,y]) a random.choices(list(actions),weightsv)[0] dpos actions[a] m.move(dpos,check_correctnessFalse) # we allow player to move outside the board, which terminates episode r reward(m) cum_reward r if rend_reward or cum_reward -1000: lpath.append(n) break alpha np.exp(-n / 10e5) gamma 0.5 ai action_idx[a] Q[x,y,ai] (1 - alpha) * Q[x,y,ai] alpha * (r gamma * Q[xdpos[0], ydpos[1]].max()) n1这段训练循环的要点动作选择random.choices(..., weightsv)按 Q 值归一化概率抽样实现探索/利用平衡回合终止m.move(dpos, check_correctnessFalse)允许 Peter 走出棋盘以终止回合对应end_reward累计奖励低于 -1000 同样终止防止无限游荡学习率衰减alpha np.exp(-n / 10e5)随步数指数衰减越到后期对 Q 值的修正幅度越小对应伪代码中减小 αQ 值更新直接实现 Bellman 公式(1-α)Q α(r γ·max(Q[s])其中gamma 0.5是本课的折扣因子取值路径记录每个回合结束把步数n追加到lpath供后面分析学习过程使用。执行结束后Q 表被更新为能反映每个状态下不同动作吸引力的数值。把它可视化每格画一个指向偏好移动方向的向量为简化用圆点代替箭头头图5000 个 epoch 训练后可视化的 Q 表白色格子内的箭头指示各状态的偏好移动方向策略检查直接用 Q 表导航由于 Q 表列出了每个状态下每个动作的吸引力用它定义高效导航非常简单。最直接的做法是选择 Q 值最高的动作代码块 9def qpolicy_strict(m): x,y m.human v probs(Q[x,y]) a list(actions)[np.argmax(v)] return a walk(m,qpolicy_strict)尝试几次后你可能发现代码有时会卡住需要按 Notebook 的 STOP 按钮中断。这是因为可能出现两个状态在最优 Q 值上互相指向的情况导致智能体在这两个状态间无限来回。这正是随机游走与简单贪婪策略都存在的缺陷也引出了两道动手练习Challenge任务 1修改walk函数把路径最大长度限制在一定步数例如 100观察上述代码偶尔会返回这个上限值。任务 2修改walk函数使其不返回此前已经访问过的位置。这能防止walk死循环但智能体仍可能被困在无法逃脱的角落。更好的导航训练时所用的探索/利用混合策略更优的导航策略是训练时使用的按 Q 值比例随机选择代码块 10def qpolicy(m): x,y m.human v probs(Q[x,y]) a random.choices(list(actions),weightsv)[0] return a print_statistics(qpolicy)该策略虽然仍可能让智能体回到已探索过的位置但运行print_statistics内部模拟 100 次后平均路径长度会显著缩短通常在 3-6 步之间相比随机游走的 30-40 步是数量级的提升。这说明 Q-Learning 学到的知识已经能稳定指引 Peter 高效觅食。观察学习过程路径长度曲线说明了什么学习过程本质上是探索与利用的动态平衡。绘制每个 epoch 的路径长度可以看到一条非常有信息量的曲线图横轴为训练 epoch纵轴为该回合路径长度用于观察学习进程曲线的三个阶段值得仔细解读平均路径长度先上升对环境一无所知时智能体很容易被困在坏状态水域或狼附近并快速结束回合随着知识积累它能探索更久但此时还不知道苹果确切位置所以路径反而变长路径长度随后下降学到足够多知识后智能体更容易达成目标路径开始变短。但由于仍保留探索它经常偏离最优路线去尝试新选项所以路径比最优略长长度偶发骤增曲线某处可能突然跳高体现了过程随机性——新更新的 Q 值可能在某个时刻破坏了之前学到的系数。理想情况下应通过降低学习率来抑制例如训练末期只对 Q 值做微小调整本课代码中alpha np.exp(-n / 10e5)正是这种衰减思想的体现。超参数决定学习质量的关键学习过程的成败与质量高度依赖学习率learning rate、学习率衰减learning rate decay和折扣因子discount factor。这些参数被称为超参数Hyperparameters以区别于训练中被优化的参数Parameters例如 Q 表系数。寻找最佳超参数取值的过程称为超参数优化Hyperparameter Optimization是一个值得单独探讨的主题——在扩展练习中你会真切体会到调参的影响。扩展练习更真实的世界本课配套作业 assignment.md 要求把世界改得更真实Peter 移动会消耗能量并累积疲劳吃苹果可恢复能量在树下或草地上休息可消除疲劳他还需要找到并击败狼而只有能量和疲劳达到一定水平才能赢下战斗。此时状态不再只是棋盘位置还包含能量与疲劳需要扩展状态表示如(Board, energy, fatigue)元组或派生自Board的状态类。仓库中的参考答案 solution/assignment-solution.ipynb 展示了如何用state类封装这些信息并将训练 epoch 提升至 10000、把学习率衰减调整为alpha np.exp(-n / 3000)见 solution/notebook.ipynb。由于打赢狼是稀有事件作业明确指出你可能需要调整超参数、尤其是 epoch 数量训练时间会显著变长。完成作业后应保留随机游走代码并与你的 Q-Learning 算法在胜/负场次上做对比。小结通过这一课你完成了从环境建模、基线策略到 Q 表学习、策略评估的完整闭环Board环境rlboard.py提供了可复现的网格世界奖励函数把找苹果、躲狼、避水的目标数字化Bellman 方程与 α、γ 两个超参数驱动 Q 表不断更新探索/利用平衡让平均路径从 30-40 步降到 3-6 步。沿着本仓库课程主线继续深入下一课 2-Gym 会把同样的 Q-Learning 思想应用到 OpenAI Gym 的 CartPole 连续状态问题中——把本课打下的基础迁移过去你将看到强化学习方法论在不同问题上的统一威力。【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考