news 2026/9/8 23:35:13

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

强化学习(Reinforcement Learning,RL)是与监督学习、无监督学习并列的机器学习核心范式之一,它解决的是「在不确定环境中持续做决策」的问题。本指南以 ML-For-Beginners 课程第 8 章(8-Reinforcement/README.md)为骨架,围绕童话「彼得与狼」场景,系统讲解 RL 的核心概念、环境与奖励函数的建模方式、Q-Learning 算法原理,并结合仓库中的棋盘环境实现与 OpenAI Gym 的 CartPole 实验,展示从离散状态到连续状态的完整实战路径。读完本文,你将理解为什么教计算机玩游戏不能当分类问题处理,并能复现一整套从随机游走到 Q-Table 收敛的寻路强化学习流程。

机器学习第三大范式:强化学习与「决策」

课程前几章已经介绍了机器学习的两种基本问题形态:

  • 监督学习(Supervised):数据集本身提供问题「示例答案」,例如 4-Classification/README.md(分类)与 2-Regression/README.md(回归);
  • 无监督学习(Unsupervised):没有带标签的训练数据,典型的例子是 5-Clustering/README.md(聚类)。

强化学习(RL)被视作与此并列的第三大基础范式,它的核心不是「拟合标签」也不是「发现结构」,而是决策:在每一步做出正确的选择,或者至少从错误选择中学习。

这一章引入两种不依赖人工标注数据的任务类型:

  • 半监督学习(Semi-supervised Learning):拥有大量未标注数据,可用它们对模型进行预训练;
  • 强化学习(Reinforcement Learning):智能体(agent)通过在某个模拟环境中反复做实验来学会「如何表现」。

可以把 RL 理解为「基于经验的试错决策」。例如,设想一个受规则约束的模拟环境——股票市场:当你施加一项新监管政策时,其效果是正向还是负向?若结果是负面的,你需要接受这一负向强化,从中学习并调整路线;若结果为正,则应基于这份正向强化继续推进。这也是「强化」二字的由来。

本仓库为该章节设定的区域主题是**「彼得与狼」(俄罗斯)**——源自俄国作曲家谢尔盖·普罗科菲耶夫的著名音乐童话:年轻的小主人公彼得勇敢地走出家门,来到林中空地追捕那只狼。课程中训练机器学习算法帮助彼得完成两件事:

  • 探索周边环境,构建一张最优导航地图(对应 Q-Learning 寻路);
  • 学习使用滑板并在其上保持平衡,以便移动得更快(对应 CartPole 平衡问题)。

为什么教电脑玩游戏不是「分类问题」

假设我们希望让计算机学会下棋或玩「超级马里奥」。要让电脑玩好游戏,需要它在每个游戏状态下预测下一步动作。这看起来很像分类问题,但实际上并不是——因为我们并没有「状态→正确动作」的现成数据集。即使我们有部分现成记录(例如已收录的棋局、玩家玩马里奥的录像),它们也几乎不可能覆盖足够数量的可能状态。

因此,与其寻找已有游戏数据,强化学习的基本思路是「让计算机自己玩很多次,然后观察结果」。此时延迟回报问题出现了:在 RL 中,我们通常要等到整局游戏结束才知道输赢,无法仅凭一步棋就断言「这一步是好是坏」——多数情况下,只有游戏结束时才会收到一次奖励。

为了在这种不确定条件下训练模型,应用强化学习必须具备两样东西:

  1. 环境(Environment)与模拟器(Simulator):允许我们把游戏反复玩很多次。模拟器要定义游戏的全部规则,以及所有可能的状态(states)与动作(actions)。
  2. 奖励函数(Reward Function):告诉我们每一步或每一局玩得有多好。

我们将深入一种经典的 RL 算法——Q-Learning(Q 学习),它专门用于解决上述「延迟奖励 + 状态搜索」的问题。

实战环境:Peter 的 8×8 方格世界

第一课 8-Reinforcement/1-QLearning/README.md 把 Peter 的世界建模成一个width×height的方形棋盘,每个格子可以是以下六类之一:

格子类型含义
地面 groundPeter 和其他角色可以行走
水域 water显然无法行走
树 / 草地 tree & grass可以休息的地点
苹果 apple正向目标,Peter 乐于找到它来充饥
狼 wolf危险目标,应当规避

