1
Fork 0

feat(convergence analysis): updated convergence analysis script to also print out index of found item

This commit is contained in:
Robin Meersman 2026-05-15 20:52:06 +02:00
parent fc80799899
commit 7334565d69
6 changed files with 60 additions and 19 deletions

3
.gitignore vendored
View file

@ -523,3 +523,6 @@ Network Trash Folder
Temporary Items Temporary Items
.apdisk .apdisk
*.pdf *.pdf
# plot directory
poster_plots/

View file

@ -23,7 +23,7 @@ morphology:
morph_mode: CENTRALIZED morph_mode: CENTRALIZED
experiment: experiment:
exp_name: "final-models/centralized/" exp_name: "final-models-v2/centralized/"
seed: 42 seed: 42
torch_deterministic: true torch_deterministic: true
cuda: true cuda: true
@ -34,8 +34,8 @@ logging:
save_checkpoints: true save_checkpoints: true
upload_final_model: true upload_final_model: true
upload_checkpoints: true upload_checkpoints: true
checkpoint_frequency: 20 checkpoint_frequency: 10
wandb_project_name: "final-models" wandb_project_name: "final-models-v2"
evaluation: evaluation:
evaluate_checkpoints: true evaluate_checkpoints: true

View file

@ -9,12 +9,14 @@ eval_seed: 0
# Cross-model comparison settings # Cross-model comparison settings
# We use 10 episodes to get a more robust average for the final poster results. # We use 10 episodes to get a more robust average for the final poster results.
comparison_base_seed: 0 comparison_base_seed: 0
comparison_num_episodes: 2 comparison_num_episodes: 10
comparison_output_csv: "runs/evaluation/comparison.csv" comparison_output_csv: "runs/evaluation/comparison.csv"
# Paths to the .cleanrl_model files to be compared (relative to workspace root). # Paths to the .cleanrl_model files to be compared (relative to workspace root).
comparison_models: comparison_models:
- "runs/input-space-2-arms/2026-05-02/08-14-58/final_model.flax" - "runs/final-v2-centralized/artifacts/12-19-01_checkpoint_v22/checkpoint_step_230.flax"
- "runs/final-v2-fully-conn/artifacts/14-02-00_checkpoint_v17/checkpoint_step_180.flax"
- "runs/final-v2-ring/artifacts/15-27-03_checkpoint_v21/checkpoint_step_220.flax"
# Path to the morphologies to evaluate against. # Path to the morphologies to evaluate against.
comparison_morphologies: comparison_morphologies:

View file

@ -26,7 +26,7 @@ morphology:
morph_mode: FULLY_CONNECTED morph_mode: FULLY_CONNECTED
experiment: experiment:
exp_name: "final-models/fully-connected/" exp_name: "final-models-v2/fully-connected/"
seed: 42 seed: 42
torch_deterministic: true torch_deterministic: true
cuda: true cuda: true
@ -37,8 +37,8 @@ logging:
save_checkpoints: true save_checkpoints: true
upload_final_model: true upload_final_model: true
upload_checkpoints: true upload_checkpoints: true
checkpoint_frequency: 20 checkpoint_frequency: 10
wandb_project_name: "final-models" wandb_project_name: "final-models-v2"
evaluation: evaluation:
evaluate_checkpoints: true evaluate_checkpoints: true

View file

@ -26,7 +26,7 @@ morphology:
morph_mode: RING morph_mode: RING
experiment: experiment:
exp_name: "final-models/ring/" exp_name: "final-models-v2/ring/"
seed: 42 seed: 42
torch_deterministic: true torch_deterministic: true
cuda: true cuda: true
@ -37,8 +37,8 @@ logging:
save_checkpoints: true save_checkpoints: true
upload_final_model: true upload_final_model: true
upload_checkpoints: true upload_checkpoints: true
checkpoint_frequency: 20 checkpoint_frequency: 10
wandb_project_name: "final-models" wandb_project_name: "final-models-v2"
evaluation: evaluation:
evaluate_checkpoints: true evaluate_checkpoints: true

View file

