1. 从零开始为什么选择这两个环境入门强化学习如果你刚开始接触强化学习面对Gym库里几十个环境可能会有点懵。我当年也一样不知道该从哪个下手。折腾了一圈之后我发现CartPole-v1和Pendulum-v1这两个环境简直是给新手量身定做的“黄金搭档”。它们一个简单直观一个稍微复杂但原理清晰组合起来能帮你把强化学习的核心概念吃得透透的。先说说CartPole-v1也就是我们常说的“倒立摆”或者“小车立杆”。这个游戏的目标特别简单控制一个小车在轨道上左右移动让车上的一根杆子保持直立不倒。你只需要做一件事决定是向左推还是向右推。听起来是不是跟小时候玩的平衡木游戏有点像它的状态空间只有4个维度动作也只有2个选择左或右非常适合用来理解最基础的状态State、动作Action和奖励Reward这套交互逻辑。我刚开始学的时候就是用它来验证我的第一个Q-learning算法有没有写对效果立竿见影。而Pendulum-v1也就是“钟摆”环境则把我们带入了连续控制的世界。在这个环境里你要控制的是一个悬挂着的钟摆目标是通过施加扭矩让它从自然下垂的状态摆动并稳定在竖直向上的位置。这里的关键变化是你的动作不再是简单的“左”或“右”而是一个在**[-2, 2]** 之间连续变化的扭矩值。这意味着你不能再用那种查表式的离散方法了必须开始思考如何用函数去近似一个连续的策略。从CartPole到Pendulum就像是从学骑带辅助轮的自行车过渡到骑真正的自行车是理解现代深度强化学习比如DDPG、PPO这些算法的绝佳跳板。我强烈建议新手把这两个环境作为你的第一个“训练场”。它们代码量小物理模型清晰反馈及时你能在几分钟内就看到自己算法是成功还是失败。更重要的是通过对比学习你能深刻体会到离散动作空间和连续动作空间问题的本质区别这是后续攻克更复杂环境比如机器人控制、自动驾驶的基石。接下来我们就深入这两个环境的内部看看它们到底是怎么运作的。2. 庖丁解牛彻底搞懂CartPole-v1的环境设计让我们先把这个看似简单的游戏拆开来看。当你用gym.make(CartPole-v1)创建一个环境时背后到底发生了什么我最初以为就是一个简单的物理模拟但真正去读源码对有时候官方文档说得不清不楚就得去GitHub翻源码才发现里面的设计充满了巧思。2.1 状态空间四个数字描述整个世界CartPole的状态空间是一个包含4个浮点数的数组[cart_position, cart_velocity, pole_angle, pole_angular_velocity]。这4个值就是智能体Agent感知世界的全部信息。小车位置 (cart_position)小车中心相对于轨道中心的水平距离单位是米。范围是[-2.4, 2.4]。一旦超出这个范围游戏就结束了。你可以把它想象成平衡木的边界。小车速度 (cart_velocity)小车水平移动的速度单位是米/秒。这个值可正可负代表了运动方向。杆子角度 (pole_angle)杆子与竖直向上方向之间的夹角单位是弧度。这里有个细节在v1版本中游戏结束的阈值是角度超过±12度约±0.209弧度。也就是说杆子稍微歪一点没问题但歪得太厉害就失败了。杆子角速度 (pole_angular_velocity)杆子倾斜角度的变化率单位是弧度/秒。它告诉你杆子正在以多快的速度倒下去。当你调用env.reset()时环境会随机初始化这4个值通常在某个小范围内这意味着每次游戏开始时的状态都略有不同避免了智能体死记硬背某一种初始情况。我一开始写算法时曾试图把状态空间离散化成一个个格子这是经典Q-learning的做法但后来发现对于这种连续状态直接用神经网络来近似Q函数或策略函数效果要好得多。2.2 动作与奖励简单背后的设计哲学动作空间简单到令人发指0表示向左施加一个单位的力1表示向右施加一个单位的力。这个力的大小是固定的。但奖励机制才是CartPole设计的精髓所在在游戏结束前的每一步你都会获得1的奖励。这设计太妙了它直接把我们的目标转化成了最大化累积奖励而累积奖励就等于杆子保持不倒的步数。你的目标非常清晰尽可能活得更久。但这里有个新手常踩的“坑”即使你这一步动作直接导致了游戏结束比如杆子倒了这一步的奖励依然是1。这听起来有点反直觉对吧失败的一步居然还有奖励。所以在你自己的算法里通常需要做一个判断如果doneTrue游戏结束就把这一步的奖励设为一个负值比如-10或-100以此来惩罚导致失败的动作。这是我调试算法时学到的第一课。2.3 游戏结束条件与步数限制游戏在三种情况下会结束杆子角度超过 ±12度。小车位置超出[-2.4, 2.4]米的轨道范围。步数达到500步v1版本。这500步的上限是个关键。它意味着这个环境的最高得分就是500分。当你的算法能稳定达到500分时就说明它已经“学会”了这个游戏。这个设计防止了智能体找到一个“永动机”式的无聊策略虽然在这个简单物理模型里很难也给了我们一个明确的训练目标。我训练的第一个DQN智能体从平均几十步到稳定在500步看着学习曲线上升的那个过程成就感真的爆棚。3. 进阶挑战深入Pendulum-v1的连续控制世界搞定了CartPole我们就可以挑战更“真实”的Pendulum-v1了。说它真实是因为它的动作是连续的更贴近我们实际控制电机、机器人的场景。但它的状态和奖励函数设计也相对更复杂、更精巧。3.1 状态空间为什么用cos和sin表示角度Pendulum的状态空间是3维的[cos(theta), sin(theta), thetadot]。这里theta是钟摆与竖直向上方向的夹角0度代表竖直向上thetadot是角速度。你可能会问为什么不像CartPole那样直接用角度theta而要拆成cos和sin两个值这是一个非常漂亮的工程技巧。因为角度本身具有周期性从359度到0度或者说从-π到π是一个跳跃。如果你直接把角度值喂给神经网络这个跳跃点会给学习带来不必要的麻烦。而用(cosθ, sinθ)这对值就能在二维平面上平滑、唯一地表示任何一个角度完美避免了周期性问题。我第一次看到这个设计时忍不住拍案叫绝这就是那种将数学洞察力应用于工程实践的典范。3.2 动作空间连续扭矩的威力动作空间是一个标量值范围在[-2.0, 2.0]之间代表施加在钟摆铰链上的扭矩。正值通常表示试图逆时针转动具体取决于环境定义负值则表示顺时针。这里的“连续”是真正的连续你可以输出-1.357这样的任意值。这立刻排除了DQN这类离散动作算法你必须使用像DDPG、TD3、PPO或SAC这类能处理连续动作的算法。3.3 奖励函数一个多目标的平衡艺术Pendulum的奖励函数是理解其设计思想的核心reward -(θ² 0.1 * θ̇² 0.001 * u²)这个函数始终返回负值所以我们的目标是最大化奖励也就是让这个负数的绝对值尽可能小。我们可以把它拆成三部分来看θ²角度平方项这是最主要的项。目标是将钟摆保持在竖直向上θ0任何角度的偏离都会带来平方级的惩罚。平方意味着大的偏离会受到非常严厉的惩罚。0.1 * θ̇²角速度平方项这一项鼓励稳定性。即使角度接近0但如果钟摆摆动得很剧烈角速度大也会被惩罚。系数0.1意味着对稳定性的要求次于对角度本身的要求。0.001 * u²动作平方项这一项鼓励节能。u是你施加的扭矩。扭矩越大消耗的能量越多惩罚也越大。系数0.001非常小说明节能是一个相对次要的目标但在性能相近的策略中更节能的那个会胜出。这个奖励函数清晰地告诉我们Pendulum-v1的终极目标是一个多目标优化首要任务是立住其次是稳定地立住最后是用最小的能量代价实现前两者。这种设计比CartPole那种简单的“存活奖励”要高级得多也更能训练出平滑、高效的控制策略。4. 实战演练手把手实现你的第一个智能体理论说得再多不如亲手跑一遍代码。下面我就用最流行的稳定基线3Stable-Baselines3库带你分别用PPO算法训练Pendulum用DQN算法训练CartPole。为什么选SB3因为它封装得好代码简洁让我们能聚焦于算法和环境的理解而不是纠结于实现细节。4.1 用PPO算法征服Pendulum-v1PPO近端策略优化是目前最流行的强化学习算法之一它在连续控制问题上表现非常稳健。我们先安装必要的库pip install gym0.26.2 # 注意版本兼容性 pip install stable-baselines3[extra] pip install pyglet # 用于渲染可视化然后让我们用PPO来训练一个控制钟摆的智能体import gym from stable_baselines3 import PPO from stable_baselines3.common.env_util import make_vec_env from stable_baselines3.common.evaluation import evaluate_policy import time # 1. 创建并行化环境可以加速训练 env make_vec_env(Pendulum-v1, n_envs4) # 2. 实例化PPO模型 # MlpPolicy是指用多层感知机作为策略网络 # verbose1 让我们能看到训练日志 model PPO(MlpPolicy, env, verbose1, learning_rate3e-4, n_steps2048, # 每次更新前收集的步数 batch_size64, n_epochs10, # 每次更新时对数据集的利用次数 gamma0.99, # 折扣因子看重未来奖励 gae_lambda0.95, # 广义优势估计参数 clip_range0.2, # PPO特有的裁剪参数保证更新稳定 ent_coef0.0, # 熵系数鼓励探索这里设为0 tensorboard_log./ppo_pendulum_tensorboard/) # 3. 开始训练我们先训练10万步看看效果 print(开始训练PPO智能体...) start_time time.time() model.learn(total_timesteps100000) end_time time.time() print(f训练完成耗时 {end_time - start_time:.2f} 秒) # 4. 保存模型方便以后调用 model.save(ppo_pendulum_v1) # 5. 评估训练好的模型 print(\n评估模型性能...) mean_reward, std_reward evaluate_policy(model, env, n_eval_episodes10) print(f平均奖励: {mean_reward:.2f} /- {std_reward:.2f}) # 6. 可视化一下智能体的表现 obs env.reset() for i in range(200): action, _states model.predict(obs, deterministicTrue) # 使用确定性策略 obs, rewards, dones, info env.step(action) env.render(modehuman) # 弹出窗口展示 time.sleep(0.05) # 放慢速度便于观察 if dones.any(): break env.close()运行这段代码你会看到控制台开始输出训练日志。PPO算法会周期性地打印出“ep_rew_mean”平均回合奖励等指标。对于Pendulum-v1因为奖励是负值所以这个数会从-1500左右开始随机策略很差逐渐上升到-200甚至更高越好越接近0。训练完成后evaluate_policy函数会测试10个回合给出平均得分。最后那段可视化代码会弹出一个窗口让你亲眼看到智能体是如何控制钟摆竖立起来的。我第一次看到自己训练的智能体成功稳住钟摆时感觉就像魔法一样。4.2 用DQN算法攻克CartPole-v1对于离散动作的CartPole我们可以用更经典的DQN深度Q网络算法。DQN的核心思想是用一个神经网络来近似Q函数状态-动作价值函数。import gym from stable_baselines3 import DQN from stable_baselines3.common.evaluation import evaluate_policy import time # 1. 创建环境 env gym.make(CartPole-v1) # 2. 实例化DQN模型 # 注意我们使用‘MlpPolicy’因为状态是向量不是图像 model DQN(MlpPolicy, env, verbose1, learning_rate1e-3, buffer_size50000, # 经验回放缓冲区大小 learning_starts1000, # 先收集一些经验再开始学习 batch_size32, # 从缓冲区采样的小批量大小 tau1.0, # 目标网络更新率1.0表示硬更新 gamma0.99, # 折扣因子 train_freq4, # 每4步训练一次 gradient_steps1, # 每次训练进行1次梯度下降 target_update_interval100, # 每100步更新一次目标网络 exploration_fraction0.1, # 前10%的训练步数用于探索 exploration_initial_eps1.0, # 初始探索率 exploration_final_eps0.02, # 最终探索率 tensorboard_log./dqn_cartpole_tensorboard/) # 3. 训练模型 print(开始训练DQN智能体...) start_time time.time() model.learn(total_timesteps50000) # CartPole比较简单5万步通常就够了 end_time time.time() print(f训练完成耗时 {end_time - start_time:.2f} 秒) # 4. 保存模型 model.save(dqn_cartpole_v1) # 5. 评估模型 print(\n评估模型性能...) mean_reward, std_reward evaluate_policy(model, env, n_eval_episodes10, deterministicTrue) print(f平均奖励步数: {mean_reward:.2f} /- {std_reward:.2f}) # 注意CartPole的奖励就是步数最高500 # 6. 观看它的表演 obs env.reset() total_reward 0 for i in range(500): action, _states model.predict(obs, deterministicTrue) obs, reward, done, info env.step(action) total_reward reward env.render() # 默认的渲染模式 time.sleep(0.05) if done: print(f游戏结束本次得分{total_reward}) break env.close()DQN的训练日志里你会关注“ep_rew_mean”和“loss”。对于CartPole-v1当平均奖励稳定在500左右时就说明你的智能体已经完美掌握了这个游戏。exploration_final_eps0.02意味着即使训练完成后智能体仍有2%的概率随机选择动作这有助于防止策略过于僵化。你可以尝试调整这些超参数比如learning_rate、batch_size观察它们对训练速度和最终性能的影响这是理解算法敏感性的好方法。5. 避坑指南与性能优化实战心得照着教程跑通代码只是第一步。在实际项目中你会遇到各种奇怪的问题。下面分享几个我踩过的坑和总结的优化技巧希望能帮你少走弯路。5.1 环境版本与渲染的“坑”第一个大坑就是版本兼容性。OpenAI Gym后来移交给了Farama基金会并发展出了Gymnasium。如果你安装的是最新版的gym比如0.26.0以上它的API和旧版有变化。最明显的就是env.step()的返回值从4个变成了5个(observation, reward, terminated, truncated, info)。如果你用旧代码跑新环境会直接报错。我建议新手明确指定版本pip install gym0.26.2这个版本比较稳定资料也多。另一个常见问题是渲染Render。在服务器或无图形界面的环境下直接调用env.render()可能会报错。这时候你有几个选择使用modergb_array然后自己处理图像或者干脆不渲染专注于训练。使用gymnasium的render_modehuman参数创建环境它有时兼容性更好。如果是远程服务器可以考虑用xvfb虚拟一个显示设备。5.2 超参数调优没有银弹只有实验强化学习的性能对超参数非常敏感。以PPO训练Pendulum为例学习率learning_rate通常从3e-4开始尝试这是很多论文使用的默认值。如果训练曲线震荡得厉害可以调小如1e-4如果学习太慢可以适当调大。折扣因子gamma取值在0.9到0.999之间。对于Pendulum这种没有明确“结束”而是持续控制的任务可以设得高一些如0.99让智能体更关注长期收益。广义优势估计gae_lambda通常在0.9到0.98之间。它平衡了偏差和方差0.95是一个不错的起点。裁剪范围clip_rangePPO的核心通常设为0.1到0.3。它限制了每次策略更新的幅度防止一次更新得太猛导致性能崩溃。我的经验是不要一次性调整所有参数。先固定其他参数系统地调整一两个观察训练曲线强烈建议使用TensorBoard。Stable-Baselines3在创建模型时指定了tensorboard_log路径训练后你可以在命令行用tensorboard --logdir ./ppo_pendulum_tensorboard/来启动可视化界面里面有很多有用的图表。5.3 奖励塑形与课程学习给智能体“开小灶”有时候环境自带的奖励函数可能不够“友好”导致智能体很难学习。这时候可以尝试奖励塑形Reward Shaping即设计一个中间奖励来引导智能体。例如在Pendulum中除了最终的目标我们还可以在钟摆接近竖直时给予额外的小奖励。但要注意不合理的奖励塑形可能会让智能体学到“刷分”的作弊行为而不是我们真正想要的目标。另一个高级技巧是课程学习Curriculum Learning。就像人学东西从易到难一样我们可以让智能体先在一个简单的环境中学习再逐步增加难度。对于CartPole我们可以先修改环境让杆子倒下的角度阈值从±30度开始等智能体学会后再逐步缩小到±12度。Gym环境可以通过包装器Wrapper很容易地实现这种修改。这些技巧在解决更复杂的真实世界问题时非常有用。从CartPole-v1到Pendulum-v1你走过的这条路正是无数强化学习从业者入门时的路径。这两个环境麻雀虽小五脏俱全涵盖了状态表示、离散/连续动作、奖励设计、算法选择DQN/PPO等等核心概念。当你能够熟练地在这两个环境上训练出稳定的智能体并理解其背后的每一个设计选择时你就已经拿到了打开强化学习大门的钥匙。接下来无论是挑战更复杂的机器人模拟环境如MuJoCo还是进军基于图像输入的Atari游戏你都有了坚实的理论和实践基础。记住强化学习是一个高度实验性的学科多动手写代码多观察训练过程多思考为什么你的进步会快得多。