morphml 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of morphml might be problematic. Click here for more details.
- morphml/__init__.py +14 -0
- morphml/api/__init__.py +26 -0
- morphml/api/app.py +326 -0
- morphml/api/auth.py +193 -0
- morphml/api/client.py +338 -0
- morphml/api/models.py +132 -0
- morphml/api/rate_limit.py +192 -0
- morphml/benchmarking/__init__.py +36 -0
- morphml/benchmarking/comparison.py +430 -0
- morphml/benchmarks/__init__.py +56 -0
- morphml/benchmarks/comparator.py +409 -0
- morphml/benchmarks/datasets.py +280 -0
- morphml/benchmarks/metrics.py +199 -0
- morphml/benchmarks/openml_suite.py +201 -0
- morphml/benchmarks/problems.py +289 -0
- morphml/benchmarks/suite.py +318 -0
- morphml/cli/__init__.py +5 -0
- morphml/cli/commands/experiment.py +329 -0
- morphml/cli/main.py +457 -0
- morphml/cli/quickstart.py +312 -0
- morphml/config.py +278 -0
- morphml/constraints/__init__.py +19 -0
- morphml/constraints/handler.py +205 -0
- morphml/constraints/predicates.py +285 -0
- morphml/core/__init__.py +3 -0
- morphml/core/crossover.py +449 -0
- morphml/core/dsl/README.md +359 -0
- morphml/core/dsl/__init__.py +72 -0
- morphml/core/dsl/ast_nodes.py +364 -0
- morphml/core/dsl/compiler.py +318 -0
- morphml/core/dsl/layers.py +368 -0
- morphml/core/dsl/lexer.py +336 -0
- morphml/core/dsl/parser.py +455 -0
- morphml/core/dsl/search_space.py +386 -0
- morphml/core/dsl/syntax.py +199 -0
- morphml/core/dsl/type_system.py +361 -0
- morphml/core/dsl/validator.py +386 -0
- morphml/core/graph/__init__.py +40 -0
- morphml/core/graph/edge.py +124 -0
- morphml/core/graph/graph.py +507 -0
- morphml/core/graph/mutations.py +409 -0
- morphml/core/graph/node.py +196 -0
- morphml/core/graph/serialization.py +361 -0
- morphml/core/graph/visualization.py +431 -0
- morphml/core/objectives/__init__.py +20 -0
- morphml/core/search/__init__.py +33 -0
- morphml/core/search/individual.py +252 -0
- morphml/core/search/parameters.py +453 -0
- morphml/core/search/population.py +375 -0
- morphml/core/search/search_engine.py +340 -0
- morphml/distributed/__init__.py +76 -0
- morphml/distributed/fault_tolerance.py +497 -0
- morphml/distributed/health_monitor.py +348 -0
- morphml/distributed/master.py +709 -0
- morphml/distributed/proto/README.md +224 -0
- morphml/distributed/proto/__init__.py +74 -0
- morphml/distributed/proto/worker.proto +170 -0
- morphml/distributed/proto/worker_pb2.py +79 -0
- morphml/distributed/proto/worker_pb2_grpc.py +423 -0
- morphml/distributed/resource_manager.py +416 -0
- morphml/distributed/scheduler.py +567 -0
- morphml/distributed/storage/__init__.py +33 -0
- morphml/distributed/storage/artifacts.py +381 -0
- morphml/distributed/storage/cache.py +366 -0
- morphml/distributed/storage/checkpointing.py +329 -0
- morphml/distributed/storage/database.py +459 -0
- morphml/distributed/worker.py +549 -0
- morphml/evaluation/__init__.py +5 -0
- morphml/evaluation/heuristic.py +237 -0
- morphml/exceptions.py +55 -0
- morphml/execution/__init__.py +5 -0
- morphml/execution/local_executor.py +350 -0
- morphml/integrations/__init__.py +28 -0
- morphml/integrations/jax_adapter.py +206 -0
- morphml/integrations/pytorch_adapter.py +530 -0
- morphml/integrations/sklearn_adapter.py +206 -0
- morphml/integrations/tensorflow_adapter.py +230 -0
- morphml/logging_config.py +93 -0
- morphml/meta_learning/__init__.py +66 -0
- morphml/meta_learning/architecture_similarity.py +277 -0
- morphml/meta_learning/experiment_database.py +240 -0
- morphml/meta_learning/knowledge_base/__init__.py +19 -0
- morphml/meta_learning/knowledge_base/embedder.py +179 -0
- morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
- morphml/meta_learning/knowledge_base/meta_features.py +265 -0
- morphml/meta_learning/knowledge_base/vector_store.py +271 -0
- morphml/meta_learning/predictors/__init__.py +27 -0
- morphml/meta_learning/predictors/ensemble.py +221 -0
- morphml/meta_learning/predictors/gnn_predictor.py +552 -0
- morphml/meta_learning/predictors/learning_curve.py +231 -0
- morphml/meta_learning/predictors/proxy_metrics.py +261 -0
- morphml/meta_learning/strategy_evolution/__init__.py +27 -0
- morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
- morphml/meta_learning/strategy_evolution/bandit.py +276 -0
- morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
- morphml/meta_learning/transfer.py +581 -0
- morphml/meta_learning/warm_start.py +286 -0
- morphml/optimizers/__init__.py +74 -0
- morphml/optimizers/adaptive_operators.py +399 -0
- morphml/optimizers/bayesian/__init__.py +52 -0
- morphml/optimizers/bayesian/acquisition.py +387 -0
- morphml/optimizers/bayesian/base.py +319 -0
- morphml/optimizers/bayesian/gaussian_process.py +635 -0
- morphml/optimizers/bayesian/smac.py +534 -0
- morphml/optimizers/bayesian/tpe.py +411 -0
- morphml/optimizers/differential_evolution.py +220 -0
- morphml/optimizers/evolutionary/__init__.py +61 -0
- morphml/optimizers/evolutionary/cma_es.py +416 -0
- morphml/optimizers/evolutionary/differential_evolution.py +556 -0
- morphml/optimizers/evolutionary/encoding.py +426 -0
- morphml/optimizers/evolutionary/particle_swarm.py +449 -0
- morphml/optimizers/genetic_algorithm.py +486 -0
- morphml/optimizers/gradient_based/__init__.py +22 -0
- morphml/optimizers/gradient_based/darts.py +550 -0
- morphml/optimizers/gradient_based/enas.py +585 -0
- morphml/optimizers/gradient_based/operations.py +474 -0
- morphml/optimizers/gradient_based/utils.py +601 -0
- morphml/optimizers/hill_climbing.py +169 -0
- morphml/optimizers/multi_objective/__init__.py +56 -0
- morphml/optimizers/multi_objective/indicators.py +504 -0
- morphml/optimizers/multi_objective/nsga2.py +647 -0
- morphml/optimizers/multi_objective/visualization.py +427 -0
- morphml/optimizers/nsga2.py +308 -0
- morphml/optimizers/random_search.py +172 -0
- morphml/optimizers/simulated_annealing.py +181 -0
- morphml/plugins/__init__.py +35 -0
- morphml/plugins/custom_evaluator_example.py +81 -0
- morphml/plugins/custom_optimizer_example.py +63 -0
- morphml/plugins/plugin_system.py +454 -0
- morphml/reports/__init__.py +30 -0
- morphml/reports/generator.py +362 -0
- morphml/tracking/__init__.py +7 -0
- morphml/tracking/experiment.py +309 -0
- morphml/tracking/logger.py +301 -0
- morphml/tracking/reporter.py +357 -0
- morphml/utils/__init__.py +6 -0
- morphml/utils/checkpoint.py +189 -0
- morphml/utils/comparison.py +390 -0
- morphml/utils/export.py +407 -0
- morphml/utils/progress.py +392 -0
- morphml/utils/validation.py +392 -0
- morphml/version.py +7 -0
- morphml/visualization/__init__.py +50 -0
- morphml/visualization/analytics.py +423 -0
- morphml/visualization/architecture_diagrams.py +353 -0
- morphml/visualization/architecture_plot.py +223 -0
- morphml/visualization/convergence_plot.py +174 -0
- morphml/visualization/crossover_viz.py +386 -0
- morphml/visualization/graph_viz.py +338 -0
- morphml/visualization/pareto_plot.py +149 -0
- morphml/visualization/plotly_dashboards.py +422 -0
- morphml/visualization/population.py +309 -0
- morphml/visualization/progress.py +260 -0
- morphml-1.0.0.dist-info/METADATA +434 -0
- morphml-1.0.0.dist-info/RECORD +158 -0
- morphml-1.0.0.dist-info/WHEEL +4 -0
- morphml-1.0.0.dist-info/entry_points.txt +3 -0
- morphml-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"""Optimizer comparison and benchmarking tools.
|
|
2
|
+
|
|
3
|
+
This module provides tools to systematically compare different optimization
|
|
4
|
+
algorithms on the same search space and evaluation budget.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Run multiple optimizers with same budget
|
|
8
|
+
- Statistical comparison (mean, std, confidence intervals)
|
|
9
|
+
- Convergence curve comparison
|
|
10
|
+
- Sample efficiency analysis
|
|
11
|
+
- Result visualization
|
|
12
|
+
|
|
13
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
14
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from morphml.core.dsl import SearchSpace
|
|
23
|
+
from morphml.logging_config import get_logger
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OptimizerComparison:
|
|
29
|
+
"""
|
|
30
|
+
Compare multiple optimizers on the same problem.
|
|
31
|
+
|
|
32
|
+
Runs each optimizer multiple times and collects statistics for
|
|
33
|
+
rigorous comparison including convergence speed and sample efficiency.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> from morphml.benchmarking import OptimizerComparison
|
|
37
|
+
>>> from morphml.optimizers import GeneticAlgorithm, optimize_with_pso
|
|
38
|
+
>>>
|
|
39
|
+
>>> comparison = OptimizerComparison(
|
|
40
|
+
... search_space=space,
|
|
41
|
+
... evaluator=my_evaluator,
|
|
42
|
+
... budget=100,
|
|
43
|
+
... num_runs=10
|
|
44
|
+
... )
|
|
45
|
+
>>>
|
|
46
|
+
>>> comparison.add_optimizer('GA', GeneticAlgorithm(space, config))
|
|
47
|
+
>>> comparison.add_optimizer('PSO', ParticleSwarmOptimizer(space, config))
|
|
48
|
+
>>>
|
|
49
|
+
>>> results = comparison.run()
|
|
50
|
+
>>> comparison.plot_comparison()
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self, search_space: SearchSpace, evaluator: Callable, budget: int = 100, num_runs: int = 5
|
|
55
|
+
):
|
|
56
|
+
"""
|
|
57
|
+
Initialize comparison.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
search_space: SearchSpace to search
|
|
61
|
+
evaluator: Fitness evaluation function
|
|
62
|
+
budget: Number of evaluations per run
|
|
63
|
+
num_runs: Number of independent runs per optimizer
|
|
64
|
+
"""
|
|
65
|
+
self.search_space = search_space
|
|
66
|
+
self.evaluator = evaluator
|
|
67
|
+
self.budget = budget
|
|
68
|
+
self.num_runs = num_runs
|
|
69
|
+
|
|
70
|
+
self.optimizers: Dict[str, Any] = {}
|
|
71
|
+
self.results: Dict[str, List[Dict]] = {}
|
|
72
|
+
self.statistics: Dict[str, Dict] = {}
|
|
73
|
+
|
|
74
|
+
logger.info(f"Initialized OptimizerComparison: " f"budget={budget}, num_runs={num_runs}")
|
|
75
|
+
|
|
76
|
+
def add_optimizer(self, name: str, optimizer: Any) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Add optimizer to comparison.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
name: Name for this optimizer
|
|
82
|
+
optimizer: Optimizer instance or factory function
|
|
83
|
+
"""
|
|
84
|
+
self.optimizers[name] = optimizer
|
|
85
|
+
logger.debug(f"Added optimizer: {name}")
|
|
86
|
+
|
|
87
|
+
def run(self) -> Dict[str, Dict]:
|
|
88
|
+
"""
|
|
89
|
+
Run all optimizers and collect results.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Statistics dictionary with results for each optimizer
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> results = comparison.run()
|
|
96
|
+
>>> print(f"GA mean: {results['GA']['mean_best']:.4f}")
|
|
97
|
+
"""
|
|
98
|
+
logger.info(
|
|
99
|
+
f"Running comparison with {len(self.optimizers)} optimizers, "
|
|
100
|
+
f"{self.num_runs} runs each"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
for name, optimizer_template in self.optimizers.items():
|
|
104
|
+
logger.info(f"\nBenchmarking {name}...")
|
|
105
|
+
|
|
106
|
+
run_results = []
|
|
107
|
+
|
|
108
|
+
for run_id in range(self.num_runs):
|
|
109
|
+
logger.info(f" Run {run_id + 1}/{self.num_runs}")
|
|
110
|
+
|
|
111
|
+
# Create fresh optimizer instance
|
|
112
|
+
if callable(optimizer_template):
|
|
113
|
+
optimizer = optimizer_template()
|
|
114
|
+
else:
|
|
115
|
+
optimizer = optimizer_template
|
|
116
|
+
|
|
117
|
+
# Run optimization
|
|
118
|
+
start_time = time.time()
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
best = optimizer.optimize(self.evaluator)
|
|
122
|
+
elapsed_time = time.time() - start_time
|
|
123
|
+
|
|
124
|
+
# Collect results
|
|
125
|
+
history = optimizer.get_history() if hasattr(optimizer, "get_history") else []
|
|
126
|
+
|
|
127
|
+
run_results.append(
|
|
128
|
+
{
|
|
129
|
+
"run_id": run_id,
|
|
130
|
+
"best_fitness": best.fitness,
|
|
131
|
+
"best_individual": best,
|
|
132
|
+
"time_seconds": elapsed_time,
|
|
133
|
+
"history": history,
|
|
134
|
+
"success": True,
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
logger.info(
|
|
139
|
+
f" Best fitness: {best.fitness:.4f}, " f"Time: {elapsed_time:.2f}s"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f" Run failed: {e}")
|
|
144
|
+
run_results.append({"run_id": run_id, "success": False, "error": str(e)})
|
|
145
|
+
|
|
146
|
+
self.results[name] = run_results
|
|
147
|
+
|
|
148
|
+
# Compute statistics
|
|
149
|
+
self.statistics = self._compute_statistics()
|
|
150
|
+
|
|
151
|
+
# Print summary
|
|
152
|
+
self._print_summary()
|
|
153
|
+
|
|
154
|
+
return self.statistics
|
|
155
|
+
|
|
156
|
+
def _compute_statistics(self) -> Dict[str, Dict]:
|
|
157
|
+
"""
|
|
158
|
+
Compute statistics across runs for each optimizer.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Statistics dictionary
|
|
162
|
+
"""
|
|
163
|
+
stats = {}
|
|
164
|
+
|
|
165
|
+
for name, runs in self.results.items():
|
|
166
|
+
# Filter successful runs
|
|
167
|
+
successful_runs = [r for r in runs if r.get("success", False)]
|
|
168
|
+
|
|
169
|
+
if not successful_runs:
|
|
170
|
+
stats[name] = {"error": "All runs failed"}
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# Extract metrics
|
|
174
|
+
best_fitnesses = [r["best_fitness"] for r in successful_runs]
|
|
175
|
+
times = [r["time_seconds"] for r in successful_runs]
|
|
176
|
+
|
|
177
|
+
# Compute statistics
|
|
178
|
+
stats[name] = {
|
|
179
|
+
"num_successful_runs": len(successful_runs),
|
|
180
|
+
"num_failed_runs": len(runs) - len(successful_runs),
|
|
181
|
+
# Best fitness statistics
|
|
182
|
+
"mean_best": np.mean(best_fitnesses),
|
|
183
|
+
"std_best": np.std(best_fitnesses),
|
|
184
|
+
"min_best": np.min(best_fitnesses),
|
|
185
|
+
"max_best": np.max(best_fitnesses),
|
|
186
|
+
"median_best": np.median(best_fitnesses),
|
|
187
|
+
# Confidence interval (95%)
|
|
188
|
+
"ci_lower": np.percentile(best_fitnesses, 2.5),
|
|
189
|
+
"ci_upper": np.percentile(best_fitnesses, 97.5),
|
|
190
|
+
# Time statistics
|
|
191
|
+
"mean_time": np.mean(times),
|
|
192
|
+
"std_time": np.std(times),
|
|
193
|
+
# All runs data
|
|
194
|
+
"all_best_fitnesses": best_fitnesses,
|
|
195
|
+
"all_times": times,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return stats
|
|
199
|
+
|
|
200
|
+
def _print_summary(self) -> None:
|
|
201
|
+
"""Print comparison summary."""
|
|
202
|
+
print("\n" + "=" * 70)
|
|
203
|
+
print("OPTIMIZER COMPARISON SUMMARY")
|
|
204
|
+
print("=" * 70)
|
|
205
|
+
print(f"Budget: {self.budget} evaluations")
|
|
206
|
+
print(f"Runs per optimizer: {self.num_runs}")
|
|
207
|
+
print("=" * 70)
|
|
208
|
+
|
|
209
|
+
# Sort by mean best fitness (descending)
|
|
210
|
+
sorted_optimizers = sorted(
|
|
211
|
+
self.statistics.items(), key=lambda x: x[1].get("mean_best", -np.inf), reverse=True
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
print(f"\n{'Optimizer':<20} {'Mean Best':<12} {'Std':<10} {'Time (s)':<10}")
|
|
215
|
+
print("-" * 70)
|
|
216
|
+
|
|
217
|
+
for name, stats in sorted_optimizers:
|
|
218
|
+
if "error" in stats:
|
|
219
|
+
print(f"{name:<20} {'FAILED':<12}")
|
|
220
|
+
else:
|
|
221
|
+
print(
|
|
222
|
+
f"{name:<20} "
|
|
223
|
+
f"{stats['mean_best']:<12.4f} "
|
|
224
|
+
f"{stats['std_best']:<10.4f} "
|
|
225
|
+
f"{stats['mean_time']:<10.2f}"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
print("=" * 70 + "\n")
|
|
229
|
+
|
|
230
|
+
# Winner
|
|
231
|
+
if sorted_optimizers and "error" not in sorted_optimizers[0][1]:
|
|
232
|
+
winner = sorted_optimizers[0][0]
|
|
233
|
+
print(f"🏆 Best Optimizer: {winner}")
|
|
234
|
+
print(f" Mean Fitness: {sorted_optimizers[0][1]['mean_best']:.4f}")
|
|
235
|
+
print(f" Std: {sorted_optimizers[0][1]['std_best']:.4f}")
|
|
236
|
+
print("=" * 70 + "\n")
|
|
237
|
+
|
|
238
|
+
def plot_comparison(self, save_path: Optional[str] = None) -> None:
|
|
239
|
+
"""
|
|
240
|
+
Plot comparison results.
|
|
241
|
+
|
|
242
|
+
Creates box plots showing distribution of best fitnesses.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
save_path: Optional path to save figure
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
import matplotlib.pyplot as plt
|
|
249
|
+
except ImportError:
|
|
250
|
+
logger.warning("matplotlib not available, cannot plot")
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
if not self.statistics:
|
|
254
|
+
logger.warning("No results to plot. Run comparison first.")
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
# Prepare data
|
|
258
|
+
names = []
|
|
259
|
+
data = []
|
|
260
|
+
|
|
261
|
+
for name, stats in self.statistics.items():
|
|
262
|
+
if "all_best_fitnesses" in stats:
|
|
263
|
+
names.append(name)
|
|
264
|
+
data.append(stats["all_best_fitnesses"])
|
|
265
|
+
|
|
266
|
+
if not data:
|
|
267
|
+
logger.warning("No successful runs to plot")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
# Create box plot
|
|
271
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
272
|
+
|
|
273
|
+
bp = ax.boxplot(data, labels=names, patch_artist=True)
|
|
274
|
+
|
|
275
|
+
# Color boxes
|
|
276
|
+
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA07A", "#98D8C8"]
|
|
277
|
+
for patch, color in zip(bp["boxes"], colors * len(names)):
|
|
278
|
+
patch.set_facecolor(color)
|
|
279
|
+
patch.set_alpha(0.7)
|
|
280
|
+
|
|
281
|
+
ax.set_xlabel("Optimizer", fontsize=12, fontweight="bold")
|
|
282
|
+
ax.set_ylabel("Best Fitness", fontsize=12, fontweight="bold")
|
|
283
|
+
ax.set_title(
|
|
284
|
+
f"Optimizer Comparison ({self.num_runs} runs, {self.budget} evaluations)",
|
|
285
|
+
fontsize=14,
|
|
286
|
+
fontweight="bold",
|
|
287
|
+
)
|
|
288
|
+
ax.grid(True, alpha=0.3, axis="y")
|
|
289
|
+
|
|
290
|
+
plt.xticks(rotation=45, ha="right")
|
|
291
|
+
plt.tight_layout()
|
|
292
|
+
|
|
293
|
+
if save_path:
|
|
294
|
+
plt.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
295
|
+
logger.info(f"Comparison plot saved to {save_path}")
|
|
296
|
+
else:
|
|
297
|
+
plt.show()
|
|
298
|
+
|
|
299
|
+
plt.close()
|
|
300
|
+
|
|
301
|
+
def plot_convergence(self, save_path: Optional[str] = None) -> None:
|
|
302
|
+
"""
|
|
303
|
+
Plot convergence curves for all optimizers.
|
|
304
|
+
|
|
305
|
+
Shows how best fitness evolves over iterations.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
save_path: Optional path to save figure
|
|
309
|
+
"""
|
|
310
|
+
try:
|
|
311
|
+
import matplotlib.pyplot as plt
|
|
312
|
+
except ImportError:
|
|
313
|
+
logger.warning("matplotlib not available, cannot plot")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
fig, ax = plt.subplots(figsize=(12, 6))
|
|
317
|
+
|
|
318
|
+
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA07A", "#98D8C8", "#C7CEEA"]
|
|
319
|
+
|
|
320
|
+
for (name, runs), color in zip(self.results.items(), colors * len(self.results)):
|
|
321
|
+
# Average convergence across runs
|
|
322
|
+
all_histories = [r["history"] for r in runs if r.get("success") and r.get("history")]
|
|
323
|
+
|
|
324
|
+
if not all_histories:
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
# Find max length
|
|
328
|
+
max_len = max(len(h) for h in all_histories)
|
|
329
|
+
|
|
330
|
+
# Pad and average
|
|
331
|
+
padded_histories = []
|
|
332
|
+
for hist in all_histories:
|
|
333
|
+
if len(hist) < max_len:
|
|
334
|
+
# Pad with last value
|
|
335
|
+
last_val = hist[-1]["best_fitness"] if hist else 0
|
|
336
|
+
padded = hist + [{"best_fitness": last_val}] * (max_len - len(hist))
|
|
337
|
+
else:
|
|
338
|
+
padded = hist
|
|
339
|
+
|
|
340
|
+
padded_histories.append([h.get("best_fitness", 0) for h in padded])
|
|
341
|
+
|
|
342
|
+
avg_convergence = np.mean(padded_histories, axis=0)
|
|
343
|
+
std_convergence = np.std(padded_histories, axis=0)
|
|
344
|
+
|
|
345
|
+
iterations = range(len(avg_convergence))
|
|
346
|
+
|
|
347
|
+
ax.plot(iterations, avg_convergence, label=name, linewidth=2, color=color)
|
|
348
|
+
ax.fill_between(
|
|
349
|
+
iterations,
|
|
350
|
+
avg_convergence - std_convergence,
|
|
351
|
+
avg_convergence + std_convergence,
|
|
352
|
+
alpha=0.2,
|
|
353
|
+
color=color,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
ax.set_xlabel("Iteration", fontsize=12, fontweight="bold")
|
|
357
|
+
ax.set_ylabel("Best Fitness", fontsize=12, fontweight="bold")
|
|
358
|
+
ax.set_title("Convergence Comparison", fontsize=14, fontweight="bold")
|
|
359
|
+
ax.legend()
|
|
360
|
+
ax.grid(True, alpha=0.3)
|
|
361
|
+
|
|
362
|
+
plt.tight_layout()
|
|
363
|
+
|
|
364
|
+
if save_path:
|
|
365
|
+
plt.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
366
|
+
logger.info(f"Convergence plot saved to {save_path}")
|
|
367
|
+
else:
|
|
368
|
+
plt.show()
|
|
369
|
+
|
|
370
|
+
plt.close()
|
|
371
|
+
|
|
372
|
+
def get_results(self) -> Dict[str, List[Dict]]:
|
|
373
|
+
"""Get raw results."""
|
|
374
|
+
return self.results
|
|
375
|
+
|
|
376
|
+
def get_statistics(self) -> Dict[str, Dict]:
|
|
377
|
+
"""Get computed statistics."""
|
|
378
|
+
return self.statistics
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# Convenience function
|
|
382
|
+
def compare_optimizers(
|
|
383
|
+
optimizers: Dict[str, Any],
|
|
384
|
+
search_space: SearchSpace,
|
|
385
|
+
evaluator: Callable,
|
|
386
|
+
budget: int = 100,
|
|
387
|
+
num_runs: int = 5,
|
|
388
|
+
plot: bool = True,
|
|
389
|
+
) -> Dict[str, Dict]:
|
|
390
|
+
"""
|
|
391
|
+
Quick comparison of multiple optimizers.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
optimizers: Dictionary of {name: optimizer} pairs
|
|
395
|
+
search_space: SearchSpace to search
|
|
396
|
+
evaluator: Fitness evaluation function
|
|
397
|
+
budget: Number of evaluations per run
|
|
398
|
+
num_runs: Number of runs per optimizer
|
|
399
|
+
plot: Whether to generate plots
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Statistics dictionary
|
|
403
|
+
|
|
404
|
+
Example:
|
|
405
|
+
>>> from morphml.benchmarking import compare_optimizers
|
|
406
|
+
>>> from morphml.optimizers import GeneticAlgorithm, optimize_with_pso
|
|
407
|
+
>>>
|
|
408
|
+
>>> results = compare_optimizers(
|
|
409
|
+
... optimizers={
|
|
410
|
+
... 'GA': GeneticAlgorithm(space, config),
|
|
411
|
+
... 'PSO': ParticleSwarmOptimizer(space, config)
|
|
412
|
+
... },
|
|
413
|
+
... search_space=space,
|
|
414
|
+
... evaluator=my_evaluator,
|
|
415
|
+
... budget=100,
|
|
416
|
+
... num_runs=5
|
|
417
|
+
... )
|
|
418
|
+
"""
|
|
419
|
+
comparison = OptimizerComparison(search_space, evaluator, budget, num_runs)
|
|
420
|
+
|
|
421
|
+
for name, optimizer in optimizers.items():
|
|
422
|
+
comparison.add_optimizer(name, optimizer)
|
|
423
|
+
|
|
424
|
+
results = comparison.run()
|
|
425
|
+
|
|
426
|
+
if plot:
|
|
427
|
+
comparison.plot_comparison()
|
|
428
|
+
comparison.plot_convergence()
|
|
429
|
+
|
|
430
|
+
return results
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Benchmark suite for NAS optimizers."""
|
|
2
|
+
|
|
3
|
+
from morphml.benchmarks.comparator import ConvergenceAnalyzer, OptimizerComparator
|
|
4
|
+
|
|
5
|
+
# Phase 2: Extended benchmarking
|
|
6
|
+
from morphml.benchmarks.datasets import DatasetLoader, get_dataset_info
|
|
7
|
+
from morphml.benchmarks.metrics import (
|
|
8
|
+
compare_optimizers,
|
|
9
|
+
compute_all_metrics,
|
|
10
|
+
convergence_rate,
|
|
11
|
+
final_best_fitness,
|
|
12
|
+
sample_efficiency,
|
|
13
|
+
)
|
|
14
|
+
from morphml.benchmarks.problems import (
|
|
15
|
+
ComplexProblem,
|
|
16
|
+
ConstrainedProblem,
|
|
17
|
+
MultiModalProblem,
|
|
18
|
+
SimpleProblem,
|
|
19
|
+
get_all_problems,
|
|
20
|
+
)
|
|
21
|
+
from morphml.benchmarks.suite import BenchmarkResult, BenchmarkSuite
|
|
22
|
+
|
|
23
|
+
# OpenML integration (optional)
|
|
24
|
+
try:
|
|
25
|
+
from morphml.benchmarks.openml_suite import OpenMLSuite, run_openml_benchmark
|
|
26
|
+
|
|
27
|
+
_OPENML_AVAILABLE = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
OpenMLSuite = None
|
|
30
|
+
run_openml_benchmark = None
|
|
31
|
+
_OPENML_AVAILABLE = False
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Core
|
|
35
|
+
"BenchmarkSuite",
|
|
36
|
+
"BenchmarkResult",
|
|
37
|
+
"OptimizerComparator",
|
|
38
|
+
"ConvergenceAnalyzer",
|
|
39
|
+
"SimpleProblem",
|
|
40
|
+
"ComplexProblem",
|
|
41
|
+
"MultiModalProblem",
|
|
42
|
+
"ConstrainedProblem",
|
|
43
|
+
"get_all_problems",
|
|
44
|
+
# Phase 2: Datasets
|
|
45
|
+
"DatasetLoader",
|
|
46
|
+
"get_dataset_info",
|
|
47
|
+
# Phase 2: Metrics
|
|
48
|
+
"sample_efficiency",
|
|
49
|
+
"convergence_rate",
|
|
50
|
+
"final_best_fitness",
|
|
51
|
+
"compute_all_metrics",
|
|
52
|
+
"compare_optimizers",
|
|
53
|
+
# Phase 2: OpenML (if available)
|
|
54
|
+
"OpenMLSuite",
|
|
55
|
+
"run_openml_benchmark",
|
|
56
|
+
]
|