Frank_LBM/scripts/251006_FYP.py
2026-02-15 19:21:28 +08:00

190 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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=== 测试结束 ===")