☰
ML-For-Beginners 强化学习实战:把 Q-Learning 从 CartPole 迁移到 Mountain Car 环境(Train Mountain Car 任务解析)
2026/10/18 21:11:33 网站建设 项目流程

ML-For-Beginners 强化学习实战:把 Q-Learning 从 CartPole 迁移到 Mountain Car 环境(Train Mountain Car 任务解析)

【免费下载链接】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 周强化学习部分的德语任务文档 assignment.md(“Train Mountain Car”)展开,围绕 OpenAI Gym 的统一环境 API,讲清 Mountain Car 环境的动作空间、观测空间与奖励机制,并说明如何以最小代码改动把课程中已实现的 Q-Learning 算法从 CartPole 迁移到 Mountain Car 上完成训练,最终帮助读者掌握“同一算法跨 Gym 环境复用”这一强化学习的核心工作流。

任务背景:Gym 统一 API 让算法可以“换环境不换代码”

这份任务文档的开篇就点出了 OpenAI Gym 的设计前提:所有 Gym 环境都共享同一套 API——即相同的reset、step、render方法,以及相同的Action Space(动作空间)与Observation Space(观测空间)抽象。德语原文的表述是:

„[OpenAI Gym] wurde so konzipiert, dass alle Umgebungen dieselbe API bereitstellen – d.h. dieselben Methodenreset,stepundrendersowie dieselben Abstraktionen vonAction SpaceundObservation Space. Daher sollte es möglich sein, dieselben Reinforcement-Learning-Algorithmen mit minimalen Codeänderungen an verschiedene Umgebungen anzupassen.“

(译注:Gym 被设计为让所有环境提供相同 API,因此相同的强化学习算法只需极少的代码修改即可适配不同环境。)

这也是本任务在课程中的位置所在:在第 8 周前一课(CartPole Skating,见 8-Reinforcement/2-Gym/README.md 与配套 notebook.ipynb)中,课程用 Gym 的 CartPole 环境完整实现了一套 Q-Learning;而本任务要求学员把这套现成算法搬到 Mountain Car 环境上。前一课之所以值得先读透,是因为任务中“最小改动”的起点代码全部来自它。

从文档目录结构看,德语版课程资料(translations/de/8-Reinforcement/2-Gym/下的 README.md、notebook.ipynb)与英文原版一一对应,文档末尾的免责声明(Haftungsausschluss)也说明该德语文档由 AI 翻译服务自动生成,英文原文才是权威来源(对应 8-Reinforcement/2-Gym/assignment.md)。

Mountain Car 环境:动作、观测与奖励机制

Mountain Car(MountainCar-v0)模拟一辆被困在山谷中的车,目标是冲出山谷、登上山顶拿到旗帜。它的规则要点在任务文档中全部给出,完整整理如下:

动作空间(Action Space)

每一步从 3 个离散动作中选一个:

值含义
0向左加速(Nach links beschleunigen)
1不加速(Nicht beschleunigen)
2向右加速(Nach rechts beschleunigen)

观测空间(Observation Space)

观测向量只有两个连续值:

序号观测最小值最大值
0车的位置(Position des Autos)-1.20.6
1车的速度(Geschwindigkeit des Autos)-0.070.07

与 CartPole 的 4 维观测(小车位置、小车速度、杆角度、杆角速度)相比,Mountain Car 只有 2 维,状态离散化的工作量更小,这也是它适合作为 CartPole 之后的下一个练习环境的原因。

奖励机制与回合终止条件

Mountain Car 的奖励系统被文档形容为“相当棘手”(ziemlich knifflig),规则是:

  • 当代理到达山顶旗帜(位置 = 0.5)时,奖励为0;
  • 当代理位置小于 0.5时,每步奖励为-1。

回合(episode)在以下任一条件满足时结束:

  • 车的位置大于 0.5(成功登顶);
  • 回合长度超过 200 步(超时失败)。

核心难点:动力不足,必须靠摆荡积累动能

文档特别强调了这个问题最关键的“trick”:

汽车的动力不足以一次性爬上山坡,因此唯一成功的方式是左右来回行驶以积累动能(Schwung aufzubauen)。

这意味着策略不能是“始终朝山顶方向加速”的贪心行为——那只会让车在坡上反复打滑。代理必须学会先向山谷另一侧倒退,换取势能,再冲上坡顶,即学会一种延迟满足的多步规划能力。这也正是 Mountain Car 相比 CartPole(奖励恒为 +1、目标是尽量存活)在奖励设计上更“反直觉”的地方:它的总回报永远是负数,训练目标是让累计 -1 惩罚尽量少,并尽快触发“位置 > 0.5”的终止条件。

