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,237 @@
|
|
|
1
|
+
"""Heuristic evaluators for fast architecture assessment.
|
|
2
|
+
|
|
3
|
+
These evaluators estimate architecture quality without training,
|
|
4
|
+
enabling rapid architecture search and development.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from morphml.evaluation import HeuristicEvaluator
|
|
8
|
+
>>>
|
|
9
|
+
>>> evaluator = HeuristicEvaluator()
|
|
10
|
+
>>> score = evaluator.combined_score(graph)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Dict
|
|
14
|
+
|
|
15
|
+
from morphml.core.graph import ModelGraph
|
|
16
|
+
from morphml.logging_config import get_logger
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HeuristicEvaluator:
|
|
22
|
+
"""
|
|
23
|
+
Fast heuristic evaluation without training.
|
|
24
|
+
|
|
25
|
+
Provides multiple proxy metrics that correlate with actual
|
|
26
|
+
performance, enabling rapid architecture assessment.
|
|
27
|
+
|
|
28
|
+
Metrics:
|
|
29
|
+
- Parameter count
|
|
30
|
+
- FLOPs estimation
|
|
31
|
+
- Graph depth
|
|
32
|
+
- Graph width
|
|
33
|
+
- Connectivity ratio
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> evaluator = HeuristicEvaluator(
|
|
37
|
+
... param_weight=0.3,
|
|
38
|
+
... depth_weight=0.3,
|
|
39
|
+
... width_weight=0.2
|
|
40
|
+
... )
|
|
41
|
+
>>> score = evaluator(graph)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
param_weight: float = 0.3,
|
|
47
|
+
depth_weight: float = 0.3,
|
|
48
|
+
width_weight: float = 0.2,
|
|
49
|
+
connectivity_weight: float = 0.2,
|
|
50
|
+
target_params: int = 1000000,
|
|
51
|
+
target_depth: int = 20,
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Initialize heuristic evaluator.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
param_weight: Weight for parameter penalty
|
|
58
|
+
depth_weight: Weight for depth score
|
|
59
|
+
width_weight: Weight for width score
|
|
60
|
+
connectivity_weight: Weight for connectivity
|
|
61
|
+
target_params: Target parameter count
|
|
62
|
+
target_depth: Target network depth
|
|
63
|
+
"""
|
|
64
|
+
self.param_weight = param_weight
|
|
65
|
+
self.depth_weight = depth_weight
|
|
66
|
+
self.width_weight = width_weight
|
|
67
|
+
self.connectivity_weight = connectivity_weight
|
|
68
|
+
self.target_params = target_params
|
|
69
|
+
self.target_depth = target_depth
|
|
70
|
+
|
|
71
|
+
def __call__(self, graph: ModelGraph) -> float:
|
|
72
|
+
"""Evaluate using combined score."""
|
|
73
|
+
return self.combined_score(graph)
|
|
74
|
+
|
|
75
|
+
def parameter_score(self, graph: ModelGraph) -> float:
|
|
76
|
+
"""
|
|
77
|
+
Score based on parameter count.
|
|
78
|
+
|
|
79
|
+
Penalizes large models, rewards compact ones.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
graph: Architecture to evaluate
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Score in [0, 1]
|
|
86
|
+
"""
|
|
87
|
+
params = graph.estimate_parameters()
|
|
88
|
+
|
|
89
|
+
# Sigmoid-like scoring
|
|
90
|
+
ratio = params / self.target_params
|
|
91
|
+
if ratio < 0.5:
|
|
92
|
+
score = 1.0
|
|
93
|
+
elif ratio < 1.0:
|
|
94
|
+
score = 1.0 - 0.5 * (ratio - 0.5)
|
|
95
|
+
elif ratio < 2.0:
|
|
96
|
+
score = 0.75 - 0.5 * (ratio - 1.0)
|
|
97
|
+
else:
|
|
98
|
+
score = max(0.1, 0.25 - 0.1 * (ratio - 2.0))
|
|
99
|
+
|
|
100
|
+
return score
|
|
101
|
+
|
|
102
|
+
def depth_score(self, graph: ModelGraph) -> float:
|
|
103
|
+
"""
|
|
104
|
+
Score based on network depth.
|
|
105
|
+
|
|
106
|
+
Moderate depth is preferred.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
graph: Architecture to evaluate
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Score in [0, 1]
|
|
113
|
+
"""
|
|
114
|
+
depth = graph.get_depth()
|
|
115
|
+
|
|
116
|
+
if depth < 5:
|
|
117
|
+
score = 0.5 + 0.1 * depth # Too shallow
|
|
118
|
+
elif depth <= self.target_depth:
|
|
119
|
+
score = 0.8 + 0.01 * depth # Good range
|
|
120
|
+
else:
|
|
121
|
+
score = max(0.3, 1.0 - 0.02 * (depth - self.target_depth)) # Too deep
|
|
122
|
+
|
|
123
|
+
return min(1.0, score)
|
|
124
|
+
|
|
125
|
+
def width_score(self, graph: ModelGraph) -> float:
|
|
126
|
+
"""
|
|
127
|
+
Score based on network width.
|
|
128
|
+
|
|
129
|
+
Moderate width is preferred.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
graph: Architecture to evaluate
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Score in [0, 1]
|
|
136
|
+
"""
|
|
137
|
+
width = graph.get_max_width()
|
|
138
|
+
|
|
139
|
+
if width < 3:
|
|
140
|
+
score = 0.5 + 0.1 * width
|
|
141
|
+
elif width <= 10:
|
|
142
|
+
score = 0.8 + 0.02 * width
|
|
143
|
+
else:
|
|
144
|
+
score = max(0.5, 1.0 - 0.02 * (width - 10))
|
|
145
|
+
|
|
146
|
+
return min(1.0, score)
|
|
147
|
+
|
|
148
|
+
def connectivity_score(self, graph: ModelGraph) -> float:
|
|
149
|
+
"""
|
|
150
|
+
Score based on connectivity ratio.
|
|
151
|
+
|
|
152
|
+
Measures how well-connected the graph is.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
graph: Architecture to evaluate
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Score in [0, 1]
|
|
159
|
+
"""
|
|
160
|
+
num_nodes = len(graph.nodes)
|
|
161
|
+
num_edges = len(graph.edges)
|
|
162
|
+
|
|
163
|
+
if num_nodes <= 1:
|
|
164
|
+
return 0.5
|
|
165
|
+
|
|
166
|
+
# Expected edges for DAG: between n-1 (linear) and n*(n-1)/2 (complete)
|
|
167
|
+
min_edges = num_nodes - 1
|
|
168
|
+
max_edges = num_nodes * (num_nodes - 1) / 2
|
|
169
|
+
|
|
170
|
+
if num_edges < min_edges:
|
|
171
|
+
return 0.0 # Disconnected
|
|
172
|
+
|
|
173
|
+
# Normalize
|
|
174
|
+
connectivity_ratio = (num_edges - min_edges) / (max_edges - min_edges + 1)
|
|
175
|
+
|
|
176
|
+
# Prefer moderate connectivity
|
|
177
|
+
if connectivity_ratio < 0.3:
|
|
178
|
+
score = 0.6 + connectivity_ratio
|
|
179
|
+
elif connectivity_ratio < 0.7:
|
|
180
|
+
score = 0.9
|
|
181
|
+
else:
|
|
182
|
+
score = 0.9 - 0.5 * (connectivity_ratio - 0.7)
|
|
183
|
+
|
|
184
|
+
return score
|
|
185
|
+
|
|
186
|
+
def combined_score(self, graph: ModelGraph) -> float:
|
|
187
|
+
"""
|
|
188
|
+
Combined heuristic score.
|
|
189
|
+
|
|
190
|
+
Weighted combination of all metrics.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
graph: Architecture to evaluate
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Combined score in [0, 1]
|
|
197
|
+
"""
|
|
198
|
+
param_score = self.parameter_score(graph)
|
|
199
|
+
depth_score = self.depth_score(graph)
|
|
200
|
+
width_score = self.width_score(graph)
|
|
201
|
+
connectivity_score = self.connectivity_score(graph)
|
|
202
|
+
|
|
203
|
+
combined = (
|
|
204
|
+
self.param_weight * param_score
|
|
205
|
+
+ self.depth_weight * depth_score
|
|
206
|
+
+ self.width_weight * width_score
|
|
207
|
+
+ self.connectivity_weight * connectivity_score
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Ensure in [0, 1]
|
|
211
|
+
combined = max(0.0, min(1.0, combined))
|
|
212
|
+
|
|
213
|
+
logger.debug(
|
|
214
|
+
f"Heuristic scores: param={param_score:.3f}, depth={depth_score:.3f}, "
|
|
215
|
+
f"width={width_score:.3f}, connectivity={connectivity_score:.3f}, "
|
|
216
|
+
f"combined={combined:.3f}"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return combined
|
|
220
|
+
|
|
221
|
+
def get_all_scores(self, graph: ModelGraph) -> Dict[str, float]:
|
|
222
|
+
"""
|
|
223
|
+
Get all individual scores.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
graph: Architecture to evaluate
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dictionary of all scores
|
|
230
|
+
"""
|
|
231
|
+
return {
|
|
232
|
+
"parameter": self.parameter_score(graph),
|
|
233
|
+
"depth": self.depth_score(graph),
|
|
234
|
+
"width": self.width_score(graph),
|
|
235
|
+
"connectivity": self.connectivity_score(graph),
|
|
236
|
+
"combined": self.combined_score(graph),
|
|
237
|
+
}
|
morphml/exceptions.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Custom exceptions for MorphML."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MorphMLError(Exception):
|
|
5
|
+
"""Base exception for all MorphML errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DSLError(MorphMLError):
|
|
11
|
+
"""Raised when DSL parsing or compilation fails."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GraphError(MorphMLError):
|
|
17
|
+
"""Raised when graph operations fail."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SearchSpaceError(MorphMLError):
|
|
23
|
+
"""Raised when search space definition is invalid."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OptimizerError(MorphMLError):
|
|
29
|
+
"""Raised when optimizer encounters an error."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EvaluationError(MorphMLError):
|
|
35
|
+
"""Raised when architecture evaluation fails."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConfigurationError(MorphMLError):
|
|
41
|
+
"""Raised when configuration is invalid."""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DistributedError(MorphMLError):
|
|
47
|
+
"""Raised when distributed operations fail."""
|
|
48
|
+
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ValidationError(MorphMLError):
|
|
53
|
+
"""Raised when validation fails."""
|
|
54
|
+
|
|
55
|
+
pass
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""Local executor for running experiments on a single machine.
|
|
2
|
+
|
|
3
|
+
Provides LocalExecutor class for orchestrating the complete
|
|
4
|
+
experiment lifecycle on a single machine.
|
|
5
|
+
|
|
6
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
7
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Callable, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from morphml.config import get_config
|
|
15
|
+
from morphml.core.dsl.search_space import SearchSpace
|
|
16
|
+
from morphml.core.search.search_engine import SearchEngine
|
|
17
|
+
from morphml.evaluation.heuristic import HeuristicEvaluator
|
|
18
|
+
from morphml.logging_config import get_logger
|
|
19
|
+
from morphml.utils.checkpoint import Checkpoint
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LocalExecutor:
|
|
25
|
+
"""
|
|
26
|
+
Executes experiments on local machine.
|
|
27
|
+
|
|
28
|
+
Manages the complete experiment lifecycle:
|
|
29
|
+
1. Initialize search from search space
|
|
30
|
+
2. Run optimizer for multiple generations
|
|
31
|
+
3. Evaluate candidates using evaluator
|
|
32
|
+
4. Track results and save checkpoints
|
|
33
|
+
5. Return best model
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> executor = LocalExecutor()
|
|
37
|
+
>>> results = executor.run(
|
|
38
|
+
... search_space=space,
|
|
39
|
+
... optimizer=ga,
|
|
40
|
+
... evaluator=evaluator,
|
|
41
|
+
... max_evaluations=1000
|
|
42
|
+
... )
|
|
43
|
+
>>> best_model = results['best_graph']
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
47
|
+
"""
|
|
48
|
+
Initialize local executor.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
config: Configuration dictionary (uses global config if None)
|
|
52
|
+
"""
|
|
53
|
+
self.config = config or get_config()
|
|
54
|
+
self.logger = logger
|
|
55
|
+
self.checkpoint_dir = Path(self.config.get("checkpoints_dir", "./checkpoints"))
|
|
56
|
+
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
def run(
|
|
59
|
+
self,
|
|
60
|
+
search_space: SearchSpace,
|
|
61
|
+
optimizer: SearchEngine,
|
|
62
|
+
evaluator: Optional[Callable] = None,
|
|
63
|
+
max_evaluations: int = 1000,
|
|
64
|
+
checkpoint_interval: int = 100,
|
|
65
|
+
callbacks: Optional[list] = None,
|
|
66
|
+
verbose: bool = True,
|
|
67
|
+
) -> Dict[str, Any]:
|
|
68
|
+
"""
|
|
69
|
+
Run complete experiment.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
search_space: Search space definition
|
|
73
|
+
optimizer: Optimizer instance (e.g., GeneticAlgorithm)
|
|
74
|
+
evaluator: Evaluator for fitness computation (defaults to HeuristicEvaluator)
|
|
75
|
+
max_evaluations: Budget in number of evaluations
|
|
76
|
+
checkpoint_interval: Save checkpoint every N evaluations
|
|
77
|
+
callbacks: Optional callbacks to call each generation
|
|
78
|
+
verbose: Print progress information
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dictionary with results:
|
|
82
|
+
- best_graph: Best architecture found
|
|
83
|
+
- best_fitness: Best fitness value
|
|
84
|
+
- num_evaluations: Total evaluations performed
|
|
85
|
+
- elapsed_time: Total time in seconds
|
|
86
|
+
- final_generation: Final generation number
|
|
87
|
+
- statistics: Final statistics
|
|
88
|
+
- history: Complete history
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> executor = LocalExecutor()
|
|
92
|
+
>>> results = executor.run(
|
|
93
|
+
... search_space=space,
|
|
94
|
+
... optimizer=GeneticAlgorithm(space, config),
|
|
95
|
+
... max_evaluations=500
|
|
96
|
+
... )
|
|
97
|
+
"""
|
|
98
|
+
if verbose:
|
|
99
|
+
self.logger.info("=" * 70)
|
|
100
|
+
self.logger.info("Starting Experiment")
|
|
101
|
+
self.logger.info("=" * 70)
|
|
102
|
+
self.logger.info(f"Search space: {search_space.name}")
|
|
103
|
+
self.logger.info(f"Optimizer: {optimizer.__class__.__name__}")
|
|
104
|
+
self.logger.info(f"Budget: {max_evaluations} evaluations")
|
|
105
|
+
|
|
106
|
+
# Use heuristic evaluator if none provided
|
|
107
|
+
if evaluator is None:
|
|
108
|
+
if verbose:
|
|
109
|
+
self.logger.info("No evaluator provided, using HeuristicEvaluator")
|
|
110
|
+
evaluator = HeuristicEvaluator()
|
|
111
|
+
|
|
112
|
+
# Configure optimizer
|
|
113
|
+
optimizer.config["max_evaluations"] = max_evaluations
|
|
114
|
+
|
|
115
|
+
# Track progress
|
|
116
|
+
start_time = time.time()
|
|
117
|
+
num_evaluations = 0
|
|
118
|
+
last_checkpoint = 0
|
|
119
|
+
|
|
120
|
+
# Initialize population
|
|
121
|
+
if verbose:
|
|
122
|
+
self.logger.info("Initializing population...")
|
|
123
|
+
|
|
124
|
+
population = optimizer.initialize_population(optimizer.config.get("population_size", 50))
|
|
125
|
+
|
|
126
|
+
if verbose:
|
|
127
|
+
self.logger.info(f"Population initialized with {len(population)} individuals")
|
|
128
|
+
|
|
129
|
+
# Evaluate initial population
|
|
130
|
+
for individual in population.individuals:
|
|
131
|
+
if individual.fitness is None:
|
|
132
|
+
individual.set_fitness(evaluator(individual.graph))
|
|
133
|
+
num_evaluations += 1
|
|
134
|
+
|
|
135
|
+
# Track best
|
|
136
|
+
optimizer._update_best(population)
|
|
137
|
+
optimizer._record_history(population)
|
|
138
|
+
|
|
139
|
+
if verbose:
|
|
140
|
+
self.logger.info(
|
|
141
|
+
f"Initial population evaluated. Best fitness: {population.best_fitness():.4f}"
|
|
142
|
+
)
|
|
143
|
+
self.logger.info("")
|
|
144
|
+
self.logger.info("Starting evolution...")
|
|
145
|
+
|
|
146
|
+
# Main evolution loop
|
|
147
|
+
generation = 0
|
|
148
|
+
callbacks = callbacks or []
|
|
149
|
+
|
|
150
|
+
while num_evaluations < max_evaluations and not optimizer.should_stop():
|
|
151
|
+
# Evolution step
|
|
152
|
+
population = optimizer.step(population, evaluator)
|
|
153
|
+
|
|
154
|
+
# Evaluate new individuals
|
|
155
|
+
for individual in population.individuals:
|
|
156
|
+
if individual.fitness is None and num_evaluations < max_evaluations:
|
|
157
|
+
individual.set_fitness(evaluator(individual.graph))
|
|
158
|
+
num_evaluations += 1
|
|
159
|
+
|
|
160
|
+
# Update tracking
|
|
161
|
+
optimizer._update_best(population)
|
|
162
|
+
optimizer._record_history(population)
|
|
163
|
+
optimizer.generation = generation
|
|
164
|
+
optimizer.num_evaluations = num_evaluations
|
|
165
|
+
|
|
166
|
+
# Call callbacks
|
|
167
|
+
for callback in callbacks:
|
|
168
|
+
callback(optimizer, population)
|
|
169
|
+
|
|
170
|
+
# Checkpoint
|
|
171
|
+
if num_evaluations - last_checkpoint >= checkpoint_interval:
|
|
172
|
+
self._save_checkpoint(optimizer, num_evaluations)
|
|
173
|
+
last_checkpoint = num_evaluations
|
|
174
|
+
|
|
175
|
+
# Log progress
|
|
176
|
+
if verbose and generation % 10 == 0:
|
|
177
|
+
stats = optimizer.get_statistics()
|
|
178
|
+
elapsed = time.time() - start_time
|
|
179
|
+
evals_per_sec = num_evaluations / elapsed if elapsed > 0 else 0
|
|
180
|
+
|
|
181
|
+
self.logger.info(
|
|
182
|
+
f"Gen {generation:3d}: "
|
|
183
|
+
f"best={stats['best_fitness']:.4f}, "
|
|
184
|
+
f"mean={stats['mean_fitness']:.4f}, "
|
|
185
|
+
f"evals={num_evaluations}/{max_evaluations}, "
|
|
186
|
+
f"({evals_per_sec:.1f} eval/s)"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
generation += 1
|
|
190
|
+
|
|
191
|
+
# Finalize
|
|
192
|
+
elapsed_time = time.time() - start_time
|
|
193
|
+
best_graph = optimizer.get_best()
|
|
194
|
+
final_stats = optimizer.get_statistics()
|
|
195
|
+
|
|
196
|
+
results = {
|
|
197
|
+
"best_graph": best_graph,
|
|
198
|
+
"best_fitness": final_stats["best_fitness"],
|
|
199
|
+
"num_evaluations": num_evaluations,
|
|
200
|
+
"elapsed_time": elapsed_time,
|
|
201
|
+
"final_generation": generation,
|
|
202
|
+
"statistics": final_stats,
|
|
203
|
+
"history": optimizer.get_history(),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if verbose:
|
|
207
|
+
self.logger.info("")
|
|
208
|
+
self.logger.info("=" * 70)
|
|
209
|
+
self.logger.info("Experiment Complete")
|
|
210
|
+
self.logger.info("=" * 70)
|
|
211
|
+
self.logger.info(f"Best fitness: {final_stats['best_fitness']:.4f}")
|
|
212
|
+
self.logger.info(f"Evaluations: {num_evaluations}")
|
|
213
|
+
self.logger.info(f"Time: {elapsed_time:.2f}s")
|
|
214
|
+
self.logger.info(f"Avg time per evaluation: {elapsed_time/num_evaluations*1000:.2f}ms")
|
|
215
|
+
self.logger.info("=" * 70)
|
|
216
|
+
|
|
217
|
+
return results
|
|
218
|
+
|
|
219
|
+
def _save_checkpoint(self, optimizer: SearchEngine, num_evals: int) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Save optimizer state to checkpoint.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
optimizer: Optimizer to checkpoint
|
|
225
|
+
num_evals: Current number of evaluations
|
|
226
|
+
"""
|
|
227
|
+
checkpoint_path = self.checkpoint_dir / f"checkpoint_{num_evals}.pkl"
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
Checkpoint.save(optimizer, str(checkpoint_path))
|
|
231
|
+
self.logger.debug(f"Checkpoint saved: {checkpoint_path}")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
self.logger.warning(f"Failed to save checkpoint: {e}")
|
|
234
|
+
|
|
235
|
+
def load_checkpoint(self, checkpoint_path: str) -> SearchEngine:
|
|
236
|
+
"""
|
|
237
|
+
Load optimizer from checkpoint.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
checkpoint_path: Path to checkpoint file
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Restored optimizer
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> executor = LocalExecutor()
|
|
247
|
+
>>> optimizer = executor.load_checkpoint("checkpoint_500.pkl")
|
|
248
|
+
"""
|
|
249
|
+
checkpoint_path = Path(checkpoint_path)
|
|
250
|
+
|
|
251
|
+
if not checkpoint_path.exists():
|
|
252
|
+
raise FileNotFoundError(f"Checkpoint not found: {checkpoint_path}")
|
|
253
|
+
|
|
254
|
+
self.logger.info(f"Loading checkpoint: {checkpoint_path}")
|
|
255
|
+
optimizer = Checkpoint.load(str(checkpoint_path))
|
|
256
|
+
|
|
257
|
+
self.logger.info(
|
|
258
|
+
f"Checkpoint loaded: generation {optimizer.generation}, "
|
|
259
|
+
f"{optimizer.num_evaluations} evaluations"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return optimizer
|
|
263
|
+
|
|
264
|
+
def resume(
|
|
265
|
+
self,
|
|
266
|
+
checkpoint_path: str,
|
|
267
|
+
search_space: SearchSpace,
|
|
268
|
+
evaluator: Optional[Callable] = None,
|
|
269
|
+
max_additional_evaluations: int = 500,
|
|
270
|
+
verbose: bool = True,
|
|
271
|
+
) -> Dict[str, Any]:
|
|
272
|
+
"""
|
|
273
|
+
Resume experiment from checkpoint.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
checkpoint_path: Path to checkpoint file
|
|
277
|
+
search_space: Search space (must match checkpointed experiment)
|
|
278
|
+
evaluator: Evaluator for fitness computation
|
|
279
|
+
max_additional_evaluations: Additional evaluation budget
|
|
280
|
+
verbose: Print progress information
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Results dictionary
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> executor = LocalExecutor()
|
|
287
|
+
>>> results = executor.resume(
|
|
288
|
+
... "checkpoint_500.pkl",
|
|
289
|
+
... search_space=space,
|
|
290
|
+
... max_additional_evaluations=500
|
|
291
|
+
... )
|
|
292
|
+
"""
|
|
293
|
+
# Load checkpoint
|
|
294
|
+
optimizer = self.load_checkpoint(checkpoint_path)
|
|
295
|
+
|
|
296
|
+
# Calculate remaining budget
|
|
297
|
+
prev_evals = optimizer.num_evaluations
|
|
298
|
+
total_budget = prev_evals + max_additional_evaluations
|
|
299
|
+
|
|
300
|
+
if verbose:
|
|
301
|
+
self.logger.info(f"Resuming from {prev_evals} evaluations")
|
|
302
|
+
self.logger.info(f"Additional budget: {max_additional_evaluations}")
|
|
303
|
+
self.logger.info(f"Total budget: {total_budget}")
|
|
304
|
+
|
|
305
|
+
# Continue execution
|
|
306
|
+
return self.run(
|
|
307
|
+
search_space=search_space,
|
|
308
|
+
optimizer=optimizer,
|
|
309
|
+
evaluator=evaluator,
|
|
310
|
+
max_evaluations=total_budget,
|
|
311
|
+
verbose=verbose,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# Convenience function
|
|
316
|
+
def run_experiment(
|
|
317
|
+
search_space: SearchSpace,
|
|
318
|
+
optimizer: SearchEngine,
|
|
319
|
+
evaluator: Optional[Callable] = None,
|
|
320
|
+
max_evaluations: int = 1000,
|
|
321
|
+
**kwargs,
|
|
322
|
+
) -> Dict[str, Any]:
|
|
323
|
+
"""
|
|
324
|
+
Convenience function to run experiment.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
search_space: Search space definition
|
|
328
|
+
optimizer: Optimizer instance
|
|
329
|
+
evaluator: Evaluator function
|
|
330
|
+
max_evaluations: Evaluation budget
|
|
331
|
+
**kwargs: Additional arguments for LocalExecutor.run()
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Results dictionary
|
|
335
|
+
|
|
336
|
+
Example:
|
|
337
|
+
>>> results = run_experiment(
|
|
338
|
+
... search_space=space,
|
|
339
|
+
... optimizer=ga,
|
|
340
|
+
... max_evaluations=1000
|
|
341
|
+
... )
|
|
342
|
+
"""
|
|
343
|
+
executor = LocalExecutor()
|
|
344
|
+
return executor.run(
|
|
345
|
+
search_space=search_space,
|
|
346
|
+
optimizer=optimizer,
|
|
347
|
+
evaluator=evaluator,
|
|
348
|
+
max_evaluations=max_evaluations,
|
|
349
|
+
**kwargs,
|
|
350
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Framework integrations for MorphML.
|
|
2
|
+
|
|
3
|
+
Provides adapters to convert ModelGraph architectures to various ML frameworks:
|
|
4
|
+
- PyTorch (with training support)
|
|
5
|
+
- TensorFlow/Keras
|
|
6
|
+
- JAX/Flax
|
|
7
|
+
- Scikit-learn
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from morphml.integrations import PyTorchAdapter
|
|
11
|
+
>>> adapter = PyTorchAdapter()
|
|
12
|
+
>>> model = adapter.build_model(graph)
|
|
13
|
+
>>> trainer = adapter.get_trainer(model, config)
|
|
14
|
+
>>> results = trainer.train(train_loader, val_loader)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from morphml.integrations.jax_adapter import JAXAdapter
|
|
18
|
+
from morphml.integrations.pytorch_adapter import PyTorchAdapter, PyTorchTrainer
|
|
19
|
+
from morphml.integrations.sklearn_adapter import SklearnAdapter
|
|
20
|
+
from morphml.integrations.tensorflow_adapter import TensorFlowAdapter
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"PyTorchAdapter",
|
|
24
|
+
"PyTorchTrainer",
|
|
25
|
+
"TensorFlowAdapter",
|
|
26
|
+
"JAXAdapter",
|
|
27
|
+
"SklearnAdapter",
|
|
28
|
+
]
|