这一环境在仓库中由独立 Python 模块 8-Reinforcement/1-QLearning/rlboard.py 实现。由于环境代码本身不是理解算法概念的关键,课程直接在 Notebook 中导入它来生成棋盘:

from rlboard import * width, height = 8, 8 m = Board(width, height) m.randomize(seed=13) m.plot()

在 rlboard.py 源码中,Board使用整数常量枚举了格子类型(empty=0 / water=1 / wolf=2 / tree=3 / apple=4),randomize()会按num_water / num_wolves / num_trees / num_apples参数随机铺出水域、树林、狼与苹果,并用随机数种子保证可复现——seed=13正是让每次运行都能得到同一张示例棋盘的原因。Board.at()is_valid()move()等成员则分别承担查询当前格内容、校验坐标合法性、执行移动的职责。

动作、策略与基线:随机游走

在上述世界中,Peter 的目标是找到苹果、同时避开狼与其他障碍。为此,他在任一位置可以选取四个动作之一:上(U)、下(D)、左(L)、右(R)。课程将其定义为坐标增量字典:

actions = { "U": (0, -1), "D": (0, 1), "L": (-1, 0), "R": (1, 0) } action_idx = { a: i for i, a in enumerate(actions.keys()) }

这里的两个关键术语是:

  • 策略(Policy):智能体的行动方略,是一个「在任意给定状态下返回动作」的函数。本问题中状态即整张棋盘(含玩家当前位置);
  • 目标(Goal):强化学习最终学到的,是一个能高效解决问题的好策略。

作为对照基线,课程首先实现了最简单的策略——随机游走(random walk):在到达苹果前,每一步都从合法动作中随机挑选一个方向。

def random_policy(m): return random.choice(list(actions)) def walk(m, policy, start_position=None): n = 0 # number of steps 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) break n += 1

把随机策略跑上 100 次并统计(walk返回路径长度;被狼吃掉或掉进水里返回-1),可以看到平均路径长度约 30~40 步——而距离最近的苹果平均只有 5~6 步。这说明随机游走「盲目乱转」,是个非常低效的基线。从 rlboard.py 的实现看,walk循环会一直持续到触达苹果(返回步数)或落入狼/水(返回-1)为止,这也是我们评估任意策略的基本框架。

奖励函数:让「好坏」可以被量化

要让策略变得聪明,必须定义每个状态的好坏。课程用奖励函数为每个状态返回一个分数,分数越高代表越好:

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

奖励函数的取值逻辑非常直白:

情形奖励
走出棋盘 / 落入水域 / 撞上狼-10(终局负奖励)
抵达苹果+10(目标奖励)
其他普通移动-0.1(时间成本,鼓励少绕路)

奖励函数设计的关键洞见是:在绝大多数情况下,我们只在游戏结束时才收到实质性奖励。因此算法必须「记住」那些最终带来正收益的中间步骤并提高其权重,同时抑制导致坏结果的步骤——这正是接下来 Q-Learning 要解决的问题。

Q-Learning 与 Q-Table:记忆「每个状态下哪个动作更好」

Q-Learning 的策略由一个称为Q-Table(Q 表)的函数或数据结构定义,它记录「在给定状态下每个动作的『优良程度』」。之所以叫 Q-Table,是因为通常方便把它表示成一张表或多维数组。棋盘尺寸为width×height,于是 Q-Table 可以表示成形状为width × height × len(actions)的 numpy 数组:

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

注意初始化时所有格子的值相等(4 个动作即 0.25),这正对应「随机游走」策略——每个状态下各动作同等「好」。把 Q 表传给m.plot(Q)后,每个格子中央会出现一个指向偏好移动方向的「箭头」;当所有方向等概率时,便显示为一个圆点。而在 rlboard.py 的image()实现中,每个空格箭头方向正是由该格 Q 值经probs()归一化后对四个方向的概率加权计算出来的——可视化与数据模型严格对应。

贝尔曼方程:处理延迟奖励的核心

一旦开始移动,每个动作都有对应奖励,理论上我们可以依据「即时奖励最高」来选下一步。但大多数状态下的一步并不会立刻达成「吃到苹果」的目标,所以无法立刻判断哪个方向更优。

