From 03dee0205a9273a21016f15cf780cb870cd78ae6 Mon Sep 17 00:00:00 2001 From: huangfu <3045324663@qq.com> Date: Tue, 3 Feb 2026 16:24:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=A1=B9=E7=9B=AE=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Env/__pycache__/scenario_env.cpython-313.pyc | Bin 12915 -> 12636 bytes Env/__pycache__/scenario_env.cpython-39.pyc | Bin 6958 -> 7046 bytes Env/scenario_env.py | 38 +- README.md | 49 ++- dataset/loader.py | 103 +++++ dataset/magail_dataset.py | 61 --- ...vents.out.tfevents.1769858761.Hfkk.41584.0 | Bin 5884 -> 0 bytes ...vents.out.tfevents.1769859823.Hfkk.44173.0 | Bin 1532 -> 0 bytes ...vents.out.tfevents.1769860931.Hfkk.47961.0 | Bin 227 -> 0 bytes ...vents.out.tfevents.1769862091.Hfkk.51799.0 | Bin 88 -> 0 bytes ...vents.out.tfevents.1769862096.Hfkk.51875.0 | Bin 1581 -> 0 bytes ...vents.out.tfevents.1769862459.Hfkk.53251.0 | Bin 1532 -> 0 bytes ...vents.out.tfevents.1769862828.Hfkk.54652.0 | Bin 1532 -> 0 bytes ...vents.out.tfevents.1769863419.Hfkk.56732.0 | Bin 3370 -> 0 bytes ...vents.out.tfevents.1769864090.Hfkk.59264.0 | Bin 88 -> 0 bytes ...vents.out.tfevents.1769875401.Hfkk.64137.0 | Bin 15072 -> 0 bytes ...ents.out.tfevents.1769966347.Hfkk.245768.0 | Bin 0 -> 3080 bytes ...ents.out.tfevents.1769969460.Hfkk.256592.0 | Bin 0 -> 372 bytes ...ents.out.tfevents.1769969543.Hfkk.257044.0 | Bin 0 -> 152686 bytes scripts/README.md | 30 +- scripts/visualize.py | 395 ++++++++++++++++++ scripts/visualize_expert_trajectory.py | 105 ----- scripts/visualize_replay.py | 93 ----- scripts/visualize_trained_policy.py | 189 --------- train_bc.py | 44 +- train_magail.py | 2 +- visualize_bc.py | 17 - 27 files changed, 558 insertions(+), 568 deletions(-) create mode 100644 dataset/loader.py delete mode 100644 dataset/magail_dataset.py delete mode 100644 logs/20260131-192601/events.out.tfevents.1769858761.Hfkk.41584.0 delete mode 100644 logs/20260131-194343/events.out.tfevents.1769859823.Hfkk.44173.0 delete mode 100644 logs/20260131-200211/events.out.tfevents.1769860931.Hfkk.47961.0 delete mode 100644 logs/20260131-202131/events.out.tfevents.1769862091.Hfkk.51799.0 delete mode 100644 logs/20260131-202136/events.out.tfevents.1769862096.Hfkk.51875.0 delete mode 100644 logs/20260131-202739/events.out.tfevents.1769862459.Hfkk.53251.0 delete mode 100644 logs/20260131-203348/events.out.tfevents.1769862828.Hfkk.54652.0 delete mode 100644 logs/20260131-204339/events.out.tfevents.1769863419.Hfkk.56732.0 delete mode 100644 logs/20260131-205450/events.out.tfevents.1769864090.Hfkk.59264.0 delete mode 100644 logs/20260201-000321/events.out.tfevents.1769875401.Hfkk.64137.0 create mode 100644 logs/bc/20260202-011907/events.out.tfevents.1769966347.Hfkk.245768.0 create mode 100644 logs/bc/20260202-021100/events.out.tfevents.1769969460.Hfkk.256592.0 create mode 100644 logs/bc/20260202-021223/events.out.tfevents.1769969543.Hfkk.257044.0 create mode 100644 scripts/visualize.py delete mode 100644 scripts/visualize_expert_trajectory.py delete mode 100644 scripts/visualize_replay.py delete mode 100644 scripts/visualize_trained_policy.py delete mode 100644 visualize_bc.py diff --git a/Env/__pycache__/scenario_env.cpython-313.pyc b/Env/__pycache__/scenario_env.cpython-313.pyc index 5bb51a4e735e6466b9dc969194142791421fc777..7d8143bf8a823d9dd258dc90f393f21ea0af1565 100644 GIT binary patch delta 1187 zcmY*YZ)jUp6ujd|Z2O$APP|a0y=3R+9VI>F~`?}#9+3U%>?#0{Q2->MI zNh|cg7hW5lGAnNLAHCi9{+&q1w3zRxshPGo~EOic=ayQQHO8AQ$ zJU3`{oTi#;(qbnc-?5&sEUQtf8@7DAbWd5nUAhs=ck+~FJtpMQ(~Ah@G1HmDHg{gM zb_QF%p!)!wY=k~FXBiE8;E?|q}JIGCKI8c136PswqCOWZ9JC<3CWp4(KeHY6%_g>~N@$FdlHStE^*exNj zE_zmGmS;L*v@J%P)irT+9lq)vfM{S=j@gsN@ghU))Y>g%w9KHotlD`9I_i;Ii9c1gHp`Y*&Yr{M*!hQ~NmcbGQ z!=;pISF}0x3}HJUt8is_AY|4aWBeLZ-KA7Y#vY=+ppcSA8s}3XyaeG?*#0?d2Lg#XejW~2Nyx0p@xg90%}I?Hmi3q%?WRv5g_ zz#Py#d^YmHp*I+3=YOX&W;q$KyGpJyc!`3sr&dx3QC=iVa5#Mro(ClzPE6PaE<~Gr zv)FXDdy;=4$~PEnzKs302k~aQ-`2Nr9Lw;2I%Jn^$xS4~w`srK%s2^;9+csqbOTqR Uaj>&j;c)W8%pZuoNgu8GFF4mQaR2}S delta 1368 zcmb7EZD?Cn7(OQ?8EI~s+ur8Gr1@BD)7>m>!_r7=l`@w&nOa7>3|-K8+uqf7X&O%M zI?JdEg(8gEk5_16KfJ<_AwxPM=tLOeeu-!=4kFhL)ZhLXU1gwxo^#VX1NMg=F3)+- z`#$gIIo!MB42Opz*Vk4{J`HMf*gM9jEsTNCq56iT zFA;)ri|^nQdy0nb5$8jMi~tdFAJ$mQzhd^*V|w;sj}E{{YYa}to9Z>~AF}m-g?wr+ zqzi_HHaxs8MDS6F3MtPs4bAL{qgiIb&%!&xNIZv%-6kc6Mo*gZKl}J$gqX!F8ZvE< zoipcxlGIidO?E!_NIGpwXv=DwyQC?(q!ebQ5*3K7mZ zkS3JrdD&Em{czj!YrB5nb*UmL3L!Hz897Hmw@nmfiI5`b-s?PTm9Q>}z-)d{!+u`p z3RALDkcvebt|B(UC%%`1BtQuj_AaC5dYc59=MoJ0yYRbJ$se@5wHrm8u-d;dv^J!= zYtE$ROm3a}G=C#sOOMcRw;A!iRb}Ji+QphLsri!IM~>Cp8O@#ffp6Pzt~qPGsPSTr zAJq848b7S@!*}?jyY~O;#WlLYy?}~;g3kk;uo&!y_anjTH^D;|=RB+WB?b!o74qRF z@P!ZBj1j@{@YD8nYSMcl>h!X4;LYmI@b~uA0@FI!U@D5F$m9|`+vla?dAXuni&9C} zUFErHdP{Sq0z1;^Uvw4l$Iu=du&b0!K7cc^5WWU)#GbSRbMG*cd>iBNR`ti&B@6xt zro`RqO8jShnB^nJ{R}WkVX(}cnxwNdJ*`466ILn#xRU6^D%?u6SQ=Vq5`RVP%37KlGY zOmW}9of(VQ_5$8XcGyDqpTmMJPHCsF<+l1iYrPe?8Q4x8(|R)7T^TK!eTb-ehXDz( mgvTj}e%)3&KU-cPi|}3FRXhhuvet6Jg5#Ud|AyFZaQ_B=^+)~y diff --git a/Env/__pycache__/scenario_env.cpython-39.pyc b/Env/__pycache__/scenario_env.cpython-39.pyc index 55a29a3acb305fcc5f955cd3dcdbc783c18f63b9..d924eb81edd35c1f77dc6010c435cc7479a0b53d 100644 GIT binary patch delta 1041 zcmYLHTWb?R6yDi3X?BxMn%bmE>!nr&qxR~hA|+6)7sT3|LJ6(QxVve$-R{P-tEDBi zlzMr!Oci`fL0?1!FQ6}i;G3vad~y-|4dUh5GaHKw=i76>bLPx^XXfd#+r4qBD6)Xx ziJ=wqL))|XD=E(7C|V7gd*Q_w=^p9P>-FSKp)AZaS6ViN0b!LaNoDCSf!D}IE8EM` z1|ik_jtzl(>}9l{WZ0kRp`Bjms!-WgCe@NkSGAzhy&;SVxdwJIHc5Q;Ja%kWBe><( zC!)iTFoxzfAWUJ`NqLEs$&9cjUacJyAcJ&{z)AiSeX*mxY66j%ksJ4;u4dR2Oh+|! zKZM=2S;+G~a?u3WQ{8E2-j^);&n?7E-BmqDt+q>J<$q;x8AC4JonVWJKDnlmgh`CZ zxYyf6DjXvbaj}K#WWN*dG6Psxh-!}Qfn!;Qt_D+sQ*5)o`@m^T=Hf7lKj`H!%&TK= ziO$>qG=akT%j{rscfwb27`C@H6*iL0hYU=wyjV}Z7rQQ@>q}Dx%JHaY0OczuI9Y2^ z+bAd{$Hdi+t@03t*;=DYJocmUczBMN+N?8`2`_NB#0FDGLq&8Susf+oWRtb0&xVAV z+De#}(y8PqK0mQ6W`#{m7e~Ls;wxCUL82genec zVWe^q*v8X0I0OpYxVMDh*MTulL3iN_M(j(bDa%{GL33LX1QHkP@k$#+nJ|*2Jvws1 z#sA+CBAk;rlsSwe_;t2U0q6p#Y(0B8m5*d1d*J|Pav48lUz;l}z&QJseMkn`O7jpt J>CerRe*wu<4W(lc9jw`3DgwhO~2|f$=p}8L+gg%LNM5VA3 zP-awHA-WD{SxGM&2_;8mzm-!BLN!y-^0EsK&E2HKkRuTE-|Hepo> zA&xKwa2mv#W^Y16?IFn%=7V)0f{$iFegd)D z063U;7$zJQ*i@XP6tGk`qjnIg4G8$xQVU@c!0{M)SXr`2W%eVUj$LVx8oFpVc#@Li ZGUn$>i*%CpB_8t^*yqFqEPt{k`wNuV4h;YR diff --git a/Env/scenario_env.py b/Env/scenario_env.py index e7cad7f..8488854 100644 --- a/Env/scenario_env.py +++ b/Env/scenario_env.py @@ -100,33 +100,13 @@ class MultiAgentScenarioEnv(ScenarioEnv): for scenario_id in _obj_to_clean_this_frame: self.engine.traffic_manager.current_traffic_data.pop(scenario_id) - # Fix: Ensure all objects are cleared properly before reset - # Instead of manually clearing, we just let the engine handle it, but we might need to - # ensure no stale references in managers. - - # The error "KeyError" in clear_objects usually means we are trying to clear an object - # that is already gone from _spawned_objects but still tracked by a manager. - - # Try to clear only objects that actually exist in the engine - # existing_objects = list(self.engine.get_objects().keys()) - # if existing_objects: - # self.engine.clear_objects(existing_objects) - - # Force clear agent manager's spawned objects to avoid stale references - if hasattr(self.engine, 'agent_manager') and self.engine.agent_manager: - # Check if it's ScenarioAgentManager or VehicleAgentManager - # ScenarioAgentManager might not have spawned_objects directly exposed or named differently - # But BaseAgentManager usually has it. - # If it's ScenarioAgentManager, it might be using a different structure. - - # Safe clear for BaseAgentManager subclasses - if hasattr(self.engine.agent_manager, 'spawned_objects'): - self.engine.agent_manager.spawned_objects.clear() - - # Also clear active_objects if present (VehicleAgentManager uses this) - if hasattr(self.engine.agent_manager, '_active_objects'): - self.engine.agent_manager._active_objects.clear() - + # Clear vehicles we spawned via engine.spawn_object() so _object_clean_check() passes + ids_to_clear = [v.id for v in self.controlled_agents.values()] + if ids_to_clear: + self.engine.clear_objects(ids_to_clear) + self.controlled_agents.clear() + self.controlled_agent_ids.clear() + self.engine.reset() self.reset_sensors() self.engine.taskMgr.step() @@ -141,9 +121,6 @@ class MultiAgentScenarioEnv(ScenarioEnv): self.episode_rewards = defaultdict(float) self.episode_lengths = defaultdict(int) - self.controlled_agents.clear() - self.controlled_agent_ids.clear() - super().reset(seed) # 初始化场景 self._spawn_controlled_agents() @@ -217,6 +194,7 @@ class MultiAgentScenarioEnv(ScenarioEnv): self.controlled_agents[agent_id].before_step(action) self.engine.step() + self.engine.after_step() for agent_id in action_dict: if agent_id in self.controlled_agents: diff --git a/README.md b/README.md index 5e0f964..42dd3da 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,11 @@ MAGAIL4AutoDrive/ │ ├── simple_idm_policy.py # ConstantVelocityPolicy 占位策略 │ └── ... ├── dataset/ # 数据集加载器 -│ ├── expert_dataset.py # 通用专家数据加载类 -│ └── magail_dataset.py # MAGAIL 训练专用数据加载器 +│ ├── loader.py # 主流水线:load_expert_pkl、MAGAILExpertDataset +│ └── expert_dataset.py # 可选 107 维/5 维管线 ├── scripts/ # 工具脚本(数据、回放、可视化、分析) │ ├── generate_expert_data.py # 从 Waymo 生成专家 (obs, act) pkl -│ ├── visualize_replay.py # 原始专家数据回放 -│ ├── visualize_trained_policy.py # BC/MAGAIL 策略可视化统一入口 +│ ├── visualize.py # 可视化统一入口(replay / policy / trajectory) │ ├── analyze_expert_data.py # 数据分布分析 │ ├── launch_tensorboard.py # 启动 TensorBoard │ ├── README.md # 脚本用法说明 @@ -44,7 +43,6 @@ MAGAIL4AutoDrive/ │ └── magail/ ├── train_bc.py # [根目录] BC 训练 ├── train_magail.py # [根目录] MAGAIL 训练 -├── visualize_bc.py # [根目录] BC 可视化薄包装 -> scripts/visualize_trained_policy.py └── README.md ``` @@ -56,6 +54,34 @@ MAGAIL4AutoDrive/ 所有默认路径均为相对项目根,便于在不同设备上复用。 +## 数据处理流程 + +从 Waymo Motion 原始数据到本项目训练用专家 pkl,依次为: + +**1) 下载 Waymo Motion(TFRecord)** +安装 `gsutil` 并登录 Google 账号后,例如只下载 training_20s: + +```bash +gsutil -m cp -r "gs://waymo_open_dataset_motion_v_1_2_0/uncompressed/scenario/training_20s" ./waymo/ +``` + +**2) ScenarioNet Convert(TFRecord → ScenarioNet 场景库)** +需安装 ScenarioNet、MetaDrive 及 TensorFlow 2.11、protobuf 3.20;转换时不用 GPU。 + +```bash +python -m scenarionet.convert_waymo -d data/exp_converted --raw_data_path ./waymo/training_20s --num_workers 64 +``` + +**3) ScenarioNet Filter(按需筛选场景)** +从 convert 得到的场景库中筛掉含红绿灯、天桥等场景,输出到如 `data/exp_filtered`。具体命令以 ScenarioNet 文档为准(Operations → Filter)。 + +**4) 本项目:生成专家 pkl** +使用筛选后的场景目录,生成训练用 pkl 到 `data/training_data`: + +```bash +python scripts/generate_expert_data.py --data_dir data/exp_filtered --output_dir data/training_data --num_scenarios 100 --start_index 0 +``` + ## 核心工作流 ### 1. 数据准备 @@ -67,21 +93,20 @@ python scripts/generate_expert_data.py --data_dir data/exp_filtered --output_dir ### 2. 行为克隆 (BC) - **训练**:`python train_bc.py`(模型保存到 `models/bc/`,日志到 `logs/bc/`) -- **可视化**:`python visualize_bc.py` 或 `python scripts/visualize_trained_policy.py --policy_type bc --model_path models/bc/policy_best.pt` +- **可视化**:`python scripts/visualize.py policy --policy_type bc --model_path models/bc/policy_best.pt` ### 3. 多智能体对抗模仿学习 (MAGAIL) - **训练**:`python train_magail.py`(模型保存到 `models/magail/`,日志到 `logs/magail/`) -- **可视化**:`python scripts/visualize_trained_policy.py --policy_type magail --model_path models/magail/model_50_actor.pth` +- **可视化**:`python scripts/visualize.py policy --policy_type magail --model_path models/magail/model_50_actor.pth` -### 4. 策略可视化统一入口 -BC 与 MAGAIL 共用 `scripts/visualize_trained_policy.py`,通过 `--policy_type bc|magail`(或根据 `--model_path` 自动推断)选择模型类型。根目录 `visualize_bc.py` 为 BC 的薄包装。详见 [scripts/README_visualize.md](scripts/README_visualize.md) 与 [scripts/README.md](scripts/README.md)。 +### 4. 可视化统一入口 +可视化统一使用 `scripts/visualize.py`,子命令:`replay`(场景回放)、`policy`(BC/MAGAIL 策略)、`trajectory`(专家轨迹 2D 动画)。详见 [scripts/README.md](scripts/README.md)。 ## 文件与模块职责 ### 根目录脚本 -- **train_bc.py**:BC 训练,加载 `data/training_data` 下 pkl,模型与日志写入 `models/bc/`、`logs/bc/` -- **train_magail.py**:MAGAIL 训练,环境使用 `BCScenarioEnv`(45 维),模型与日志写入 `models/magail/`、`logs/magail/` -- **visualize_bc.py**:薄包装,调用 `scripts/visualize_trained_policy.py --policy_type bc` +- **train_bc.py**:BC 训练,从 `dataset.loader` 加载专家 pkl,模型与日志写入 `models/bc/`、`logs/bc/` +- **train_magail.py**:MAGAIL 训练,环境使用 `BCScenarioEnv`(45 维),从 `dataset.loader` 加载专家数据,模型与日志写入 `models/magail/`、`logs/magail/` ### Env 模块 - **Env/bc_env.py**:`BCScenarioEnv`,45 维观测(Ego 5 维 + 10 邻居×4 维),BC 与 MAGAIL 训练/评估共用 diff --git a/dataset/loader.py b/dataset/loader.py new file mode 100644 index 0000000..c1693cd --- /dev/null +++ b/dataset/loader.py @@ -0,0 +1,103 @@ +""" +统一数据加载:BC/MAGAIL 训练用专家 pkl 的加载函数与 Dataset。 +主训练流水线使用本模块;dataset/expert_dataset.py 为可选 107 维/5 维管线。 +""" +import os +import glob +import pickle +import numpy as np +import torch +from torch.utils.data import Dataset + + +def load_expert_pkl(expert_data_path): + """从目录或单个 pkl 加载专家 (obs, acts),返回 concat 后的 obs_data, act_data。""" + if os.path.isdir(expert_data_path): + pkl_files = glob.glob(os.path.join(expert_data_path, "*.pkl")) + if not pkl_files: + raise FileNotFoundError(f"No .pkl files in {expert_data_path}") + print(f"Found {len(pkl_files)} pickle files in {expert_data_path}") + elif os.path.exists(expert_data_path): + pkl_files = [expert_data_path] + else: + raise FileNotFoundError(f"Expert data path not found: {expert_data_path}") + + obs_data, act_data = [], [] + for pkl_file in pkl_files: + try: + with open(pkl_file, "rb") as f: + data = pickle.load(f) + if isinstance(data, list): + for traj in data: + if "obs" in traj and "acts" in traj: + obs_data.append(traj["obs"]) + act_data.append(traj["acts"]) + elif isinstance(data, dict): + if "observations" in data and "actions" in data: + obs_data.append(data["observations"]) + act_data.append(data["actions"]) + else: + print(f"Skipping {pkl_file}: Unknown data format {type(data)}") + except Exception as e: + print(f"Error loading {pkl_file}: {e}") + + if len(obs_data) == 0: + raise ValueError("No valid data loaded from provided path.") + obs_data = np.concatenate(obs_data, axis=0) + act_data = np.concatenate(act_data, axis=0) + print(f"Total loaded samples: {len(obs_data)}") + return obs_data, act_data + + +class MAGAILExpertDataset(Dataset): + def __init__(self, data_dir, transform=None): + """ + Args: + data_dir (str): Directory containing .pkl files from generate_expert_data.py + transform (callable, optional): Optional transform to be applied on a sample. + """ + self.data_dir = data_dir + self.transform = transform + self.trajectories = [] + self.flat_data = [] # (obs, act) pairs + + # Load all .pkl files + pkl_files = glob.glob(os.path.join(data_dir, "*.pkl")) + print(f"Loading data from {len(pkl_files)} files in {data_dir}...") + + for pkl_file in pkl_files: + try: + with open(pkl_file, "rb") as f: + data = pickle.load(f) + # data is a list of dicts: {'obs': (T, 45), 'acts': (T, 2), ...} + self.trajectories.extend(data) + except Exception as e: + print(f"Error loading {pkl_file}: {e}") + + # Flatten for training Discriminator/BC + print(f"Processing {len(self.trajectories)} trajectories...") + for traj in self.trajectories: + obs = traj["obs"] + acts = traj["acts"] + + # obs: (T, 45), acts: (T, 2) + for i in range(len(obs)): + self.flat_data.append((obs[i], acts[i])) + + print(f"Total samples: {len(self.flat_data)}") + + def __len__(self): + return len(self.flat_data) + + def __getitem__(self, idx): + obs, act = self.flat_data[idx] + + obs = torch.from_numpy(obs).float() + act = torch.from_numpy(act).float() + + sample = {"state": obs, "action": act} + + if self.transform: + sample = self.transform(sample) + + return sample diff --git a/dataset/magail_dataset.py b/dataset/magail_dataset.py deleted file mode 100644 index e753527..0000000 --- a/dataset/magail_dataset.py +++ /dev/null @@ -1,61 +0,0 @@ -import torch -from torch.utils.data import Dataset -import pickle -import numpy as np -import os -import glob - -class MAGAILExpertDataset(Dataset): - def __init__(self, data_dir, transform=None): - """ - Args: - data_dir (str): Directory containing .pkl files from generate_expert_data.py - transform (callable, optional): Optional transform to be applied on a sample. - """ - self.data_dir = data_dir - self.transform = transform - self.trajectories = [] - self.flat_data = [] # (obs, act) pairs - - # Load all .pkl files - pkl_files = glob.glob(os.path.join(data_dir, "*.pkl")) - print(f"Loading data from {len(pkl_files)} files in {data_dir}...") - - for pkl_file in pkl_files: - try: - with open(pkl_file, 'rb') as f: - data = pickle.load(f) - # data is a list of dicts: {'obs': (T, 45), 'acts': (T, 2), ...} - self.trajectories.extend(data) - except Exception as e: - print(f"Error loading {pkl_file}: {e}") - - # Flatten for training Discriminator/BC - print(f"Processing {len(self.trajectories)} trajectories...") - for traj in self.trajectories: - obs = traj['obs'] - acts = traj['acts'] - - # obs: (T, 45), acts: (T, 2) - # We pair them up - for i in range(len(obs)): - self.flat_data.append((obs[i], acts[i])) - - print(f"Total samples: {len(self.flat_data)}") - - def __len__(self): - return len(self.flat_data) - - def __getitem__(self, idx): - obs, act = self.flat_data[idx] - - # Convert to tensor - obs = torch.from_numpy(obs).float() - act = torch.from_numpy(act).float() - - sample = {'state': obs, 'action': act} - - if self.transform: - sample = self.transform(sample) - - return sample diff --git a/logs/20260131-192601/events.out.tfevents.1769858761.Hfkk.41584.0 b/logs/20260131-192601/events.out.tfevents.1769858761.Hfkk.41584.0 deleted file mode 100644 index 228172d3bfee5228ebdb7ce39657d3163ce48cb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5884 zcmZwKc{G*zAHZ=-wrlJzvS%BrK{8X{B*~oynPjP>MqL_LbFIIw8`0F1B??WpRQ6Mo zrI3^*x!=jiQZg;WMDv@9lnG^tekk)B&**v1=RD8-^Pabl*XKFz=eW*d|GwtZo%7^g zfAGE%&Fyv6R@g1@=Z9H-8OaM1uCej7WGxr+!Xm;2yTknj0hST_LPPxp(UwsHzK|!d zvyP2gYr?h>cR$(czWQjV`KZNaTecpa=zg1B* z8P<2jcwzeGf+#0B52|JWvCPk+ss6Wp0Q7ogWD5b3pnT|LCmR50oa!OyS4BxYU^5g~+@|5m;NPrg6K(m3eSjV~28Gz(v$R2_QymWA+o~k_n*-j zgM?Q%|M;;p(A!2;!|s!p}-lLI8**PEKb4r3WRWFf`E$ zK;LqX$Pl1UXduap+1N!7xqv5R_a)gwP_OYB%vW&R4nVXKsI*!Jq^SS^isOyme z0aBoWO#Ackt0^*35}0Zsso_>pUEDAV%@VbUqpr!{|P`YcZw|tkO~d-OY;?M($|&%K(-xZ4?*tlD{$)U z$@2ijr)mb!lw%wUw>CZmK(dVu)&xkE2Kqw27UxxWeE^{6R1ZNvcwWc#kM6wyAhw8{ z&Hx%4JA$qZSbqUPPb=50B0y?1&>G)Hyk25-5P&|dB6|pWYjq7bpuBScw3DhCKrTNW zMgykpi@|AsJiYNFi1D;b{ZgO-HZWZ8XHRUPPTL@J+YNksAMd|lgEU97ha0$-*5SG{ zuA9IHx2c+8gO|03&{iGmNB}ZOnsOpQY#PYnXcHdw53vQHIjV=CO3n@JmQyhbKvu`d z=?tI;%f3f5ql@?ebg(sd69Lkof!0ml!NYCe@&U-MjqD*PX61eShsn-V01BjP29VQ* zXk_5Dc@TiO+H6k(q)7ucIo`+K+}g_kWTr#*5LC;#i<1X@3IWK6su@5QpGKk82}v9P z(o!n)AwXI*PzI+Pmj<_A0H88uvWK8}wO$-_@IWpAO;a@kD564$mX;Um0?^op++74{ zF%5Ll>@jwo{7wRZ8t2F!fC^xK-OlI?AVAtQkiqv) z@Xk{C5CFQFN%j!brrVEYw5BQn=p9uvfX06fN1CfdNdRQN9|sd49U5qSbsy$;s+0kc zcr@8V(1Y$L*h%C!2SBz|%>a@P3PnvN<}v{ES)84K0O`^|TdM|eXR#XmUh|&nA?V`B z2>vtwMj8NF#FNt*Kn|>6)VY^q20*sW`}Y$dJsN1D_7yhRF~kB-XoBh?NH1|1UwWh7 z0YHYg$mtBAnKHxPZbZI?IzDL8s35Fi5@s4R92H!gk~2p;Jjs)wK- zyJv85Re?MJ%~CZ3Xo*q)I<41U13+id!DIqtNCTPj-eRrlm>d8KSVQ&@q+&CTFT|!) z0Z=+sGl1M${ZY+VW$yr}WS>Pk0a`)>g&m#3i?*9?1fb+dvWK81&J1?`>pJ`wu#u`6 zK${b|DCS`Q3IO65y*f>RjA$VC@)=y^X{irD2F7F$L534Ec)gPg{8(aAHf|@pTomteu|vV06MGs4Vp!)#Tciu4oYHuNX2zuc=iwkPhIpA;T9aS@cTAul!q0$mn06LbJQ9*!A zX`mCW)7Yyq;~D_@X)4vkh5iZdLzfX3F!Z)+aSsS diff --git a/logs/20260131-194343/events.out.tfevents.1769859823.Hfkk.44173.0 b/logs/20260131-194343/events.out.tfevents.1769859823.Hfkk.44173.0 deleted file mode 100644 index 29fd8bc7978e7578af7993485d6a4238d05636a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1532 zcmZwGeMl2=9LMolPQ43B%}tTaR0th%!b;(i(g?#s!KAXFS|{CN( zveaA!{b8+8Ydx@PJGCs)d`P8HniO*|pL#N4=B(Jc?)Y+dfBty?KEKyLmm^yK{?z4| zka1{3#r+bl1@&=ei1(Uwp}B?NU8)1FkT391|`qf|PQ!bo4G zP^yYa#qx>l;+N~|{JZbRBjiYLCic>|D_etxqiAl4R-3BxJs>$al9efPd&Fz~bx&t~ zEMm#>*Wu`DG=!;3iDF6LRP`2QL!i^b*!vNI%dwzrgt>A$kSj+7>aRxa6U0Gy%2+~g8vdgDC3jnB(s|7$x zQv*J6(_9Qd=H3J23}__}^zG1~-8nIJ0)QI&*dBu3>Syh>4o3_C4REyp=%Mr~E{PsK z4nQ4?*dzlAcBk#x>1= z#5~aX)&x4pCLRKyxLUS{pjXZ`dg;`H9)NapwE*aE?z-PuX<=kh`%CKi&Q=4}f~Q5*HXyFc0+Tb0OWS%qIcJ z_=4>rsM4aKyLSwE0H}kj1whRSwfK_Swg-R)C32C$_uGf?Ktz|CP8mg`0H{sM_7HTo zzk*J3#7+Z{i>n1dVQtm8uJ=<70KHC=1~VXp2ihB0K{tD?bpX_w&h`+r@%uSCeK_eJ S06Do@0F;qgg|{dSqyGTl$nkps diff --git a/logs/20260131-200211/events.out.tfevents.1769860931.Hfkk.47961.0 b/logs/20260131-200211/events.out.tfevents.1769860931.Hfkk.47961.0 deleted file mode 100644 index 93541ff97dc69e4f37eafce46896913a9d9d7dd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmeZZfPjCKJmzv9egE=8b^I+yDc+=_#LPTB*Rs^S5-X!1JuaP+)V$*SqNM!9q7=R2 z(%js{qDsB;qRf)iBE3|Qs`#|boYZ)T$OX@ts&Y_sZ{86y;=Hth>n6xtEnzM}E-s(^ z;$r<0kOiW;;V$-@7oD0c166(E;8a;oiA_+|f?WJu9AMRDi8-QzY+r289|~Bg2vy5i n{pK5I5KOHYmk1ZHPikUOUS?i;d{JUas_3e1vn>Nn(k21`{_jms diff --git a/logs/20260131-202131/events.out.tfevents.1769862091.Hfkk.51799.0 b/logs/20260131-202131/events.out.tfevents.1769862091.Hfkk.51799.0 deleted file mode 100644 index 82f1c3716b4a1f11f342c8408da319ff5499296b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmeZZfPjCKJmzwq-}L!YP5doKDc+=_#LPTB*Rs^S5-X!1JuaP+)V$*SqNM!9q7=R2 h(%js{qDsB;qRf)iBE3|Qs`#|boYZ)T$cfk!TL6RLB0c~B diff --git a/logs/20260131-202136/events.out.tfevents.1769862096.Hfkk.51875.0 b/logs/20260131-202136/events.out.tfevents.1769862096.Hfkk.51875.0 deleted file mode 100644 index 27481dc1f9b4396e72d667c7b428e4e095fd8e16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1581 zcmZwHZ%7ki9Kdm>mAfbZyD5lPT3G6sWF?&au@pl}OpGv6T9eLL=|;DIi5d7tQ&Z99 zG}qF=3eB8o4GG)L7n1!Y1z+Sph(ZO^LL{@5<<9GlA9v5ohwt-yKCgc19vf$^A>E3- zY~HneT`_(v%r~D@s?_o{1EJO>C1lHyOh>4-8Zuv_BrnRf`l2EwSu8h_Djh+}33sI^ zP!$r2jhld+XXm#ub3aZ7C1CL}7MFW&Y%mH$C_|&w#_QZ4NF|ji$tl(5vT&vz9&C)j zd}5h85cwl9T^p2z(rY#4hx5ja=OUR_RPyx)mioU9K@#MfK`2SJN_|N|Ds_ak({91d z*+a4&%!8PPjx5Ym?PEfl&>~k0fC{Y5 zc)^*)B>=*^SN&;_7Z3DhuHP}`-5CQw;WoC1Ad&2~qoZ^t4uCSbS^)GkqzM<*PY?jq zShE~TgEsL%C6-l(GooArK!#ejhajsfnEF#u=?6d$xmo};Z)(JI&~QHhjZ7qLr$L)} zpsTk6skXRBEdbOt$@UN=Bczm?yX=&$59-o9it15n>eZ!`_s!UHW7$5Ch1 z16ly8`^okYw^V>dVywkhiZhkp_u*p#D=AsI`+`5dbv8^$-*~prA}x zl_vms__5OkKmoQoJnF=<6@YRtj~<~x7!M>G*HdlNp<@6Pd4=sE=y!V=b)z-Q1VC9_ rEdV;0T#L`X`LGXc($7`x$qYz>g3#7%!nnTc;|cfb^;ka#?(5M%a~AA# diff --git a/logs/20260131-202739/events.out.tfevents.1769862459.Hfkk.53251.0 b/logs/20260131-202739/events.out.tfevents.1769862459.Hfkk.53251.0 deleted file mode 100644 index 5a473fd3ec730a9310ab9d539912e40e1277740e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1532 zcmZwFeMl2=9LMoyTHb|hGiM+^q-JQ!Emsj&KBQ7Dk)d<-PY>gCw>r|Pw>gVg58y*e zk2cXtDfEv>&{kSmv|S;DIeMT6>_O4cqVgfch*3Vo&UMF^yZiHa-{<$@$ElYrKJ)eJ z7UWHwTVvCI%m@oApb5%^ZMTvp%NCUuLz66|$y`Dgln}H5GnW?^6ZBE6f~G7ajgj6; zZ={My{oAm+SG@X&WDb86{3mzCB5kgAhg7XdZLT*ODiJW>FX_2}wYP@n+hSWhrjQ>{t>ddl|@v{Oqk z`3aGu0Q85iML@D!=kYkZ`Z)k4x7J=@L5Ki!rn-WW&$OEXXqN9G=w$gRCh7j$c>qdm Qk^z-bkiqx1{i44Xi}$^Q6_6bT#gz)Y^6*VGi@-FbiUehsicIYE7aw*(Mr*3%3J9Q zjK!30<7Rng?16&ab$hnQVLkFc&%L{cf^iv+XP7OPqgL++^2dghTjuv8J zS8gHzZO!`omH}-RfQB{5r5#^a0Z>3T*F#YDmv=6GbPEMQTD}$m^@ZLg^qmp60La83drZ0j`Ij+1g;_u5NY!&`Z7+0sXwwL_CZ_7XT>g!}Uo9%0JOr_BB1ttw+U(e*=Ydk{w$qiK>h+yU}q7E_@q<TGb6LG9XL<3RHde=NsA(hF+K1rlpxz#09Oo)`EJRwdI87Ecj7bhgf#!3~3#mNep zl2nMv#Y%EO7DGzjelq2krCqH%cj%#&3(sES;JbM4dTYXxAOhkN5}cKbKOnqt$J1j% zsgAJI)jgf1t9Z8ibhRa6L6}l?k~BtGD$8+GyWAOY(6#z0W(YR*4R!Wj5REzOZiM`(R$;SVzDjg|E-T{4^*3xiIxi2%rZ%rF+O3E*Mwu z@(=cN)Scc`o9xX?VQLN#@nHp;Xn!CApn?6OFbZVA0_~N5qVYL)))9cJNxFw1>#%ao zg3X#y0P1CG4$y(>)98g~p$34~ia!XaK!z+()Yw(cW3&+Eg`f3P4)-a47{cVu2#ujd0q^4SfJq0-zqI<^cUu zT#9OrnYaQF|2A05rt(5Y#Xz!ftJ6+yKbLjy|0Ov>+%!eDkLW zfSP_wi=#j$ED+ymE1r51s0ToK*XbUDR!{q2l~ipGK&?#80s6aKg_@^Y)ByCbB|M1& zEn|U%+7P@ksPQfU)wj|;1bve#!@eWV?f`U;sX0JZYfd8f2;oHl@(77cr9h@EkXmyP zcX^Lr1)wdVbPqw#LrDC{tMxhn#V|Doh~Ibu)ef~f0#M)Ok1{C`j|F;dnTpk#lGgy# z)j{_VbXS#)xAB8X0GefL4$wA_B9wonatPd{lZI|fAOT@Tn1zzbZ|-_$a`Ecbd-FW& zZ|ejLq8nBhPzwmwg6LNncp?0e8CdYNk?!Gw;hIF;)xTjoSYX>k*BlE31IN)V!{BfL zTIFL^Oo7Z;Ac0*z-aGe20|3o1Jp|>I6k}0y+Z6z^_oYwg0I8x2kVlZH8-UEFohm8N zJ1kIZUNP3ccQ^xp9A@Yqf{d1*#+!?N{}O=wnVJJsXPl1=IxD&WsN((9a}>y&1-e#W zh10KzDgo$#J>5f)XH*^DSN1#@fJ&K~0~B^H4-q%qZvv2G^zLx@7u|WOtmvF4AUJO7MpVK`A6@2z9c1^T{e||fdngcX7m4RlC z%%23HBRSh|Qy^;=X!m`LSG?4m2cTWKbPqv!gck44yj23;P$pAzfc(Gx3Z3##Bf-^< zM?PKxabE35y_~Ia0kuHDS`hnJC!Rz93c!NE|L7hrh-m1?mwSqO!GaW~=2$S= zo{l0%^?m^$y^Y;}QXnA+_YmZL{|261(IW()W~Sx<^*g1ao^_kD F{s(8?8#Dj_ diff --git a/logs/20260131-205450/events.out.tfevents.1769864090.Hfkk.59264.0 b/logs/20260131-205450/events.out.tfevents.1769864090.Hfkk.59264.0 deleted file mode 100644 index 3cccc12fc41b79a1609888636149d992727bf2a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmeZZfPjCKJmzw`J)QfkF8-FI6mL>dVrHJ6YguYuiIq{19+yr@YF=@EQBrM*}QKepaQD#YMkzOiDReV}zPHH?vWZy*2UjR;DAvpj5 diff --git a/logs/20260201-000321/events.out.tfevents.1769875401.Hfkk.64137.0 b/logs/20260201-000321/events.out.tfevents.1769875401.Hfkk.64137.0 deleted file mode 100644 index 5a256d3da1f7fe6588bc528f8eab66e179734a76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15072 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

5KM0!}|k`{ohrJ4h>wP{au zhrhH1(8VMUamSR5WP@-RNQJ0iX`StVd9Wmp`Q!8@>a`N~$>^ueEJR)nT>H0D5pylZYT+ z8K|&2iZ(l$-vf}_W!59er6P_xg)2D&C`GC{AmtItM8RT!CxDJPnT{4gQ)HlS4l#7- zu4cSFNN{F7f|mIu&})e7Ps?Mc7=if`Wro1RY*FYuF1prbdN zIwj7*-xf4UJx*|7SrolHvg#B}pr6H7=S*<1$3NB%?U(j|E&cnUTd)Y4E(2xFolTVk z3m(F2eCiSF5oBc*O;0;C<^t%oRC7Ra8^2m#|ET{PfUX5SpCN+$WuUR!Vri_=Gi3m+ z3uZln%9bpr&z^tu1<-M+=783!f42VSIs6KMZrqw5CxQZGpdkb2&^A?cFMxK`upU9f zj22NVy~Fqond?%`0jX?jww{$xwE;j{5vhqHC{PC4nm315d$!C4&}FGdkZGYlJ!G3T z3wG#}RC7SJhVQNad^-tm0ezfKEE7RNGSDXTIdq=oL1zRxvmQaos`F^zEW51$(iqEX z4yfT|gZ0r+75uLV_v{o?MNqH|v_B%ATHGkl1<=M(tVfXJ@5||cEzW-e=!{fzKqGwL zS$mXZN5D%qcin^x5fmZ=W!omuHRl!ap`K+L>k;(+K{8#}xm*oEVN%Tj?f&%IdYPdI ze$c8YoVQg3g~~v4%Mz%r|IMDTLreFt9zj7H7Sm5FI^PG-0jcJIba%Y8_78JXh8^1D zzVa6l6ea^*y*!U@QW$OnpxF~xkD$E9h16$t$zK5aMXEWVym9r`u}9+8!_nT$amvnC N5cg;|S-sTj{{Zi%FhBqR diff --git a/logs/bc/20260202-011907/events.out.tfevents.1769966347.Hfkk.245768.0 b/logs/bc/20260202-011907/events.out.tfevents.1769966347.Hfkk.245768.0 new file mode 100644 index 0000000000000000000000000000000000000000..0c24fa88dbf1c61d05b5be043310ce125ce121eb GIT binary patch literal 3080 zcmZwIe^8Tk9Kdm)Q1-x(F@B0AU{kI8_9S!u+J)UX%7F`Pf`AEqg zf|4bzruMbQCjgW{)eNAVwkBM&HZK)`*6f`hBS0_FK;7kMO&lXn3qX&l9)gnkMog2L zn{)ugEhnclfQ%kGyhmQR5`aDrm>VZRFVjG>4U48cP1-5|$_XTU2s)O*Mq?%?J^&r2 zY6j4s^^N$XeWDhCVgv<$5g>aSsAi8360+2j02CiW_7F7SEkIR2X$$~VMb!+TMYje# zeJ)xEK$TkcGyz&d11+jT&{l5ueE>?+kv#PAwpa!Lt_rUVn zJJ3MK7q+8(ae@GVf}~^*K^+ltbne^zDFC#Ysu@5oJ+*j4+U^nndL&xSB0ww}$lLd0 zG&Sh|GXQ;`MfMQXB1uG*Z|?R2parUC0EI^$!bguBV1tv?&^(1%L2j5U=9nTaety>T zrIw@D=J{1q=6M-xfX5_qh#N5425DXUknWQ(N3g*x)x!BYXSO&4&`|@~L(td}Jz|%Hz;DeERWpD(?W^(OZSpt(TB7z0 zAV6#yNNJ}>C{_JF01Z(+1dSWNM595W@U@($Y6j5e6IHnJN_sQ^>CfZ`6Ch_A=(_AU zdOe;q2tYf!$sU6Eomv!O^1cE7GA&gzfIeGaiKpx5+yQ9d_htbBT1Erit38Q=e)OvV zAmc-_hamH4AF4Au>;#|(RLuYiov6UecSV{3=;j}faRTH*1EJt9RBMjt2O!fUvWKAQ zC2GDeLIo|TGa29~@m>)I}AXggbLWBWHs+z9@&^*;c5Vzwby5@4a1%UXmRkfFrGeUGjA)|zemDRPRgpae@pR|WYrR=t1CZT*vSt7!wC};a XE>>{C(Z12@vt)EnzM}E-s(^ z;$r<0kOiV%%Utbkf9P+PfvP@naH=flYnW<5E`BZ!uN@ C+j}Gc literal 0 HcmV?d00001 diff --git a/logs/bc/20260202-021223/events.out.tfevents.1769969543.Hfkk.257044.0 b/logs/bc/20260202-021223/events.out.tfevents.1769969543.Hfkk.257044.0 new file mode 100644 index 0000000000000000000000000000000000000000..9753b3cfd62a700d4f57f9d6428af2dbfdddb283 GIT binary patch literal 152686 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 literal 0 HcmV?d00001 diff --git a/scripts/README.md b/scripts/README.md index 86de988..0dcffa1 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -22,36 +22,31 @@ --- -### 回放与可视化 +### 可视化(统一入口) | 脚本 | 用途 | 用法示例 | |------|------|----------| -| [visualize_replay.py](visualize_replay.py) | 原始专家轨迹回放(ExpertReplayEnv) | `python scripts/visualize_replay.py --data_dir data/exp_filtered --num_scenarios 1 --horizon 200` | -| [visualize_trained_policy.py](visualize_trained_policy.py) | **BC/MAGAIL 共用**:加载训练好的策略在 45 维场景中可视化 | 见下方「训练策略可视化」小节 | +| [visualize.py](visualize.py) | **replay**:场景回放(ExpertReplayEnv);**policy**:BC/MAGAIL 策略;**trajectory**:专家轨迹 2D 动画 | 见下方 | -#### 训练策略可视化(visualize_trained_policy.py) +**子命令**: -使用训练好的 **BC** 或 **MAGAIL** 模型在 45 维场景环境中运行,并实时渲染俯瞰图(top-down view)。统一入口:`scripts/visualize_trained_policy.py`。 - -**BC 模型**: +- **replay**(原始专家轨迹回放): ```bash -python scripts/visualize_trained_policy.py --policy_type bc --model_path models/bc/policy_best.pt --data_dir data/exp_filtered --num_scenarios 1 +python scripts/visualize.py replay --data_dir data/exp_filtered --num_scenarios 1 --horizon 500 ``` -**MAGAIL 模型**: +- **policy**(BC 或 MAGAIL 训练策略): ```bash -python scripts/visualize_trained_policy.py --policy_type magail --model_path models/magail/model_50_actor.pth --data_dir data/exp_filtered --num_scenarios 1 --deterministic +python scripts/visualize.py policy --policy_type bc --model_path models/bc/policy_best.pt --data_dir data/exp_filtered --num_scenarios 1 +python scripts/visualize.py policy --policy_type magail --model_path models/magail/model_50_actor.pth --num_scenarios 1 --deterministic ``` -**自动推断类型**(根据 `--model_path` 扩展名:`.pt` → BC,否则 → MAGAIL): +- **trajectory**(专家轨迹 matplotlib 俯视图动画): ```bash -python scripts/visualize_trained_policy.py --model_path models/bc/policy_best.pt -python scripts/visualize_trained_policy.py --model_path models/magail/model_50_actor.pth +python scripts/visualize.py trajectory --data_dir data/exp_filtered --scenario_idx 0 ``` -**根目录 BC 薄包装**:`python visualize_bc.py --model_path models/bc/policy_best.pt` - -**参数**:`--policy_type`(`auto`|`bc`|`magail`)、`--model_path`(默认 `models/bc/policy_best.pt`)、`--data_dir`、`--start_index`、`--num_scenarios`、`--horizon`、`--deterministic`(仅 MAGAIL)。环境统一为 45 维 `BCScenarioEnv`,渲染为 MetaDrive top_down。数据目录未指定时默认 `data/exp_filtered`(不存在则 `data/exp_converted`)。 +**公共参数**:`--data_dir`(默认 `data/exp_filtered`)、`--start_index`、`--num_scenarios`、`--horizon`。policy 模式另有 `--policy_type`(auto/bc/magail)、`--model_path`、`--deterministic`(仅 MAGAIL)。 --- @@ -62,7 +57,6 @@ python scripts/visualize_trained_policy.py --model_path models/magail/model_50_a | [analyze_expert_data.py](analyze_expert_data.py) | 分析专家数据分布与统计 | 见脚本内 `__main__`(依赖 env 与数据目录配置) | | [check_track_fields.py](check_track_fields.py) | 检查 Waymo 轨迹字段 | 见脚本内 `__main__` | | [check_database_info.py](check_database_info.py) | 检查数据库/场景信息 | 见脚本内 `__main__`(含硬编码路径,可按需改为 `data/exp_filtered`) | -| [visualize_expert_trajectory.py](visualize_expert_trajectory.py) | 用 matplotlib 画专家轨迹动画 | 依赖 `env.expert_trajectories`,与当前 env 接口可能不一致,可选使用 | --- @@ -79,4 +73,4 @@ python scripts/visualize_trained_policy.py --model_path models/magail/model_50_a 1. **数据准备**:`generate_expert_data.py` → 输出到 `data/training_data/*.pkl` 2. **BC 训练**:根目录 `train_bc.py` → 模型保存到 `models/bc/`,日志到 `logs/bc/` 3. **MAGAIL 训练**:根目录 `train_magail.py` → 模型保存到 `models/magail/`,日志到 `logs/magail/` -4. **可视化**:`visualize_trained_policy.py`(或根目录 `visualize_bc.py` 仅 BC)→ 从 `models/bc` 或 `models/magail` 加载模型,数据目录默认 `data/exp_filtered` +4. **可视化**:`scripts/visualize.py`(子命令 replay / policy / trajectory)→ 数据目录默认 `data/exp_filtered` diff --git a/scripts/visualize.py b/scripts/visualize.py new file mode 100644 index 0000000..7b411db --- /dev/null +++ b/scripts/visualize.py @@ -0,0 +1,395 @@ +""" +Unified visualization: replay (scenario replay), policy (BC/MAGAIL), trajectory (2D expert trajectory animation). +Usage: python scripts/visualize.py [args...] +""" +import argparse +import os +import sys +import time +import numpy as np +import torch + +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if project_root not in sys.path: + sys.path.insert(0, project_root) + +# --- Replay --- +def _run_replay(args): + from Env.expert_replay_env import ExpertReplayEnv + + data_path = os.path.abspath(args.data_dir) + if not os.path.exists(data_path): + raise ValueError(f"Data directory {data_path} not found") + + from metadrive.scenario.utils import read_dataset_summary + _, summary_lookup, _ = read_dataset_summary(data_path) + if args.start_index >= len(summary_lookup): + raise ValueError( + f"start_index={args.start_index} out of range. Dataset has {len(summary_lookup)} scenarios." + ) + max_available = len(summary_lookup) - args.start_index + num_to_run = min(args.num_scenarios, max_available) + + env_config = { + "data_directory": data_path, + "is_multi_agent": True, + "num_controlled_agents": 100, + "horizon": args.horizon, + "use_render": True, + "sequential_seed": True, + "reactive_traffic": False, + "start_scenario_index": args.start_index, + "num_scenarios": -1, + "log_level": 40, + } + + print(f"Initializing ExpertReplayEnv with data from {data_path}...") + env = ExpertReplayEnv(config=env_config) + + try: + for i in range(args.start_index, args.start_index + num_to_run): + print(f"\n--- Playing Scenario {i} ---") + try: + obs = env.reset(seed=i) + except Exception as e: + print(f"Error resetting scenario {i}: {e}") + continue + + print(f"Scenario loaded. Controlled agents: {len(env.controlled_agents)}") + + for step in range(args.horizon): + obs, rewards, dones, infos = env.step(None) + env.render( + mode="top_down", + text={"Step": step, "Agents": len(env.controlled_agents), "Scenario": i}, + ) + time.sleep(0.05) + if dones["__all__"]: + print(f"Scenario {i} finished at step {step}") + break + except KeyboardInterrupt: + print("Interrupted by user") + except Exception as e: + import traceback + traceback.print_exc() + print(f"Global error: {e}") + finally: + env.close() + print("Environment closed.") + + +# --- Policy (BC / MAGAIL) --- +def _resolve_data_dir(data_dir_arg): + if data_dir_arg: + data_dir = data_dir_arg + else: + data_dir = os.path.join(project_root, "data", "exp_filtered") + if not os.path.exists(data_dir): + data_dir = os.path.join(project_root, "data", "exp_converted") + if not os.path.exists(data_dir): + raise FileNotFoundError(f"Data directory not found at {data_dir}. Please specify --data_dir.") + return data_dir + + +def _resolve_model_path(model_path, policy_type): + if os.path.exists(model_path): + return model_path + if policy_type == "bc": + candidate = os.path.join(project_root, "models", "bc", os.path.basename(model_path)) + else: + candidate = os.path.join(project_root, "models", "magail", os.path.basename(model_path)) + if os.path.exists(candidate): + return candidate + if policy_type == "magail" and not model_path.endswith("_actor.pth"): + candidate = os.path.join(project_root, "models", "magail", os.path.basename(model_path) + "_actor.pth") + if os.path.exists(candidate): + return candidate + raise FileNotFoundError(f"Model path {model_path} not found.") + + +def _run_policy(args): + from Env.bc_env import BCScenarioEnv + from metadrive.engine.engine_utils import close_engine + + policy_type = (args.policy_type or "auto").lower() + if policy_type == "auto": + policy_type = "bc" if args.model_path.endswith(".pt") else "magail" + + data_dir = _resolve_data_dir(args.data_dir) + data_path = os.path.abspath(data_dir) + env_config = { + "data_directory": data_path, + "is_multi_agent": True, + "num_controlled_agents": 3, + "horizon": args.horizon, + "use_render": True, + "sequential_seed": True, + "start_scenario_index": args.start_index, + "num_scenarios": args.num_scenarios, + "log_level": 40, + } + + print(f"Initializing BCScenarioEnv (policy_type={policy_type})...") + try: + env = BCScenarioEnv(env_config, agent2policy={}) + except Exception as e: + print(f"Error init env: {e}. Trying to close lingering engine...") + try: + close_engine() + except Exception: + pass + env = BCScenarioEnv(env_config, agent2policy={}) + + state_dim = 45 + action_dim = 2 + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model_path = _resolve_model_path(args.model_path, policy_type) + print(f"Loading model from {model_path}...") + + if policy_type == "bc": + from Algorithm.policy import StateIndependentPolicy + policy = StateIndependentPolicy( + state_shape=(state_dim,), + action_shape=(action_dim,), + hidden_units=(256, 256), + hidden_activation=torch.nn.Tanh(), + ).to(device) + policy.load_state_dict(torch.load(model_path, map_location=device)) + policy.eval() + else: + from train_magail import Actor + actor = Actor(state_dim, action_dim).to(device) + actor.load_state_dict(torch.load(model_path, map_location=device)) + actor.eval() + + try: + for i in range(args.start_index, args.start_index + args.num_scenarios): + print(f"\n--- Playing Scenario {i} ---") + try: + obs_dict = env.reset(seed=i) + except Exception as e: + print(f"Error resetting {i}: {e}. Skipping.") + try: + close_engine() + env = BCScenarioEnv(env_config, agent2policy={}) + except Exception: + pass + continue + + print(f"Scenario loaded. Controlled agents: {len(obs_dict)}") + step_count = 0 + episode_reward = 0.0 + + while True: + agent_ids = list(obs_dict.keys()) + obs_list = [obs_dict[aid] for aid in agent_ids] + obs_tensor = torch.FloatTensor(np.array(obs_list)).to(device) + + with torch.no_grad(): + if policy_type == "bc": + actions_np = policy(obs_tensor).cpu().numpy() + else: + dist = actor(obs_tensor) + if args.deterministic: + actions_np = torch.tanh(dist.mean).cpu().numpy() + else: + actions_np = torch.tanh(dist.sample()).cpu().numpy() + + actions = {aid: actions_np[idx].flatten() for idx, aid in enumerate(agent_ids)} + obs_dict, rewards, dones, infos = env.step(actions) + episode_reward += sum(rewards.values()) + + env.render( + mode="top_down", + text={ + "Scenario": i, + "Step": step_count, + "Agents": len(obs_dict), + "Total Reward": f"{episode_reward:.2f}", + }, + ) + step_count += 1 + + if dones["__all__"] or step_count >= args.horizon: + print(f"Scenario finished at step {step_count}, reward {episode_reward:.2f}") + break + except KeyboardInterrupt: + print("Interrupted.") + finally: + env.close() + + +# --- Trajectory (matplotlib 2D animation) --- +def _build_expert_trajectories_from_env(env): + """Build expert_trajectories dict from env (ExpertReplayEnv has traffic_manager.current_traffic_data).""" + if hasattr(env, "expert_trajectories") and env.expert_trajectories: + return env.expert_trajectories + if not hasattr(env, "engine") or not hasattr(env.engine, "traffic_manager"): + return {} + from metadrive.type import MetaDriveType + data = getattr(env.engine.traffic_manager, "current_traffic_data", None) + if not data: + return {} + expert_trajs = {} + for scenario_id, track in data.items(): + if track.get("type") != MetaDriveType.VEHICLE or "state" not in track: + continue + state = track["state"] + positions = state.get("position") + if positions is None: + continue + valid = state.get("valid", np.ones(len(positions), dtype=bool)) + valid = np.asarray(valid).flatten() + if valid.size != len(positions): + valid = np.ones(len(positions), dtype=bool) + first_show = int(np.argmax(valid)) if valid.any() else 0 + last_show = len(valid) - 1 - int(np.argmax(valid[::-1])) if valid.any() else len(positions) - 1 + obj_id = track.get("metadata", {}).get("object_id", str(scenario_id)) + expert_trajs[obj_id] = { + "positions": np.asarray(positions), + "start_timestep": first_show, + "end_timestep": last_show, + } + return expert_trajs + + +def _run_trajectory_animation(expert_trajs, scenario_idx): + import matplotlib.pyplot as plt + from matplotlib.animation import FuncAnimation + + if len(expert_trajs) == 0: + print("No expert trajectories to visualize.") + return + + fig, ax = plt.subplots(figsize=(12, 12)) + max_timestep = max(t["end_timestep"] for t in expert_trajs.values()) + min_timestep = min(t["start_timestep"] for t in expert_trajs.values()) + + colors = plt.cm.tab10(np.linspace(0, 1, len(expert_trajs))) + for idx, (obj_id, traj) in enumerate(expert_trajs.items()): + positions = np.asarray(traj["positions"]) + if positions.ndim >= 2: + positions = positions[:, :2] + else: + continue + ax.plot( + positions[:, 0], positions[:, 1], + color=colors[idx], alpha=0.3, linewidth=1, + label=f"Vehicle {str(obj_id)[:6]}", + ) + + scatter = ax.scatter([], [], s=200, c="red", marker="o", edgecolors="black", linewidths=2) + time_text = ax.text(0.02, 0.95, "", transform=ax.transAxes, fontsize=14) + ax.set_xlabel("X (m)") + ax.set_ylabel("Y (m)") + ax.set_title(f"Expert Trajectory Visualization - Scenario {scenario_idx}") + ax.legend(loc="upper right", fontsize=8) + ax.grid(True, alpha=0.3) + ax.axis("equal") + + def update(frame): + current_time = min_timestep + frame + current_positions = [] + for traj in expert_trajs.values(): + st, et = traj["start_timestep"], traj["end_timestep"] + if st <= current_time <= et: + pos = np.asarray(traj["positions"]) + if pos.ndim >= 2: + pos = pos[current_time - st, :2] + else: + continue + current_positions.append(pos) + if current_positions: + scatter.set_offsets(np.array(current_positions)) + time_text.set_text(f"Time: {frame * 0.1:.1f}s (Frame {frame})") + return scatter, time_text + + anim = FuncAnimation( + fig, update, frames=max_timestep - min_timestep + 1, + interval=100, blit=True, repeat=True, + ) + plt.tight_layout() + plt.show() + return anim + + +def _run_trajectory(args): + from Env.expert_replay_env import ExpertReplayEnv + + data_dir = _resolve_data_dir(args.data_dir) + data_path = os.path.abspath(data_dir) + env_config = { + "data_directory": data_path, + "is_multi_agent": True, + "num_controlled_agents": 100, + "horizon": 500, + "use_render": False, + "sequential_seed": True, + "reactive_traffic": False, + "start_scenario_index": args.scenario_idx, + "num_scenarios": 1, + "log_level": 40, + } + + env = ExpertReplayEnv(config=env_config) + try: + env.reset(seed=args.scenario_idx) + expert_trajs = _build_expert_trajectories_from_env(env) + _run_trajectory_animation(expert_trajs, args.scenario_idx) + finally: + env.close() + + +# --- Main --- +def main(): + parser = argparse.ArgumentParser( + description="Unified visualization: replay, policy (BC/MAGAIL), trajectory.", + ) + subparsers = parser.add_subparsers(dest="mode", required=True, help="replay | policy | trajectory") + + # Common args for data_dir (used by all) + def add_common_data_args(p): + p.add_argument("--data_dir", type=str, default="data/exp_filtered", help="Waymo scenario directory") + p.add_argument("--start_index", type=int, default=0) + p.add_argument("--num_scenarios", type=int, default=1) + p.add_argument("--horizon", type=int, default=200) + + # replay + pr = subparsers.add_parser("replay", help="Replay scenario with ExpertReplayEnv (no policy)") + add_common_data_args(pr) + pr.set_defaults(horizon=500) + + # policy + pp = subparsers.add_parser("policy", help="Visualize BC or MAGAIL trained policy") + add_common_data_args(pp) + pp.add_argument("--policy_type", type=str, default="auto", choices=["auto", "bc", "magail"]) + pp.add_argument("--model_path", type=str, default="models/bc/policy_best.pt") + pp.add_argument("--deterministic", action="store_true", help="MAGAIL: use mean action") + + # trajectory + pt = subparsers.add_parser("trajectory", help="2D matplotlib animation of expert trajectories") + pt.add_argument("--data_dir", type=str, default="data/exp_filtered") + pt.add_argument("--scenario_idx", type=int, default=0) + + args = parser.parse_args() + + # Resolve data_dir relative to project root when default + if args.mode != "trajectory": + if args.data_dir in ("data/exp_filtered", "data/exp_converted"): + args.data_dir = os.path.join(project_root, args.data_dir) + else: + if args.data_dir in ("data/exp_filtered", "data/exp_converted"): + args.data_dir = os.path.join(project_root, args.data_dir) + + if args.mode == "replay": + _run_replay(args) + elif args.mode == "policy": + _run_policy(args) + elif args.mode == "trajectory": + _run_trajectory(args) + else: + parser.error(f"Unknown mode: {args.mode}") + + +if __name__ == "__main__": + main() diff --git a/scripts/visualize_expert_trajectory.py b/scripts/visualize_expert_trajectory.py deleted file mode 100644 index 941f8fc..0000000 --- a/scripts/visualize_expert_trajectory.py +++ /dev/null @@ -1,105 +0,0 @@ -import sys -import os - -# 添加路径 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) -env_dir = os.path.join(project_root, "Env") -sys.path.insert(0, project_root) -sys.path.insert(0, env_dir) - -# 现在可以导入了 -from scenario_env import MultiAgentScenarioEnv -from metadrive.engine.asset_loader import AssetLoader -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.animation import FuncAnimation - -class DummyPolicy: - """ - 占位策略,用于数据检查时初始化环境 - 不需要实际执行动作,只是为了满足环境初始化要求 - """ - def act(self, *args, **kwargs): - # 返回零动作 [throttle, steering] - return np.array([0.0, 0.0]) - -def visualize_expert_trajectory(env, scenario_idx=0): - """ - 可视化专家轨迹的俯视图动画 - """ - env.reset() - expert_trajs = env.expert_trajectories - - if len(expert_trajs) == 0: - print("当前场景无专家轨迹") - return - - # 设置绘图 - fig, ax = plt.subplots(figsize=(12, 12)) - - # 获取所有轨迹的最大时间长度 - max_timestep = max(traj["end_timestep"] for traj in expert_trajs.values()) - min_timestep = min(traj["start_timestep"] for traj in expert_trajs.values()) - - # 绘制完整轨迹(淡色) - colors = plt.cm.tab10(np.linspace(0, 1, len(expert_trajs))) - for idx, (obj_id, traj) in enumerate(expert_trajs.items()): - positions = traj["positions"][:, :2] - ax.plot(positions[:, 0], positions[:, 1], - color=colors[idx], alpha=0.3, linewidth=1, - label=f'Vehicle {obj_id[:6]}') - - # 初始化当前位置标记 - scatter = ax.scatter([], [], s=200, c='red', marker='o', edgecolors='black', linewidths=2) - time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=14) - - ax.set_xlabel('X (m)') - ax.set_ylabel('Y (m)') - ax.set_title(f'Expert Trajectory Visualization - Scenario {scenario_idx}') - ax.legend(loc='upper right', fontsize=8) - ax.grid(True, alpha=0.3) - ax.axis('equal') - - def update(frame): - current_time = min_timestep + frame - - # 收集当前时间所有车辆的位置 - current_positions = [] - for traj in expert_trajs.values(): - if traj["start_timestep"] <= current_time <= traj["end_timestep"]: - idx = current_time - traj["start_timestep"] - pos = traj["positions"][idx, :2] - current_positions.append(pos) - - if len(current_positions) > 0: - current_positions = np.array(current_positions) - scatter.set_offsets(current_positions) - - time_text.set_text(f'Time: {frame * 0.1:.1f}s (Frame {frame})') - return scatter, time_text - - anim = FuncAnimation(fig, update, frames=max_timestep-min_timestep+1, - interval=100, blit=True, repeat=True) - - plt.tight_layout() - plt.show() - - return anim - -if __name__ == "__main__": - WAYMO_DATA_DIR = r"/home/huangfukk/mdsn" - data_dir = AssetLoader.file_path(WAYMO_DATA_DIR, "exp_filtered", unix_style=False) - - env = MultiAgentScenarioEnv( - config={ - "data_directory": data_dir, - "is_multi_agent": True, - "num_controlled_agents": 3, - "use_render": False, - }, - agent2policy=DummyPolicy() - ) - - # 可视化第一个场景 - anim = visualize_expert_trajectory(env, scenario_idx=0) diff --git a/scripts/visualize_replay.py b/scripts/visualize_replay.py deleted file mode 100644 index 3c46690..0000000 --- a/scripts/visualize_replay.py +++ /dev/null @@ -1,93 +0,0 @@ -import argparse -import os -import sys -import time - -# Add project root to Python path so we can import Env module -project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if project_root not in sys.path: - sys.path.insert(0, project_root) - -from Env.expert_replay_env import ExpertReplayEnv - -def visualize_replay(args): - data_path = os.path.abspath(args.data_dir) - if not os.path.exists(data_path): - raise ValueError(f"Data directory {data_path} not found") - - # Same as data generation: avoid MetaDrive assertion when requested num_scenarios > available. - from metadrive.scenario.utils import read_dataset_summary - _, summary_lookup, _ = read_dataset_summary(data_path) - if args.start_index >= len(summary_lookup): - raise ValueError( - f"start_index={args.start_index} out of range. Dataset has {len(summary_lookup)} scenarios." - ) - max_available = len(summary_lookup) - args.start_index - num_to_run = min(args.num_scenarios, max_available) - - env_config = { - "data_directory": data_path, - "is_multi_agent": True, - "num_controlled_agents": 100, - "horizon": args.horizon, - "use_render": True, # Enable rendering - "sequential_seed": True, - "reactive_traffic": False, - "start_scenario_index": args.start_index, - "num_scenarios": -1, - "log_level": 40, # ERROR - # "pstats": True, # For performance debugging - } - - print(f"Initializing ExpertReplayEnv with data from {data_path}...") - env = ExpertReplayEnv(config=env_config) - - try: - for i in range(args.start_index, args.start_index + num_to_run): - print(f"\n--- Playing Scenario {i} ---") - try: - obs = env.reset(seed=i) - except Exception as e: - print(f"Error resetting scenario {i}: {e}") - continue - - print(f"Scenario loaded. Controlled agents: {len(env.controlled_agents)}") - - for step in range(args.horizon): - # Step - obs, rewards, dones, infos = env.step(None) - - # Render - env.render(mode="top_down", - text={ - "Step": step, - "Agents": len(env.controlled_agents), - "Scenario": i - }) - - # Sleep to control playback speed - time.sleep(0.05) - - if dones["__all__"]: - print(f"Scenario {i} finished at step {step}") - break - - except KeyboardInterrupt: - print("Interrupted by user") - except Exception as e: - import traceback - traceback.print_exc() - print(f"Global error: {e}") - finally: - env.close() - print("Environment closed.") - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--data_dir", type=str, default="/home/huangfukk/MAGAIL4AutoDrive/data/exp_filtered", help="Path to Waymo data") - parser.add_argument("--start_index", type=int, default=0) - parser.add_argument("--num_scenarios", type=int, default=1) - parser.add_argument("--horizon", type=int, default=500) - - args = parser.parse_args() - visualize_replay(args) diff --git a/scripts/visualize_trained_policy.py b/scripts/visualize_trained_policy.py deleted file mode 100644 index 83e1bee..0000000 --- a/scripts/visualize_trained_policy.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -Unified visualization for BC and MAGAIL trained policies. -Use --policy_type bc or magail (or auto-detect from --model_path: .pt -> bc, else magail). -""" -import argparse -import os -import sys -import torch -import numpy as np - -# Add project root to Python path -project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if project_root not in sys.path: - sys.path.insert(0, project_root) - -from Env.bc_env import BCScenarioEnv -from metadrive.engine.engine_utils import close_engine - - -def _resolve_data_dir(args): - """Resolve data directory: explicit or auto-detect under project data/.""" - if args.data_dir: - data_dir = args.data_dir - else: - current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - data_dir = os.path.join(current_dir, "data", "exp_filtered") - if not os.path.exists(data_dir): - data_dir = os.path.join(current_dir, "data", "exp_converted") - if not os.path.exists(data_dir): - raise FileNotFoundError(f"Data directory not found at {data_dir}. Please specify --data_dir.") - return data_dir - - -def _resolve_model_path(model_path, policy_type): - """Resolve model path: if not found, try models/bc or models/magail.""" - if os.path.exists(model_path): - return model_path - if policy_type == "bc": - candidate = os.path.join("models", "bc", model_path) - else: - candidate = os.path.join("models", "magail", model_path) - if os.path.exists(candidate): - return candidate - if policy_type == "magail" and not model_path.endswith("_actor.pth"): - candidate = model_path + "_actor.pth" - if os.path.exists(candidate): - return candidate - raise FileNotFoundError(f"Model path {model_path} not found (tried {candidate}).") - - -def visualize_model(args): - policy_type = (args.policy_type or "auto").lower() - if policy_type == "auto": - policy_type = "bc" if args.model_path.endswith(".pt") else "magail" - - data_dir = _resolve_data_dir(args) - data_path = os.path.abspath(data_dir) - env_config = { - "data_directory": data_path, - "is_multi_agent": True, - "num_controlled_agents": 3, - "horizon": args.horizon, - "use_render": True, - "sequential_seed": True, - "start_scenario_index": args.start_index, - "num_scenarios": args.num_scenarios, - "log_level": 40, - } - - print(f"Initializing BCScenarioEnv (policy_type={policy_type})...") - try: - env = BCScenarioEnv(env_config, agent2policy={}) - except Exception as e: - print(f"Error init env: {e}. Trying to close lingering engine...") - try: - close_engine() - except Exception: - pass - env = BCScenarioEnv(env_config, agent2policy={}) - - state_dim = 45 - action_dim = 2 - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - model_path = _resolve_model_path(args.model_path, policy_type) - print(f"Loading model from {model_path}...") - - if policy_type == "bc": - from Algorithm.policy import StateIndependentPolicy - policy = StateIndependentPolicy( - state_shape=(state_dim,), - action_shape=(action_dim,), - hidden_units=(256, 256), - hidden_activation=torch.nn.Tanh(), - ).to(device) - policy.load_state_dict(torch.load(model_path, map_location=device)) - policy.eval() - else: - from train_magail import Actor - actor = Actor(state_dim, action_dim).to(device) - actor.load_state_dict(torch.load(model_path, map_location=device)) - actor.eval() - - try: - for i in range(args.start_index, args.start_index + args.num_scenarios): - print(f"\n--- Playing Scenario {i} ---") - try: - obs_dict = env.reset(seed=i) - except Exception as e: - print(f"Error resetting {i}: {e}. Skipping.") - try: - close_engine() - env = BCScenarioEnv(env_config, agent2policy={}) - except Exception: - pass - continue - - print(f"Scenario loaded. Controlled agents: {len(obs_dict)}") - step_count = 0 - episode_reward = 0.0 - - while True: - actions = {} - agent_ids = list(obs_dict.keys()) - obs_list = [obs_dict[aid] for aid in agent_ids] - obs_tensor = torch.FloatTensor(np.array(obs_list)).to(device) - - with torch.no_grad(): - if policy_type == "bc": - actions_np = policy(obs_tensor).cpu().numpy() - else: - dist = actor(obs_tensor) - if args.deterministic: - actions_np = torch.tanh(dist.mean).cpu().numpy() - else: - actions_np = torch.tanh(dist.sample()).cpu().numpy() - - for idx, aid in enumerate(agent_ids): - actions[aid] = actions_np[idx].flatten() - - obs_dict, rewards, dones, infos = env.step(actions) - episode_reward += sum(rewards.values()) - - env.render( - mode="top_down", - text={ - "Scenario": i, - "Step": step_count, - "Agents": len(obs_dict), - "Total Reward": f"{episode_reward:.2f}", - }, - ) - step_count += 1 - - if dones["__all__"] or step_count >= args.horizon: - print(f"Scenario finished at step {step_count}, reward {episode_reward:.2f}") - break - - except KeyboardInterrupt: - print("Interrupted.") - finally: - env.close() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Visualize BC or MAGAIL trained policy in 45-dim scenario env." - ) - parser.add_argument( - "--policy_type", - type=str, - default="auto", - choices=["auto", "bc", "magail"], - help="Policy type: bc (StateIndependentPolicy .pt) or magail (Actor _actor.pth). auto = infer from model_path.", - ) - parser.add_argument( - "--model_path", - type=str, - default="models/bc/policy_best.pt", - help="Path to model: BC .pt (e.g. models/bc/policy_best.pt) or MAGAIL _actor.pth (e.g. models/magail/model_50_actor.pth)", - ) - parser.add_argument("--data_dir", type=str, default=None, help="Waymo data directory (default: data/exp_filtered)") - parser.add_argument("--start_index", type=int, default=0) - parser.add_argument("--num_scenarios", type=int, default=1) - parser.add_argument("--horizon", type=int, default=200) - parser.add_argument("--deterministic", action="store_true", help="For MAGAIL: use mean action instead of sampling") - - args = parser.parse_args() - visualize_model(args) diff --git a/train_bc.py b/train_bc.py index 74ad5f8..d134f53 100644 --- a/train_bc.py +++ b/train_bc.py @@ -3,8 +3,6 @@ BC 训练脚本:负责数据加载、环境评估、日志与保存;BC 算 使用方式不变:python train_bc.py [--expert_data_path data/training_data] [--save_dir models/bc] ... """ import os -import glob -import pickle import numpy as np import torch import argparse @@ -17,45 +15,7 @@ from torch.utils.tensorboard import SummaryWriter from Algorithm.policy import StateIndependentPolicy from Algorithm.bc import train_bc_epoch, eval_bc_epoch from Env.bc_env import BCScenarioEnv - - -def load_expert_data(expert_data_path): - """从目录或单个 pkl 加载专家 (obs, acts),返回 concat 后的 obs_data, act_data.""" - if os.path.isdir(expert_data_path): - pkl_files = glob.glob(os.path.join(expert_data_path, "*.pkl")) - if not pkl_files: - raise FileNotFoundError(f"No .pkl files in {expert_data_path}") - print(f"Found {len(pkl_files)} pickle files in {expert_data_path}") - elif os.path.exists(expert_data_path): - pkl_files = [expert_data_path] - else: - raise FileNotFoundError(f"Expert data path not found: {expert_data_path}") - - obs_data, act_data = [], [] - for pkl_file in pkl_files: - try: - with open(pkl_file, "rb") as f: - data = pickle.load(f) - if isinstance(data, list): - for traj in data: - if "obs" in traj and "acts" in traj: - obs_data.append(traj["obs"]) - act_data.append(traj["acts"]) - elif isinstance(data, dict): - if "observations" in data and "actions" in data: - obs_data.append(data["observations"]) - act_data.append(data["actions"]) - else: - print(f"Skipping {pkl_file}: Unknown data format {type(data)}") - except Exception as e: - print(f"Error loading {pkl_file}: {e}") - - if len(obs_data) == 0: - raise ValueError("No valid data loaded from provided path.") - obs_data = np.concatenate(obs_data, axis=0) - act_data = np.concatenate(act_data, axis=0) - print(f"Total loaded samples: {len(obs_data)}") - return obs_data, act_data +from dataset.loader import load_expert_pkl def evaluate_policy(policy, args, device): @@ -126,7 +86,7 @@ def main(args): print(f"TensorBoard logging to: {log_dir}") os.makedirs(args.save_dir, exist_ok=True) - obs_data, act_data = load_expert_data(args.expert_data_path) + obs_data, act_data = load_expert_pkl(args.expert_data_path) obs_tensor = torch.FloatTensor(obs_data) act_tensor = torch.FloatTensor(act_data) dataset = TensorDataset(obs_tensor, act_tensor) diff --git a/train_magail.py b/train_magail.py index 061027e..6cac4dc 100644 --- a/train_magail.py +++ b/train_magail.py @@ -9,7 +9,7 @@ import argparse import signal import sys from torch.utils.data import DataLoader -from dataset.magail_dataset import MAGAILExpertDataset +from dataset.loader import MAGAILExpertDataset from Env.bc_env import BCScenarioEnv # --- Networks --- diff --git a/visualize_bc.py b/visualize_bc.py deleted file mode 100644 index 2ac0946..0000000 --- a/visualize_bc.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Thin wrapper: forwards to scripts/visualize_trained_policy.py --policy_type bc. -Use: python visualize_bc.py [--model_path models/bc/policy_best.pt] [other args...] -Or call directly: python scripts/visualize_trained_policy.py --policy_type bc --model_path models/bc/policy_best.pt -""" -import subprocess -import sys -import os - -def main(): - script_dir = os.path.dirname(os.path.abspath(__file__)) - script = os.path.join(script_dir, "scripts", "visualize_trained_policy.py") - cmd = [sys.executable, script, "--policy_type", "bc"] + sys.argv[1:] - sys.exit(subprocess.run(cmd).returncode) - -if __name__ == "__main__": - main()