ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ML-For-Beginners 强化学习实战:用「彼得与狼」网格世界从零实现 Q-Learning

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 周强化学习模块的第一课8-Reinforcement/1-QLearning 的中文版 translations/README.zh-cn.md。将以俄罗斯音乐童话《彼得与狼》为原型搭建一个网格世界环境完整走通「环境建模 → 随机行走基线 → 奖励函数 → Q-Table 与贝尔曼方程 → 探索/利用平衡 → 策略评估」的 Q-Learning 全流程。读完后你将能独立实现一个表格型强化学习智能体理解 Q-Table 的含义、学习率与折扣因子等超参数的作用并知道如何通过统计路径长度来验证学习效果。强化学习的三个核心概念代理、状态与动作强化学习Reinforcement Learning, RL围绕三个基本概念展开代理agent、状态state和每个状态下的一组动作actions。代理在某个指定状态下执行一个动作就会得到一个奖励reward。原文档用超级马里奥的游戏来建立直觉你是马里奥站在悬崖边上头顶有一枚硬币。「你是马里奥、在游戏关卡中、处于某个特定位置」——这就是一个状态。向右移动一步一个动作会坠下悬崖得到一个低分按下跳跃按钮则能活下来并得分这是一个积极结果应给予正向分数。借助强化学习加一个模拟器游戏就能学会「如何玩游戏以最大化奖励」——既活下来又尽可能多地得分。RL 与监督学习、无监督学习并列为机器学习的基本范式。与分类/回归不同RL 通常没有标注数据集而是要让计算机反复「玩」很多次、观察结果来学习行为。这也是为什么本课只需要两样东西一个可重复模拟的环境定义规则、状态和动作以及一个告诉我们表现好坏的奖励函数。先决条件和运行环境本课用 Python 做实验你需要能在本地或云上运行 Jupyter Notebook打开本课的教学笔记本跟随本文逐节编译、运行即可注意如果你是从云端打开代码还需要额外获取笔记本代码中用到的rlboard.py文件并将其放到与笔记本相同的目录中笔记本第 1 个代码块通过from rlboard import *引用它。从源码结构看rlboard.py 是一个完整的环境模拟器依赖matplotlib、numpy、cv2三个库见 rlboard.py 头部导入因此运行环境需要安装这三者。环境把「彼得与狼」建成 8x8 棋盘本课让彼得Peter在环境中探索、收集苹果、躲避狼。为简单起见把世界抽象为一个widthxheight大小的方板每个单元格可以是以下之一地面ground彼得和其他生物可以在上面行走水water不能在上面行走树或草tree/grass可以休息的地方苹果apple彼得想找到用来果腹的食物狼wolf危险应该避免遇到。创建示例棋盘的核心代码如下原文档「代码块 1」from rlboard import * width, height 8,8 m Board(width,height) m.randomize(seed13) m.plot()这段代码会打印一张与环境示意图类似的图片。结合 rlboard.py 源码可以确认棋盘的实现细节单元格类型定义在Board.Cell内部类中empty0, water1, wolf2, tree3, apple4Board.Cell后文m.at()的返回值就是这些整型常量randomize(seed13)负责随机撒布地图元素其默认参数为water_size5, num_water3, num_wolves1, num_trees5, num_apples3randomize 方法。也就是说一个 8x8 棋盘上默认有 3 条水洼、1 只狼、5 棵树、3 个苹果——这正是后文「到最近苹果的平均距离约 5-6 步」的地图来源plot()最终调用image()渲染出像素图Board.plot并且支持传入 Q-Table 参数在空格子上画出策略箭头后文会用到。动作与策略Policy彼得的目标是找到苹果同时避开狼和其他障碍物。在任何位置他只能选择四个动作之一上、下、左、右。我们用字典把动作映射到对应的坐标变化对例如向右移动R对应(1,0)原文档「代码块 2」actions { U : (0,-1), D : (0,1), L : (-1,0), R : (1,0) } action_idx { a : i for i,a in enumerate(actions.keys()) }这里有两个约定值得注意坐标系中x是行上下对应第二个分量、y是列左右对应第一个分量这与 numpy 数组索引Q[x, y]保持一致action_idx建立了「动作字符 → 索引」的映射后面更新 Q-Table 时需要用它索引第三维len(actions)4。概括一下本场景的策略与目标策略policy代理彼得的策略由一个函数定义该函数返回任意给定状态下的动作。状态由棋盘表示包括玩家的当前位置m.human属性目标强化学习的目的是最终学习到一个好策略让我们能高效地解决问题。作为基线先考虑最简单的策略——随机走动random walk。基线随机走动策略及其统计随机走动策略就是每次从允许的动作中随机挑一个直到找到苹果原文档「代码块 3」def 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的调用会返回本次路径长度每次运行结果可能不同。它的终止逻辑有三条踩到苹果返回步数n成功掉进水里或被狼吃掉返回-1对无效/水域格子则重试选动作。注意Board类本身也内置了一个功能等价的 walk 方法且支持save_to参数把每一步渲染成图片序列——课程动画如随机走动 GIF就是基于它生成的。多次运行该实验例如 100 次并打印统计信息原文档「代码块 4」def 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 步这个数字相当大——随机策略的探索是极其低效的这就是我们需要学习算法的动机。奖励函数延迟奖励问题要让策略更智能我们需要知道哪些动作比其他动作「更好」这通过奖励函数定义它为每个状态返回一个分数值数字越大奖励越好原文档「代码块 5」move_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奖励设计包含三个数值常量每走一步-0.1轻微惩罚鼓励走短路径、到达苹果10目标奖励、掉水/遇狼/越界-10终止惩罚。奖励函数有一个关键特性在大多数情况下我们只在游戏结束时才得到实质性奖励。这意味着算法必须能「记住」那些最终导向正奖励的「好」步骤并提高它们的重要性同时抑制所有导向坏结果的举动——延迟奖励delayed reward的归属问题正是强化学习区别于监督学习的核心难点也是下一节贝尔曼方程要解决的问题。Q-Table用表格记录每个动作的「优点」本课使用的算法叫Q-Learning。在该算法中策略由一个称为Q-Table的函数或数据结构定义它记录给定状态下每个动作的「优点goodness」。之所以叫 Q-Table是因为用表格多维数组表示它往往很方便。棋盘尺寸是widthxheight所以可以用形状为widthxheightxlen(actions)的 numpy 数组表示 Q-Table原文档「代码块 6」Q np.ones((width,height,len(actions)),dtypenp.float)*1.0/len(actions)注意我们把 Q-Table 的所有值初始化为相等的数值1/len(actions) 0.25即 4 个动作各 0.25。这对应「随机走动」策略——每个状态中所有移动同样好。把 Q-Table 传给plot函数即可在棋盘上可视化表格m.plot(Q)。在 rlboard.py 的 image 方法 中可以看到对每个空格子先调用probs(Q[x,y])把 Q 值归一化成概率再按四个方向加权求和画出一条「箭头」draw_line表示该格子偏好的移动方向由于初始时所有方向等权画出来的是一个点。现在需要运行模拟、探索环境学习一份更好的 Q-Table 数值分布让我们能更快地找到通往苹果的路。Q-Learning 的本质贝尔曼方程一旦开始移动每个动作都有对应的奖励理论上可以按最高即时奖励选择下一个动作。但在大多数状态下这一步并不会直接实现「到达苹果」的目标因此无法立即判断哪个方向更好。请记住重要的不是直接结果而是我们在模拟结束时获得的最终结果。为处理这种延迟奖励需要借助**动态规划dynamic programming**的原则递归地思考问题假设现在处于状态s想移动到下一个状态s。这样做会收到奖励函数定义的即时奖励r(s,a)以及一些未来奖励。如果我们假设 Q-Table 正确反映了每个动作的「吸引力」那么在状态s我们会选择使Q(s,a)最大的动作a。因此在状态s能获得的最佳未来奖励可定义为maxaQ(s,a)最大值在状态s的所有可能动作a上计算。由此得到计算状态s下动作a的 Q-Table 值的Bellman 公式$$Q(s,a) \leftarrow (1-\alpha),Q(s,a) \alpha,\big(r(s,a) \gamma,\max_{a} Q(s,a)\big)$$其中 γ 是所谓的折扣因子discount factor决定你应在多大程度上偏好当前奖励而非未来奖励反之亦然。学习算法伪代码与「探索 vs 利用」基于上面的等式学习算法的伪代码如下用相同的数字为所有状态和动作初始化 Q-Table Q设置学习率 α ← 1多次重复模拟从随机位置开始重复在状态s选择一个动作a通过移动到新状态s来执行动作如果遇到游戏结束情况或总奖励太小——退出模拟计算新状态下的奖励r根据 Bellman 方程更新 Q 函数Q(s,a)←(1-α)Q(s,a)α(rγ maxaQ(s,a))s←s更新总奖励并减小 α。在上面的算法中步骤 2.1「如何选动作」并没有指定。两种极端探索explore随机选择动作会随机探索环境很可能经常死亡也会探索到通常不会去的区域利用exploit选择 Q-Table 中值最高的最优动作。但这会阻止我们探索其他状态可能永远找不到最优解。最佳做法是在两者之间取得平衡以与 Q-Table 值成比例的概率选择动作。一开始 Q-Table 值全部相同等价于随机选择随着对环境的了解越来越多会更可能走最优路线同时仍允许代理偶尔选择未探索的路径。Python 实现5000 个 epoch 的训练循环实现学习算法之前先需要一个函数把 Q-Table 中的任意数字转换为对应动作的概率向量原文档「代码块 7」def probs(v,eps1e-4): v v-v.min()eps v v/v.sum() return v这里向原始向量加上小的eps是为了在初始阶段向量所有分量相同时避免除以 0。注rlboard.py 中内置了一个不带eps的同名probs用于绘图本课代码块版本用eps保证训练早期数值稳定两者用途不同。接下来运行学习算法共 5000 次实验即epochs原文档「代码块 8」lpath [] for 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) # 允许走出棋盘走出即终止本局 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这段训练循环中的关键设计可以逐条对应到前面讲的原理代码含义v probs(Q[x,y])random.choices(..., weightsv)探索/利用平衡按 Q 值比例采样动作而非贪心取最大m.move(dpos,check_correctnessFalse)允许走出棋盘边界越界时reward()返回end_reward-10episode 随之终止。这与 Board.move 的check_correctness参数语义一致cum_reward -1000兜底保护防止无限打转时累计惩罚过深强制结束本局alpha np.exp(-n / 10e5)学习率随步数 n 指数衰减训练后期只小幅调整 Q-Table避免「破坏」已学到的值gamma 0.5折扣因子权衡即时奖励与未来奖励Q[x,y,ai] (1-alpha)*Q[x,y,ai] alpha*(r gamma*Q[s].max())贝尔曼更新action_idx把动作字符映射回第三维索引Q[xdpos[0], ydpos[1]].max()即max_a Q(s,a)lpath.append(n)记录每个 episode 的步数用于绘制学习曲线学习率 α 的写法np.exp(-n / 10e5)中10e5是步数尺度由于单次 episode 通常只有几十到几百步α 在单局内几乎不变衰减主要发生在跨 epoch 上。执行算法后Q-Table 已被更新为定义每个状态下各动作「吸引力」的数值可以用m.plot(Q)重新可视化——训练前后对比见前文两张learned.png环境图初始时所有格子是点训练后箭头清晰地指向苹果方向。策略评估贪心策略会「挂起」吗由于 Q-Table 列出了每个状态下每个动作的「吸引力」用它定义高效导航非常容易。最简单的情况是选择 Q-Table 值最高的动作原文档「代码块 9」def qpolicy_strict(m): x,y m.human v probs(Q[x,y]) a list(actions)[np.argmax(v)] return a walk(m,qpolicy_strict)如果你多次运行上面的代码可能会注意到它有时会「挂起」需要按笔记本中的 STOP 按钮中断。原因是可能存在两个状态在最优 Q 值方面相互「指向」的情况此时代理会在这两个状态之间无限期地来回移动。这正是贪心策略的缺陷纯利用会导致局部环路。原文档给出两个挑战任务供实践对应笔记本中的 Exercise 部分任务 1修改walk函数把路径最大长度限制为一定步数比如 100并时不时观察上面的代码返回这个上限值——验证「挂起」确实发生了。任务 2修改walk函数使其不再回到之前去过的地方。这可以防止walk循环但代理仍可能被「困」在无法逃脱的位置。概率导航策略平均路径 3-6 步更好的导航策略是训练时用过的那种——结合利用与探索以与 Q-Table 值成比例的概率选择每个动作。该策略仍可能让代理返回已探索过的位置但会导致到达目标位置的平均路径非常短print_statistics会运行 100 次模拟原文档「代码块 10」def qpolicy(m): x,y m.human v probs(Q[x,y]) a random.choices(list(actions),weightsv)[0] return a print_statistics(qpolicy)运行后应得到比随机走动小得多的平均路径长度范围为 3-6 步——与地图中「到最近苹果平均 5-6 步」的下限几乎重合说明学到的策略已接近最优。对比随机走动的 30-40 步量化地证明了 Q-Learning 的有效性。观察学习过程路径长度曲线与超参数观察平均路径长度在学习过程中的变化非常有价值plt.plot(lpath)可复现下图学习曲线可概括为三个阶段平均路径长度先增加。起初对环境一无所知时很可能陷入水或狼等坏状态而早早结束随着学到一些知识、能更长时间地探索环境虽然还不知道苹果在哪但存活步数变多平均路径长度反而上升。随知识积累路径长度下降。学到足够多后代理更容易达成目标路径开始变短但由于仍对探索保持开放常常偏离最佳路径去探索新选项使路径略长于最优。长度突然增加。曲线上某个时刻长度会突然跳升这体现了过程的随机性我们可能在某个时点用新值「覆盖」并破坏了 Q-Table 系数。理想情况下应通过降低学习率来最小化这种情况例如训练结尾只对小数值做调整——这正是训练代码中alpha np.exp(-n / 10e5)的用途。总体上要记住学习过程的成功与质量显著依赖于学习率、学习率衰减、折扣因子等参数。这些参数通常称为超参数hyperparameters以区别于训练期间被优化的参数parameters例如 Q-Table 系数本身。寻找最佳超参数值的过程称为超参数优化hyperparameter optimization值得单独成专题。进阶作业更真实的世界学完本课后可继续完成配套作业 A More Realistic World英文版见 assignment.md给世界加入能量与疲劳规则——移动消耗能量、吃苹果补充能量、在树下/草地上休息消除疲劳且必须以足够的能量和较低的疲劳才能击败狼。作业要求修改奖励函数、扩展状态表示如(Board, energy, fatigue)元组或派生Board类重新训练并对比随机走动与 Q-Learning 的胜负率。由于「打赢狼」是稀有事件可能需要大幅调整 epoch 数等超参数。该作业的参考解答见 solution/assignment-solution.ipynb。小结与后续学习路径本文完整复刻了 ML-For-Beginners 强化学习第一课的内容脉络关键要点回顾环境建模Board类rlboard.py把《彼得与狼》抽象为 8x8 网格Cell常量定义地面/水/狼/树/苹果五类格子基线对照随机走动平均 30-40 步找到苹果且存在被狼吃死的概率延迟奖励奖励函数只在终止状态给出实质分数±10中间步骤仅 -0.1Q-Table 贝尔曼方程Q(s,a) ← (1-α)Q(s,a) α(r γ max Q(s,a))初始化为均匀的 0.25 即等价随机策略探索/利用平衡按 Q 值归一化后的概率采样动作probsrandom.choices(weights...)效果验证概率导航策略平均路径 3-6 步逼近地图理论下限超参数意识学习率衰减、折扣因子等显著影响收敛质量与曲线形态。学完本节内容后可以继续学习第 8 周的第二课 使用 Gym 模拟环境把 Q-Learning 从自建的格子世界迁移到标准强化学习库 OpenAI Gym 中第 8 周总览见 8-Reinforcement/README.md。【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表