-
Notifications
You must be signed in to change notification settings - Fork 24
[WIP] Pragnay/driving behaviours eval #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3.0
Are you sure you want to change the base?
Changes from 24 commits
2dcb107
d978227
a838e0c
a29e714
632fcc5
3fba62f
c91c669
80dff96
efebf1d
73f6eaf
5fe75e5
2f124e2
6df0a20
8078486
fa3974c
567ca12
ce6d027
4c3b43f
ae64373
f1228b0
1fac4f9
7f484f9
8994942
2e2879c
30a025a
6ca63cb
970dcf5
c807f6a
98ec822
b1da1c1
96bf636
11d8095
2e0ef69
3e77d2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| ; Configuration for driving behaviour evaluation maps and rewards. | ||
| ; Currently evaluates 5 broad driving behaviours: lead vehicle interaction, lane change, dense traffic, obstacles, vulnerable road user interactions (VRUs). | ||
| ; Currently uses safe reward conditioning values for evaluation | ||
|
|
||
| [eval_lead_vehicle_interaction] | ||
| map_dir = "resources/drive/binaries/lead_vehicle_interaction" | ||
| human_replay_eval = True | ||
| render_eval = True | ||
|
|
||
| [eval_lane_change] | ||
| map_dir = "resources/drive/binaries/lane_change" | ||
| human_replay_eval = True | ||
| render_eval = True | ||
|
|
||
| [eval_dense_traffic] | ||
| map_dir = "resources/drive/binaries/dense_traffic" | ||
| human_replay_eval = True | ||
| render_eval = True | ||
|
|
||
| [eval_obstacles] | ||
| map_dir = "resources/drive/binaries/obstacles" | ||
| human_replay_eval = True | ||
| render_eval = True | ||
|
|
||
| [eval_vru_interaction] | ||
| map_dir = "resources/drive/binaries/vru_interaction" | ||
| human_replay_eval = True | ||
| render_eval = True | ||
|
|
||
| [eval_driving_rewards] | ||
| ; Reward conditioning values (min=max to fix the value). | ||
| ; Names match the env reward_bound_* keys. | ||
| ; High penalties for unsafe behavior | ||
| collision = -0.5 | ||
| offroad = -0.5 | ||
| overspeed = -1.0 | ||
| traffic_light = -1.0 | ||
| reverse = -0.0075 | ||
| comfort = -0.1 | ||
|
|
||
| ; Standard driving rewards | ||
| goal_radius = 8.0 | ||
| lane_align = 0.0025 | ||
| lane_center = -0.00075 | ||
| velocity = 0.005 | ||
| center_bias = 0.0 | ||
| vel_align = 1.0 | ||
| timestep = -0.00005 | ||
|
|
||
| ; Neutral scaling factors | ||
| throttle = 1.0 | ||
| steer = 1.0 | ||
| acc = 1.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -964,3 +964,126 @@ def log_stats(self, global_step=None): | |
| if global_step is not None: | ||
| payload["train_step"] = global_step | ||
| self.logger.wandb.log(payload) | ||
|
|
||
|
|
||
| class DrivingBehavioursEvaluator: | ||
| """Evaluates a policy on the 5 driving behaviour classes using live in-process weights.""" | ||
|
|
||
| # Sections in driving_behaviours_eval.ini that describe scenario classes | ||
| EVAL_SECTIONS_PREFIX = "eval_" | ||
| REWARD_SECTION = "eval_driving_rewards" | ||
|
|
||
| def __init__(self, env_name: str, behaviours_config: Dict, device="cuda", logger=None): | ||
| self.env_name = env_name | ||
| self.behaviours_config = behaviours_config | ||
| if isinstance(device, int): | ||
| device = f"cuda:{device}" | ||
| self.device = device | ||
| self.logger = logger | ||
| self.reward_config = behaviours_config.get(self.REWARD_SECTION, {}) | ||
| self.classes = [ | ||
| (name, cfg) | ||
| for name, cfg in behaviours_config.items() | ||
| if name.startswith(self.EVAL_SECTIONS_PREFIX) and name != self.REWARD_SECTION | ||
| ] | ||
|
|
||
| def _build_class_env_config(self, class_cfg: Dict) -> Dict: | ||
| """Build env config for one scenario class with fixed reward conditioning.""" | ||
| import re | ||
| import sys | ||
| from pufferlib.pufferl import load_config | ||
|
|
||
| original_argv = sys.argv | ||
| sys.argv = ["pufferl"] | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this supposed to do? |
||
| try: | ||
| eval_config = load_config(self.env_name) | ||
| finally: | ||
| sys.argv = original_argv | ||
|
|
||
| eval_config["vec"] = dict(backend="PufferEnv", num_envs=1) | ||
| eval_config["train"]["device"] = self.device | ||
| eval_config["env"]["control_mode"] = "control_sdc_only" | ||
| eval_config["env"]["init_mode"] = "create_all_valid" | ||
| eval_config["env"]["episode_length"] = 91 | ||
| eval_config["env"]["resample_frequency"] = 0 | ||
|
|
||
| map_dir = class_cfg.get("map_dir", "") | ||
| if isinstance(map_dir, str): | ||
| map_dir = map_dir.strip('"') | ||
| eval_config["env"]["map_dir"] = map_dir | ||
| # Set num_maps to the number of available bins so we cover all scenarios | ||
| available_maps = len([f for f in os.listdir(map_dir) if f.endswith(".bin")]) if os.path.isdir(map_dir) else 1 | ||
| eval_config["env"]["num_maps"] = available_maps | ||
|
|
||
| # Discover valid reward bound names | ||
| valid_bounds = set() | ||
| for key in eval_config["env"]: | ||
| m = re.match(r"reward_bound_(.+)_min$", key) | ||
| if m: | ||
| valid_bounds.add(m.group(1)) | ||
|
|
||
| # Fix reward conditioning to eval_driving_rewards values | ||
| for key, val in self.reward_config.items(): | ||
| if key not in valid_bounds: | ||
| continue | ||
| eval_config["env"][f"reward_bound_{key}_min"] = float(val) | ||
| eval_config["env"][f"reward_bound_{key}_max"] = float(val) | ||
|
|
||
| return eval_config | ||
|
|
||
| def evaluate_class(self, class_cfg: Dict, policy) -> Dict: | ||
| """Run human-replay rollouts on all maps in the class map_dir and return averaged metrics.""" | ||
| from collections import defaultdict | ||
| from pufferlib.pufferl import load_env | ||
|
|
||
| print(f"Evaluating class") | ||
|
|
||
| eval_config = self._build_class_env_config(class_cfg) | ||
| num_maps = eval_config["env"]["num_maps"] | ||
| print(f"Built eval config for class with map_dir: {eval_config['env']['map_dir']}") | ||
|
|
||
| vecenv = load_env(self.env_name, eval_config) | ||
| print(f"Loaded vecenv") | ||
| policy.eval() | ||
| print(f"Set policy to eval mode") | ||
| rollout_evaluator = HumanReplayEvaluator(eval_config) | ||
| all_stats = defaultdict(list) | ||
| print(f"Starting rollouts for class with {num_maps} maps") | ||
| try: | ||
| for _ in range(num_maps): | ||
| result = rollout_evaluator.rollout(eval_config, vecenv, policy) or {} | ||
| for k, v in result.items(): | ||
| try: | ||
| all_stats[k].append(float(v)) | ||
| except (TypeError, ValueError): | ||
| pass | ||
| # Reset for next map | ||
| vecenv.reset() | ||
| finally: | ||
| vecenv.close() | ||
| import gc | ||
|
|
||
| gc.collect() | ||
| import torch | ||
|
|
||
| if torch.cuda.is_available(): | ||
| torch.cuda.empty_cache() | ||
|
|
||
| return {k: float(np.mean(v)) for k, v in all_stats.items() if v} | ||
|
|
||
| def log_stats(self, all_results: Dict[str, Dict], global_step=None): | ||
| """Log per-class metrics to wandb under driving_behaviours/<class>/<metric>.""" | ||
| if not (self.logger and hasattr(self.logger, "wandb") and self.logger.wandb): | ||
| return | ||
| payload = {} | ||
| for class_name, metrics in all_results.items(): | ||
| short = class_name[len(self.EVAL_SECTIONS_PREFIX) :] | ||
| for k, v in metrics.items(): | ||
| try: | ||
| payload[f"driving_behaviours/{short}/{k}"] = float(v) | ||
| except (TypeError, ValueError): | ||
| pass | ||
| if global_step is not None: | ||
| payload["train_step"] = global_step | ||
| if payload: | ||
| self.logger.wandb.log(payload) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1023,6 +1023,17 @@ def process_all_maps( | |
| if not success: | ||
| print(f" {name}: {error}") | ||
|
|
||
| # Write manifest.json mapping each bin to its source JSON | ||
| manifest = {} | ||
| for i, map_path, binary_path, *_ in tasks: | ||
| _, _, success, _ = results[i] | ||
| if success: | ||
| manifest[f"map_{i:03d}.bin"] = map_path.name | ||
| manifest_path = binary_dir / "manifest.json" | ||
| with open(manifest_path, "w") as f: | ||
| json.dump(manifest, f, indent=2) | ||
| print(f"Wrote manifest to {manifest_path} ({len(manifest)} entries)") | ||
|
Comment on lines
+1074
to
+1083
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mpragnay really nice touch |
||
|
|
||
|
|
||
| def test_performance(timeout=10, atn_cache=1024, num_agents=1024): | ||
| import time | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -193,11 +193,12 @@ static int make_gif_from_frames(const char *pattern, int fps, const char *palett | |
|
|
||
| int eval_gif(const char *map_name, const char *policy_name, int show_grid, int obs_only, int lasers, | ||
| int show_human_logs, int frame_skip, const char *view_mode, const char *output_topdown, | ||
| const char *output_agent, int num_maps, int zoom_in) { | ||
| const char *output_agent, int num_maps, int zoom_in, const char *ini_file) { | ||
|
|
||
| // Parse configuration from INI file | ||
| env_init_config conf = {0}; | ||
| const char *ini_file = "pufferlib/config/ocean/drive.ini"; | ||
| if (ini_file == NULL) | ||
| ini_file = "pufferlib/config/ocean/drive.ini"; | ||
| if (ini_parse(ini_file, handler, &conf) < 0) { | ||
| fprintf(stderr, "Error: Could not load %s. Cannot determine environment configuration.\n", ini_file); | ||
| return -1; | ||
|
|
@@ -350,25 +351,29 @@ int eval_gif(const char *map_name, const char *policy_name, int show_grid, int o | |
| char filename_topdown[256]; | ||
| char filename_agent[256]; | ||
|
|
||
| if (output_topdown != NULL && output_agent != NULL) { | ||
| strcpy(filename_topdown, output_topdown); | ||
| strcpy(filename_agent, output_agent); | ||
| } else { | ||
| char policy_base[256]; | ||
| strcpy(policy_base, policy_name); | ||
| *strrchr(policy_base, '.') = '\0'; | ||
| char policy_base[256]; | ||
| strcpy(policy_base, policy_name); | ||
| *strrchr(policy_base, '.') = '\0'; | ||
|
|
||
| char map[256]; | ||
| strcpy(map, basename((char *)map_name)); | ||
| *strrchr(map, '.') = '\0'; | ||
| char map[256]; | ||
| strcpy(map, basename((char *)map_name)); | ||
| *strrchr(map, '.') = '\0'; | ||
|
|
||
| char video_dir[256]; | ||
| sprintf(video_dir, "%s/video", policy_base); | ||
| char mkdir_cmd[512]; | ||
| snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p \"%s\"", video_dir); | ||
| system(mkdir_cmd); | ||
| char video_dir[256]; | ||
| sprintf(video_dir, "%s/video", policy_base); | ||
| char mkdir_cmd[512]; | ||
| snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p \"%s\"", video_dir); | ||
| system(mkdir_cmd); | ||
|
Comment on lines
+354
to
+366
|
||
|
|
||
| if (output_topdown != NULL) { | ||
| strcpy(filename_topdown, output_topdown); | ||
| } else { | ||
| sprintf(filename_topdown, "%s/video/%s_topdown.mp4", policy_base, map); | ||
| } | ||
|
|
||
| if (output_agent != NULL) { | ||
| strcpy(filename_agent, output_agent); | ||
| } else { | ||
| sprintf(filename_agent, "%s/video/%s_agent.mp4", policy_base, map); | ||
| } | ||
|
|
||
|
|
@@ -555,6 +560,6 @@ int main(int argc, char *argv[]) { | |
| } | ||
|
|
||
| eval_gif(map_name, policy_name, show_grid, obs_only, lasers, show_human_logs, frame_skip, view_mode, output_topdown, | ||
| output_agent, num_maps, zoom_in); | ||
| output_agent, num_maps, zoom_in, ini_file); | ||
| return 0; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.