任务指令:用最小改动迁移 Q-Learning 算法

任务文档的“Anweisungen”(指令)部分原文如下(德→中):

把你的强化学习算法改造到能解决 Mountain Car 问题。从 notebook.ipynb 中的现有代码出发,替换环境、修改状态离散化函数,并尝试以最少的代码改动训练现有算法。然后通过调整超参数来优化结果。

注意:很可能需要调整超参数,算法才能收敛。

对照课程提供的代码资产,可以拆解出明确的改动清单:

改动点起点(CartPole 课中代码)迁移到 Mountain Car
环境创建gym.make("CartPole-v1")gym.make("MountainCar-v0")
动作空间2 个动作(actions = (0, 1))3 个动作(actions = (0, 1, 2))
状态离散化4 维观测的discretize/discretize_bins2 维观测(位置、速度),需重新设定区间与分箱数
随机探索np.random.randint(env.action_space.n)无需改动,action_space.n自动变为 3
训练主循环、Q-Table、Qbest保存逻辑保持不变保持不变
超参数alpha、gamma、epsilon需要重新调参使算法收敛

值得注意的是,env.action_space.n、env.reset()、env.step(a)、done标志等调用都与具体环境无关,这是 Gym 统一 API 带来的直接收益——真正需要改的只有环境名、离散化函数和动作数。

起点代码解析:CartPole 课里的 Q-Learning 是怎么写的

任务要求“从现有代码出发”,而这份代码在 8-Reinforcement/2-Gym/README.md 中逐块讲解(配套可运行 notebook 为 8-Reinforcement/2-Gym/notebook.ipynb)。下面按源码逐段梳理,作为迁移的参照系。

1. 环境初始化与观测范围

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())

用env.observation_space.low/env.observation_space.high可以打印出观测各维度的上下界——这一步对 Mountain Car 迁移尤其重要,因为 Mountain Car 的位置界是 (-1.2, 0.6)、速度界是 (-0.07, 0.07),离散化分箱必须依据这些真实区间来设置。

2. 状态离散化:两种方法

Q-Learning 要求状态是有限离散值集,而 Gym 的观测是连续实数,因此需要离散化。课程给了两种实现:

方法一:线性缩放取整

def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))

方法二:分箱(bins)

def create_bins(i, num): return np.arange(num+1)*(i[1]-i[0])/num + i[0] ints = [(-5, 5), (-2, 2), (-0.5, 0.5), (-2, 2)] # 各参数取值区间 nbins = [20, 20, 10, 10] # 各参数分箱数 bins = [create_bins(ints[i], nbins[i]) for i in range(4)] def discretize_bins(x): return tuple(np.digitize(x[i], bins[i]) for i in range(4))

课程明确指出:discretize的缩放取整方式下,状态取值可能无界,状态总数不可知;discretize_bins则状态总数完全可控(各维度分箱数的乘积)。对于 Mountain Car,推荐采用分箱法:位置区间 (-1.2, 0.6) 和速度区间 (-0.07, 0.07) 都是已知有界的,正好满足“若已知某值的区间,可将其划分为若干个 bins”这一前提。一个符合课程思路的参考写法是:

# Mountain Car 参考离散化(位置 12 箱、速度 10 箱;箱数可再调) pos_bins = create_bins((-1.2, 0.6), 12) vel_bins = create_bins((-0.07, 0.07), 10) def discretize_mc(x): return (np.digitize(x[0], pos_bins), np.digitize(x[1], vel_bins))

该示例是按课程 README 中discretize_bins的模式改写、供任务作答参考的,并非仓库自带代码;分箱数量属于可自由实验的超参数(箱数越多状态越精细,但 Q-Table 越大、收敛越慢)。

3. Q-Table 结构与qvalues辅助函数

因为discretize方式下状态维度无法精确预知,课程没有使用 20x20x10x10x2 的张量,而是用字典表示 Q-Table,以 (状态, 动作) 对为键:

Q = {} actions = (0, 1) # Mountain Car 中应改为 (0, 1, 2) def qvalues(state): return [Q.get((state, a), 0) for a in actions]

qvalues(state)返回该状态下所有动作的 Q 值列表,未访问过的 (状态, 动作) 对默认取 0。迁移到 Mountain Car 时这段逻辑完全不用动,只需把actions扩到 3 元组。

4. 超参数与训练主循环

课程设定的超参数(code block 10)及其含义:

# hyperparameters alpha = 0.3 # 学习率:每步按多大比例更新 Q-Table 当前值 gamma = 0.9 # 折扣因子:未来奖励相对于当前奖励的权重 epsilon = 0.90 # 探索/利用因子:90% 的情况按 Q-Table 概率采样动作,10% 随机动作