记住:重要的不是即时结果,而是模拟结束时的最终结果。

为了刻画这种延迟奖励,需要借助动态规划思想,把问题递归地看待。假设当前处于状态s,准备走到下一个状态s',我们会获得即时奖励r(s,a)加上某种未来奖励。若 Q-Table 已正确反映每个动作的「吸引力」,则在状态s'我们应当选择使Q(s',a')最大的动作a',故状态s处可获得的最佳未来奖励就是maxₐ' Q(s',a')。由此得到计算 Q 值的贝尔曼公式(Bellman Equation)

Q(s,a) ← (1-α)·Q(s,a) + α·( r(s,a) + γ·maxₐ' Q(s',a') )

其中γ(gamma,折扣因子)决定你应当更看重当前奖励还是未来奖励。γ 越接近 1,智能体越有「远见」;γ 偏小则更短视。

学习算法伪代码

基于贝尔曼方程,课程给出了完整的学习算法骨架:

  1. 用相同数值初始化 Q-Table(所有状态与动作);
  2. 设定学习率 α ← 1;
  3. 多次重复模拟:
    1. 从随机位置出发;
    2. 重复执行:
      1. 在状态s选择一个动作a
      2. 执行动作,移动到新状态s'
      3. 若触发终局条件或累计奖励过小,退出本次模拟;
      4. 在新状态计算奖励r
      5. 按贝尔曼公式更新 Q 函数:Q(s,a)(1-α)Q(s,a)+α(r+γ·maxₐ'Q(s',a'))
      6. ss'
      7. 更新总奖励并降低 α。

探索与利用的平衡(Explore vs. Exploit)

在上述伪代码第 2.1 步,我们尚未说明「如何选动作」:如果完全随机选动作,智能体是在随机探索(explore)环境——会频繁「死亡」,也会走到常规路线之外;反之,如果总是利用(exploit)已知的 Q-Table 取当前最优动作,又会阻碍探索其他状态,很可能找不到全局最优解。因此最佳策略是按 Q-Table 值的概率比例来选择动作:初始 Q 值相等时它等价于随机选择;随着对环境了解更多,智能体越来越倾向于沿最优路线前进,同时偶尔仍会尝试未探索的路径。

Python 实战:5000 次 Epoch 训练出导航策略

在动手实现前,需要先把 Q-Table 中的任意数值转成「各动作概率向量」:

def probs(v, eps=1e-4): v = v - v.min() + eps v = v / v.sum() return v

加入一个极小量eps是为了避免初始情况下(向量各分量相等)除以 0。随后跑 5000 次实验(epoch),并把动作选择做成按 Q 值概率采样以平衡探索与利用:

lpath = [] for epoch in range(5000): m.random_start() # 随机初始位置 n = 0 cum_reward = 0 while True: x, y = m.human v = probs(Q[x, y]) a = random.choices(list(actions), weights=v)[0] dpos = actions[a] m.move(dpos, check_correctness=False) # 允许移出棋盘以终止回合 r = reward(m) cum_reward += r if r == end_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[x + dpos[0], y + dpos[1]].max()) n += 1

该实现体现了三处关键细节(可对照 notebook.ipynb 完整复现):

  • 学习率 α 采用指数衰减np.exp(-n / 10e5),训练越往后对 Q-Table 的修正越小;
  • 折扣因子 γ 取 0.5,即中等程度地看重未来奖励;
  • 更新时直接引用m.move()前后坐标,并通过Q[x+dpos[0], y+dpos[1]].max()实现贝尔曼方程中的「下一状态最大 Q 值」。

训练结束后,Q-Table 已记录「每个格子上哪个方向更吸引人」,用m.plot(Q)即可看到箭头收敛到指向苹果的路径。可以用两种策略验证效果:

  • 严格最优策略(贪心取 Q 值最大方向)——注意有时会「卡死」:可能出现两个状态互为最优指向、智能体在两者间无限往返的情形,此时需要手动中断 Notebook;
  • 概率策略 qpolicy(训练时同款的概率采样):print_statistics(qpolicy)会得到3~6 步的平均路径长度,相比随机游走的 30~40 步大幅缩短,接近「最近苹果距离 5~6 步」的理论上界。

课程还给出了两个进阶挑战:限制walk最大步数(如 100)以观察偶尔超时;或禁止走回已访问过的位置以消除死循环。另外,观察学习过程中平均路径长度的曲线会发现三个阶段性的现象——初期路径变长(对世界一无所知、常被困在坏状态);随后逐渐变短(知识积累使达成目标更容易,但仍会偏离最优路径去探索);中期偶有突变(随机过程可能用新值「破坏」已学到的 Q-Table 系数,应通过降低学习率来缓解)。

这引出了超参数(hyperparameters)概念:学习率 α、学习率衰减策略、折扣因子 γ 等,与训练中直接优化出来的参数(如 Q-Table 系数)相区分;寻找最佳超参数的过程称为超参数优化,属于更进一步的专题。

从离散走向连续:第二课用 OpenAI Gym 让 Peter 学会「滑板」

上一节的棋盘世界状态是离散的(格点坐标),8-Reinforcement/2-Gym/README.md 指出,现实中很多问题(如下棋)同样具备「棋盘 + 规则 + 离散状态」的结构,但还有大量问题状态是连续的(由一个或多个实数描述)。为让 Peter 逃离狼的速度更快,第二课教他滑滑板并保持平衡——采用著名的CartPole(倒立摆)简化模型:水平滑轨上有一个可左右移动的小车,目标是让小车顶上的竖直杆保持平衡。

课程使用的模拟环境是 OpenAI 维护的Gym仿真库,它提供了从倒立摆到 Atari 游戏的各种环境。安装并初始化环境的代码为:

import sys !{sys.executable} -m pip install gym import gym import matplotlib.pyplot as plt import numpy as np import random env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample())

每个 Gym 环境都对应两个关键定义:

  • 观测空间(observation space):环境反馈给智能体的信息结构。CartPole 中每次返回 4 个数值——小车位置、小车速度、杆的倾角、杆的旋转速率;
  • 动作空间(action space):可选动作集合。CartPole 的动作空间是离散的,只有两个动作:

值得注意的是,CartPole 中每一步的奖励恒为 1,因为目标就是「存活得尽可能久」(让杆尽量长时间保持竖直)。这一环境有一个公认的「解决」判据:连续 100 次试验的平均奖励达到 195

由于 Q-Learning 需要离散状态来建表,第二课的核心工程问题是状态离散化(state discretization),课程给出了两种方案:

  1. 分箱(bins):已知数值区间时,将区间切成若干 bin,再用 numpy 的np.digitize把值替换为所属 bin 编号,状态规模完全可控;
  2. 等比缩放取整:把观测值除以固定步长再转整数(如tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int)))。此法对无上下界的值(如速度)控制力较弱,但极端取值在现实中很罕见,实现最简单。

