主题
Gymnasium 上手:CartPole 与 FrozenLake
Gymnasium 是由 Farama 基金会维护的强化学习环境标准接口,是 OpenAI Gym 的官方继任者。它提供统一的 reset() / step() API 和大量经典环境,几乎所有主流强化学习库(Stable-Baselines3、CleanRL、RLlib 等)都以它为标准。本节带你安装 Gymnasium,理解它的核心接口,在 FrozenLake 上训练表格型 Q-learning,在 CartPole 上测出随机策略基线,并学会编写自定义环境和使用 Wrapper。
本站所有代码示例都在 Gymnasium 1.4.0、Python 3.13、PyTorch 2.x(CPU)下实际运行过。
安装与环境检查
pip 安装
bash
pip install gymnasium # 核心库 + 经典控制、Toy Text 环境
pip install "gymnasium[box2d]" # LunarLander、BipedalWalker 等(需要 swig)
pip install "gymnasium[mujoco]" # MuJoCo 连续控制环境
pip install "gymnasium[atari]" # Atari 游戏(ale-py)1
2
3
4
2
3
4
本站入门教程只需要第一行。验证安装:
python
import gymnasium as gym
env = gym.make("CartPole-v1")
print(env.observation_space, env.action_space)1
2
3
2
3
常见安装问题
box2d安装失败:先安装swig(pip install swig或系统包管理器),再装gymnasium[box2d]。- 从旧版 Gym 迁移:
import gym改为import gymnasium as gym;step()现在返回 5 个值而不是 4 个;reset()返回(obs, info);随机种子通过env.reset(seed=...)设置。 - 环境版本过时:例如
CliffWalking-v0在新版本中已弃用,按提示改用CliffWalking-v1。
核心 API
reset 与 step
python
obs, info = env.reset(seed=42) # 开始新回合
obs, reward, terminated, truncated, info = env.step(action) # 执行一步
env.close() # 释放资源1
2
3
2
3
这正是 智能体与环境 中交互循环的代码表达:step(action) 返回下一观测
observation_space 与 action_space
每个环境都声明了观测空间和动作空间:
| 环境 | 观测空间 | 动作空间 | 回合步数上限 |
|---|---|---|---|
FrozenLake-v1 | Discrete(16):格子编号 | Discrete(4):左、下、右、上 | 100 |
CliffWalking-v1 | Discrete(48) | Discrete(4):上、右、下、左 | 无 |
CartPole-v1 | Box(4,):小车位置、速度、杆角度、角速度 | Discrete(2):向左推、向右推 | 500 |
Pendulum-v1 | Box(3,): | Box(1,):力矩 | 200 |
常用操作:env.action_space.sample() 随机采样动作;env.observation_space.shape 获取维度;env.action_space.n 获取离散动作数。
terminated 与 truncated
这是新版 API 最重要的改动:
terminated=True:到达 MDP 的真正终止状态(杆倒了、掉进冰洞、到达终点),此后没有未来奖励,TD 目标中下一状态价值取 0;truncated=True:因外部原因被截断(最常见的是TimeLimit步数上限),下一状态并非终止状态,TD 目标中仍应自举下一状态的价值。
两者任一为真都要调用 reset() 开始新回合,但计算目标值时必须区分。本站 DQN、PPO 等代码都按此处理。
FrozenLake:表格型 Q-learning
FrozenLake-v1(4×4,is_slippery=True)中,智能体要从左上角走到右下角,避开冰洞;地面打滑,执行的动作只有 1/3 概率按预期方向移动。这里用 Q-learning 训练 2 万回合,ε 从 1.0 指数衰减到 0.05:
py
"""Gymnasium 上手:FrozenLake 表格型 Q-learning,以及 CartPole 随机策略基线。"""
import gymnasium as gym
import numpy as np
# ---------- 1. FrozenLake:表格型 Q-learning ----------
env = gym.make("FrozenLake-v1", is_slippery=True)
nS, nA = env.observation_space.n, env.action_space.n
Q = np.zeros((nS, nA))
alpha, gamma = 0.1, 0.99
eps, eps_min, eps_decay = 1.0, 0.05, 0.9995
rng = np.random.default_rng(0)
for ep in range(20_000):
s, _ = env.reset(seed=ep)
done = False
while not done:
a = int(rng.integers(nA)) if rng.random() < eps else int(np.argmax(Q[s]))
s2, r, terminated, truncated, _ = env.step(a)
target = r + gamma * np.max(Q[s2]) * (not terminated)
Q[s, a] += alpha * (target - Q[s, a])
s, done = s2, terminated or truncated
eps = max(eps_min, eps * eps_decay)
wins = 0
for ep in range(1000):
s, _ = env.reset(seed=100_000 + ep)
done = False
while not done:
s, r, terminated, truncated, _ = env.step(int(np.argmax(Q[s])))
done = terminated or truncated
wins += r
print(f"FrozenLake 贪心策略成功率(1000 回合): {wins / 1000:.3f}")
# ---------- 2. CartPole:随机策略基线 ----------
env = gym.make("CartPole-v1")
env.action_space.seed(0) # action_space 有独立的随机数生成器,需单独设种子
returns = []
for ep in range(100):
obs, _ = env.reset(seed=ep)
G, done = 0.0, False
while not done:
obs, r, terminated, truncated, _ = env.step(env.action_space.sample())
G += r
done = terminated or truncated
returns.append(G)
print(f"CartPole 随机策略:平均回报 {np.mean(returns):.1f} ± {np.std(returns):.1f}(100 回合)")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
实际运行输出(约 5 秒):
text
FrozenLake 贪心策略成功率(1000 回合): 0.728
CartPole 随机策略:平均回报 23.7 ± 12.2(100 回合)1
2
2
作为对照,动态规划 用完整环境模型求出的最优策略实测成功率约为 0.737。Q-learning 完全不知道环境模型,只靠试错就达到了接近最优的水平。
CartPole:随机策略与回报统计
CartPole-v1 中,小车上竖着一根杆,每一步向左或向右推小车,只要杆不倒(角度不超过约 12°、小车不出界)每步就得 +1,最多 500 步。上面的输出显示随机策略平均只能坚持约 24 步。这是一个很好的基线:之后训练的算法至少要显著超过它,满分是 500。
CartPole 的观测是连续向量,表格方法无法直接使用(除非离散化),这正是 DQN、REINFORCE、PPO 等使用神经网络的算法登场的地方——本站进阶教程中它们都在 CartPole 上达到了满分 500。
渲染与录制视频
python
# 实时窗口渲染(需要图形界面,需额外安装 pygame)
env = gym.make("CartPole-v1", render_mode="human")
# 获取 RGB 图像数组,便于自己保存或显示
env = gym.make("CartPole-v1", render_mode="rgb_array")
frame = env.render() # shape (400, 600, 3)
# 录制视频(需要 moviepy)
env = gym.wrappers.RecordVideo(gym.make("CartPole-v1", render_mode="rgb_array"),
video_folder="videos", episode_trigger=lambda ep: ep % 100 == 0)1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
在服务器或 Colab 等无显示器环境中,请使用 rgb_array 模式,不要使用 human。
常用 Wrapper
Wrapper 可以在不修改环境源码的情况下改变观测、动作或奖励:
| Wrapper | 作用 |
|---|---|
TimeLimit | 限制回合最大步数(gym.make 默认已按环境注册信息添加) |
RecordEpisodeStatistics | 在回合结束时把回报、长度写入 info["episode"] |
NormalizeObservation / NormalizeReward | 观测、奖励的在线标准化(连续控制中常用) |
TransformReward / ClipReward | 变换、裁剪奖励 |
RescaleAction / ClipAction | 缩放、裁剪连续动作 |
FrameStackObservation | 堆叠最近 k 帧观测 |
AtariPreprocessing | Atari 标准预处理(灰度、缩放、跳帧等),见 DQN |
并行环境(向量化环境)可以同时运行多个环境副本,显著加快同策略算法的采样:
python
envs = gym.vector.SyncVectorEnv([lambda: gym.make("CartPole-v1") for _ in range(8)])
obs, info = envs.reset(seed=0) # obs.shape == (8, 4)1
2
2
自定义环境
把自己的问题封装成 Gymnasium 环境,就能直接使用现成的算法库。只需继承 gym.Env,声明两个空间,实现 reset 和 step:
py
"""自定义 Gymnasium 环境:一维走廊。智能体从位置 0 出发,走到位置 N-1 得到奖励 1。"""
import gymnasium as gym
import numpy as np
from gymnasium import spaces
from gymnasium.utils.env_checker import check_env
class Corridor(gym.Env):
metadata = {"render_modes": ["ansi"], "render_fps": 4}
def __init__(self, length=8, render_mode=None):
self.length = length
self.observation_space = spaces.Discrete(length)
self.action_space = spaces.Discrete(2) # 0 = 向左,1 = 向右
self.render_mode = render_mode
def reset(self, *, seed=None, options=None):
super().reset(seed=seed) # 初始化 self.np_random
self.pos = 0
return self.pos, {}
def step(self, action):
self.pos = int(np.clip(self.pos + (1 if action == 1 else -1), 0, self.length - 1))
terminated = self.pos == self.length - 1
reward = 1.0 if terminated else 0.0
return self.pos, reward, terminated, False, {}
def render(self):
return "".join("A" if i == self.pos else "." for i in range(self.length))
check_env(Corridor()) # 检查是否符合 Gymnasium API 规范
gym.register(id="Corridor-v0", entry_point=Corridor, max_episode_steps=50)
env = gym.make("Corridor-v0", render_mode="ansi")
# 常用 Wrapper:记录回合统计 + 奖励变换
env = gym.wrappers.RecordEpisodeStatistics(env)
env = gym.wrappers.TransformReward(env, lambda r: 10 * r)
obs, info = env.reset(seed=0)
done = False
while not done:
obs, r, terminated, truncated, info = env.step(1) # 一直向右
done = terminated or truncated
print(env.render())
print("回合统计:", {k: float(v) for k, v in info["episode"].items()})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
实际运行输出:
text
.......A
回合统计: {'r': 1.0, 'l': 7.0, 't': 7.6e-05}1
2
2
注意 Wrapper 的嵌套顺序:RecordEpisodeStatistics 在 TransformReward 内层,所以记录的是变换之前的原始回报 1.0。check_env 会检查空间声明、返回值类型等是否符合规范(运行时会提示该环境没有通过 gym.make 创建、无法测试其他渲染模式,可以忽略)。
常见坑
- 忘记设置随机种子:调用
env.reset(seed=...)才能让实验可复现;action_space.sample()的种子需要单独用env.action_space.seed(...)设置。 - 把 truncated 当作 terminated:会让智能体误以为“时间到了”是一种失败,导致价值估计偏低。
- 旧教程的 4 返回值写法:
obs, r, done, info = env.step(a)在新版中会报错。 - 向量化环境的自动重置:回合结束时会自动 reset,返回的观测是新回合的第一个观测,真正的最终观测在
info中(具体行为由autoreset_mode决定,本站 A2C 与 PPO 代码使用SAME_STEP模式并从info["final_obs"]读取)。
小结
- Gymnasium 用
reset()/step()统一了环境接口,step返回obs, reward, terminated, truncated, info。 - 正确区分
terminated和truncated是写对强化学习代码的关键。 - 表格型 Q-learning 在 FrozenLake 上达到了接近 DP 最优解的成功率;CartPole 的随机基线约 24 分,满分 500。
- Wrapper 和向量化环境是实际项目中的常用工具。
练习题
- 把 FrozenLake 换成
map_name="8x8",需要多少回合 Q-learning 才能达到 50% 以上的成功率? - 用
gym.wrappers.DiscretizeObservation或自己写的分箱函数把 CartPole 观测离散化,再用表格型 Q-learning 训练,能得到多少分? - 给自定义的走廊环境加上“每步 −0.01”的奖励,用 Q-learning 训练并观察学到的策略。
- 用
RecordVideo录制一段随机策略玩 CartPole 的视频。 - 查阅 Gymnasium 文档,列出
Pendulum-v1的奖励函数公式。
参考文献
- Towers, M. et al. (2024). Gymnasium: A Standard Interface for Reinforcement Learning Environments. arXiv:2407.17032
- Brockman, G. et al. (2016). OpenAI Gym. arXiv:1606.01540
- Barto, A. G., Sutton, R. S., & Anderson, C. W. (1983). Neuronlike adaptive elements that can solve difficult learning control problems. IEEE Transactions on Systems, Man, and Cybernetics, SMC-13(5), 834–846.(CartPole 问题的经典来源)
下一步
入门教程到此结束!继续阅读:进阶教程:DQN——用神经网络把 Q-learning 扩展到 CartPole 这样的连续状态空间。