import gymnasium as gym import numpy as np from stable_baselines3 import PPO from stable_baselines3.common.vec_env import DummyVecEnv # 使用DummyVecEnv避免多进程问题 from stable_baselines3.common.env_util import make_vec_env from typing import Callable, Any from typing import Any, Literal import numpy as np import pybullet as p from gymnasium import spaces from PyFlyt.core.aviary import Aviary from PyFlyt.core.utils.compile_helpers import check_numpy from stable_baselines3.common.vec_env import SubprocVecEnv, DummyVecEnv import gymnasium import PyFlyt.gym_envs import numpy as np from stable_baselines3 import PPO from stable_baselines3.common.evaluation import evaluate_policy # -------------------------- # 核心:综合Wrapper(解决不动+调高度+自定义奖励) # -------------------------- class QuadXPoleFullWrapper(gymnasium.Wrapper): def __init__( self, env, hover_bias=0.2, # 基础悬停PWM(解决不动:必须>0.5才够升力) action_scale=0.2, # 动作微调范围(控制电机微调幅度,避免过大/过小) target_height=2.5, # 目标悬停高度(调高默认高度,可改3.0/4.0) reward_scaling=0.1 # 奖励缩放(避免奖励值过大导致训练不稳定) ): super().__init__(env) self.hover_bias = hover_bias # 基础悬停推力(确保无人机能起飞) self.action_scale = action_scale# 动作微调范围([-scale, +scale]) self.target_height = target_height # 目标高度 self.reward_scaling = reward_scaling# 奖励缩放系数 def reset(self, **kwargs): """重置时将无人机初始高度设为目标高度""" obs, info = self.env.reset(** kwargs) # 修改无人机初始z轴位置(PyFlyt无人机状态的第3个元素是高度) if hasattr(self.env.unwrapped, "drone"): self.env.unwrapped.drone.state[2] = self.target_height # z轴=目标高度 return obs, info def step(self, action): """1. 处理动作(确保有足够升力);2. 自定义奖励;3. 返回新状态""" # 1. 动作映射:模型输出[-1,1] → 实际PWM[hover_bias-scale, hover_bias+scale] # 保证电机有基础悬停推力,解决“不动”问题 action = action * self.action_scale + self.hover_bias # 限制动作在[0,1](避免PWM超出物理范围导致报错) action = np.clip(action, 0.0, 1.0) # 2. 执行动作,获取原始环境反馈 obs, _, term, trunc, info = self.env.step(action) # 3. 解析观测值(按PyFlyt QuadX-Pole-Balance-v3观测空间定义) pos = obs[:3] # 无人机位置 (x, y, z) orn = obs[3:7] # 无人机姿态(四元数 x, y, z, w) pole_angle = obs[10] # 杆倾斜角度(核心平衡指标,索引10为主要倾斜角) # (可选)如果需要更精准,可查看官方文档:观测空间包含杆的多个角度,取影响最大的一个 # 4. 自定义奖励计算(多维度鼓励稳定) # ① 高度奖励:越接近目标高度,奖励越高(惩罚高度误差) height_error = pos[2] - self.target_height height_reward = -1.5 * (height_error ** 2) # 权重1.5,误差越小奖励越高 # ② 姿态奖励:无人机越水平,奖励越高(惩罚姿态偏移) # 四元数x/y/z越小,姿态越接近水平(w为实部,代表水平状态) orientation_reward = -0.8 * np.sum(orn[:3] ** 2) # 权重0.8 # ③ 杆平衡奖励:杆越竖直,奖励越高(惩罚杆倾斜) pole_reward = -2.0 * (pole_angle ** 2) # 权重2.0,杆平衡是核心任务,权重更高 # ④ 动作平滑奖励:避免电机大幅调整(惩罚过大动作) action_penalty = -0.1 * np.sum(action ** 2) # 权重0.1,抑制动作波动 # ⑤ 存活奖励:每步给固定奖励,鼓励持续存活(核心目标是“尽可能久”) alive_bonus = 1.2 # 每步+1.2,存活越久总奖励越高 # 总奖励:加权求和 + 缩放 total_reward = ( height_reward + orientation_reward + pole_reward + action_penalty + alive_bonus ) * self.reward_scaling # 5. 返回处理后的结果 return obs, total_reward, term, trunc, info # -------------------------- # 1. 创建并包装环境 # -------------------------- # 原始环境配置(按官方文档,render_mode="human"实时显示) # -------------------------- env_id = "PyFlyt/QuadX-Pole-Balance-v4" # 用 make_vec_env 创建多个环境(n_envs 是并行环境数量) env = make_vec_env( env_id, n_envs=4, # 4个环境同时运行(可根据CPU核心数调整) wrapper_class=QuadXPoleFullWrapper, # 我们的自定义包装器 env_kwargs={ "render_mode": None, # 多环境训练时先不渲染,加快速度 "max_duration_seconds": 30.0, "flight_dome_size": 5.0, "angle_representation": "quaternion" }, wrapper_kwargs={ "hover_bias": 0.2, "action_scale": 0.3, "target_height": 2.5, "reward_scaling": 0.1 } ) # 查看环境空间(确认配置正确) print("动作空间(4个电机PWM):", env.action_space) print("观测空间(无人机+杆状态):", env.observation_space) # -------------------------- # 2. 定义PPO模型(适合连续动作,收敛快) # -------------------------- model = PPO( policy="MlpPolicy", # 多层感知器(处理连续动作) env=env, verbose=1, # 训练时打印详细信息(loss、reward等) tensorboard_log="./quadx_log/", # 日志保存路径(可在TensorBoard查看训练曲线) learning_rate=3e-4, # 学习率(连续动作任务常用3e-4) n_steps=2048, # PPO每批收集2048步数据 batch_size=64, # 每批数据分64个batch训练(2048÷64=32,整除) n_epochs=10, # 每批数据训练10轮 gamma=0.99, # 折扣因子(重视长期奖励) gae_lambda=0.95, # GAE参数(平衡偏差和方差) clip_range=0.2, # PPO裁剪范围(经典值0.2) ent_coef=0.01, # 熵系数(鼓励探索,避免过早收敛到局部最优) device="auto" # 自动使用GPU/CPU(有GPU会自动调用) ) # -------------------------- # 3. 训练模型 # -------------------------- print("\n=== 开始训练 ===") model.learn( total_timesteps=300000, # 总训练步数(30万步,该任务较复杂,需足够步数) log_interval=10, # 每10个批次打印一次训练信息 progress_bar=True # 显示训练进度条 ) # 保存训练好的模型(后续可直接加载,不用重新训练) model.save("quadx_pole_balance_trained_model") print("\n=== 模型已保存为:quadx_pole_balance_trained_model ===") # -------------------------- # 4. 评估训练效果 # -------------------------- print("\n=== 开始评估(5局平均奖励) ===") mean_reward, std_reward = evaluate_policy( model=model, env=env, n_eval_episodes=5, # 评估5局 render=True, # 评估时实时显示 deterministic=True # 用确定性策略(避免随机动作,体现真实训练效果) ) print(f"评估结果:平均奖励 = {mean_reward:.2f} ± {std_reward:.2f}") # (说明:平均奖励越高、标准差越小,模型越稳定;若平均存活时间接近30秒,说明训练成功) # -------------------------- # 5. 手动测试(可视化训练成果) # -------------------------- print("\n=== 开始手动测试(持续1000步) ===") obs, _ = env.reset() # 重置环境 for step in range(1000): # 模型预测动作(确定性策略) action, _ = model.predict(obs, deterministic=True) # 执行动作 obs, reward, term, trunc, info = env.step(action) # 若终止(坠毁/杆落地/超时),重置环境继续测试 if term or trunc: print(f"第{step+1}步终止,重置环境...") obs, _ = env.reset() # 关闭环境(释放资源) env.close() print("\n=== 测试结束 ===")