与此同时,Q-Table 的表示也随之调整:上一课用固定形状张量(8×8×2)即可,但 CartPole 观测空间维数不确定、部分取值无界,所以第二课改用字典结构,以(state, action)为键、Q 值为值,未出现过的条目默认返回 0:

Q = {} actions = (0, 1) def qvalues(state): return [Q.get((state, a), 0) for a in actions]

CartPole 的训练循环与超参数

第二课设置的超参数与上一课略有不同(保持恒定而非衰减,便于对比实验):

超参数取值含义
alpha0.3学习率:每步对现有 Q 值修正的程度
gamma0.9折扣因子:未来奖励相对当前奖励的权重
epsilon0.90探索/利用系数:90% 情形按 Q-Table 概率选动作,其余随机探索

训练循环(约 10 万次 epoch)还引入了两个工程改进:按每 5000 次迭代计算平均累计奖励来打印进度;同时保存历史最优的Qmax与对应Qbest表——因为训练中平均累计奖励可能下降(Q-Table 被「更差」的新值覆盖),保留最佳快照可避免模型退化:

def probs(v, eps=1e-4): v = v - v.min() + eps v = v / v.sum() return v Qmax = 0 cum_rewards = [] rewards = [] for epoch in range(100000): obs = env.reset() done = False cum_reward = 0 while not done: s = discretize(obs) if random.random() < epsilon: # exploitation - 按 Q-Table 概率选动作 v = probs(np.array(qvalues(s))) a = random.choices(actions, weights=v)[0] else: # exploration - 随机动作 a = np.random.randint(env.action_space.n) obs, rew, done, info = env.step(a) cum_reward += rew ns = discretize(obs) Q[(s, a)] = (1 - alpha) * Q.get((s, a), 0) + alpha * (rew + gamma * max(qvalues(ns))) cum_rewards.append(cum_reward) rewards.append(cum_reward) if epoch % 5000 == 0: print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}") if np.average(cum_rewards) > Qmax: Qmax = np.average(cum_rewards) Qbest = Q cum_rewards = []

