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,199 @@
|
|
|
1
|
+
"""Performance metrics for benchmarking.
|
|
2
|
+
|
|
3
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
4
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from morphml.logging_config import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def sample_efficiency(history: List[Dict], target_fitness: float) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Calculate number of evaluations to reach target fitness.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
history: Optimization history
|
|
22
|
+
target_fitness: Target fitness threshold
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Number of evaluations to reach target, or -1 if not reached
|
|
26
|
+
"""
|
|
27
|
+
for i, entry in enumerate(history):
|
|
28
|
+
if entry.get("best_fitness", 0) >= target_fitness:
|
|
29
|
+
return i + 1
|
|
30
|
+
return -1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def convergence_rate(history: List[Dict]) -> float:
|
|
34
|
+
"""
|
|
35
|
+
Calculate convergence rate (improvement per evaluation).
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
history: Optimization history
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Average improvement per evaluation
|
|
42
|
+
"""
|
|
43
|
+
if len(history) < 2:
|
|
44
|
+
return 0.0
|
|
45
|
+
|
|
46
|
+
fitnesses = [entry.get("best_fitness", 0) for entry in history]
|
|
47
|
+
improvements = np.diff(fitnesses)
|
|
48
|
+
|
|
49
|
+
return np.mean(improvements[improvements > 0]) if len(improvements) > 0 else 0.0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def final_best_fitness(history: List[Dict]) -> float:
|
|
53
|
+
"""
|
|
54
|
+
Get final best fitness achieved.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
history: Optimization history
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Best fitness value
|
|
61
|
+
"""
|
|
62
|
+
if not history:
|
|
63
|
+
return 0.0
|
|
64
|
+
|
|
65
|
+
return max(entry.get("best_fitness", 0) for entry in history)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def time_efficiency(history: List[Dict], target_fitness: float) -> float:
|
|
69
|
+
"""
|
|
70
|
+
Calculate time to reach target fitness.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
history: Optimization history
|
|
74
|
+
target_fitness: Target fitness threshold
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Time in seconds, or -1 if not reached
|
|
78
|
+
"""
|
|
79
|
+
for entry in history:
|
|
80
|
+
if entry.get("best_fitness", 0) >= target_fitness:
|
|
81
|
+
return entry.get("time_elapsed", -1)
|
|
82
|
+
return -1.0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def stability_score(histories: List[List[Dict]]) -> float:
|
|
86
|
+
"""
|
|
87
|
+
Calculate stability across multiple runs.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
histories: List of optimization histories from multiple runs
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Stability score (1 - coefficient of variation of final fitness)
|
|
94
|
+
"""
|
|
95
|
+
if not histories:
|
|
96
|
+
return 0.0
|
|
97
|
+
|
|
98
|
+
final_fitnesses = [final_best_fitness(h) for h in histories]
|
|
99
|
+
|
|
100
|
+
mean = np.mean(final_fitnesses)
|
|
101
|
+
std = np.std(final_fitnesses)
|
|
102
|
+
|
|
103
|
+
if mean == 0:
|
|
104
|
+
return 0.0
|
|
105
|
+
|
|
106
|
+
cv = std / mean # Coefficient of variation
|
|
107
|
+
stability = max(0.0, 1.0 - cv)
|
|
108
|
+
|
|
109
|
+
return stability
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def area_under_curve(history: List[Dict]) -> float:
|
|
113
|
+
"""
|
|
114
|
+
Calculate area under convergence curve (AUC).
|
|
115
|
+
|
|
116
|
+
Higher AUC indicates faster convergence.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
history: Optimization history
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
AUC value
|
|
123
|
+
"""
|
|
124
|
+
if not history:
|
|
125
|
+
return 0.0
|
|
126
|
+
|
|
127
|
+
fitnesses = [entry.get("best_fitness", 0) for entry in history]
|
|
128
|
+
|
|
129
|
+
# Trapezoidal integration
|
|
130
|
+
auc = np.trapz(fitnesses)
|
|
131
|
+
|
|
132
|
+
return auc
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def success_rate(histories: List[List[Dict]], target_fitness: float) -> float:
|
|
136
|
+
"""
|
|
137
|
+
Calculate success rate (fraction of runs reaching target).
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
histories: List of optimization histories
|
|
141
|
+
target_fitness: Target fitness threshold
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Success rate [0, 1]
|
|
145
|
+
"""
|
|
146
|
+
if not histories:
|
|
147
|
+
return 0.0
|
|
148
|
+
|
|
149
|
+
successes = sum(1 for h in histories if final_best_fitness(h) >= target_fitness)
|
|
150
|
+
|
|
151
|
+
return successes / len(histories)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def compute_all_metrics(
|
|
155
|
+
history: List[Dict], target_fitness: Optional[float] = None
|
|
156
|
+
) -> Dict[str, float]:
|
|
157
|
+
"""
|
|
158
|
+
Compute all available metrics for an optimization run.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
history: Optimization history
|
|
162
|
+
target_fitness: Optional target fitness for efficiency metrics
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Dictionary of metric names and values
|
|
166
|
+
"""
|
|
167
|
+
metrics = {
|
|
168
|
+
"final_best_fitness": final_best_fitness(history),
|
|
169
|
+
"convergence_rate": convergence_rate(history),
|
|
170
|
+
"auc": area_under_curve(history),
|
|
171
|
+
"num_evaluations": len(history),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if target_fitness is not None:
|
|
175
|
+
metrics["sample_efficiency"] = sample_efficiency(history, target_fitness)
|
|
176
|
+
metrics["time_efficiency"] = time_efficiency(history, target_fitness)
|
|
177
|
+
|
|
178
|
+
return metrics
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def compare_optimizers(
|
|
182
|
+
results: Dict[str, List[Dict]], target_fitness: Optional[float] = None
|
|
183
|
+
) -> Dict[str, Dict[str, float]]:
|
|
184
|
+
"""
|
|
185
|
+
Compare multiple optimizers across metrics.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
results: Dict mapping optimizer names to their histories
|
|
189
|
+
target_fitness: Optional target for efficiency metrics
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Dict of optimizer names to their metric dictionaries
|
|
193
|
+
"""
|
|
194
|
+
comparison = {}
|
|
195
|
+
|
|
196
|
+
for optimizer_name, history in results.items():
|
|
197
|
+
comparison[optimizer_name] = compute_all_metrics(history, target_fitness)
|
|
198
|
+
|
|
199
|
+
return comparison
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""OpenML integration for benchmarking.
|
|
2
|
+
|
|
3
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
4
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Tuple
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from morphml.logging_config import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
# Check if OpenML is available
|
|
16
|
+
try:
|
|
17
|
+
import openml
|
|
18
|
+
|
|
19
|
+
OPENML_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
OPENML_AVAILABLE = False
|
|
22
|
+
logger.warning("OpenML not available. Install with: pip install openml")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OpenMLSuite:
|
|
26
|
+
"""
|
|
27
|
+
OpenML benchmark suite for NAS evaluation.
|
|
28
|
+
|
|
29
|
+
Provides access to curated machine learning datasets from OpenML.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> suite = OpenMLSuite()
|
|
33
|
+
>>> task = suite.get_task(3) # CIFAR-10
|
|
34
|
+
>>> X_train, y_train, X_test, y_test = suite.load_task_data(task)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Curated task IDs for benchmarking
|
|
38
|
+
BENCHMARK_TASKS = {
|
|
39
|
+
"mnist": 3573,
|
|
40
|
+
"fashion_mnist": 146825,
|
|
41
|
+
"cifar10": 167124,
|
|
42
|
+
"svhn": 168757,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def __init__(self):
|
|
46
|
+
"""Initialize OpenML suite."""
|
|
47
|
+
if not OPENML_AVAILABLE:
|
|
48
|
+
raise ImportError(
|
|
49
|
+
"OpenML required for benchmark suite. " "Install with: pip install openml"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.tasks = {}
|
|
53
|
+
logger.info("Initialized OpenML benchmark suite")
|
|
54
|
+
|
|
55
|
+
def get_task(self, task_id: int):
|
|
56
|
+
"""
|
|
57
|
+
Get an OpenML task.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
task_id: OpenML task ID
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
OpenML task object
|
|
64
|
+
"""
|
|
65
|
+
if task_id in self.tasks:
|
|
66
|
+
return self.tasks[task_id]
|
|
67
|
+
|
|
68
|
+
logger.info(f"Fetching OpenML task {task_id}...")
|
|
69
|
+
task = openml.tasks.get_task(task_id)
|
|
70
|
+
self.tasks[task_id] = task
|
|
71
|
+
|
|
72
|
+
return task
|
|
73
|
+
|
|
74
|
+
def get_task_by_name(self, name: str):
|
|
75
|
+
"""
|
|
76
|
+
Get a benchmark task by name.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: Task name (e.g., 'mnist', 'cifar10')
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
OpenML task object
|
|
83
|
+
"""
|
|
84
|
+
task_id = self.BENCHMARK_TASKS.get(name.lower())
|
|
85
|
+
if task_id is None:
|
|
86
|
+
raise ValueError(f"Unknown benchmark task: {name}")
|
|
87
|
+
|
|
88
|
+
return self.get_task(task_id)
|
|
89
|
+
|
|
90
|
+
def load_task_data(
|
|
91
|
+
self, task, normalize: bool = True
|
|
92
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
93
|
+
"""
|
|
94
|
+
Load data for a task.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
task: OpenML task object
|
|
98
|
+
normalize: Whether to normalize features
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Tuple of (X_train, y_train, X_test, y_test)
|
|
102
|
+
"""
|
|
103
|
+
logger.info(f"Loading data for task {task.task_id}...")
|
|
104
|
+
|
|
105
|
+
dataset = task.get_dataset()
|
|
106
|
+
X, y, _, _ = dataset.get_data(target=task.target_name)
|
|
107
|
+
|
|
108
|
+
X = np.array(X)
|
|
109
|
+
y = np.array(y)
|
|
110
|
+
|
|
111
|
+
if normalize and X.dtype in [np.float32, np.float64]:
|
|
112
|
+
X = (X - X.mean(axis=0)) / (X.std(axis=0) + 1e-8)
|
|
113
|
+
|
|
114
|
+
# Split train/test (80/20)
|
|
115
|
+
split_idx = int(0.8 * len(X))
|
|
116
|
+
X_train, X_test = X[:split_idx], X[split_idx:]
|
|
117
|
+
y_train, y_test = y[:split_idx], y[split_idx:]
|
|
118
|
+
|
|
119
|
+
logger.info(f"Loaded task data: train={X_train.shape}, test={X_test.shape}")
|
|
120
|
+
|
|
121
|
+
return X_train, y_train, X_test, y_test
|
|
122
|
+
|
|
123
|
+
def list_benchmark_tasks(self) -> List[str]:
|
|
124
|
+
"""List available benchmark tasks."""
|
|
125
|
+
return list(self.BENCHMARK_TASKS.keys())
|
|
126
|
+
|
|
127
|
+
def get_task_info(self, task_name: str) -> Dict:
|
|
128
|
+
"""
|
|
129
|
+
Get information about a benchmark task.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
task_name: Task name
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary with task metadata
|
|
136
|
+
"""
|
|
137
|
+
task_id = self.BENCHMARK_TASKS.get(task_name)
|
|
138
|
+
if task_id is None:
|
|
139
|
+
return {}
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
task = self.get_task(task_id)
|
|
143
|
+
dataset = task.get_dataset()
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
"name": task_name,
|
|
147
|
+
"task_id": task_id,
|
|
148
|
+
"dataset_name": dataset.name,
|
|
149
|
+
"num_instances": dataset.qualities.get("NumberOfInstances"),
|
|
150
|
+
"num_features": dataset.qualities.get("NumberOfFeatures"),
|
|
151
|
+
"num_classes": dataset.qualities.get("NumberOfClasses"),
|
|
152
|
+
}
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"Error fetching task info: {e}")
|
|
155
|
+
return {"name": task_name, "task_id": task_id, "error": str(e)}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def run_openml_benchmark(optimizer, task_name: str, evaluator, num_runs: int = 5) -> List[Dict]:
|
|
159
|
+
"""
|
|
160
|
+
Run optimizer on an OpenML benchmark task.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
optimizer: Optimizer instance
|
|
164
|
+
task_name: OpenML task name
|
|
165
|
+
evaluator: Evaluator function
|
|
166
|
+
num_runs: Number of independent runs
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List of result dictionaries
|
|
170
|
+
"""
|
|
171
|
+
if not OPENML_AVAILABLE:
|
|
172
|
+
raise ImportError("OpenML required")
|
|
173
|
+
|
|
174
|
+
OpenMLSuite()
|
|
175
|
+
|
|
176
|
+
logger.info(f"Running benchmark: {task_name} with {num_runs} runs")
|
|
177
|
+
|
|
178
|
+
results = []
|
|
179
|
+
|
|
180
|
+
for run in range(num_runs):
|
|
181
|
+
logger.info(f"Run {run + 1}/{num_runs}")
|
|
182
|
+
|
|
183
|
+
# Reset optimizer for each run
|
|
184
|
+
if hasattr(optimizer, "reset"):
|
|
185
|
+
optimizer.reset()
|
|
186
|
+
|
|
187
|
+
# Run optimization
|
|
188
|
+
best = optimizer.optimize(evaluator)
|
|
189
|
+
|
|
190
|
+
result = {
|
|
191
|
+
"run": run + 1,
|
|
192
|
+
"task": task_name,
|
|
193
|
+
"best_fitness": best.fitness if hasattr(best, "fitness") else 0.0,
|
|
194
|
+
"history": optimizer.get_history() if hasattr(optimizer, "get_history") else [],
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
results.append(result)
|
|
198
|
+
|
|
199
|
+
logger.info(f"Benchmark complete: {task_name}")
|
|
200
|
+
|
|
201
|
+
return results
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Benchmark problems for optimizer evaluation."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from morphml.core.dsl import Layer, SearchSpace
|
|
6
|
+
from morphml.core.graph import ModelGraph
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BenchmarkProblem:
|
|
10
|
+
"""Base class for benchmark problems."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, name: str, search_space: SearchSpace):
|
|
13
|
+
"""Initialize benchmark problem."""
|
|
14
|
+
self.name = name
|
|
15
|
+
self.search_space = search_space
|
|
16
|
+
|
|
17
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
18
|
+
"""Evaluate architecture on this problem."""
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
def get_optimal_fitness(self) -> float:
|
|
22
|
+
"""Get known optimal fitness if available."""
|
|
23
|
+
return 1.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SimpleProblem(BenchmarkProblem):
|
|
27
|
+
"""Simple benchmark problem."""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
"""Initialize simple problem."""
|
|
31
|
+
space = SearchSpace("simple_problem")
|
|
32
|
+
space.add_layers(
|
|
33
|
+
Layer.input(shape=(3, 32, 32)),
|
|
34
|
+
Layer.conv2d(filters=[32, 64], kernel_size=3),
|
|
35
|
+
Layer.relu(),
|
|
36
|
+
Layer.output(units=10),
|
|
37
|
+
)
|
|
38
|
+
super().__init__("SimpleProblem", space)
|
|
39
|
+
|
|
40
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
41
|
+
"""Simple evaluation based on node count."""
|
|
42
|
+
num_nodes = len(graph.nodes)
|
|
43
|
+
depth = graph.get_depth()
|
|
44
|
+
|
|
45
|
+
# Prefer moderate complexity
|
|
46
|
+
node_score = 1.0 / (1.0 + abs(num_nodes - 5))
|
|
47
|
+
depth_score = 1.0 / (1.0 + abs(depth - 4))
|
|
48
|
+
|
|
49
|
+
return 0.5 * node_score + 0.5 * depth_score
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ComplexProblem(BenchmarkProblem):
|
|
53
|
+
"""Complex benchmark problem with multiple criteria."""
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
"""Initialize complex problem."""
|
|
57
|
+
space = SearchSpace("complex_problem")
|
|
58
|
+
space.add_layers(
|
|
59
|
+
Layer.input(shape=(3, 224, 224)),
|
|
60
|
+
Layer.conv2d(filters=[32, 64, 128, 256], kernel_size=[3, 5, 7]),
|
|
61
|
+
Layer.relu(),
|
|
62
|
+
Layer.batchnorm(),
|
|
63
|
+
Layer.maxpool(pool_size=[2, 3]),
|
|
64
|
+
Layer.conv2d(filters=[64, 128, 256, 512], kernel_size=[3, 5]),
|
|
65
|
+
Layer.relu(),
|
|
66
|
+
Layer.batchnorm(),
|
|
67
|
+
Layer.maxpool(pool_size=2),
|
|
68
|
+
Layer.dense(units=[256, 512, 1024, 2048]),
|
|
69
|
+
Layer.dropout(rate=[0.3, 0.5, 0.7]),
|
|
70
|
+
Layer.dense(units=[128, 256, 512]),
|
|
71
|
+
Layer.output(units=1000),
|
|
72
|
+
)
|
|
73
|
+
super().__init__("ComplexProblem", space)
|
|
74
|
+
|
|
75
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
76
|
+
"""Complex evaluation considering multiple factors."""
|
|
77
|
+
num_nodes = len(graph.nodes)
|
|
78
|
+
depth = graph.get_depth()
|
|
79
|
+
width = graph.get_max_width()
|
|
80
|
+
params = graph.estimate_parameters()
|
|
81
|
+
|
|
82
|
+
# Multiple objectives
|
|
83
|
+
node_score = math.exp(-abs(num_nodes - 12) / 5.0)
|
|
84
|
+
depth_score = math.exp(-abs(depth - 8) / 3.0)
|
|
85
|
+
width_score = math.exp(-abs(width - 4) / 2.0)
|
|
86
|
+
param_score = 1.0 / (1.0 + params / 5000000.0)
|
|
87
|
+
|
|
88
|
+
# Weighted combination
|
|
89
|
+
score = 0.25 * node_score + 0.25 * depth_score + 0.25 * width_score + 0.25 * param_score
|
|
90
|
+
|
|
91
|
+
return min(1.0, max(0.0, score))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class MultiModalProblem(BenchmarkProblem):
|
|
95
|
+
"""Multi-modal problem with multiple local optima."""
|
|
96
|
+
|
|
97
|
+
def __init__(self):
|
|
98
|
+
"""Initialize multi-modal problem."""
|
|
99
|
+
space = SearchSpace("multimodal_problem")
|
|
100
|
+
space.add_layers(
|
|
101
|
+
Layer.input(shape=(3, 32, 32)),
|
|
102
|
+
Layer.conv2d(filters=[16, 32, 64, 128, 256], kernel_size=[3, 5, 7]),
|
|
103
|
+
Layer.relu(),
|
|
104
|
+
Layer.maxpool(pool_size=[2, 3]),
|
|
105
|
+
Layer.conv2d(filters=[32, 64, 128, 256], kernel_size=[3, 5]),
|
|
106
|
+
Layer.relu(),
|
|
107
|
+
Layer.dense(units=[64, 128, 256, 512, 1024]),
|
|
108
|
+
Layer.dropout(rate=[0.2, 0.3, 0.4, 0.5, 0.6]),
|
|
109
|
+
Layer.output(units=10),
|
|
110
|
+
)
|
|
111
|
+
super().__init__("MultiModalProblem", space)
|
|
112
|
+
|
|
113
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
114
|
+
"""Evaluation with multiple peaks."""
|
|
115
|
+
num_nodes = len(graph.nodes)
|
|
116
|
+
depth = graph.get_depth()
|
|
117
|
+
|
|
118
|
+
# Create multiple peaks
|
|
119
|
+
peak1 = math.exp(-((num_nodes - 6) ** 2 + (depth - 3) ** 2) / 4.0)
|
|
120
|
+
peak2 = 0.8 * math.exp(-((num_nodes - 9) ** 2 + (depth - 5) ** 2) / 3.0)
|
|
121
|
+
peak3 = 0.6 * math.exp(-((num_nodes - 12) ** 2 + (depth - 7) ** 2) / 5.0)
|
|
122
|
+
|
|
123
|
+
return max(peak1, peak2, peak3)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ConstrainedProblem(BenchmarkProblem):
|
|
127
|
+
"""Problem with hard constraints."""
|
|
128
|
+
|
|
129
|
+
def __init__(self):
|
|
130
|
+
"""Initialize constrained problem."""
|
|
131
|
+
space = SearchSpace("constrained_problem")
|
|
132
|
+
space.add_layers(
|
|
133
|
+
Layer.input(shape=(3, 32, 32)),
|
|
134
|
+
Layer.conv2d(filters=[32, 64, 128], kernel_size=3),
|
|
135
|
+
Layer.relu(),
|
|
136
|
+
Layer.maxpool(pool_size=2),
|
|
137
|
+
Layer.conv2d(filters=[64, 128, 256], kernel_size=3),
|
|
138
|
+
Layer.relu(),
|
|
139
|
+
Layer.dense(units=[128, 256, 512]),
|
|
140
|
+
Layer.output(units=10),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Add constraints
|
|
144
|
+
def max_depth_constraint(g):
|
|
145
|
+
return g.get_depth() <= 8
|
|
146
|
+
|
|
147
|
+
def max_params_constraint(g):
|
|
148
|
+
return g.estimate_parameters() <= 1000000
|
|
149
|
+
|
|
150
|
+
space.add_constraint(max_depth_constraint)
|
|
151
|
+
space.add_constraint(max_params_constraint)
|
|
152
|
+
|
|
153
|
+
super().__init__("ConstrainedProblem", space)
|
|
154
|
+
|
|
155
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
156
|
+
"""Evaluation with penalty for constraint violation."""
|
|
157
|
+
# Check constraints
|
|
158
|
+
depth = graph.get_depth()
|
|
159
|
+
params = graph.estimate_parameters()
|
|
160
|
+
|
|
161
|
+
penalty = 0.0
|
|
162
|
+
|
|
163
|
+
if depth > 8:
|
|
164
|
+
penalty += 0.5 * (depth - 8) / 8.0
|
|
165
|
+
|
|
166
|
+
if params > 1000000:
|
|
167
|
+
penalty += 0.5 * (params - 1000000) / 1000000.0
|
|
168
|
+
|
|
169
|
+
# Base fitness
|
|
170
|
+
base_fitness = 0.5 + 0.05 * len(graph.nodes)
|
|
171
|
+
|
|
172
|
+
# Apply penalty
|
|
173
|
+
return max(0.0, base_fitness - penalty)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class NoisyProblem(BenchmarkProblem):
|
|
177
|
+
"""Problem with noisy evaluations."""
|
|
178
|
+
|
|
179
|
+
def __init__(self, noise_level: float = 0.1):
|
|
180
|
+
"""Initialize noisy problem."""
|
|
181
|
+
space = SearchSpace("noisy_problem")
|
|
182
|
+
space.add_layers(
|
|
183
|
+
Layer.input(shape=(3, 32, 32)),
|
|
184
|
+
Layer.conv2d(filters=[32, 64], kernel_size=3),
|
|
185
|
+
Layer.relu(),
|
|
186
|
+
Layer.maxpool(pool_size=2),
|
|
187
|
+
Layer.dense(units=[128, 256]),
|
|
188
|
+
Layer.output(units=10),
|
|
189
|
+
)
|
|
190
|
+
super().__init__("NoisyProblem", space)
|
|
191
|
+
self.noise_level = noise_level
|
|
192
|
+
|
|
193
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
194
|
+
"""Evaluation with added noise."""
|
|
195
|
+
import random
|
|
196
|
+
|
|
197
|
+
# Base evaluation
|
|
198
|
+
num_nodes = len(graph.nodes)
|
|
199
|
+
base_fitness = 0.6 + 0.04 * num_nodes
|
|
200
|
+
|
|
201
|
+
# Add noise
|
|
202
|
+
noise = random.gauss(0, self.noise_level)
|
|
203
|
+
noisy_fitness = base_fitness + noise
|
|
204
|
+
|
|
205
|
+
return max(0.0, min(1.0, noisy_fitness))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class RastriginProblem(BenchmarkProblem):
|
|
209
|
+
"""Rastrigin-like function adapted for graphs."""
|
|
210
|
+
|
|
211
|
+
def __init__(self):
|
|
212
|
+
"""Initialize Rastrigin problem."""
|
|
213
|
+
space = SearchSpace("rastrigin_problem")
|
|
214
|
+
space.add_layers(
|
|
215
|
+
Layer.input(shape=(3, 32, 32)),
|
|
216
|
+
Layer.conv2d(filters=[16, 32, 64, 128], kernel_size=[3, 5, 7]),
|
|
217
|
+
Layer.relu(),
|
|
218
|
+
Layer.maxpool(pool_size=[2, 3]),
|
|
219
|
+
Layer.dense(units=[64, 128, 256, 512]),
|
|
220
|
+
Layer.output(units=10),
|
|
221
|
+
)
|
|
222
|
+
super().__init__("RastriginProblem", space)
|
|
223
|
+
|
|
224
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
225
|
+
"""Rastrigin-like evaluation."""
|
|
226
|
+
num_nodes = len(graph.nodes)
|
|
227
|
+
depth = graph.get_depth()
|
|
228
|
+
|
|
229
|
+
# Rastrigin-like function
|
|
230
|
+
A = 10
|
|
231
|
+
x = (num_nodes - 8) / 4.0
|
|
232
|
+
y = (depth - 5) / 3.0
|
|
233
|
+
|
|
234
|
+
value = 2 * A
|
|
235
|
+
value += x**2 - A * math.cos(2 * math.pi * x)
|
|
236
|
+
value += y**2 - A * math.cos(2 * math.pi * y)
|
|
237
|
+
|
|
238
|
+
# Convert to fitness (0-1 range, higher is better)
|
|
239
|
+
fitness = 1.0 / (1.0 + value / 20.0)
|
|
240
|
+
|
|
241
|
+
return fitness
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class RosenbrockProblem(BenchmarkProblem):
|
|
245
|
+
"""Rosenbrock-like function adapted for graphs."""
|
|
246
|
+
|
|
247
|
+
def __init__(self):
|
|
248
|
+
"""Initialize Rosenbrock problem."""
|
|
249
|
+
space = SearchSpace("rosenbrock_problem")
|
|
250
|
+
space.add_layers(
|
|
251
|
+
Layer.input(shape=(3, 32, 32)),
|
|
252
|
+
Layer.conv2d(filters=[32, 64, 128], kernel_size=[3, 5]),
|
|
253
|
+
Layer.relu(),
|
|
254
|
+
Layer.dense(units=[128, 256, 512]),
|
|
255
|
+
Layer.output(units=10),
|
|
256
|
+
)
|
|
257
|
+
super().__init__("RosenbrockProblem", space)
|
|
258
|
+
|
|
259
|
+
def evaluate(self, graph: ModelGraph) -> float:
|
|
260
|
+
"""Rosenbrock-like evaluation."""
|
|
261
|
+
num_nodes = len(graph.nodes)
|
|
262
|
+
depth = graph.get_depth()
|
|
263
|
+
|
|
264
|
+
# Rosenbrock-like function
|
|
265
|
+
a = 1
|
|
266
|
+
b = 100
|
|
267
|
+
|
|
268
|
+
x = (num_nodes - 7) / 3.0
|
|
269
|
+
y = (depth - 4) / 2.0
|
|
270
|
+
|
|
271
|
+
value = (a - x) ** 2 + b * (y - x**2) ** 2
|
|
272
|
+
|
|
273
|
+
# Convert to fitness
|
|
274
|
+
fitness = 1.0 / (1.0 + value / 50.0)
|
|
275
|
+
|
|
276
|
+
return fitness
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_all_problems() -> list:
|
|
280
|
+
"""Get all benchmark problems."""
|
|
281
|
+
return [
|
|
282
|
+
SimpleProblem(),
|
|
283
|
+
ComplexProblem(),
|
|
284
|
+
MultiModalProblem(),
|
|
285
|
+
ConstrainedProblem(),
|
|
286
|
+
NoisyProblem(noise_level=0.1),
|
|
287
|
+
RastriginProblem(),
|
|
288
|
+
RosenbrockProblem(),
|
|
289
|
+
]
|