当你开始学习强化学习时,最先接触的往往是Gymnasium内置的那些经典环境,比如CartPole(平衡杆)、MountainCar(爬山车)这些。但很快你就会发现,现实中的问题远比这些标准环境复杂得多。比如你想训练一个机器人做咖啡,或者开发一个自动调节室内温度的智能系统,这些都需要完全自定义的环境。
我刚开始接触强化学习时也犯过一个错误:总想着用现成环境去套自己的问题。结果要么是环境太简单无法模拟真实场景,要么是强行修改标准环境导致代码一团糟。后来才明白,从零构建专属环境才是王道。Gymnasium之所以设计成可扩展的架构,就是为了让我们能自由创建各种场景。
自定义环境的优势很明显:
提示:即使你只是强化学习新手,也建议尽早尝试构建自定义环境。这比直接调用现成算法更能加深对强化学习本质的理解。
工欲善其事必先利其器,我们先来配置开发环境。打开你的终端(Windows用户可以用PowerShell或CMD),执行以下命令:
bash复制pip install gymnasium numpy
这里有个小细节要注意:有些教程可能还会让你安装pygame之类的额外依赖。其实Gymnasium的核心功能只需要numpy就够了,其他可视化库可以等需要时再装。我遇到过有人一次性装完所有可选依赖,结果各种版本冲突搞得头大。
Gymnasium环境的骨架由四个关键方法构成:
__init__:环境的初始化,相当于"装修毛坯房"reset:重置环境到初始状态,就像游戏里的"重新开始"step:执行动作并返回结果,这是与环境交互的核心render(可选):可视化环境状态,相当于"开直播看效果"用个生活中的例子来说:想象你在开发一个自动煮咖啡的机器人。__init__就是准备咖啡机和原料,reset是清理机器换新咖啡豆,step是按下开关煮咖啡,render则是用摄像头让你看到煮的过程。
让我们从最简单的案例开始:创建一个"温度调节器"环境。这个环境的目标是通过调节空调功率,使室内温度保持在舒适区间。
新建一个Python文件thermostat_env.py,写入以下骨架代码:
python复制import gymnasium as gym
from gymnasium import spaces
import numpy as np
class ThermostatEnv(gym.Env):
def __init__(self):
super().__init__()
# 定义动作空间:空调功率调节(-1到1之间连续值)
self.action_space = spaces.Box(low=-1, high=1, shape=(1,))
# 定义状态空间:[当前温度, 目标温度]
self.observation_space = spaces.Box(
low=np.array([-10, 18]),
high=np.array([40, 30]),
dtype=np.float32
)
def reset(self, seed=None):
# 初始化环境状态
pass
def step(self, action):
# 执行动作并返回结果
pass
def render(self):
# 可选的可视化方法
pass
这里有几个关键点需要注意:
action_space定义了智能体可以执行的动作范围observation_space限定了环境状态的取值范围spaces.Box表示连续空间,如果是离散选择可以用spaces.Discrete现在我们来填充具体逻辑。修改reset和step方法如下:
python复制def reset(self, seed=None):
super().reset(seed=seed)
# 随机初始化当前温度(15-25度之间)
self.current_temp = self.np_random.uniform(15, 25)
# 固定目标温度为22度
self.target_temp = 22
return np.array([self.current_temp, self.target_temp]), {}
def step(self, action):
# 执行动作(action是-1到1之间的值)
power_change = action[0] * 0.5 # 缩放动作幅度
self.current_temp += power_change
# 加入随机扰动模拟真实环境
self.current_temp += self.np_random.normal(0, 0.1)
# 计算奖励:越接近目标温度奖励越高
temp_diff = abs(self.current_temp - self.target_temp)
reward = -temp_diff # 负值表示惩罚
# 判断是否终止(这里设永远不终止)
terminated = False
truncated = False
# 返回:状态、奖励、终止标志、截断标志、额外信息
return (
np.array([self.current_temp, self.target_temp]),
reward,
terminated,
truncated,
{}
)
这个实现中有几个实用技巧:
self.np_random而不是直接调用np.random,这是Gymnasium推荐的做法*0.5),避免温度变化太剧烈如果你想让环境状态可视化,可以完善render方法:
python复制def render(self):
print(f"当前温度: {self.current_temp:.1f}℃ | 目标温度: {self.target_temp}℃ | 温差: {abs(self.current_temp - self.target_temp):.1f}℃")
虽然这个实现很简单,但在调试阶段非常有用。我建议至少在开发初期实现基础的可视化,这能帮你快速发现环境逻辑中的问题。
要让你的环境能像标准环境一样被调用,需要完成注册流程。创建以下目录结构:
code复制gym_thermostat/
├── __init__.py
├── envs/
│ ├── __init__.py
│ └── thermostat_env.py
└── setup.py
在gym_thermostat/envs/__init__.py中添加:
python复制from gym_thermostat.envs.thermostat_env import ThermostatEnv
在项目根目录的__init__.py中添加:
python复制from gymnasium.envs.registration import register
register(
id="Thermostat-v0",
entry_point="gym_thermostat.envs:ThermostatEnv",
)
最后创建setup.py:
python复制from setuptools import setup
setup(
name="gym_thermostat",
version="0.0.1",
install_requires=["gymnasium", "numpy"],
)
在项目根目录运行:
bash复制pip install -e .
然后就可以像使用标准环境一样测试你的环境了:
python复制import gymnasium as gym
env = gym.make("Thermostat-v0")
obs, _ = env.reset()
for _ in range(100):
action = env.action_space.sample() # 随机动作
obs, reward, terminated, truncated, info = env.step(action)
env.render()
if terminated or truncated:
obs, _ = env.reset()
env.close()
在实际开发中,我遇到过不少坑,这里分享几个典型问题的解决方案:
问题1:gymnasium.error.UnregisteredEnv: No registered env with id: Thermostat-v0
setup.py是否正确定义pip install -e .__init__.py中的注册代码是否正确问题2:动作/状态空间不匹配
step方法返回的observation在observation_space定义的范围内action参数是否符合action_space的定义问题3:环境表现不符合预期
step方法中添加print语句调试self.np_random记住,环境开发是个迭代过程。我的第一个自定义环境前后修改了十几次才稳定。关键是要保持耐心,从简单版本开始逐步完善。