把每个 epoch 的累计奖励画成原始曲线几乎看不出规律(随机性太强),因此需要计算滑动平均来观察趋势——np.convolve(x, np.ones(window)/window, mode='valid'),窗口取 100。滑动平均曲线能清楚看到奖励稳步爬升,也暴露了「中途奖励回落」的抖动。为了更稳定地收敛,可以让 α 从接近 1 逐步衰减(后期只微调 Q 值),并让 epsilon 缓慢上升逼近 1(从探索为主过渡到利用为主)。挑战题要求进一步验证:把Qbest复制回Q观察效果;或用np.argmax每步严格选最大 Q 值动作,比较与概率采样的差异。所有步骤均可对照 2-Gym/notebook.ipynb 逐步执行。

第二课给出一个重要结论:Q-Learning 在「离散状态 + 离散动作」与「连续状态(离散化后)+ 离散动作」两类问题上都能奏效;但当动作本身连续、或观测空间极为复杂(如 Atari 游戏画面)时,就需要引入神经网络等更强大的技术——这正是更进阶的深度学习课程的主题。

本节课程地图与配套练习

整个强化学习章节(第 8 章)按以下顺序组织,方便循序渐进地学习:

  1. 8-Reinforcement/1-QLearning/README.md:强化学习与 Q-Learning 入门——Peter 的 8×8 棋盘寻路,配套 notebook.ipynb 与 rlboard.py(运行 Notebook 时需将二者放在同一目录下),课后练习见 assignment.md;
  2. 8-Reinforcement/2-Gym/README.md:使用 Gym 模拟环境——CartPole 滑板平衡,配套 notebook.ipynb,课后练习见 assignment.md(目标是训练一个可以爬坡的 Mountain Car)。

小结

通过「彼得与狼」这一贯穿全章的童话设定,ML-For-Beginners 把强化学习最核心的思想串成了一条可动手复现的链路:先用Board构造离散环境、用奖励函数表达目标、用随机游走建立低效基线,再引入 Q-Table 与贝尔曼方程逐步逼近最优导航策略;随后把同样的 Q-Learning 移植到 OpenAI Gym 的连续状态 CartPole 环境中,借助状态离散化、字典型 Q-Table 与「探索/利用」超参数调优,教会智能体在实时物理模拟里保持平衡。学习者只要提供「定义期望状态的奖励函数 + 允许智能体智能探索的机会」,Q-Learning 便能自行学到足够好的策略——这正是强化学习范式区别于监督与无监督学习的独特价值所在。

本章内容「Introduction to Reinforcement Learning」由 Dmitry Soshnikov 编写;「彼得与狼」主题插画由 Jen Looper 创作。仓库的 sketchnotes/ml-reinforcement.png 还提供了一张覆盖 agent / states / actions / reward 概念的可视化速记图,可作为通读本节的辅助材料。

【免费下载链接】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/8 23:31:27

STM32H743高性能MCU实战:从选型到量产的全流程解析

最近帮客户做一套工业视觉检测的预处理板&#xff0c;主控选型的时候纠结了很久。一开始想用MPU加Linux的方案&#xff0c;但考虑到成本、功耗和现场环境&#xff0c;最后还是回到了高端MCU这条路上。在对比了NXP的RT1170、Microchip的SAMA7G54和ST的STM32H743之后&#xff0c;…

作者头像 李华
网站建设 2026/9/8 23:30:48

机器人主控板选型避坑指南:RK3588/3576/3568实战决策树

1. 为什么“选主控板”成了机器人项目最耗时的环节&#xff1f;——从三个真实翻车现场说起我帮过七家初创机器人团队做过硬件架构评审&#xff0c;几乎每一家都卡在主控板选型上。不是因为技术太难&#xff0c;而是因为没人把“选板子”当成一个系统工程来对待。最常见的翻车场…

作者头像 李华