Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a75f0db0d | |||
| 0f9f080e77 | |||
| ceb6648a31 | |||
| 95cc78d940 | |||
| 03dee0205a | |||
| 21c046aef0 | |||
| 265b0eade1 |
54
.gitignore
vendored
54
.gitignore
vendored
@@ -1,3 +1,57 @@
|
|||||||
# 日志文件
|
# 日志文件
|
||||||
Env/logs/
|
Env/logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# 虚拟环境
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# 数据和模型文件
|
||||||
|
data/
|
||||||
|
runs/
|
||||||
|
*.pkl
|
||||||
|
*.h5
|
||||||
|
*.ckpt
|
||||||
|
*.pth
|
||||||
|
*.pt
|
||||||
|
checkpoints/
|
||||||
|
models/
|
||||||
|
|
||||||
|
# 第三方库(如果已安装)
|
||||||
|
metadrive/
|
||||||
|
scenarionet/
|
||||||
|
|
||||||
|
# 系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|||||||
52
Algorithm/bc.py
Normal file
52
Algorithm/bc.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
Behavior Cloning (BC) 算法:仅包含损失与单 epoch 训练/评估逻辑。
|
||||||
|
数据加载、环境评估、日志与保存由训练脚本 (train_bc.py) 负责。
|
||||||
|
"""
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
def bc_loss(policy, states, actions):
|
||||||
|
"""
|
||||||
|
BC 损失:负对数似然 -E[log pi(a|s)]。
|
||||||
|
states: (B, state_dim), actions: (B, action_dim), 均在 policy 所在 device 上。
|
||||||
|
"""
|
||||||
|
log_pi = policy.evaluate_log_pi(states, actions)
|
||||||
|
return -log_pi.mean()
|
||||||
|
|
||||||
|
|
||||||
|
def train_bc_epoch(policy, train_loader, optimizer, device):
|
||||||
|
"""
|
||||||
|
训练一个 epoch,返回平均 train loss。
|
||||||
|
policy 与 optimizer 由调用方管理,本函数只做前向、损失、反向与 step。
|
||||||
|
"""
|
||||||
|
policy.train()
|
||||||
|
total_loss = 0.0
|
||||||
|
n_batches = 0
|
||||||
|
for states, actions in train_loader:
|
||||||
|
states = states.to(device)
|
||||||
|
actions = actions.to(device)
|
||||||
|
loss = bc_loss(policy, states, actions)
|
||||||
|
optimizer.zero_grad()
|
||||||
|
loss.backward()
|
||||||
|
optimizer.step()
|
||||||
|
total_loss += loss.item()
|
||||||
|
n_batches += 1
|
||||||
|
return total_loss / n_batches if n_batches else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def eval_bc_epoch(policy, val_loader, device):
|
||||||
|
"""
|
||||||
|
在验证集上评估一个 epoch,返回平均 val loss(无梯度)。
|
||||||
|
"""
|
||||||
|
policy.eval()
|
||||||
|
total_loss = 0.0
|
||||||
|
n_batches = 0
|
||||||
|
with torch.no_grad():
|
||||||
|
for states, actions in val_loader:
|
||||||
|
states = states.to(device)
|
||||||
|
actions = actions.to(device)
|
||||||
|
log_pi = policy.evaluate_log_pi(states, actions)
|
||||||
|
loss = -log_pi.mean().item()
|
||||||
|
total_loss += loss
|
||||||
|
n_batches += 1
|
||||||
|
return total_loss / n_batches if n_batches else 0.0
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
188
Env/bc_env.py
Normal file
188
Env/bc_env.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
from Env.scenario_env import MultiAgentScenarioEnv
|
||||||
|
from Env.utils import filter_traffic_tracks_to_birth_lists
|
||||||
|
from metadrive.component.vehicle.vehicle_type import DefaultVehicle
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class BCScenarioEnv(MultiAgentScenarioEnv):
|
||||||
|
"""
|
||||||
|
Environment for Behavior Cloning Evaluation.
|
||||||
|
Uses the same 45-dim observation as ExpertReplayEnv:
|
||||||
|
- Ego State (5): x, y, vx, vy, heading
|
||||||
|
- Neighbors (40): 10 nearest * (rel_x, rel_y, vx, vy)
|
||||||
|
|
||||||
|
Spawns background (static) vehicles so that observation distribution matches expert data collection:
|
||||||
|
expert data is generated with ExpertReplayEnv which includes bg_* in active_agents, so the policy
|
||||||
|
was trained on obs that can include those neighbors. Demo should use the same scene for consistency.
|
||||||
|
"""
|
||||||
|
def reset(self, seed=None):
|
||||||
|
# Clear background vehicles from previous episode so engine.reset() passes _object_clean_check
|
||||||
|
if getattr(self, "engine", None) is not None:
|
||||||
|
ids_bg = [
|
||||||
|
oid for oid, obj in self.engine.get_objects().items()
|
||||||
|
if (getattr(obj, "name", None) or getattr(obj, "id", None) or "").startswith("bg_")
|
||||||
|
]
|
||||||
|
if ids_bg:
|
||||||
|
self.engine.clear_objects(ids_bg, force_destroy=True)
|
||||||
|
for aid in list(self.engine.agent_manager.active_agents.keys()):
|
||||||
|
if aid.startswith("bg_"):
|
||||||
|
self.engine.agent_manager.active_agents.pop(aid, None)
|
||||||
|
obs = super().reset(seed=seed)
|
||||||
|
self._spawn_background_vehicles()
|
||||||
|
return self._get_all_obs()
|
||||||
|
|
||||||
|
def _build_birth_lists_from_traffic(self):
|
||||||
|
"""Same lane/static filter as expert data; return background_vehicles so we spawn them (match training obs)."""
|
||||||
|
car_birth_info_list, background_vehicles, obj_to_clean, stats = filter_traffic_tracks_to_birth_lists(
|
||||||
|
self.engine.traffic_manager.current_traffic_data,
|
||||||
|
self.engine.traffic_manager.sdc_scenario_id,
|
||||||
|
self.engine.map_manager,
|
||||||
|
return_stats=True,
|
||||||
|
)
|
||||||
|
if stats["n_controlled"] == 0 and stats["n_total"] > 0:
|
||||||
|
print(
|
||||||
|
"[BCScenarioEnv] 0 controlled agents: total_vehicles={}, off_lane={}, static={}, no_valid={}.".format(
|
||||||
|
stats["n_total"],
|
||||||
|
stats["n_off_lane"],
|
||||||
|
stats["n_static"],
|
||||||
|
stats["n_no_valid"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return car_birth_info_list, background_vehicles, obj_to_clean
|
||||||
|
|
||||||
|
def _spawn_background_vehicles(self):
|
||||||
|
"""Spawn all static background vehicles once at reset (no show_time filter; same as ExpertReplayEnv)."""
|
||||||
|
for sid, car in self.background_vehicles.items():
|
||||||
|
bg_id = f"bg_{car['id']}"
|
||||||
|
if bg_id in self.engine.agent_manager.active_agents:
|
||||||
|
continue
|
||||||
|
vehicle_config = {}
|
||||||
|
if "length" in car and "width" in car:
|
||||||
|
vehicle_config = {"length": car["length"], "width": car["width"]}
|
||||||
|
v = self.engine.spawn_object(
|
||||||
|
DefaultVehicle,
|
||||||
|
name=bg_id,
|
||||||
|
vehicle_config=vehicle_config,
|
||||||
|
position=car["begin"],
|
||||||
|
heading=car["heading"],
|
||||||
|
)
|
||||||
|
v.set_velocity([0, 0])
|
||||||
|
self.engine.agent_manager.active_agents[bg_id] = v
|
||||||
|
v.valid_mask = car.get("valid")
|
||||||
|
v.start_t = car.get("show_time")
|
||||||
|
|
||||||
|
def _update_background_vehicles(self):
|
||||||
|
# Static vehicles are spawned once at init and never removed.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def step(self, action_dict):
|
||||||
|
self.round += 1
|
||||||
|
for agent_id, action in action_dict.items():
|
||||||
|
if agent_id in self.controlled_agents:
|
||||||
|
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:
|
||||||
|
self.controlled_agents[agent_id].after_step()
|
||||||
|
self._spawn_controlled_agents()
|
||||||
|
self._update_background_vehicles()
|
||||||
|
obs = self._get_all_obs()
|
||||||
|
|
||||||
|
# Reward shaping for evaluation/rollout monitoring (BC training itself doesn't use env reward).
|
||||||
|
speed_coef = float(self.config.get("reward_speed_coef", 0.05))
|
||||||
|
collision_distance = float(self.config.get("collision_distance", 6.0))
|
||||||
|
collision_penalty = float(self.config.get("collision_penalty", 100.0))
|
||||||
|
|
||||||
|
# Pre-collect all active vehicles (includes background vehicles).
|
||||||
|
active_agents = list(self.engine.agent_manager.active_agents.items())
|
||||||
|
|
||||||
|
rewards = {}
|
||||||
|
infos = {}
|
||||||
|
for aid, vehicle in self.controlled_agents.items():
|
||||||
|
# Speed reward
|
||||||
|
speed = getattr(vehicle, "speed", None)
|
||||||
|
if speed is None:
|
||||||
|
speed = float(np.linalg.norm(vehicle.velocity))
|
||||||
|
r_speed = speed_coef * float(speed)
|
||||||
|
|
||||||
|
# Near-collision penalty (distance-based, simulator-agnostic)
|
||||||
|
min_dist = float("inf")
|
||||||
|
for other_id, other_vehicle in active_agents:
|
||||||
|
if other_id == aid:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
dist = float(np.linalg.norm(vehicle.position - other_vehicle.position))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if dist < min_dist:
|
||||||
|
min_dist = dist
|
||||||
|
|
||||||
|
near_collision = bool(min_dist < collision_distance)
|
||||||
|
r_collision = -collision_penalty if near_collision else 0.0
|
||||||
|
|
||||||
|
rewards[aid] = float(r_speed + r_collision)
|
||||||
|
infos[aid] = {
|
||||||
|
"near_collision": near_collision,
|
||||||
|
"min_dist": (min_dist if np.isfinite(min_dist) else None),
|
||||||
|
"r_speed": float(r_speed),
|
||||||
|
"r_collision": float(r_collision),
|
||||||
|
}
|
||||||
|
dones = {aid: False for aid in self.controlled_agents}
|
||||||
|
dones["__all__"] = self.episode_step >= self.config["horizon"]
|
||||||
|
return obs, rewards, dones, infos
|
||||||
|
|
||||||
|
def _get_all_obs(self):
|
||||||
|
# Implement custom observation: 30m range, 10 nearest vehicles
|
||||||
|
obs_dict = {}
|
||||||
|
|
||||||
|
for agent_id, vehicle in self.controlled_agents.items():
|
||||||
|
# 1. Ego State
|
||||||
|
ego_state = [
|
||||||
|
vehicle.position[0], vehicle.position[1],
|
||||||
|
vehicle.velocity[0], vehicle.velocity[1],
|
||||||
|
vehicle.heading_theta
|
||||||
|
]
|
||||||
|
|
||||||
|
# 2. Neighbors
|
||||||
|
neighbors = []
|
||||||
|
# Iterate through all vehicles in the engine
|
||||||
|
candidates = []
|
||||||
|
# Use engine.agent_manager.active_agents to find neighbors
|
||||||
|
# Note: This includes background vehicles if they are in active_agents
|
||||||
|
for other_id, other_vehicle in self.engine.agent_manager.active_agents.items():
|
||||||
|
if other_id == agent_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if vehicle is valid/active
|
||||||
|
# (MetaDrive manages active_agents, so they should be active)
|
||||||
|
|
||||||
|
dist = np.linalg.norm(vehicle.position - other_vehicle.position)
|
||||||
|
if dist < 30.0:
|
||||||
|
candidates.append((dist, other_vehicle))
|
||||||
|
|
||||||
|
# Sort by distance
|
||||||
|
candidates.sort(key=lambda x: x[0])
|
||||||
|
|
||||||
|
# Take top 10
|
||||||
|
top_10 = candidates[:10]
|
||||||
|
|
||||||
|
neighbor_feats = []
|
||||||
|
for _, neighbor in top_10:
|
||||||
|
neighbor_feats.extend([
|
||||||
|
neighbor.position[0] - vehicle.position[0], # Relative pos
|
||||||
|
neighbor.position[1] - vehicle.position[1],
|
||||||
|
neighbor.velocity[0], # Absolute vel
|
||||||
|
neighbor.velocity[1]
|
||||||
|
])
|
||||||
|
|
||||||
|
# Pad if < 10
|
||||||
|
missing = 10 - len(top_10)
|
||||||
|
if missing > 0:
|
||||||
|
neighbor_feats.extend([0.0] * (4 * missing))
|
||||||
|
|
||||||
|
# Flatten
|
||||||
|
obs = np.array(ego_state + neighbor_feats, dtype=np.float32)
|
||||||
|
obs_dict[agent_id] = obs
|
||||||
|
|
||||||
|
return obs_dict
|
||||||
@@ -35,132 +35,44 @@ class ExpertReplayEnv(MultiAgentScenarioEnv):
|
|||||||
if self.engine is None:
|
if self.engine is None:
|
||||||
raise ValueError("Broken MetaDrive instance.")
|
raise ValueError("Broken MetaDrive instance.")
|
||||||
|
|
||||||
self.background_vehicles = {} # Vehicles that exist but are static/background
|
self.background_vehicles = {}
|
||||||
|
|
||||||
# Helper function to check if a position is on a valid lane
|
|
||||||
def is_on_lane(pos, map_manager, threshold=2.0):
|
|
||||||
# Check if point is close to any lane in the road network
|
|
||||||
# This can be expensive if checked for every point, so we check sample points
|
|
||||||
# or rely on lane index if available.
|
|
||||||
# Waymo tracks don't have lane index, just positions.
|
|
||||||
# We can use map.road_network.get_closest_lane_index(pos)
|
|
||||||
if map_manager is None or map_manager.current_map is None:
|
|
||||||
return True # If no map, assume valid
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Use a larger search radius to catch slightly offset lanes
|
|
||||||
lane, lane_index = map_manager.current_map.road_network.get_closest_lane_index(pos, return_lane=True)
|
|
||||||
if lane is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check lateral distance
|
|
||||||
long, lat = lane.local_coordinates(pos)
|
|
||||||
width = lane.width
|
|
||||||
# Allow being slightly off-lane (e.g. changing lanes)
|
|
||||||
# But parking lots are usually far from defined lanes in Waymo converted maps
|
|
||||||
if abs(lat) <= (width / 2 + threshold):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# --- MODIFIED SECTION START ---
|
|
||||||
# Capture expert tracks before they are cleaned
|
|
||||||
self.expert_tracks = {}
|
self.expert_tracks = {}
|
||||||
# Capture SDC track for ego replay (MetaDrive default agent)
|
|
||||||
self.sdc_track = None
|
self.sdc_track = None
|
||||||
self.sdc_vehicle = None
|
self.sdc_vehicle = None
|
||||||
|
|
||||||
|
# 在加载新场景前,必须清除上一轮通过 spawn_object 生成的物体,否则 engine.reset() 内 _object_clean_check 会报错
|
||||||
|
# 从 engine 当前对象中按名称筛选(与 manager 无关的对象需在此清理),并强制销毁
|
||||||
|
ids_to_clear = []
|
||||||
|
for oid, obj in self.engine.get_objects().items():
|
||||||
|
name = getattr(obj, "name", None) or getattr(obj, "id", None)
|
||||||
|
if name and (str(name).startswith("controlled_") or str(name).startswith("bg_")):
|
||||||
|
ids_to_clear.append(oid)
|
||||||
|
if ids_to_clear:
|
||||||
|
self.engine.clear_objects(ids_to_clear, force_destroy=True)
|
||||||
|
self.controlled_agents.clear()
|
||||||
|
self.controlled_agent_ids.clear()
|
||||||
|
for aid in list(self.engine.agent_manager.active_agents.keys()):
|
||||||
|
if aid.startswith("bg_") or aid.startswith("controlled_"):
|
||||||
|
self.engine.agent_manager.active_agents.pop(aid, None)
|
||||||
|
|
||||||
if self.replay_sdc and hasattr(self.engine, "traffic_manager"):
|
if self.replay_sdc and hasattr(self.engine, "traffic_manager"):
|
||||||
sdc_sid = self.engine.traffic_manager.sdc_scenario_id
|
sdc_sid = self.engine.traffic_manager.sdc_scenario_id
|
||||||
self.sdc_track = self.engine.traffic_manager.current_traffic_data.get(sdc_sid, None)
|
self.sdc_track = self.engine.traffic_manager.current_traffic_data.get(sdc_sid, None)
|
||||||
_obj_to_clean_this_frame = []
|
|
||||||
self.car_birth_info_list = []
|
|
||||||
|
|
||||||
# Pre-filter: Check tracks against map AND check for static vehicles
|
|
||||||
|
|
||||||
for scenario_id, track in self.engine.traffic_manager.current_traffic_data.items():
|
|
||||||
if scenario_id == self.engine.traffic_manager.sdc_scenario_id:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if track["type"] == MetaDriveType.VEHICLE:
|
|
||||||
_obj_to_clean_this_frame.append(scenario_id)
|
|
||||||
|
|
||||||
valid = track['state']['valid']
|
|
||||||
if not valid.any():
|
|
||||||
continue
|
|
||||||
|
|
||||||
first_show = np.argmax(valid)
|
|
||||||
last_show = len(valid) - 1 - np.argmax(valid[::-1])
|
|
||||||
mid_show = (first_show + last_show) // 2
|
|
||||||
|
|
||||||
# 1. Lane check (existing logic)
|
|
||||||
points_to_check = [first_show, mid_show, last_show]
|
|
||||||
on_road_count = 0
|
|
||||||
is_valid_track = True
|
|
||||||
start_pos = track['state']['position'][first_show]
|
|
||||||
if not is_on_lane(start_pos, self.engine.map_manager, threshold=5.0): # 5m tolerance
|
|
||||||
mid_pos = track['state']['position'][mid_show]
|
|
||||||
if not is_on_lane(mid_pos, self.engine.map_manager, threshold=5.0):
|
|
||||||
is_valid_track = False
|
|
||||||
|
|
||||||
# 2. Static check
|
|
||||||
# Calculate total displacement and max speed
|
|
||||||
positions = track['state']['position'][valid.astype(bool)]
|
|
||||||
velocities = track['state']['velocity'][valid.astype(bool)]
|
|
||||||
|
|
||||||
total_displacement = 0
|
|
||||||
max_speed = 0
|
|
||||||
if len(positions) > 1:
|
|
||||||
total_displacement = np.linalg.norm(positions[-1] - positions[0])
|
|
||||||
max_speed = np.max(np.linalg.norm(velocities, axis=1))
|
|
||||||
|
|
||||||
is_static = False
|
|
||||||
if total_displacement < 5.0 and max_speed < 1.0: # Relaxed threshold: <5m move and <1m/s
|
|
||||||
is_static = True
|
|
||||||
|
|
||||||
# Decision logic:
|
|
||||||
# - If off-road AND static: Skip completely (don't even spawn as background)
|
|
||||||
# - If off-road but moving: Maybe keep? Or skip? Usually off-road moving is weird, skip.
|
|
||||||
# - If on-road but static: Spawn as BACKGROUND (visible but not controlled agent)
|
|
||||||
# - If on-road and moving: Spawn as CONTROLLED agent
|
|
||||||
|
|
||||||
if not is_valid_track:
|
|
||||||
# Skip off-road vehicles entirely (both static and moving off-road)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if is_static:
|
|
||||||
# Add to background list, but NOT to car_birth_info_list (which is for controlled agents)
|
|
||||||
# We need a way to spawn them. Let's add a separate list.
|
|
||||||
self.background_vehicles[scenario_id] = {
|
|
||||||
'id': track['metadata']['object_id'],
|
|
||||||
'show_time': first_show,
|
|
||||||
'begin': (track['state']['position'][first_show, 0], track['state']['position'][first_show, 1]),
|
|
||||||
'heading': track['state']['heading'][first_show],
|
|
||||||
'end': (track['state']['position'][last_show, 0], track['state']['position'][last_show, 1]),
|
|
||||||
'scenario_id': scenario_id,
|
|
||||||
'length': track['state']['length'][first_show],
|
|
||||||
'width': track['state']['width'][first_show],
|
|
||||||
'valid': valid # Need validity to know when to show/hide
|
|
||||||
}
|
|
||||||
continue # Do not add to controlled list
|
|
||||||
|
|
||||||
# Store the full track for replay (only for controlled agents)
|
from Env.utils import filter_traffic_tracks_to_birth_lists
|
||||||
self.expert_tracks[scenario_id] = track
|
traffic_data = self.engine.traffic_manager.current_traffic_data
|
||||||
|
car_birth_info_list, self.background_vehicles, obj_to_clean = filter_traffic_tracks_to_birth_lists(
|
||||||
self.car_birth_info_list.append({
|
traffic_data,
|
||||||
'id': track['metadata']['object_id'],
|
self.engine.traffic_manager.sdc_scenario_id,
|
||||||
'show_time': first_show,
|
self.engine.map_manager,
|
||||||
'begin': (track['state']['position'][first_show, 0], track['state']['position'][first_show, 1]),
|
)
|
||||||
'heading': track['state']['heading'][first_show],
|
for entry in car_birth_info_list:
|
||||||
'end': (track['state']['position'][last_show, 0], track['state']['position'][last_show, 1]),
|
sid = entry["scenario_id"]
|
||||||
'scenario_id': scenario_id, # Keep track of original ID to lookup tracks
|
if sid in traffic_data:
|
||||||
'length': track['state']['length'][first_show],
|
self.expert_tracks[sid] = traffic_data[sid]
|
||||||
'width': track['state']['width'][first_show]
|
self.car_birth_info_list = car_birth_info_list
|
||||||
})
|
for scenario_id in obj_to_clean:
|
||||||
|
|
||||||
for scenario_id in _obj_to_clean_this_frame:
|
|
||||||
self.engine.traffic_manager.current_traffic_data.pop(scenario_id)
|
self.engine.traffic_manager.current_traffic_data.pop(scenario_id)
|
||||||
# --- MODIFIED SECTION END ---
|
|
||||||
|
|
||||||
self.engine.reset()
|
self.engine.reset()
|
||||||
self.reset_sensors()
|
self.reset_sensors()
|
||||||
@@ -185,7 +97,7 @@ class ExpertReplayEnv(MultiAgentScenarioEnv):
|
|||||||
# We covered most of it.
|
# We covered most of it.
|
||||||
|
|
||||||
self._spawn_controlled_agents()
|
self._spawn_controlled_agents()
|
||||||
self._spawn_background_vehicles() # Initial spawn for background
|
self._spawn_all_background_vehicles_at_init()
|
||||||
|
|
||||||
# Ensure SDC/ego is moved to the correct initial expert state.
|
# Ensure SDC/ego is moved to the correct initial expert state.
|
||||||
if self.replay_sdc:
|
if self.replay_sdc:
|
||||||
@@ -202,119 +114,33 @@ class ExpertReplayEnv(MultiAgentScenarioEnv):
|
|||||||
|
|
||||||
return self._get_all_obs()
|
return self._get_all_obs()
|
||||||
|
|
||||||
def _spawn_background_vehicles(self):
|
def _spawn_all_background_vehicles_at_init(self):
|
||||||
# Spawn static/background vehicles
|
"""Spawn all static background vehicles once at reset (no show_time filter; no removal by valid)."""
|
||||||
# Since they are static, we might just spawn them once if their show_time is 0
|
|
||||||
# But Waymo tracks have valid bits, they might appear/disappear.
|
|
||||||
# For optimization, if they are truly static (never move), we just spawn them when show_time matches.
|
|
||||||
|
|
||||||
# We need to track spawned background vehicles to remove them if they become invalid?
|
|
||||||
# Since we defined them as "static", they probably stay put.
|
|
||||||
# But validity might change (e.g. late spawn).
|
|
||||||
|
|
||||||
# For simplicity in this step, let's just iterate and spawn if time matches
|
|
||||||
for sid, car in self.background_vehicles.items():
|
for sid, car in self.background_vehicles.items():
|
||||||
if car['show_time'] == self.round:
|
bg_id = f"bg_{car['id']}"
|
||||||
# Spawn as a Traffic Vehicle (not PolicyVehicle), or just a static object?
|
if bg_id in self.engine.agent_manager.active_agents:
|
||||||
# Using DefaultVehicle is fine, but don't add to controlled_agents
|
continue
|
||||||
|
vehicle_config = {}
|
||||||
# Check duplication
|
if 'length' in car and 'width' in car:
|
||||||
bg_id = f"bg_{car['id']}"
|
vehicle_config = {
|
||||||
# if bg_id in self.engine.obj_to_id: # obj_to_id might not be available in all versions
|
"length": car['length'],
|
||||||
if bg_id in self.engine.agent_manager.active_agents:
|
"width": car['width']
|
||||||
continue
|
}
|
||||||
|
v = self.engine.spawn_object(
|
||||||
vehicle_config = {}
|
DefaultVehicle,
|
||||||
if 'length' in car and 'width' in car:
|
name=bg_id,
|
||||||
vehicle_config = {
|
vehicle_config=vehicle_config,
|
||||||
"length": car['length'],
|
position=car['begin'],
|
||||||
"width": car['width']
|
heading=car['heading']
|
||||||
}
|
)
|
||||||
|
v.set_velocity([0, 0])
|
||||||
v = self.engine.spawn_object(
|
self.engine.agent_manager.active_agents[bg_id] = v
|
||||||
DefaultVehicle,
|
v.valid_mask = car.get('valid')
|
||||||
name=bg_id,
|
v.start_t = car['show_time']
|
||||||
vehicle_config=vehicle_config,
|
|
||||||
position=car['begin'],
|
|
||||||
heading=car['heading']
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set color to grey/dark to indicate background
|
|
||||||
v.set_velocity([0, 0])
|
|
||||||
# Maybe set color? MetaDrive vehicles random color.
|
|
||||||
# v.set_color(...) if supported
|
|
||||||
|
|
||||||
# Register as an active object but NOT controlled agent
|
|
||||||
# The engine manages it.
|
|
||||||
# CRITICAL: We need it in self.engine.agent_manager.active_agents for Observation?
|
|
||||||
# If we want it to be seen by Lidar/Observation, it needs to be an "agent" or "traffic".
|
|
||||||
# DefaultVehicle spawned this way is just an object.
|
|
||||||
# We should add it to traffic manager? Or just leave it as object?
|
|
||||||
# MultiAgentScenarioEnv._get_all_obs iterates self.engine.agent_manager.active_agents
|
|
||||||
|
|
||||||
# If we want it in observation, we must add it to active_agents OR iterate over all objects.
|
|
||||||
# Adding to active_agents is easier for compatibility.
|
|
||||||
self.engine.agent_manager.active_agents[bg_id] = v
|
|
||||||
|
|
||||||
# Store valid mask to remove it later if needed?
|
|
||||||
v.valid_mask = car['valid']
|
|
||||||
v.start_t = car['show_time']
|
|
||||||
|
|
||||||
def _update_background_vehicles(self):
|
def _update_background_vehicles(self):
|
||||||
# Remove background vehicles if they become invalid
|
# Static vehicles are spawned once at init and never removed (no spawn/remove by show_time or valid).
|
||||||
# Or spawn new ones
|
pass
|
||||||
self._spawn_background_vehicles()
|
|
||||||
|
|
||||||
# Check validity for existing
|
|
||||||
to_remove = []
|
|
||||||
for aid, v in self.engine.agent_manager.active_agents.items():
|
|
||||||
if aid.startswith("bg_"):
|
|
||||||
# Check validity
|
|
||||||
if hasattr(v, 'valid_mask'):
|
|
||||||
curr_step = self.round
|
|
||||||
if curr_step >= len(v.valid_mask) or not v.valid_mask[curr_step]:
|
|
||||||
to_remove.append(aid)
|
|
||||||
|
|
||||||
for aid in to_remove:
|
|
||||||
self.engine.agent_manager.active_agents.pop(aid, None)
|
|
||||||
# if aid in self.engine.obj_to_id:
|
|
||||||
# self.engine.clear_objects([self.engine.obj_to_id[aid]])
|
|
||||||
# Instead, we should find the object by ID and clear it.
|
|
||||||
# Since we don't track obj directly, we can't easily clear it without obj ref.
|
|
||||||
# Wait, active_agents stores the vehicle object.
|
|
||||||
# So we can just clear that object.
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Re-iterate to clear objects properly
|
|
||||||
for aid in to_remove:
|
|
||||||
# We need to find the vehicle object to clear it.
|
|
||||||
# But we popped it from active_agents.
|
|
||||||
# Wait, we should get it before pop.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _update_background_vehicles(self):
|
|
||||||
# Remove background vehicles if they become invalid
|
|
||||||
# Or spawn new ones
|
|
||||||
self._spawn_background_vehicles()
|
|
||||||
|
|
||||||
# Check validity for existing
|
|
||||||
to_remove = []
|
|
||||||
objects_to_clear = []
|
|
||||||
|
|
||||||
for aid, v in self.engine.agent_manager.active_agents.items():
|
|
||||||
if aid.startswith("bg_"):
|
|
||||||
# Check validity
|
|
||||||
if hasattr(v, 'valid_mask'):
|
|
||||||
curr_step = self.round
|
|
||||||
if curr_step >= len(v.valid_mask) or not v.valid_mask[curr_step]:
|
|
||||||
to_remove.append(aid)
|
|
||||||
objects_to_clear.append(v)
|
|
||||||
|
|
||||||
for aid in to_remove:
|
|
||||||
self.engine.agent_manager.active_agents.pop(aid, None)
|
|
||||||
|
|
||||||
if objects_to_clear:
|
|
||||||
self.engine.clear_objects(objects_to_clear)
|
|
||||||
|
|
||||||
def _spawn_controlled_agents(self):
|
def _spawn_controlled_agents(self):
|
||||||
for car in self.car_birth_info_list:
|
for car in self.car_birth_info_list:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import numpy as np
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
class InverseDynamics:
|
class InverseDynamics:
|
||||||
def __init__(self, max_steering=0.7, max_acc=15.0, length=4.5):
|
def __init__(self, max_steering=0.7, max_acc=8.0, length=4.5):
|
||||||
"""
|
"""
|
||||||
:param max_steering: Max steering angle in radians (approx 40 degrees)
|
:param max_steering: Max steering angle in radians (approx 40 degrees)
|
||||||
:param max_acc: Max acceleration in m/s^2
|
:param max_acc: Max acceleration in m/s^2
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ class MultiAgentScenarioEnv(ScenarioEnv):
|
|||||||
self.round = 0
|
self.round = 0
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_controlled_in_scenario(self) -> int:
|
||||||
|
"""整个场景中受控车轨迹总数(car_birth_info_list 长度),会在不同 show_time 陆续 spawn。"""
|
||||||
|
return len(getattr(self, "car_birth_info_list", []))
|
||||||
|
|
||||||
def reset(self, seed: Union[None, int] = None):
|
def reset(self, seed: Union[None, int] = None):
|
||||||
self.round = 0
|
self.round = 0
|
||||||
if self.logger is None:
|
if self.logger is None:
|
||||||
@@ -76,30 +81,23 @@ class MultiAgentScenarioEnv(ScenarioEnv):
|
|||||||
if self.engine is None:
|
if self.engine is None:
|
||||||
raise ValueError("Broken MetaDrive instance.")
|
raise ValueError("Broken MetaDrive instance.")
|
||||||
|
|
||||||
# 记录专家数据中每辆车的位置,接着全部清除,只保留位置等信息,用于后续生成
|
# 注意:_build_birth_lists_from_traffic() 在 engine.reset() 之前执行,读的是当前 engine 的
|
||||||
_obj_to_clean_this_frame = []
|
# current_traffic_data 与 map_manager.current_map。若复用同一 env 连续 reset(0)、reset(1),
|
||||||
self.car_birth_info_list = []
|
# MetaDrive 可能已按 seed 更新了 traffic 为 scenario 1,但 map 仍为 scenario 0(在 engine.reset() 才切图),
|
||||||
for scenario_id, track in self.engine.traffic_manager.current_traffic_data.items():
|
# 导致 is_on_lane( scenario_1 车位, scenario_0 地图 ) 全为 False → 全部 off_lane → 0 受控车。
|
||||||
if scenario_id == self.engine.traffic_manager.sdc_scenario_id:
|
# 因此多场景时应“每个 scenario 单独建 env”(start_scenario_index=i, num_scenarios=1)再 reset(seed=i)。
|
||||||
continue
|
self.background_vehicles = getattr(self, "background_vehicles", {})
|
||||||
else:
|
self.car_birth_info_list, self.background_vehicles, _obj_to_clean = self._build_birth_lists_from_traffic()
|
||||||
if track["type"] == MetaDriveType.VEHICLE:
|
for scenario_id in _obj_to_clean:
|
||||||
_obj_to_clean_this_frame.append(scenario_id)
|
|
||||||
valid = track['state']['valid']
|
|
||||||
first_show = np.argmax(valid) if valid.any() else -1
|
|
||||||
last_show = len(valid) - 1 - np.argmax(valid[::-1]) if valid.any() else -1
|
|
||||||
# id,出现时间,出生点坐标,出生朝向,目的地
|
|
||||||
self.car_birth_info_list.append({
|
|
||||||
'id': track['metadata']['object_id'],
|
|
||||||
'show_time': first_show,
|
|
||||||
'begin': (track['state']['position'][first_show, 0], track['state']['position'][first_show, 1]),
|
|
||||||
'heading': track['state']['heading'][first_show],
|
|
||||||
'end': (track['state']['position'][last_show, 0], track['state']['position'][last_show, 1])
|
|
||||||
})
|
|
||||||
|
|
||||||
for scenario_id in _obj_to_clean_this_frame:
|
|
||||||
self.engine.traffic_manager.current_traffic_data.pop(scenario_id)
|
self.engine.traffic_manager.current_traffic_data.pop(scenario_id)
|
||||||
|
|
||||||
|
# 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.engine.reset()
|
||||||
self.reset_sensors()
|
self.reset_sensors()
|
||||||
self.engine.taskMgr.step()
|
self.engine.taskMgr.step()
|
||||||
@@ -114,14 +112,32 @@ class MultiAgentScenarioEnv(ScenarioEnv):
|
|||||||
self.episode_rewards = defaultdict(float)
|
self.episode_rewards = defaultdict(float)
|
||||||
self.episode_lengths = defaultdict(int)
|
self.episode_lengths = defaultdict(int)
|
||||||
|
|
||||||
self.controlled_agents.clear()
|
|
||||||
self.controlled_agent_ids.clear()
|
|
||||||
|
|
||||||
super().reset(seed) # 初始化场景
|
super().reset(seed) # 初始化场景
|
||||||
self._spawn_controlled_agents()
|
self._spawn_controlled_agents()
|
||||||
|
|
||||||
return self._get_all_obs()
|
return self._get_all_obs()
|
||||||
|
|
||||||
|
def _build_birth_lists_from_traffic(self):
|
||||||
|
"""Build car_birth_info_list and obj_to_clean from current_traffic_data. Override for filtered (lane/static) selection."""
|
||||||
|
_obj_to_clean_this_frame = []
|
||||||
|
car_birth_info_list = []
|
||||||
|
for scenario_id, track in self.engine.traffic_manager.current_traffic_data.items():
|
||||||
|
if scenario_id == self.engine.traffic_manager.sdc_scenario_id:
|
||||||
|
continue
|
||||||
|
if track["type"] == MetaDriveType.VEHICLE:
|
||||||
|
_obj_to_clean_this_frame.append(scenario_id)
|
||||||
|
valid = track["state"]["valid"]
|
||||||
|
first_show = int(np.argmax(valid)) if valid.any() else -1
|
||||||
|
last_show = len(valid) - 1 - int(np.argmax(valid[::-1])) if valid.any() else -1
|
||||||
|
car_birth_info_list.append({
|
||||||
|
"id": track["metadata"]["object_id"],
|
||||||
|
"show_time": first_show,
|
||||||
|
"begin": (track["state"]["position"][first_show, 0], track["state"]["position"][first_show, 1]),
|
||||||
|
"heading": track["state"]["heading"][first_show],
|
||||||
|
"end": (track["state"]["position"][last_show, 0], track["state"]["position"][last_show, 1]),
|
||||||
|
})
|
||||||
|
return car_birth_info_list, {}, _obj_to_clean_this_frame
|
||||||
|
|
||||||
def _spawn_controlled_agents(self):
|
def _spawn_controlled_agents(self):
|
||||||
# ego_vehicle = self.engine.agent_manager.active_agents.get("default_agent")
|
# ego_vehicle = self.engine.agent_manager.active_agents.get("default_agent")
|
||||||
# ego_position = ego_vehicle.position if ego_vehicle else np.array([0, 0])
|
# ego_position = ego_vehicle.position if ego_vehicle else np.array([0, 0])
|
||||||
@@ -190,6 +206,7 @@ class MultiAgentScenarioEnv(ScenarioEnv):
|
|||||||
self.controlled_agents[agent_id].before_step(action)
|
self.controlled_agents[agent_id].before_step(action)
|
||||||
|
|
||||||
self.engine.step()
|
self.engine.step()
|
||||||
|
self.engine.after_step()
|
||||||
|
|
||||||
for agent_id in action_dict:
|
for agent_id in action_dict:
|
||||||
if agent_id in self.controlled_agents:
|
if agent_id in self.controlled_agents:
|
||||||
|
|||||||
221
Env/utils.py
221
Env/utils.py
@@ -2,6 +2,227 @@ import numpy as np
|
|||||||
import torch
|
import torch
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from metadrive.type import MetaDriveType
|
||||||
|
|
||||||
|
|
||||||
|
def _static_obbs_overlap(car_a, car_b):
|
||||||
|
"""
|
||||||
|
Check if two static vehicle OBBs overlap (2D SAT).
|
||||||
|
car_a, car_b: dicts with "begin" (x, y), "heading" (rad), "length", "width".
|
||||||
|
begin is center; half-extents are length/2, width/2.
|
||||||
|
"""
|
||||||
|
def _get_corners(car):
|
||||||
|
cx, cy = car["begin"][0], car["begin"][1]
|
||||||
|
h = float(car["heading"])
|
||||||
|
L2 = float(car["length"]) / 2.0
|
||||||
|
W2 = float(car["width"]) / 2.0
|
||||||
|
ux, uy = np.cos(h), np.sin(h)
|
||||||
|
vx, vy = -np.sin(h), np.cos(h)
|
||||||
|
return np.array([
|
||||||
|
[cx + L2 * ux + W2 * vx, cy + L2 * uy + W2 * vy],
|
||||||
|
[cx + L2 * ux - W2 * vx, cy + L2 * uy - W2 * vy],
|
||||||
|
[cx - L2 * ux - W2 * vx, cy - L2 * uy - W2 * vy],
|
||||||
|
[cx - L2 * ux + W2 * vx, cy - L2 * uy + W2 * vy],
|
||||||
|
])
|
||||||
|
|
||||||
|
def _get_axes(car):
|
||||||
|
h = float(car["heading"])
|
||||||
|
return [
|
||||||
|
np.array([np.cos(h), np.sin(h)]),
|
||||||
|
np.array([-np.sin(h), np.cos(h)]),
|
||||||
|
]
|
||||||
|
|
||||||
|
corners_a = _get_corners(car_a)
|
||||||
|
corners_b = _get_corners(car_b)
|
||||||
|
axes = _get_axes(car_a) + _get_axes(car_b)
|
||||||
|
|
||||||
|
for axis in axes:
|
||||||
|
proj_a = corners_a @ axis
|
||||||
|
proj_b = corners_b @ axis
|
||||||
|
min_a, max_a = proj_a.min(), proj_a.max()
|
||||||
|
min_b, max_b = proj_b.min(), proj_b.max()
|
||||||
|
if max_a < min_b or max_b < min_a:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _deduplicate_background_by_collision(background_vehicles):
|
||||||
|
"""
|
||||||
|
Merge static tracks that collide (same physical vehicle). Build collision graph,
|
||||||
|
find connected components, keep one representative per component (min show_time, then scenario_id).
|
||||||
|
"""
|
||||||
|
if not background_vehicles:
|
||||||
|
return background_vehicles
|
||||||
|
items = list(background_vehicles.items())
|
||||||
|
n = len(items)
|
||||||
|
# Build adjacency by index
|
||||||
|
parent = list(range(n))
|
||||||
|
|
||||||
|
def find(i):
|
||||||
|
if parent[i] != i:
|
||||||
|
parent[i] = find(parent[i])
|
||||||
|
return parent[i]
|
||||||
|
|
||||||
|
def union(i, j):
|
||||||
|
pi, pj = find(i), find(j)
|
||||||
|
if pi != pj:
|
||||||
|
parent[pi] = pj
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(i + 1, n):
|
||||||
|
if _static_obbs_overlap(items[i][1], items[j][1]):
|
||||||
|
union(i, j)
|
||||||
|
|
||||||
|
# Representative per component: index with min (show_time, scenario_id)
|
||||||
|
comp_rep = {}
|
||||||
|
for i in range(n):
|
||||||
|
r = find(i)
|
||||||
|
sid, car = items[i][0], items[i][1]
|
||||||
|
key = (car.get("show_time", 0), sid)
|
||||||
|
if r not in comp_rep or key < comp_rep[r][0]:
|
||||||
|
comp_rep[r] = (key, sid, car)
|
||||||
|
|
||||||
|
return {sid: car for (_, sid, car) in comp_rep.values()}
|
||||||
|
|
||||||
|
|
||||||
|
def is_on_lane(pos, map_manager, threshold=2.0):
|
||||||
|
"""Check if a position is on a valid lane (within lateral tolerance)."""
|
||||||
|
if map_manager is None or map_manager.current_map is None:
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
lane, _ = map_manager.current_map.road_network.get_closest_lane_index(pos, return_lane=True)
|
||||||
|
if lane is None:
|
||||||
|
return False
|
||||||
|
long, lat = lane.local_coordinates(pos)
|
||||||
|
width = lane.width
|
||||||
|
if abs(lat) <= (width / 2 + threshold):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def filter_traffic_tracks_to_birth_lists(
|
||||||
|
current_traffic_data,
|
||||||
|
sdc_scenario_id,
|
||||||
|
map_manager,
|
||||||
|
*,
|
||||||
|
lane_threshold=5.0,
|
||||||
|
static_displacement_threshold=5.0,
|
||||||
|
static_speed_threshold=1.0,
|
||||||
|
return_stats=False,
|
||||||
|
deduplicate_static_by_collision=True,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Filter traffic tracks into controlled (car_birth_info_list) and background lists.
|
||||||
|
|
||||||
|
- controlled (car_birth_info_list): 非 SDC、类型 VEHICLE、至少一帧 valid、在车道内、且非静态
|
||||||
|
(位移/速度超过阈值)。用于策略控制或专家回放,spawn 时机为 show_time == round。
|
||||||
|
- background (background_vehicles): 同上但在车道内且判定为静态(位移 < 5m、速度 < 1 m/s)。
|
||||||
|
仅作场景占位与观测邻居,spawn 时机为 show_time == round,按 valid 在 step 中移除。
|
||||||
|
|
||||||
|
Returns (car_birth_info_list, background_vehicles, obj_to_clean) or, if return_stats=True,
|
||||||
|
(car_birth_info_list, background_vehicles, obj_to_clean, stats_dict).
|
||||||
|
stats_dict: n_total, n_no_valid, n_off_lane, n_static, n_controlled.
|
||||||
|
"""
|
||||||
|
car_birth_info_list = []
|
||||||
|
background_vehicles = {}
|
||||||
|
obj_to_clean = []
|
||||||
|
n_total = 0
|
||||||
|
n_no_valid = 0
|
||||||
|
n_off_lane = 0
|
||||||
|
n_static = 0
|
||||||
|
|
||||||
|
for scenario_id, track in current_traffic_data.items():
|
||||||
|
if scenario_id == sdc_scenario_id:
|
||||||
|
continue
|
||||||
|
if track["type"] != MetaDriveType.VEHICLE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
n_total += 1
|
||||||
|
obj_to_clean.append(scenario_id)
|
||||||
|
valid = track["state"]["valid"]
|
||||||
|
if not valid.any():
|
||||||
|
n_no_valid += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
first_show = int(np.argmax(valid))
|
||||||
|
last_show = len(valid) - 1 - int(np.argmax(valid[::-1]))
|
||||||
|
mid_show = (first_show + last_show) // 2
|
||||||
|
|
||||||
|
start_pos = track["state"]["position"][first_show]
|
||||||
|
is_valid_track = True
|
||||||
|
if not is_on_lane(start_pos, map_manager, threshold=lane_threshold):
|
||||||
|
mid_pos = track["state"]["position"][mid_show]
|
||||||
|
if not is_on_lane(mid_pos, map_manager, threshold=lane_threshold):
|
||||||
|
is_valid_track = False
|
||||||
|
|
||||||
|
if not is_valid_track:
|
||||||
|
n_off_lane += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
positions = track["state"]["position"][valid.astype(bool)]
|
||||||
|
velocities = track["state"]["velocity"][valid.astype(bool)]
|
||||||
|
total_displacement = 0.0
|
||||||
|
max_speed = 0.0
|
||||||
|
if len(positions) > 1:
|
||||||
|
total_displacement = float(np.linalg.norm(positions[-1] - positions[0]))
|
||||||
|
max_speed = float(np.max(np.linalg.norm(velocities, axis=1)))
|
||||||
|
is_static = total_displacement < static_displacement_threshold and max_speed < static_speed_threshold
|
||||||
|
|
||||||
|
if is_static:
|
||||||
|
n_static += 1
|
||||||
|
background_vehicles[scenario_id] = {
|
||||||
|
"id": track["metadata"]["object_id"],
|
||||||
|
"show_time": first_show,
|
||||||
|
"begin": (
|
||||||
|
float(track["state"]["position"][first_show, 0]),
|
||||||
|
float(track["state"]["position"][first_show, 1]),
|
||||||
|
),
|
||||||
|
"heading": float(track["state"]["heading"][first_show]),
|
||||||
|
"end": (
|
||||||
|
float(track["state"]["position"][last_show, 0]),
|
||||||
|
float(track["state"]["position"][last_show, 1]),
|
||||||
|
),
|
||||||
|
"scenario_id": scenario_id,
|
||||||
|
"length": track["state"]["length"][first_show],
|
||||||
|
"width": track["state"]["width"][first_show],
|
||||||
|
"valid": valid,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
car_birth_info_list.append({
|
||||||
|
"id": track["metadata"]["object_id"],
|
||||||
|
"show_time": first_show,
|
||||||
|
"begin": (
|
||||||
|
float(track["state"]["position"][first_show, 0]),
|
||||||
|
float(track["state"]["position"][first_show, 1]),
|
||||||
|
),
|
||||||
|
"heading": float(track["state"]["heading"][first_show]),
|
||||||
|
"end": (
|
||||||
|
float(track["state"]["position"][last_show, 0]),
|
||||||
|
float(track["state"]["position"][last_show, 1]),
|
||||||
|
),
|
||||||
|
"scenario_id": scenario_id,
|
||||||
|
"length": track["state"]["length"][first_show],
|
||||||
|
"width": track["state"]["width"][first_show],
|
||||||
|
})
|
||||||
|
|
||||||
|
if deduplicate_static_by_collision and background_vehicles:
|
||||||
|
background_vehicles = _deduplicate_background_by_collision(background_vehicles)
|
||||||
|
|
||||||
|
if return_stats:
|
||||||
|
stats = {
|
||||||
|
"n_total": n_total,
|
||||||
|
"n_no_valid": n_no_valid,
|
||||||
|
"n_off_lane": n_off_lane,
|
||||||
|
"n_static": n_static,
|
||||||
|
"n_controlled": len(car_birth_info_list),
|
||||||
|
}
|
||||||
|
return car_birth_info_list, background_vehicles, obj_to_clean, stats
|
||||||
|
return car_birth_info_list, background_vehicles, obj_to_clean
|
||||||
|
|
||||||
|
|
||||||
def set_seed(seed):
|
def set_seed(seed):
|
||||||
if seed == -1:
|
if seed == -1:
|
||||||
seed = np.random.randint(0, 10000)
|
seed = np.random.randint(0, 10000)
|
||||||
|
|||||||
177
README.md
177
README.md
@@ -1,98 +1,125 @@
|
|||||||
# MAGAIL4AutoDrive
|
# MAGAIL4AutoDrive
|
||||||
|
|
||||||
> 基于多智能体生成对抗模仿学习(MAGAIL)的自动驾驶训练系统 | MetaDrive + Waymo Open Motion Dataset
|
基于 **MetaDrive** 仿真器和 **Waymo Open Motion Dataset** 的自动驾驶多智能体模仿学习(MAGAIL)与行为克隆(BC)训练系统。
|
||||||
|
|
||||||
本项目利用 Waymo 真实驾驶数据,通过 MetaDrive 仿真环境构建专家回放系统,提取车辆状态与动作,用于训练多智能体模仿学习算法 (MAGAIL)。
|
本项目旨在从真实的 Waymo 驾驶数据中提取专家轨迹,并通过模仿学习(Imitation Learning)训练能够适应复杂交互场景的自动驾驶策略。
|
||||||
|
|
||||||
## 📁 核心模块
|
## 目录结构
|
||||||
|
|
||||||
* **`Env/expert_replay_env.py`**: 专家回放环境。核心类 `ExpertReplayEnv`,负责读取 Waymo 轨迹,计算逆动力学动作,并过滤非道路/静态车辆。
|
```text
|
||||||
* **`Env/inverse_dynamics.py`**: 逆动力学模块。根据车辆位置和航向计算油门、刹车和转向动作。
|
MAGAIL4AutoDrive/
|
||||||
* **`scripts/generate_expert_data.py`**: 数据收集脚本。批量运行场景并保存训练数据。
|
├── Algorithm/ # 强化学习与模仿学习算法实现
|
||||||
* **`scripts/visualize_replay.py`**: 可视化脚本。用于观察回放效果和数据质量。
|
│ ├── policy.py # 基础策略网络 (MLP 等)
|
||||||
|
│ ├── ppo.py # PPO 算法实现
|
||||||
***
|
│ ├── magail.py # MAGAIL 算法核心逻辑
|
||||||
|
│ ├── disc.py # 判别器 (Discriminator) 网络
|
||||||
## 🚀 1. 数据收集
|
│ └── ...
|
||||||
|
├── Env/ # 仿真环境封装 (MetaDrive Wrapper)
|
||||||
### 生成专家数据
|
│ ├── bc_env.py # BCScenarioEnv,45 维观测(BC/MAGAIL 共用)
|
||||||
使用 `generate_expert_data.py` 脚本从 Waymo 数据集中批量提取 (State, Action) 对。
|
│ ├── scenario_env.py # 多智能体基础场景环境
|
||||||
|
│ ├── expert_replay_env.py # 专家轨迹回放环境(数据生成与回放)
|
||||||
```bash
|
│ ├── inverse_dynamics.py # 逆动力学模块 (轨迹 -> 动作)
|
||||||
# 设置 Python 路径
|
│ ├── simple_idm_policy.py # ConstantVelocityPolicy 占位策略
|
||||||
export PYTHONPATH=$PYTHONPATH:.:./metadrive
|
│ └── ...
|
||||||
|
├── dataset/ # 数据集加载器
|
||||||
# 运行生成脚本
|
│ ├── loader.py # 主流水线:load_expert_pkl、MAGAILExpertDataset
|
||||||
# --data_dir: Waymo 数据路径 (建议使用 exp_filtered)
|
│ └── expert_dataset.py # 可选 107 维/5 维管线
|
||||||
# --output_dir: 结果保存路径
|
├── scripts/ # 工具脚本(数据、回放、可视化、分析)
|
||||||
# --num_scenarios: 要处理的场景数量
|
│ ├── generate_expert_data.py # 从 Waymo 生成专家 (obs, act) pkl
|
||||||
python scripts/generate_expert_data.py \
|
│ ├── visualize.py # 可视化统一入口(replay / policy / trajectory)
|
||||||
--data_dir data/exp_filtered \
|
│ ├── analyze_expert_data.py # 数据分布分析
|
||||||
--output_dir data/training_data \
|
│ ├── launch_tensorboard.py # 启动 TensorBoard
|
||||||
--num_scenarios 100 \
|
│ ├── README.md # 脚本用法说明
|
||||||
--start_index 0
|
│ └── ...
|
||||||
|
├── data/ # 数据目录(相对路径)
|
||||||
|
│ ├── exp_filtered/ # Waymo 场景数据
|
||||||
|
│ ├── training_data/ # 专家 pkl 输出(generate_expert_data)
|
||||||
|
│ └── trajectories/ # 其他轨迹 pkl(如 expert_dataset 输出)
|
||||||
|
├── models/ # 模型保存目录(相对路径)
|
||||||
|
│ ├── bc/ # BC 模型 (.pt)
|
||||||
|
│ └── magail/ # MAGAIL 模型 (*_actor.pth, *_critic.pth)
|
||||||
|
├── logs/ # 训练日志 (TensorBoard)
|
||||||
|
│ ├── bc/
|
||||||
|
│ └── magail/
|
||||||
|
├── train_bc.py # [根目录] BC 训练
|
||||||
|
├── train_magail.py # [根目录] MAGAIL 训练
|
||||||
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
**生成的 `.pkl` 文件结构**:
|
## 路径约定(相对项目根)
|
||||||
包含一个列表,每个元素是一条车辆轨迹(Trajectory Dictionary):
|
|
||||||
* `obs`: `(T, 45)` - 观测矩阵。包含 Ego 状态 (5维) + 10辆邻居车相对信息 (40维)。
|
|
||||||
* `acts`: `(T, 2)` - 动作矩阵。`[Steering, Accel]`,归一化到 `[-1, 1]`。
|
|
||||||
* `agent_id`: 车辆 ID。
|
|
||||||
* `scenario_id`: 所属场景 ID。
|
|
||||||
|
|
||||||
**内置过滤器**:
|
- **数据**:Waymo 场景 `data/exp_filtered`;专家 pkl `data/training_data`;其他轨迹 `data/trajectories`
|
||||||
脚本会自动过滤掉以下无效车辆:
|
- **模型**:BC `models/bc/`,MAGAIL `models/magail/`
|
||||||
1. **非道路车辆**:始终在停车场或路外行驶的车辆。
|
- **日志**:TensorBoard 写入 `logs/bc/`、`logs/magail/`
|
||||||
2. **静态车辆**:全称移动距离小于 5米 且速度从未超过 1m/s 的车辆(作为背景流存在,不收集数据)。
|
|
||||||
|
|
||||||
---
|
所有默认路径均为相对项目根,便于在不同设备上复用。
|
||||||
|
|
||||||
## 🔍 2. 数据可视化与验证
|
## 数据处理流程
|
||||||
|
|
||||||
### 回放可视化
|
从 Waymo Motion 原始数据到本项目训练用专家 pkl,依次为:
|
||||||
使用 `visualize_replay.py` 直观地观察回放效果,确认车辆行为是否自然,以及过滤逻辑是否生效。
|
|
||||||
|
**1) 下载 Waymo Motion(TFRecord)**
|
||||||
|
安装 `gsutil` 并登录 Google 账号后,例如只下载 training_20s:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 运行可视化
|
gsutil -m cp -r "gs://waymo_open_dataset_motion_v_1_2_0/uncompressed/scenario/training_20s" ./waymo/
|
||||||
# --horizon: 回放的最大步数 (Waymo 场景通常为 90 或 198 步)
|
|
||||||
python scripts/visualize_replay.py \
|
|
||||||
--data_dir data/exp_filtered \
|
|
||||||
--start_index 0 \
|
|
||||||
--num_scenarios 1 \
|
|
||||||
--horizon 200
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**观察要点**:
|
**2) ScenarioNet Convert(TFRecord → ScenarioNet 场景库)**
|
||||||
* **受控车辆 (Controlled Agents)**:控制台会显示数量(如 `Controlled agents: 2`)。这些是真正产生数据的车辆。
|
需安装 ScenarioNet、MetaDrive 及 TensorFlow 2.11、protobuf 3.20;转换时不用 GPU。
|
||||||
* **背景车辆**:如果在渲染图中看到其他车(通常是路边停放的),但受控数量很少,说明静态过滤生效了。
|
|
||||||
|
|
||||||
### 数据分析
|
|
||||||
使用 `analyze_expert_data.py` 查看生成数据的统计分布。
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts/analyze_expert_data.py --data_path data/training_data/expert_data_0_100.pkl
|
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)。
|
||||||
|
|
||||||
## 🧠 3. 模型训练 (Next Steps)
|
**4) 本项目:生成专家 pkl**
|
||||||
|
使用筛选后的场景目录,生成训练用 pkl 到 `data/training_data`:
|
||||||
|
|
||||||
有了 `data/training_data/` 下的专家数据后,您可以开始训练 MAGAIL 模型。
|
```bash
|
||||||
|
python scripts/generate_expert_data.py --data_dir data/exp_filtered --output_dir data/training_data --num_scenarios 100 --start_index 0
|
||||||
|
```
|
||||||
|
|
||||||
### 训练流程
|
## 核心工作流
|
||||||
1. **加载数据**:使用 `dataset/expert_dataset.py` 中的 `ExpertDataset` 类加载 `.pkl` 数据。
|
|
||||||
2. **初始化 MAGAIL**:
|
|
||||||
* **Generator (Policy)**: 接收观测 `(B, 45)`,输出动作 `(B, 2)`。
|
|
||||||
* **Discriminator**: 接收状态-动作对 `(s, a)`,判断是专家还是生成器。
|
|
||||||
3. **交互采样**:
|
|
||||||
* 在 `MultiAgentScenarioEnv`(非回放模式)中运行 Policy。
|
|
||||||
* 收集 Policy 生成的轨迹。
|
|
||||||
4. **对抗更新**:
|
|
||||||
* 利用专家数据和 Policy 数据训练 Discriminator。
|
|
||||||
* 利用 Discriminator 的输出作为 Reward (GAIL Reward) 训练 Policy (PPO/TRPO)。
|
|
||||||
|
|
||||||
### 推荐配置
|
### 1. 数据准备
|
||||||
* **Observation**: 45维 (Ego + 10 Neighbors)
|
使用 `scripts/generate_expert_data.py` 将 Waymo 数据转换为训练用 `.pkl`,输出到 `data/training_data/`。
|
||||||
* **Action**: 2维 Continuous (Steering, Accel)
|
|
||||||
* **Horizon**: 200 steps
|
```bash
|
||||||
* **Batch Size**: 1024+ (多智能体环境下数据量很大)
|
python scripts/generate_expert_data.py --data_dir data/exp_filtered --output_dir data/training_data --num_scenarios 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 行为克隆 (BC)
|
||||||
|
- **训练**:`python train_bc.py`(模型保存到 `models/bc/`,日志到 `logs/bc/`)
|
||||||
|
```
|
||||||
|
# 注意替换文件名
|
||||||
|
python train_bc.py --expert_data_path ./data/training/expert_data_0_50.pkl --epochs 100
|
||||||
|
```
|
||||||
|
- **可视化**:`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.py policy --policy_type magail --model_path models/magail/model_50_actor.pth`
|
||||||
|
|
||||||
|
### 4. 可视化统一入口
|
||||||
|
可视化统一使用 `scripts/visualize.py`,子命令:`replay`(场景回放)、`policy`(BC/MAGAIL 策略)、`trajectory`(专家轨迹 2D 动画)。详见 [scripts/README.md](scripts/README.md)。
|
||||||
|
|
||||||
|
## 文件与模块职责
|
||||||
|
|
||||||
|
### 根目录脚本
|
||||||
|
- **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 训练/评估共用
|
||||||
|
- **Env/scenario_env.py**:`MultiAgentScenarioEnv` 基类,Waymo 场景加载与步进
|
||||||
|
- **Env/expert_replay_env.py**:专家轨迹回放与逆动力学动作,供 `generate_expert_data.py` 与回放可视化
|
||||||
|
- **Env/inverse_dynamics.py**:轨迹 → 油门/转向动作
|
||||||
|
|
||||||
|
### Algorithm 模块
|
||||||
|
- **Algorithm/policy.py**:`StateIndependentPolicy`,BC 使用的 MLP 策略
|
||||||
|
|
||||||
|
### scripts 目录
|
||||||
|
工具脚本用途与用法见 [scripts/README.md](scripts/README.md)。
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ class ExpertTrajectoryDataset(Dataset):
|
|||||||
print(f" 观测维度: {obs_dim} (应为107)")
|
print(f" 观测维度: {obs_dim} (应为107)")
|
||||||
|
|
||||||
if save_path:
|
if save_path:
|
||||||
|
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||||
with open(save_path, "wb") as f:
|
with open(save_path, "wb") as f:
|
||||||
pickle.dump({
|
pickle.dump({
|
||||||
"trajectories": all_trajectories,
|
"trajectories": all_trajectories,
|
||||||
@@ -282,7 +283,7 @@ if __name__ == "__main__":
|
|||||||
trajectories, observations = ExpertTrajectoryDataset.collect_with_full_obs(
|
trajectories, observations = ExpertTrajectoryDataset.collect_with_full_obs(
|
||||||
env_config,
|
env_config,
|
||||||
num_scenarios=10,
|
num_scenarios=10,
|
||||||
save_path="./expert_trajectories_full.pkl"
|
save_path="data/trajectories/expert_trajectories_full.pkl"
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(trajectories) > 0:
|
if len(trajectories) > 0:
|
||||||
|
|||||||
157
dataset/loader.py
Normal file
157
dataset/loader.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
统一数据加载: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, *, filter_terminal_last_step: bool = False):
|
||||||
|
"""从目录或单个 pkl 加载专家 (obs, acts),返回 concat 后的 obs_data, act_data。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
expert_data_path: Directory containing pkl files or a single pkl file.
|
||||||
|
filter_terminal_last_step: If True, drop the last (obs, act) pair of each trajectory.
|
||||||
|
This approximates II's \"train only on non-terminal steps\" when the dataset doesn't
|
||||||
|
explicitly store dones.
|
||||||
|
"""
|
||||||
|
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 = traj["obs"]
|
||||||
|
acts = traj["acts"]
|
||||||
|
if filter_terminal_last_step and len(obs) > 0 and len(acts) > 0:
|
||||||
|
# Drop last step of each trajectory
|
||||||
|
obs = obs[:-1]
|
||||||
|
acts = acts[:-1]
|
||||||
|
if len(obs) == 0 or len(acts) == 0:
|
||||||
|
continue
|
||||||
|
obs_data.append(obs)
|
||||||
|
act_data.append(acts)
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
if "observations" in data and "actions" in data:
|
||||||
|
obs = data["observations"]
|
||||||
|
acts = data["actions"]
|
||||||
|
if filter_terminal_last_step and len(obs) > 0 and len(acts) > 0:
|
||||||
|
obs = obs[:-1]
|
||||||
|
acts = acts[:-1]
|
||||||
|
if len(obs) == 0 or len(acts) == 0:
|
||||||
|
continue
|
||||||
|
obs_data.append(obs)
|
||||||
|
act_data.append(acts)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def get_expert_scenario_ids(expert_data_path, max_ids=10):
|
||||||
|
"""
|
||||||
|
从专家 pkl 中收集出现过的 scenario_id(这些场景在采集时曾有受控车)。
|
||||||
|
用于 eval 时只在这些场景上评估,保证 eval 有受控车。
|
||||||
|
返回排序后的 list,最多 max_ids 个。
|
||||||
|
"""
|
||||||
|
if os.path.isdir(expert_data_path):
|
||||||
|
pkl_files = glob.glob(os.path.join(expert_data_path, "*.pkl"))
|
||||||
|
elif os.path.exists(expert_data_path):
|
||||||
|
pkl_files = [expert_data_path]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
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 "scenario_id" in traj:
|
||||||
|
seen.add(traj["scenario_id"])
|
||||||
|
# dict 格式通常没有 per-trajectory scenario_id,跳过
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
out = sorted(seen)[:max_ids]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class MAGAILExpertDataset(Dataset):
|
||||||
|
def __init__(self, data_dir, transform=None, *, filter_terminal_last_step: bool = False):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
max_i = len(obs)
|
||||||
|
if filter_terminal_last_step and max_i > 0:
|
||||||
|
max_i -= 1
|
||||||
|
for i in range(max_i):
|
||||||
|
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
|
||||||
@@ -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
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
76
scripts/README.md
Normal file
76
scripts/README.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# scripts 工具脚本说明
|
||||||
|
|
||||||
|
本目录包含数据生成、回放、可视化与分析等工具脚本。训练脚本(`train_bc.py`、`train_magail.py`)位于项目根目录。
|
||||||
|
|
||||||
|
## 路径约定(相对项目根)
|
||||||
|
|
||||||
|
- **数据**:`data/exp_filtered`(Waymo 场景)、`data/training_data`(专家 pkl 输出)
|
||||||
|
- **模型**:`models/bc/`(BC)、`models/magail/`(MAGAIL)
|
||||||
|
- **日志**:`logs/bc/`、`logs/magail/`(TensorBoard)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 脚本列表与用法
|
||||||
|
|
||||||
|
### 数据生成
|
||||||
|
|
||||||
|
| 脚本 | 用途 | 用法示例 |
|
||||||
|
|------|------|----------|
|
||||||
|
| [generate_expert_data.py](generate_expert_data.py) | 从 Waymo 数据生成专家 (obs, act) 的 pkl | `python scripts/generate_expert_data.py --data_dir data/exp_filtered --output_dir data/training_data --num_scenarios 100` |
|
||||||
|
|
||||||
|
**常用参数**:`--data_dir`(默认 `data/exp_filtered`)、`--output_dir`(默认 `data/training_data`)、`--start_index`、`--num_scenarios`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 可视化(统一入口)
|
||||||
|
|
||||||
|
| 脚本 | 用途 | 用法示例 |
|
||||||
|
|------|------|----------|
|
||||||
|
| [visualize.py](visualize.py) | **replay**:场景回放(ExpertReplayEnv);**policy**:BC/MAGAIL 策略;**trajectory**:专家轨迹 2D 动画 | 见下方 |
|
||||||
|
|
||||||
|
**子命令**:
|
||||||
|
|
||||||
|
- **replay**(原始专家轨迹回放):
|
||||||
|
```bash
|
||||||
|
python scripts/visualize.py replay --data_dir data/exp_filtered --num_scenarios 1 --horizon 500
|
||||||
|
```
|
||||||
|
|
||||||
|
- **policy**(BC 或 MAGAIL 训练策略):与专家数据生成/回放一致——同一套车道+静态筛选、且会生成背景车(bg_*),使观测分布与训练集一致,便于在训练集上公平演示。
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
- **trajectory**(专家轨迹 matplotlib 俯视图动画):
|
||||||
|
```bash
|
||||||
|
python scripts/visualize.py trajectory --data_dir data/exp_filtered --scenario_idx 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**公共参数**:`--data_dir`(默认 `data/exp_filtered`)、`--start_index`、`--num_scenarios`、`--horizon`。policy 模式另有 `--policy_type`(auto/bc/magail)、`--model_path`、`--deterministic`(仅 MAGAIL)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 数据分析与检查
|
||||||
|
|
||||||
|
| 脚本 | 用途 | 用法示例 |
|
||||||
|
|------|------|----------|
|
||||||
|
| [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`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
|
||||||
|
| 脚本 | 用途 | 用法示例 |
|
||||||
|
|------|------|----------|
|
||||||
|
| [launch_tensorboard.py](launch_tensorboard.py) | 启动 TensorBoard | `python scripts/launch_tensorboard.py --logdir logs`(或 `logs/bc` / `logs/magail`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与训练流程的对应关系
|
||||||
|
|
||||||
|
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. **可视化**:`scripts/visualize.py`(子命令 replay / policy / trajectory)→ 数据目录默认 `data/exp_filtered`
|
||||||
@@ -153,8 +153,8 @@ def generate_data(args):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--data_dir", type=str, default="/home/huangfukk/MAGAIL4AutoDrive/data/exp_filtered", help="Path to Waymo pickles (or filtered index)")
|
parser.add_argument("--data_dir", type=str, default="data/exp_filtered", help="Path to Waymo pickles (or filtered index)")
|
||||||
parser.add_argument("--output_dir", type=str, default="/home/huangfukk/MAGAIL4AutoDrive/data/training", help="Output directory")
|
parser.add_argument("--output_dir", type=str, default="data/training_data", help="Output directory")
|
||||||
parser.add_argument("--start_index", type=int, default=0)
|
parser.add_argument("--start_index", type=int, default=0)
|
||||||
parser.add_argument("--num_scenarios", type=int, default=10)
|
parser.add_argument("--num_scenarios", type=int, default=10)
|
||||||
|
|
||||||
|
|||||||
18
scripts/launch_tensorboard.py
Normal file
18
scripts/launch_tensorboard.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Mock imghdr module for Python 3.13 compatibility
|
||||||
|
# TensorBoard depends on imghdr which was removed in Python 3.13
|
||||||
|
if sys.version_info >= (3, 13):
|
||||||
|
if 'imghdr' not in sys.modules:
|
||||||
|
imghdr_mock = types.ModuleType('imghdr')
|
||||||
|
imghdr_mock.what = lambda filename, h=None: None
|
||||||
|
# Mock tests list which tensorboard appends to
|
||||||
|
imghdr_mock.tests = []
|
||||||
|
sys.modules['imghdr'] = imghdr_mock
|
||||||
|
|
||||||
|
from tensorboard import main as tb_main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(tb_main.run_main())
|
||||||
400
scripts/visualize.py
Normal file
400
scripts/visualize.py
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
"""
|
||||||
|
Unified visualization: replay (scenario replay), policy (BC/MAGAIL), trajectory (2D expert trajectory animation).
|
||||||
|
Usage: python scripts/visualize.py <replay|policy|trajectory> [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)
|
||||||
|
|
||||||
|
print(f"Initializing ExpertReplayEnv with data from {data_path}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i in range(args.start_index, args.start_index + num_to_run):
|
||||||
|
print(f"\n--- Playing Scenario {i} ---")
|
||||||
|
# Each scenario uses a fresh env (start_scenario_index=i, num_scenarios=1) so the second
|
||||||
|
# scenario and beyond are fully cleaned and loaded like a single-scenario run.
|
||||||
|
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": i,
|
||||||
|
"num_scenarios": 1,
|
||||||
|
"log_level": 40,
|
||||||
|
}
|
||||||
|
env = ExpertReplayEnv(config=env_config)
|
||||||
|
try:
|
||||||
|
obs = env.reset(seed=i)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error resetting scenario {i}: {e}")
|
||||||
|
env.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Scenario loaded. Controlled agents (current): {len(env.controlled_agents)}, total in scenario: {env.num_controlled_in_scenario}")
|
||||||
|
|
||||||
|
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
|
||||||
|
env.close()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Interrupted by user")
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
print(f"Global error: {e}")
|
||||||
|
finally:
|
||||||
|
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 (current): {len(obs_dict)}, total in scenario: {env.num_controlled_in_scenario}")
|
||||||
|
if len(obs_dict) == 0:
|
||||||
|
print(f"Scenario {i} has no controlled agents (all filtered out). Skipping.")
|
||||||
|
continue
|
||||||
|
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()
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
199
train_bc.py
Normal file
199
train_bc.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
"""
|
||||||
|
BC 训练脚本:负责数据加载、环境评估、日志与保存;BC 算法由 Algorithm.bc 提供。
|
||||||
|
使用方式不变:python train_bc.py [--expert_data_path data/training_data] [--save_dir models/bc] ...
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import argparse
|
||||||
|
from torch.utils.data import DataLoader, TensorDataset
|
||||||
|
from torch.optim import Adam
|
||||||
|
from torch.optim.lr_scheduler import ExponentialLR
|
||||||
|
from datetime import datetime
|
||||||
|
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
|
||||||
|
from dataset.loader import load_expert_pkl, get_expert_scenario_ids
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_policy(policy, args, device):
|
||||||
|
"""在 BCScenarioEnv 中评估策略:仅使用专家数据中出现过的 scenario_id,保证 eval 有受控车。
|
||||||
|
输出与 replay 对齐:agents (current)=reset 时受控车数,total in scenario=该场景受控轨迹总数(car_birth_info_list 长度)。"""
|
||||||
|
waymo_data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
|
||||||
|
data_dir = os.path.join(waymo_data_dir, "exp_filtered")
|
||||||
|
if not os.path.exists(data_dir):
|
||||||
|
data_dir = os.path.join(waymo_data_dir, "exp_converted")
|
||||||
|
if not os.path.exists(data_dir):
|
||||||
|
print(f"[ERROR] Could not find scenario data in {waymo_data_dir}. Evaluation skipped.")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
scenario_ids = get_expert_scenario_ids(args.expert_data_path, max_ids=5)
|
||||||
|
if not scenario_ids:
|
||||||
|
print("[WARN] No scenario_id in expert pkl, falling back to scenarios [0,1,2]. Eval may have 0 controlled agents.")
|
||||||
|
scenario_ids = [0, 1, 2]
|
||||||
|
|
||||||
|
total_rewards = []
|
||||||
|
total_steps = []
|
||||||
|
collision_episodes = 0
|
||||||
|
horizon = 200
|
||||||
|
|
||||||
|
for idx, scenario_id in enumerate(scenario_ids):
|
||||||
|
env_config = {
|
||||||
|
"data_directory": data_dir,
|
||||||
|
"is_multi_agent": True,
|
||||||
|
"num_controlled_agents": 100,
|
||||||
|
"use_render": False,
|
||||||
|
"sequential_seed": True,
|
||||||
|
"horizon": horizon,
|
||||||
|
"start_scenario_index": scenario_id,
|
||||||
|
"num_scenarios": 1,
|
||||||
|
}
|
||||||
|
env = BCScenarioEnv(env_config, agent2policy=None)
|
||||||
|
try:
|
||||||
|
obs_dict = env.reset(seed=scenario_id)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Eval Episode {idx} (scenario {scenario_id}): reset failed: {e}")
|
||||||
|
env.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
n_controlled = len(env.controlled_agents)
|
||||||
|
n_total_in_scenario = getattr(env, "num_controlled_in_scenario", n_controlled)
|
||||||
|
if n_controlled == 0:
|
||||||
|
print(
|
||||||
|
f" Eval Episode {idx} (scenario {scenario_id}): 0 controlled agents (total in scenario: {n_total_in_scenario}), skip."
|
||||||
|
)
|
||||||
|
env.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
episode_reward = 0.0
|
||||||
|
step_count = 0
|
||||||
|
had_near_collision = False
|
||||||
|
dones = {"__all__": False}
|
||||||
|
while not dones["__all__"] and step_count < horizon:
|
||||||
|
step_count += 1
|
||||||
|
if not obs_dict:
|
||||||
|
obs_dict, _, dones, _ = env.step({})
|
||||||
|
continue
|
||||||
|
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():
|
||||||
|
actions, _ = policy.sample(obs_tensor)
|
||||||
|
actions = actions.cpu().numpy()
|
||||||
|
action_dict = {aid: act for aid, act in zip(agent_ids, actions)}
|
||||||
|
obs_dict, rewards, dones, infos = env.step(action_dict)
|
||||||
|
episode_reward += sum(rewards.values())
|
||||||
|
if infos:
|
||||||
|
for _aid, info in infos.items():
|
||||||
|
if isinstance(info, dict) and info.get("near_collision", False):
|
||||||
|
had_near_collision = True
|
||||||
|
break
|
||||||
|
|
||||||
|
total_rewards.append(episode_reward)
|
||||||
|
total_steps.append(step_count)
|
||||||
|
if had_near_collision:
|
||||||
|
collision_episodes += 1
|
||||||
|
print(
|
||||||
|
f" Eval Episode {idx} (scenario {scenario_id}): Total Reward {episode_reward:.2f}, steps {step_count}, "
|
||||||
|
f"agents (current): {n_controlled}, total in scenario: {n_total_in_scenario}"
|
||||||
|
)
|
||||||
|
env.close()
|
||||||
|
|
||||||
|
if not total_rewards:
|
||||||
|
print(" No valid eval episodes (all skipped or failed).")
|
||||||
|
return 0.0, 0.0, 0.0
|
||||||
|
avg_reward = float(np.mean(total_rewards))
|
||||||
|
avg_steps = float(np.mean(total_steps)) if total_steps else 0.0
|
||||||
|
collision_rate = float(collision_episodes / max(1, len(total_rewards)))
|
||||||
|
print(
|
||||||
|
f" Average Evaluation Reward: {avg_reward:.2f} | Mean Episode Length: {avg_steps:.1f} | "
|
||||||
|
f"Collision Rate (near): {collision_rate:.3f}"
|
||||||
|
)
|
||||||
|
return avg_reward, collision_rate, avg_steps
|
||||||
|
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
print(f"Using device: {device}")
|
||||||
|
|
||||||
|
os.makedirs("logs/bc", exist_ok=True)
|
||||||
|
log_dir = os.path.join("logs", "bc", datetime.now().strftime("%Y%m%d-%H%M%S"))
|
||||||
|
writer = SummaryWriter(log_dir)
|
||||||
|
print(f"TensorBoard logging to: {log_dir}")
|
||||||
|
os.makedirs(args.save_dir, exist_ok=True)
|
||||||
|
|
||||||
|
obs_data, act_data = load_expert_pkl(
|
||||||
|
args.expert_data_path,
|
||||||
|
filter_terminal_last_step=args.filter_terminal_last_step,
|
||||||
|
)
|
||||||
|
obs_tensor = torch.FloatTensor(obs_data)
|
||||||
|
act_tensor = torch.FloatTensor(act_data)
|
||||||
|
dataset = TensorDataset(obs_tensor, act_tensor)
|
||||||
|
train_size = int(0.8 * len(dataset))
|
||||||
|
val_size = len(dataset) - train_size
|
||||||
|
train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])
|
||||||
|
train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True)
|
||||||
|
val_loader = DataLoader(val_dataset, batch_size=args.batch_size, shuffle=False)
|
||||||
|
print(f"Dataset loaded. Train size: {len(train_dataset)}, Val size: {len(val_dataset)}")
|
||||||
|
|
||||||
|
state_dim = obs_data.shape[1]
|
||||||
|
action_dim = act_data.shape[1]
|
||||||
|
print(f"State Dim: {state_dim}, Action Dim: {action_dim}")
|
||||||
|
|
||||||
|
policy = StateIndependentPolicy(
|
||||||
|
state_shape=(state_dim,),
|
||||||
|
action_shape=(action_dim,),
|
||||||
|
hidden_units=(256, 256),
|
||||||
|
hidden_activation=torch.nn.Tanh(),
|
||||||
|
).to(device)
|
||||||
|
optimizer = Adam(policy.parameters(), lr=args.lr)
|
||||||
|
scheduler = ExponentialLR(optimizer, gamma=0.99)
|
||||||
|
|
||||||
|
best_val_loss = float("inf")
|
||||||
|
for epoch in range(args.epochs):
|
||||||
|
avg_train_loss = train_bc_epoch(policy, train_loader, optimizer, device)
|
||||||
|
scheduler.step()
|
||||||
|
avg_val_loss = eval_bc_epoch(policy, val_loader, device)
|
||||||
|
|
||||||
|
print(f"Epoch {epoch+1}/{args.epochs} | Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f}")
|
||||||
|
writer.add_scalar("Loss/train", avg_train_loss, epoch)
|
||||||
|
writer.add_scalar("Loss/val", avg_val_loss, epoch)
|
||||||
|
writer.add_scalar("Learning_rate", scheduler.get_last_lr()[0], epoch)
|
||||||
|
|
||||||
|
if avg_val_loss < best_val_loss:
|
||||||
|
best_val_loss = avg_val_loss
|
||||||
|
torch.save(policy.state_dict(), os.path.join(args.save_dir, "policy_best.pt"))
|
||||||
|
|
||||||
|
# Periodic checkpointing (II-style)
|
||||||
|
if args.checkpoint_freq > 0 and (epoch + 1) % args.checkpoint_freq == 0:
|
||||||
|
torch.save(policy.state_dict(), os.path.join(args.save_dir, f"policy_epoch{epoch+1}.pt"))
|
||||||
|
|
||||||
|
if (epoch + 1) % args.eval_freq == 0:
|
||||||
|
eval_reward, eval_collision_rate, eval_mean_steps = evaluate_policy(policy, args, device)
|
||||||
|
writer.add_scalar("Reward/eval", eval_reward, epoch)
|
||||||
|
writer.add_scalar("Eval/collision_rate_near", eval_collision_rate, epoch)
|
||||||
|
writer.add_scalar("Eval/mean_episode_length", eval_mean_steps, epoch)
|
||||||
|
|
||||||
|
torch.save(policy.state_dict(), os.path.join(args.save_dir, "policy_final.pt"))
|
||||||
|
writer.close()
|
||||||
|
print("Training finished.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--expert_data_path", type=str, default="data/training_data", help="Path to expert data pickle or directory")
|
||||||
|
parser.add_argument("--save_dir", type=str, default="models/bc", help="Directory to save models")
|
||||||
|
parser.add_argument("--epochs", type=int, default=100)
|
||||||
|
parser.add_argument("--batch_size", type=int, default=64)
|
||||||
|
parser.add_argument("--lr", type=float, default=3e-4)
|
||||||
|
parser.add_argument("--eval_freq", type=int, default=10)
|
||||||
|
parser.add_argument("--checkpoint_freq", type=int, default=50, help="Save policy_epochN.pt every N epochs. Set <=0 to disable.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--filter_terminal_last_step",
|
||||||
|
action="store_true",
|
||||||
|
help="Drop the last (obs, act) pair of each trajectory to approximate training on non-terminal steps (II-style).",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args)
|
||||||
159
train_magail.py
159
train_magail.py
@@ -9,7 +9,8 @@ import argparse
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from torch.utils.data import DataLoader
|
from torch.utils.data import DataLoader
|
||||||
from dataset.magail_dataset import MAGAILExpertDataset
|
from dataset.loader import MAGAILExpertDataset
|
||||||
|
from Env.bc_env import BCScenarioEnv
|
||||||
|
|
||||||
# --- Networks ---
|
# --- Networks ---
|
||||||
|
|
||||||
@@ -79,18 +80,30 @@ class PPO:
|
|||||||
self.K_epochs = K_epochs
|
self.K_epochs = K_epochs
|
||||||
self.mse_loss = nn.MSELoss()
|
self.mse_loss = nn.MSELoss()
|
||||||
|
|
||||||
|
def _log_prob_from_dist(self, dist, pre_tanh_action):
|
||||||
|
# Tanh-squashed Gaussian log-prob with correction term.
|
||||||
|
log_prob = dist.log_prob(pre_tanh_action)
|
||||||
|
correction = torch.log(1 - torch.tanh(pre_tanh_action) ** 2 + 1e-6)
|
||||||
|
return (log_prob - correction).sum(dim=-1)
|
||||||
|
|
||||||
def select_action(self, state):
|
def select_action(self, state):
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
state = torch.FloatTensor(state).cuda()
|
state = torch.FloatTensor(state).cuda()
|
||||||
dist = self.actor(state)
|
dist = self.actor(state)
|
||||||
action = dist.sample()
|
pre_tanh_action = dist.sample()
|
||||||
action_logprob = dist.log_prob(action).sum(dim=-1)
|
action = torch.tanh(pre_tanh_action)
|
||||||
return action.cpu().numpy(), action_logprob.cpu().numpy()
|
action_logprob = self._log_prob_from_dist(dist, pre_tanh_action)
|
||||||
|
return (
|
||||||
|
action.cpu().numpy(),
|
||||||
|
action_logprob.cpu().numpy(),
|
||||||
|
pre_tanh_action.cpu().numpy()
|
||||||
|
)
|
||||||
|
|
||||||
def update(self, memory):
|
def update(self, memory):
|
||||||
# Convert memory to tensors
|
# Convert memory to tensors
|
||||||
states = torch.FloatTensor(np.array(memory['states'])).cuda()
|
states = torch.FloatTensor(np.array(memory['states'])).cuda()
|
||||||
actions = torch.FloatTensor(np.array(memory['actions'])).cuda()
|
actions = torch.FloatTensor(np.array(memory['actions'])).cuda()
|
||||||
|
pre_tanh_actions = torch.FloatTensor(np.array(memory['pre_tanh_actions'])).cuda()
|
||||||
logprobs = torch.FloatTensor(np.array(memory['logprobs'])).cuda()
|
logprobs = torch.FloatTensor(np.array(memory['logprobs'])).cuda()
|
||||||
rewards = torch.FloatTensor(np.array(memory['rewards'])).cuda()
|
rewards = torch.FloatTensor(np.array(memory['rewards'])).cuda()
|
||||||
next_states = torch.FloatTensor(np.array(memory['next_states'])).cuda()
|
next_states = torch.FloatTensor(np.array(memory['next_states'])).cuda()
|
||||||
@@ -124,7 +137,7 @@ class PPO:
|
|||||||
for _ in range(self.K_epochs):
|
for _ in range(self.K_epochs):
|
||||||
# Evaluating old actions and values :
|
# Evaluating old actions and values :
|
||||||
dist = self.actor(states)
|
dist = self.actor(states)
|
||||||
action_logprobs = dist.log_prob(actions).sum(dim=-1)
|
action_logprobs = self._log_prob_from_dist(dist, pre_tanh_actions)
|
||||||
dist_entropy = dist.entropy().sum(dim=-1)
|
dist_entropy = dist.entropy().sum(dim=-1)
|
||||||
state_values = self.critic(states).squeeze()
|
state_values = self.critic(states).squeeze()
|
||||||
|
|
||||||
@@ -152,12 +165,7 @@ class PPO:
|
|||||||
# --- Training Loop ---
|
# --- Training Loop ---
|
||||||
|
|
||||||
def train(args):
|
def train(args):
|
||||||
# 1. Setup Environment (Dummy for now, usually you run simulation here)
|
# 1. Setup Environment (45-dim obs via BCScenarioEnv)
|
||||||
# But for MAGAIL we need to collect generated trajectories.
|
|
||||||
# We need the Env class to be importable.
|
|
||||||
from Env.scenario_env import MultiAgentScenarioEnv
|
|
||||||
from Env.simple_idm_policy import ConstantVelocityPolicy # Just for init
|
|
||||||
|
|
||||||
# Config for Env
|
# Config for Env
|
||||||
env_config = {
|
env_config = {
|
||||||
"data_directory": args.data_dir,
|
"data_directory": args.data_dir,
|
||||||
@@ -200,12 +208,7 @@ def train(args):
|
|||||||
yield batch
|
yield batch
|
||||||
expert_iter = cycle(expert_loader)
|
expert_iter = cycle(expert_loader)
|
||||||
|
|
||||||
# 4. Initialize Env
|
# 4. Initialize Env (BCScenarioEnv provides 45-dim obs)
|
||||||
from Env.expert_replay_env import ExpertReplayEnv # Using ReplayEnv for config, but we need ScenarioEnv for simulation?
|
|
||||||
# Actually we need MultiAgentScenarioEnv for interactive training, not Replay.
|
|
||||||
from Env.scenario_env import MultiAgentScenarioEnv
|
|
||||||
from Env.simple_idm_policy import ConstantVelocityPolicy # Placeholder policy for init
|
|
||||||
|
|
||||||
# 2. Setup Models
|
# 2. Setup Models
|
||||||
# Determine state dim from environment if possible, or use fixed
|
# Determine state dim from environment if possible, or use fixed
|
||||||
# Expert data has 45 dim?
|
# Expert data has 45 dim?
|
||||||
@@ -220,48 +223,48 @@ def train(args):
|
|||||||
# We need to inject that same logic into the training env, OR
|
# We need to inject that same logic into the training env, OR
|
||||||
# subclass MultiAgentScenarioEnv in the training script to override observation.
|
# subclass MultiAgentScenarioEnv in the training script to override observation.
|
||||||
|
|
||||||
class MAGAILScenarioEnv(MultiAgentScenarioEnv):
|
# class MAGAILScenarioEnv(MultiAgentScenarioEnv):
|
||||||
def _get_all_obs(self):
|
# def _get_all_obs(self):
|
||||||
# Same logic as ExpertReplayEnv to ensure compatibility
|
# # Same logic as ExpertReplayEnv to ensure compatibility
|
||||||
obs_dict = {}
|
# obs_dict = {}
|
||||||
for agent_id, vehicle in self.controlled_agents.items():
|
# for agent_id, vehicle in self.controlled_agents.items():
|
||||||
# 1. Ego State
|
# # 1. Ego State
|
||||||
ego_state = [
|
# ego_state = [
|
||||||
vehicle.position[0], vehicle.position[1],
|
# vehicle.position[0], vehicle.position[1],
|
||||||
vehicle.velocity[0], vehicle.velocity[1],
|
# vehicle.velocity[0], vehicle.velocity[1],
|
||||||
vehicle.heading_theta
|
# vehicle.heading_theta
|
||||||
]
|
# ]
|
||||||
|
#
|
||||||
# 2. Neighbors
|
# # 2. Neighbors
|
||||||
candidates = []
|
# candidates = []
|
||||||
for other_id, other_vehicle in self.engine.agent_manager.active_agents.items():
|
# for other_id, other_vehicle in self.engine.agent_manager.active_agents.items():
|
||||||
if other_id == agent_id:
|
# if other_id == agent_id:
|
||||||
continue
|
# continue
|
||||||
dist = np.linalg.norm(vehicle.position - other_vehicle.position)
|
# dist = np.linalg.norm(vehicle.position - other_vehicle.position)
|
||||||
if dist < 30.0:
|
# if dist < 30.0:
|
||||||
candidates.append((dist, other_vehicle))
|
# candidates.append((dist, other_vehicle))
|
||||||
|
#
|
||||||
candidates.sort(key=lambda x: x[0])
|
# candidates.sort(key=lambda x: x[0])
|
||||||
top_10 = candidates[:10]
|
# top_10 = candidates[:10]
|
||||||
|
#
|
||||||
neighbor_feats = []
|
# neighbor_feats = []
|
||||||
for _, neighbor in top_10:
|
# for _, neighbor in top_10:
|
||||||
neighbor_feats.extend([
|
# neighbor_feats.extend([
|
||||||
neighbor.position[0] - vehicle.position[0],
|
# neighbor.position[0] - vehicle.position[0],
|
||||||
neighbor.position[1] - vehicle.position[1],
|
# neighbor.position[1] - vehicle.position[1],
|
||||||
neighbor.velocity[0],
|
# neighbor.velocity[0],
|
||||||
neighbor.velocity[1]
|
# neighbor.velocity[1]
|
||||||
])
|
# ])
|
||||||
|
#
|
||||||
missing = 10 - len(top_10)
|
# missing = 10 - len(top_10)
|
||||||
if missing > 0:
|
# if missing > 0:
|
||||||
neighbor_feats.extend([0.0] * (4 * missing))
|
# neighbor_feats.extend([0.0] * (4 * missing))
|
||||||
|
#
|
||||||
obs = np.array(ego_state + neighbor_feats, dtype=np.float32)
|
# obs = np.array(ego_state + neighbor_feats, dtype=np.float32)
|
||||||
obs_dict[agent_id] = obs
|
# obs_dict[agent_id] = obs
|
||||||
return obs_dict
|
# return obs_dict
|
||||||
|
|
||||||
env = MAGAILScenarioEnv(config=env_config, agent2policy={}) # Pass empty dict if we control all externally
|
env = BCScenarioEnv(env_config, agent2policy={}) # 45-dim obs
|
||||||
|
|
||||||
print("Starting training...")
|
print("Starting training...")
|
||||||
|
|
||||||
@@ -277,7 +280,15 @@ def train(args):
|
|||||||
|
|
||||||
for i_episode in range(args.max_episodes):
|
for i_episode in range(args.max_episodes):
|
||||||
# --- 1. Collect Rollouts (Interaction) ---
|
# --- 1. Collect Rollouts (Interaction) ---
|
||||||
memory = {'states': [], 'actions': [], 'logprobs': [], 'rewards': [], 'next_states': [], 'dones': []}
|
memory = {
|
||||||
|
'states': [],
|
||||||
|
'actions': [],
|
||||||
|
'pre_tanh_actions': [],
|
||||||
|
'logprobs': [],
|
||||||
|
'rewards': [],
|
||||||
|
'next_states': [],
|
||||||
|
'dones': []
|
||||||
|
}
|
||||||
|
|
||||||
# Prepare seed
|
# Prepare seed
|
||||||
available_scenarios = env.config["num_scenarios"]
|
available_scenarios = env.config["num_scenarios"]
|
||||||
@@ -338,7 +349,7 @@ def train(args):
|
|||||||
import gc
|
import gc
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
env = MAGAILScenarioEnv(config=env_config, agent2policy={})
|
env = BCScenarioEnv(env_config, agent2policy={})
|
||||||
obs_dict = env.reset(seed=seed)
|
obs_dict = env.reset(seed=seed)
|
||||||
|
|
||||||
episode_reward = 0
|
episode_reward = 0
|
||||||
@@ -349,6 +360,7 @@ def train(args):
|
|||||||
# Select actions for all agents
|
# Select actions for all agents
|
||||||
actions = {}
|
actions = {}
|
||||||
action_logprobs = {}
|
action_logprobs = {}
|
||||||
|
pre_tanh_actions = {}
|
||||||
|
|
||||||
# obs_dict: {agent_id: obs}
|
# obs_dict: {agent_id: obs}
|
||||||
# MultiAgentScenarioEnv usually returns a dict {agent_id: obs}
|
# MultiAgentScenarioEnv usually returns a dict {agent_id: obs}
|
||||||
@@ -386,9 +398,10 @@ def train(args):
|
|||||||
obs_dict = new_obs_dict
|
obs_dict = new_obs_dict
|
||||||
|
|
||||||
for agent_id, obs in obs_dict.items():
|
for agent_id, obs in obs_dict.items():
|
||||||
act, logprob = ppo_agent.select_action(obs) # Select action returns numpy
|
act, logprob, pre_tanh = ppo_agent.select_action(obs) # Select action returns numpy
|
||||||
actions[agent_id] = act.flatten() # (2,)
|
actions[agent_id] = act.flatten() # (2,)
|
||||||
action_logprobs[agent_id] = logprob # scalar
|
action_logprobs[agent_id] = logprob # scalar
|
||||||
|
pre_tanh_actions[agent_id] = pre_tanh.flatten()
|
||||||
|
|
||||||
# Step Env
|
# Step Env
|
||||||
next_obs_dict, rewards, dones, infos = env.step(actions)
|
next_obs_dict, rewards, dones, infos = env.step(actions)
|
||||||
@@ -398,6 +411,7 @@ def train(args):
|
|||||||
if agent_id in actions:
|
if agent_id in actions:
|
||||||
memory['states'].append(obs)
|
memory['states'].append(obs)
|
||||||
memory['actions'].append(actions[agent_id])
|
memory['actions'].append(actions[agent_id])
|
||||||
|
memory['pre_tanh_actions'].append(pre_tanh_actions[agent_id])
|
||||||
memory['logprobs'].append(action_logprobs[agent_id])
|
memory['logprobs'].append(action_logprobs[agent_id])
|
||||||
|
|
||||||
# Store standard environmental reward for logging (not used for update in GAIL)
|
# Store standard environmental reward for logging (not used for update in GAIL)
|
||||||
@@ -407,7 +421,7 @@ def train(args):
|
|||||||
# Next state
|
# Next state
|
||||||
if agent_id in next_obs_dict:
|
if agent_id in next_obs_dict:
|
||||||
memory['next_states'].append(next_obs_dict[agent_id])
|
memory['next_states'].append(next_obs_dict[agent_id])
|
||||||
memory['dones'].append(False)
|
memory['dones'].append(dones.get("__all__", False))
|
||||||
else:
|
else:
|
||||||
# Agent finished/vanished
|
# Agent finished/vanished
|
||||||
# We need a dummy next state or handle done correctly
|
# We need a dummy next state or handle done correctly
|
||||||
@@ -460,6 +474,10 @@ def train(args):
|
|||||||
disc_loss = exp_loss + pol_loss
|
disc_loss = exp_loss + pol_loss
|
||||||
disc_loss.backward()
|
disc_loss.backward()
|
||||||
disc_optimizer.step()
|
disc_optimizer.step()
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
disc_acc_exp = (exp_preds > 0.5).float().mean().item()
|
||||||
|
disc_acc_pol = (pol_preds < 0.5).float().mean().item()
|
||||||
|
|
||||||
# --- 3. Update Policy with GAIL Rewards ---
|
# --- 3. Update Policy with GAIL Rewards ---
|
||||||
# Reward = -log(1 - D(s, a))
|
# Reward = -log(1 - D(s, a))
|
||||||
@@ -494,11 +512,18 @@ def train(args):
|
|||||||
writer.add_scalar('Loss/Discriminator', disc_loss.item(), i_episode)
|
writer.add_scalar('Loss/Discriminator', disc_loss.item(), i_episode)
|
||||||
writer.add_scalar('Loss/Policy', ppo_loss, i_episode)
|
writer.add_scalar('Loss/Policy', ppo_loss, i_episode)
|
||||||
writer.add_scalar('Reward/Mean_GAIL', np.mean(all_gail_rewards), i_episode)
|
writer.add_scalar('Reward/Mean_GAIL', np.mean(all_gail_rewards), i_episode)
|
||||||
|
if batch_size > 0:
|
||||||
|
writer.add_scalar('Acc/Disc_Expert', disc_acc_exp, i_episode)
|
||||||
|
writer.add_scalar('Acc/Disc_Policy', disc_acc_pol, i_episode)
|
||||||
|
if len(memory['actions']) > 0:
|
||||||
|
action_arr = np.array(memory['actions'])
|
||||||
|
action_clip_ratio = (np.abs(action_arr) > 0.98).mean()
|
||||||
|
writer.add_scalar('Policy/ActionClipRatio', action_clip_ratio, i_episode)
|
||||||
|
|
||||||
print(f"Episode {i_episode}: Disc Loss {disc_loss.item():.4f} | PPO Loss {ppo_loss:.4f} | Mean Reward {np.mean(all_gail_rewards):.4f}")
|
print(f"Episode {i_episode}: Disc Loss {disc_loss.item():.4f} | PPO Loss {ppo_loss:.4f} | Mean Reward {np.mean(all_gail_rewards):.4f}")
|
||||||
|
|
||||||
if i_episode % 50 == 0:
|
if i_episode % 50 == 0:
|
||||||
ppo_agent.save(os.path.join(args.log_dir, f"model_{i_episode}"))
|
ppo_agent.save(os.path.join(args.save_dir, f"model_{i_episode}"))
|
||||||
|
|
||||||
env.close()
|
env.close()
|
||||||
if writer:
|
if writer:
|
||||||
@@ -511,11 +536,13 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument("--batch_size", type=int, default=1024)
|
parser.add_argument("--batch_size", type=int, default=1024)
|
||||||
parser.add_argument("--max_episodes", type=int, default=1000)
|
parser.add_argument("--max_episodes", type=int, default=1000)
|
||||||
parser.add_argument("--num_scenarios", type=int, default=100)
|
parser.add_argument("--num_scenarios", type=int, default=100)
|
||||||
parser.add_argument("--log_dir", type=str, default="runs/magail_exp")
|
parser.add_argument("--log_dir", type=str, default="logs/magail", help="TensorBoard log directory")
|
||||||
|
parser.add_argument("--save_dir", type=str, default="models/magail", help="Directory to save model checkpoints")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Create log dir
|
# Create log dir and save dir
|
||||||
os.makedirs(args.log_dir, exist_ok=True)
|
os.makedirs(args.log_dir, exist_ok=True)
|
||||||
|
os.makedirs(args.save_dir, exist_ok=True)
|
||||||
|
|
||||||
train(args)
|
train(args)
|
||||||
|
|||||||
Reference in New Issue
Block a user