@ -44,6 +44,7 @@ class Columns(str, Enum):
# ... (rest of the file remains same, just need to update plotting functions and obtain_data) # ... (rest of the file remains same, just need to update plotting functions and obtain_data)
"""Column names expected in every evaluation CSV.""" """Column names expected in every evaluation CSV."""
CHECKPOINT = "checkpoint"
ARCH = "architecture" ARCH = "architecture"
TIMESTEPS = "total_trained_timesteps" TIMESTEPS = "total_trained_timesteps"
REWARD = "accumulated_reward" REWARD = "accumulated_reward"
@ -108,7 +109,18 @@ def load_metrics(file_mapping: dict[str, str]) -> pd.DataFrame:
Loads one CSV per architecture, injects the architecture name as a column, Loads one CSV per architecture, injects the architecture name as a column,
and returns the combined DataFrame with only the required columns. and returns the combined DataFrame with only the required columns.
""" """
<<<<<<< Updated upstream
required = [Columns.TIMESTEPS, Columns.REWARD, Columns.VELOCITY] required = [Columns.TIMESTEPS, Columns.REWARD, Columns.VELOCITY]
=======
required = [
Columns.CHECKPOINT,
Columns.TIMESTEPS,
Columns.REWARD,
Columns.INITIAL_XY_DIST,
Columns.FINAL_XY_DIST,
Columns.EVAL_STEPS,
]
>>>>>>> Stashed changes
dfs = [] dfs = []
for arch_name, filepath in file_mapping.items(): for arch_name, filepath in file_mapping.items():
@ -130,11 +142,17 @@ def load_metrics(file_mapping: dict[str, str]) -> pd.DataFrame:
return pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame() return pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
def _convergence_timestep(series: pd.Series, timesteps: pd.Series) -> float: def _convergence_timestep(
series: pd.Series, timesteps: pd.Series, checkpoints: pd.Series
) -> tuple[float, int, int]:
"""Returns the first timestep where the smoothed series reaches 95% of its peak.""" """Returns the first timestep where the smoothed series reaches 95% of its peak."""
smoothed = series.rolling(window=SMOOTHING_WINDOW, min_periods=1).mean() smoothed = series.rolling(window=SMOOTHING_WINDOW, min_periods=1).mean()
threshold = smoothed.max() * CONVERGENCE_THRESHOLD threshold = smoothed.max() * CONVERGENCE_THRESHOLD
return timesteps[smoothed >= threshold].iloc[0]
mask = smoothed >= threshold
first_idx = mask.idxmax()
return timesteps.loc[first_idx], first_idx, checkpoints.loc[first_idx]
def analyze_convergence(df: pd.DataFrame) -> pd.DataFrame: def analyze_convergence(df: pd.DataFrame) -> pd.DataFrame:
@ -147,15 +165,27 @@ def analyze_convergence(df: pd.DataFrame) -> pd.DataFrame:
for arch in df[Columns.ARCH].unique(): for arch in df[Columns.ARCH].unique():
arch_data = df[df[Columns.ARCH] == arch].sort_values(Columns.TIMESTEPS) arch_data = df[df[Columns.ARCH] == arch].sort_values(Columns.TIMESTEPS)
reward_timestep, reward_checkpoint_idx, reward_checkpoint = _convergence_timestep(
arch_data[Columns.REWARD],
arch_data[Columns.TIMESTEPS],
arch_data[Columns.CHECKPOINT],
)
velocity_timestep, velocity_checkpoint_idx, velocity_checkpoint = _convergence_timestep(
arch_data[Columns.VELOCITY],
arch_data[Columns.TIMESTEPS],
arch_data[Columns.CHECKPOINT],
)
results.append( results.append(
{ {
"Architecture": arch, "Architecture": arch,
"Reward_Convergence_Timestep": _convergence_timestep( "Reward_Convergence_Timestep": reward_timestep,
arch_data[Columns.REWARD], arch_data[Columns.TIMESTEPS] "Reward_Convergence_Checkpoint_Idx": reward_checkpoint_idx,
), "Reward_Convergence_Checkpoint": reward_checkpoint,
"Velocity_Convergence_Timestep": _convergence_timestep( "Velocity_Convergence_Timestep": velocity_timestep,
arch_data[Columns.VELOCITY], arch_data[Columns.TIMESTEPS] "Velocity_Convergence_Checkpoint_Idx": velocity_checkpoint_idx,
), "Velocity_Convergence_Checkpoint": velocity_checkpoint,
} }
) )
@ -319,6 +349,12 @@ def run_analysis(output_dir: str, **kwargs):
return return
results = analyze_convergence(df) results = analyze_convergence(df)
print(
results[
["Architecture", "Reward_Convergence_Checkpoint_Idx", "Reward_Convergence_Checkpoint"]
]
)
plot_results(df, results, output_dir, **kwargs) plot_results(df, results, output_dir, **kwargs)
logger.info("Analysis complete. Plots saved to disk.") logger.info("Analysis complete. Plots saved to disk.")