|
27 | 27 | "FINAL_TRAINING_DIR = REPO_ROOT / '.cache' / 'final_training'\n", |
28 | 28 | "COLORS = {'RS': '#1f77b4', 'GA-STANDARD': '#ff7f0e', 'GA-MEMETIC': '#d62728', 'PSO': '#2ca02c'}\n", |
29 | 29 | "\n", |
| 30 | + "\n", |
| 31 | + "def _exp_smooth(values: np.ndarray, smoothing_slider: float) -> np.ndarray:\n", |
| 32 | + " smoothed = np.empty_like(values, dtype=np.float32)\n", |
| 33 | + " last = values[0]\n", |
| 34 | + " for idx, point in enumerate(values):\n", |
| 35 | + " last = smoothing_slider * last + (1.0 - smoothing_slider) * point\n", |
| 36 | + " smoothed[idx] = last\n", |
| 37 | + " return smoothed\n", |
| 38 | + "\n", |
| 39 | + "\n", |
30 | 40 | "def parse_experiment_name(exp_name):\n", |
31 | 41 | " if '-' not in exp_name:\n", |
32 | 42 | " return None, None\n", |
|
95 | 105 | "for ax, model in zip(axes, models):\n", |
96 | 106 | " optimizers = sorted(hpo_grouped[model].keys())\n", |
97 | 107 | " data_to_plot = [hpo_grouped[model][opt] for opt in optimizers]\n", |
98 | | - " all_values = [val for sublist in data_to_plot for val in sublist]\n", |
99 | 108 | " \n", |
100 | | - " if all_values:\n", |
101 | | - " y_min, y_max = np.percentile(all_values, [2, 98])\n", |
102 | | - " ax.set_ylim(y_min - (y_max - y_min) * 0.1, y_max + (y_max - y_min) * 0.1)\n", |
103 | 109 | " \n", |
104 | 110 | " bp = ax.boxplot(data_to_plot, tick_labels=optimizers, patch_artist=True)\n", |
105 | 111 | " for patch in bp['boxes']:\n", |
106 | 112 | " patch.set_facecolor('lightblue')\n", |
107 | 113 | " \n", |
108 | | - " ax.set_title(f'{model} - Final Fitness (HPO)', fontweight='bold')\n", |
| 114 | + " ax.set_title(f'{model}', fontweight='bold')\n", |
109 | 115 | " ax.set_xlabel('Optimizer')\n", |
110 | 116 | " ax.set_ylabel('Composite Fitness')\n", |
111 | 117 | " ax.grid(True, alpha=0.3)\n", |
|
180 | 186 | " f'{val:.4f}', ha='center', va='bottom', fontsize=9)\n", |
181 | 187 | " \n", |
182 | 188 | " ax.set_ylim(0, 1)\n", |
183 | | - " ax.set_title(f'{model} - Test Performance', fontweight='bold')\n", |
| 189 | + " ax.set_title(f'{model}', fontweight='bold')\n", |
184 | 190 | " ax.set_xlabel('Optimizer')\n", |
185 | 191 | " ax.set_ylabel('Composite Fitness')\n", |
186 | 192 | " ax.grid(True, alpha=0.3, axis='y')\n", |
|
252 | 258 | " ax.fill_between(generations, mean_curve - std_curve, mean_curve + std_curve, \n", |
253 | 259 | " color=color, alpha=0.2)\n", |
254 | 260 | " \n", |
255 | | - " ax.set_title(f'{model} - Convergence', fontweight='bold')\n", |
256 | | - " ax.set_xlabel('Generation')\n", |
| 261 | + " ax.set_title(f'{model}', fontweight='bold')\n", |
| 262 | + " ax.set_xlabel('Evaluation Count ($n$)')\n", |
257 | 263 | " ax.set_ylabel('Best Fitness')\n", |
258 | 264 | " ax.legend()\n", |
259 | 265 | " ax.grid(True, alpha=0.3)\n", |
|
262 | 268 | "plt.show()\n" |
263 | 269 | ] |
264 | 270 | }, |
| 271 | + { |
| 272 | + "cell_type": "markdown", |
| 273 | + "metadata": {}, |
| 274 | + "source": [ |
| 275 | + "## Current Fitness Overlays\n" |
| 276 | + ] |
| 277 | + }, |
| 278 | + { |
| 279 | + "cell_type": "code", |
| 280 | + "execution_count": null, |
| 281 | + "metadata": {}, |
| 282 | + "outputs": [], |
| 283 | + "source": [ |
| 284 | + "SMOOTHING_SLIDER_CURRENT_FITNESS = 0.5\n", |
| 285 | + "current_overlay = defaultdict(lambda: defaultdict(list))\n", |
| 286 | + "\n", |
| 287 | + "for exp_dir in sorted(EXPERIMENT_DIR.iterdir()):\n", |
| 288 | + " if not exp_dir.is_dir():\n", |
| 289 | + " continue\n", |
| 290 | + " model, optimizer = parse_experiment_name(exp_dir.name)\n", |
| 291 | + " if not optimizer:\n", |
| 292 | + " continue\n", |
| 293 | + " for run_dir in sorted(exp_dir.iterdir()):\n", |
| 294 | + " conv_path = run_dir / 'convergence.json'\n", |
| 295 | + " with open(conv_path) as f:\n", |
| 296 | + " conv = json.load(f)\n", |
| 297 | + " evaluations = conv.get('evaluations')\n", |
| 298 | + " current_fitness = conv.get('current_fitness')\n", |
| 299 | + " if evaluations and current_fitness:\n", |
| 300 | + " current_overlay[model][optimizer].append((evaluations, current_fitness))\n", |
| 301 | + "\n", |
| 302 | + "total_runs = sum(len(v) for model_data in current_overlay.values() for v in model_data.values())\n", |
| 303 | + "print(f\"Loaded current-fitness traces for {total_runs} runs\\n\")\n", |
| 304 | + "\n", |
| 305 | + "models = sorted(current_overlay.keys())\n", |
| 306 | + "# Prefer consistent optimizer ordering; fallback to sorted discovered optimizers\n", |
| 307 | + "preferred_order = [\"RS\", \"GA-STANDARD\", \"GA-MEMETIC\", \"PSO\"]\n", |
| 308 | + "observed_opts = sorted({opt for model_data in current_overlay.values() for opt in model_data.keys()})\n", |
| 309 | + "optimizer_order = [opt for opt in preferred_order if opt in observed_opts]\n", |
| 310 | + "optimizer_order += [opt for opt in observed_opts if opt not in optimizer_order]\n", |
| 311 | + "\n", |
| 312 | + "fig, axes = plt.subplots(len(optimizer_order), len(models), figsize=(6 * len(models), 3 * len(optimizer_order)))\n", |
| 313 | + "if len(optimizer_order) == 1 and len(models) == 1:\n", |
| 314 | + " axes = [[axes]]\n", |
| 315 | + "elif len(optimizer_order) == 1:\n", |
| 316 | + " axes = [axes]\n", |
| 317 | + "elif len(models) == 1:\n", |
| 318 | + " axes = [[ax] for ax in axes]\n", |
| 319 | + "\n", |
| 320 | + "for row_idx, optimizer in enumerate(optimizer_order):\n", |
| 321 | + " for col_idx, model in enumerate(models):\n", |
| 322 | + " ax = axes[row_idx][col_idx]\n", |
| 323 | + " runs = current_overlay[model].get(optimizer, [])\n", |
| 324 | + " if not runs:\n", |
| 325 | + " ax.text(0.5, 0.5, 'N/A', ha='center', va='center')\n", |
| 326 | + " ax.set_xticks([])\n", |
| 327 | + " ax.set_yticks([])\n", |
| 328 | + " if row_idx == 0:\n", |
| 329 | + " ax.set_title(model, fontweight='bold')\n", |
| 330 | + " if col_idx == 0:\n", |
| 331 | + " ax.set_ylabel(optimizer)\n", |
| 332 | + " continue\n", |
| 333 | + "\n", |
| 334 | + " base_eval = np.array(runs[0][0])\n", |
| 335 | + " aligned = []\n", |
| 336 | + " for evals, curr in runs:\n", |
| 337 | + " evals_arr = np.array(evals)\n", |
| 338 | + " curr_arr = np.array(curr)\n", |
| 339 | + " if len(evals_arr) != len(base_eval) or not np.allclose(evals_arr, base_eval):\n", |
| 340 | + " curr_arr = np.interp(base_eval, evals_arr, curr_arr)\n", |
| 341 | + " aligned.append(curr_arr)\n", |
| 342 | + " aligned = np.array(aligned)\n", |
| 343 | + " mean_curve = aligned.mean(axis=0)\n", |
| 344 | + " std_curve = aligned.std(axis=0)\n", |
| 345 | + " mean_smoothed = _exp_smooth(mean_curve, SMOOTHING_SLIDER_CURRENT_FITNESS)\n", |
| 346 | + " std_smoothed = _exp_smooth(std_curve, SMOOTHING_SLIDER_CURRENT_FITNESS)\n", |
| 347 | + " color = COLORS.get(optimizer, '#888888')\n", |
| 348 | + " ax.fill_between(base_eval, mean_smoothed - std_smoothed, mean_smoothed + std_smoothed,\n", |
| 349 | + " color=color, alpha=0.2)\n", |
| 350 | + " ax.plot(base_eval, mean_smoothed, label=optimizer, color=color, linewidth=2)\n", |
| 351 | + "\n", |
| 352 | + " # set per-model y-limits\n", |
| 353 | + " model_key = model.lower()\n", |
| 354 | + " if model_key == 'cnn':\n", |
| 355 | + " ax.set_ylim(0.40, 0.85)\n", |
| 356 | + " elif model_key == 'dt':\n", |
| 357 | + " ax.set_ylim(0.275, 0.36)\n", |
| 358 | + " elif model_key == 'knn':\n", |
| 359 | + " ax.set_ylim(0.225, 0.475)\n", |
| 360 | + "\n", |
| 361 | + " ax.grid(True, alpha=0.3)\n", |
| 362 | + " if row_idx == len(optimizer_order) - 1:\n", |
| 363 | + " ax.set_xlabel('Evaluation ($n$)')\n", |
| 364 | + " if col_idx == 0:\n", |
| 365 | + " ax.set_ylabel(f'{optimizer}\\nCurrent Fitness')\n", |
| 366 | + " if row_idx == 0:\n", |
| 367 | + " ax.set_title(f'{model}', fontweight='bold')\n", |
| 368 | + " ax.legend(fontsize=8, loc='best')\n", |
| 369 | + "\n", |
| 370 | + "plt.tight_layout()\n", |
| 371 | + "plt.show()\n" |
| 372 | + ] |
| 373 | + }, |
265 | 374 | { |
266 | 375 | "cell_type": "markdown", |
267 | 376 | "metadata": {}, |
|
0 commit comments