训练主循环(code block 11,含课程提到的两处改进——按 5000 次迭代打印平均累计奖励、保存历史最优 Q-TableQbest):

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: # 利用:按 Q-Table 概率分布选动作 v = probs(np.array(qvalues(s))) a = random.choices(actions, weights=v)[0] else: # 探索:随机选动作 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 = []

其中 Q 值更新就是标准的 Q-Learning 更新式Q(s,a) ← (1-α)Q(s,a) + α(rew + γ·max_a' Q(s',a'))。迁移时注意两点差异会直接影响行为:

  • 奖励语义不同:Mountain Car 的rew除终止步外恒为 -1,cum_reward是负值且随步数增长越来越负。因此“平均累计奖励越高越好”的监控指标依然成立(越接近 0 越好),但数值量级与 CartPole 的 195 分标准完全不同——Mountain Car 的成功判据应是平均用步数显著下降,或在 200 步上限内登顶的比例上升。
  • 训练步数放大:CartPole 单回合最多 500 步;Mountain Car 单回合最长 200 步,但每个 episode 都需要 20~60 步摆荡才可能成功,样本效率更低,epoch总量与epsilon的退火策略需要重新实验(文档已明确提示“很可能需要调整超参数才能收敛”)。

5. 训练进度可视化

课程用移动平均来平滑随机训练的波动(code block 12),这段代码可直接复用:

def running_average(x, window): return np.convolve(x, np.ones(window) / window, mode='valid') plt.plot(running_average(rewards, 100))

原始rewards曲线(每次 episode 的累计奖励)噪声很大,取 100 的滑动平均后才能看出收敛趋势(对应课程插图 train_progress_runav.png)。

6. 部署验证:按概率分布选动作

训练完成后,用与训练时一致的策略运行仿真并render()观察行为:

obs = env.reset() done = False while not done: s = discretize(obs) env.render() v = probs(np.array(qvalues(s))) a = random.choices(actions, weights=v)[0] obs, _, done, _ = env.step(a) env.close()

课程还留了两个进阶挑战,迁移到 Mountain Car 同样适用:

  • Task 3:课程保存了历史最优Qbest,用Qbest覆盖Q再演示,对比行为差异;
  • Task 4:把“按概率分布采样动作”改为np.argmax直接选 Q 值最大的动作,观察平衡/登顶表现是否更稳定。

评分标准(Rubric)

任务文档给出的三级评分标准完整保留如下(德译中):

标准优秀(Vorbildlich)合格(Angemessen)待改进(Verbesserungswürdig)
Q-Learning 算法成功从 CartPole 示例迁移而来,代码改动最小,且能在200 步以内解决登顶取旗问题从网络获取了新的 Q-Learning 算法但文档良好;或迁移了现有算法但未达到预期效果未能成功迁移任何算法,但采取了实质性解题步骤(如实现了状态离散化、Q-Table 数据结构等)

“优秀”档实际上给出了可量化的验收指标:< 200 步登顶——这恰好与环境的回合上限(200 步)呼应,意味着策略必须接近最优效率地摆荡。

相关资源与作答路径

完成本任务时,仓库中可直接引用的材料有:

  • 德语文档本身:translations/de/8-Reinforcement/2-Gym/assignment.md,及其英文权威原文 8-Reinforcement/2-Gym/assignment.md;
  • 起点代码(CartPole Q-Learning 全量实现):8-Reinforcement/2-Gym/notebook.ipynb、逐块讲解见 8-Reinforcement/2-Gym/README.md(德语版 translations/de/8-Reinforcement/2-Gym/README.md);
  • 前序课程(离散棋盘上的 Q-Learning 原型,本课程的算法思想源头):8-Reinforcement/1-QLearning/README.md 与 8-Reinforcement/1-QLearning/notebook.ipynb;
  • 参考实现目录:8-Reinforcement/2-Gym/solution/ 下提供了 notebook.ipynb 解答,另有 R(solution/R)与 Julia(solution/Julia)语言的解答说明。

小结

这份 “Train Mountain Car” 任务的价值在于它用最精炼的方式演示了 Gym 统一 API 的红利:算法骨架(Q-Table 字典、qvalues、ε-策略、Q 值更新式、Qbest保存)一行不改,只需替换环境名、重写 2 维观测的离散化函数、把动作集从 2 扩到 3,再针对 Mountain Car 的负奖励与 200 步上限重新调参,就能把 CartPole 的平衡策略训练流程复用到“摆荡登顶”这一需要延迟满足的新问题上。掌握这一迁移流程后,面对 Gym 中其他经典控制环境(文档也提到 Gym 的环境从 CartPole 一直到 Atari 游戏)时,都可以沿用同样的思路起步。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询