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,312 @@
|
|
|
1
|
+
"""Quick-start CLI helper for MorphML.
|
|
2
|
+
|
|
3
|
+
Provides interactive setup and template generation for common NAS tasks.
|
|
4
|
+
|
|
5
|
+
Run:
|
|
6
|
+
morphml quickstart
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from morphml.logging_config import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
TEMPLATES = {
|
|
20
|
+
"cnn_classification": """\"\"\"CNN Image Classification NAS Example.
|
|
21
|
+
|
|
22
|
+
Generated by: morphml quickstart
|
|
23
|
+
\"\"\"
|
|
24
|
+
|
|
25
|
+
from morphml.core.dsl import Layer, SearchSpace
|
|
26
|
+
from morphml.constraints import ConstraintHandler, MaxParametersConstraint
|
|
27
|
+
from morphml.evaluation import HeuristicEvaluator
|
|
28
|
+
from morphml.optimizers import GeneticAlgorithm
|
|
29
|
+
from morphml.utils import ArchitectureExporter
|
|
30
|
+
|
|
31
|
+
# Define search space
|
|
32
|
+
space = SearchSpace("{name}")
|
|
33
|
+
space.add_layers(
|
|
34
|
+
Layer.input(shape=(3, 32, 32)),
|
|
35
|
+
|
|
36
|
+
# First conv block
|
|
37
|
+
Layer.conv2d(filters=[32, 64], kernel_size=[3, 5]),
|
|
38
|
+
Layer.batchnorm(),
|
|
39
|
+
Layer.relu(),
|
|
40
|
+
Layer.maxpool(pool_size=2),
|
|
41
|
+
|
|
42
|
+
# Second conv block
|
|
43
|
+
Layer.conv2d(filters=[64, 128], kernel_size=3),
|
|
44
|
+
Layer.batchnorm(),
|
|
45
|
+
Layer.relu(),
|
|
46
|
+
Layer.maxpool(pool_size=2),
|
|
47
|
+
|
|
48
|
+
# Classification head
|
|
49
|
+
Layer.flatten(),
|
|
50
|
+
Layer.dense(units=[128, 256, 512]),
|
|
51
|
+
Layer.relu(),
|
|
52
|
+
Layer.dropout(rate=[0.3, 0.5]),
|
|
53
|
+
Layer.dense(units={num_classes}),
|
|
54
|
+
Layer.softmax(),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Set up constraints
|
|
58
|
+
handler = ConstraintHandler()
|
|
59
|
+
handler.add_constraint(MaxParametersConstraint(max_params={max_params}))
|
|
60
|
+
|
|
61
|
+
# Create evaluator
|
|
62
|
+
evaluator = HeuristicEvaluator()
|
|
63
|
+
|
|
64
|
+
# Configure optimizer
|
|
65
|
+
optimizer = GeneticAlgorithm(
|
|
66
|
+
search_space=space,
|
|
67
|
+
config={{
|
|
68
|
+
"population_size": {population_size},
|
|
69
|
+
"num_generations": {num_generations},
|
|
70
|
+
"mutation_rate": 0.2,
|
|
71
|
+
"crossover_rate": 0.8,
|
|
72
|
+
}}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Run optimization
|
|
76
|
+
print(f"Starting NAS: {{optimizer.config['num_generations']}} generations...")
|
|
77
|
+
best = optimizer.optimize(evaluator)
|
|
78
|
+
|
|
79
|
+
# Check constraints
|
|
80
|
+
if not handler.check(best.graph):
|
|
81
|
+
print("\\nWarning: Best architecture violates constraints:")
|
|
82
|
+
print(handler.format_violations(best.graph))
|
|
83
|
+
else:
|
|
84
|
+
print("\\n✓ Best architecture satisfies all constraints")
|
|
85
|
+
|
|
86
|
+
# Show results
|
|
87
|
+
print(f"\\nBest Architecture:")
|
|
88
|
+
print(f" Fitness: {{best.fitness:.4f}}")
|
|
89
|
+
print(f" Parameters: {{best.graph.estimate_parameters():,}}")
|
|
90
|
+
print(f" Depth: {{best.graph.depth()}}")
|
|
91
|
+
|
|
92
|
+
# Export architecture
|
|
93
|
+
exporter = ArchitectureExporter()
|
|
94
|
+
pytorch_code = exporter.to_pytorch(best.graph, "{name}_model")
|
|
95
|
+
|
|
96
|
+
output_file = "{name}_model.py"
|
|
97
|
+
with open(output_file, 'w') as f:
|
|
98
|
+
f.write(pytorch_code)
|
|
99
|
+
|
|
100
|
+
print(f"\\n✓ Exported to {{output_file}}")
|
|
101
|
+
""",
|
|
102
|
+
"simple_nas": """\"\"\"Simple NAS Example.
|
|
103
|
+
|
|
104
|
+
Generated by: morphml quickstart
|
|
105
|
+
\"\"\"
|
|
106
|
+
|
|
107
|
+
from morphml.core.dsl import Layer, SearchSpace
|
|
108
|
+
from morphml.evaluation import HeuristicEvaluator
|
|
109
|
+
from morphml.optimizers import GeneticAlgorithm
|
|
110
|
+
|
|
111
|
+
# Define search space
|
|
112
|
+
space = SearchSpace("{name}")
|
|
113
|
+
space.add_layers(
|
|
114
|
+
Layer.input(shape=(3, 32, 32)),
|
|
115
|
+
Layer.conv2d(filters=[32, 64, 128], kernel_size=3),
|
|
116
|
+
Layer.relu(),
|
|
117
|
+
Layer.maxpool(pool_size=2),
|
|
118
|
+
Layer.flatten(),
|
|
119
|
+
Layer.dense(units=[128, 256]),
|
|
120
|
+
Layer.dense(units={num_classes}),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Run quick search
|
|
124
|
+
evaluator = HeuristicEvaluator()
|
|
125
|
+
optimizer = GeneticAlgorithm(space, config={{
|
|
126
|
+
"population_size": 10,
|
|
127
|
+
"num_generations": 20,
|
|
128
|
+
}})
|
|
129
|
+
|
|
130
|
+
best = optimizer.optimize(evaluator)
|
|
131
|
+
print(f"Best fitness: {{best.fitness:.4f}}")
|
|
132
|
+
""",
|
|
133
|
+
"advanced_nas": """\"\"\"Advanced NAS with Adaptive Operators.
|
|
134
|
+
|
|
135
|
+
Generated by: morphml quickstart
|
|
136
|
+
\"\"\"
|
|
137
|
+
|
|
138
|
+
from morphml.core.dsl import Layer, SearchSpace
|
|
139
|
+
from morphml.constraints import ConstraintHandler, MaxParametersConstraint, DepthConstraint
|
|
140
|
+
from morphml.evaluation import HeuristicEvaluator
|
|
141
|
+
from morphml.optimizers import GeneticAlgorithm
|
|
142
|
+
from morphml.optimizers.adaptive_operators import AdaptiveOperatorScheduler
|
|
143
|
+
from morphml.visualization.crossover_viz import quick_crossover_viz
|
|
144
|
+
from morphml.utils import ArchitectureExporter
|
|
145
|
+
|
|
146
|
+
# Define search space
|
|
147
|
+
space = SearchSpace("{name}")
|
|
148
|
+
space.add_layers(
|
|
149
|
+
Layer.input(shape=(3, 32, 32)),
|
|
150
|
+
Layer.conv2d(filters=[32, 64, 128], kernel_size=[3, 5, 7]),
|
|
151
|
+
Layer.batchnorm(),
|
|
152
|
+
Layer.relu(),
|
|
153
|
+
Layer.maxpool(pool_size=2),
|
|
154
|
+
Layer.conv2d(filters=[64, 128, 256], kernel_size=[3, 5]),
|
|
155
|
+
Layer.batchnorm(),
|
|
156
|
+
Layer.relu(),
|
|
157
|
+
Layer.maxpool(pool_size=2),
|
|
158
|
+
Layer.flatten(),
|
|
159
|
+
Layer.dense(units=[128, 256, 512, 1024]),
|
|
160
|
+
Layer.relu(),
|
|
161
|
+
Layer.dropout(rate=[0.3, 0.5, 0.7]),
|
|
162
|
+
Layer.dense(units={num_classes}),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Set up constraints
|
|
166
|
+
handler = ConstraintHandler()
|
|
167
|
+
handler.add_constraint(MaxParametersConstraint(max_params={max_params}))
|
|
168
|
+
handler.add_constraint(DepthConstraint(min_depth=5, max_depth=20))
|
|
169
|
+
|
|
170
|
+
# Create adaptive scheduler
|
|
171
|
+
scheduler = AdaptiveOperatorScheduler(
|
|
172
|
+
initial_crossover=0.8,
|
|
173
|
+
initial_mutation=0.2
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Run optimization
|
|
177
|
+
evaluator = HeuristicEvaluator()
|
|
178
|
+
optimizer = GeneticAlgorithm(space, config={{
|
|
179
|
+
"population_size": {population_size},
|
|
180
|
+
"num_generations": {num_generations},
|
|
181
|
+
}})
|
|
182
|
+
|
|
183
|
+
print("Running advanced NAS with adaptive operators...")
|
|
184
|
+
best = optimizer.optimize(evaluator)
|
|
185
|
+
|
|
186
|
+
# Visualize if parents available
|
|
187
|
+
if best.parent_ids and len(best.parent_ids) == 2:
|
|
188
|
+
try:
|
|
189
|
+
quick_crossover_viz(
|
|
190
|
+
optimizer.population.get(best.parent_ids[0]).graph,
|
|
191
|
+
optimizer.population.get(best.parent_ids[1]).graph,
|
|
192
|
+
"best_parents_crossover.png"
|
|
193
|
+
)
|
|
194
|
+
print("\\n✓ Saved crossover visualization")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f"\\nVisualization skipped: {{e}}")
|
|
197
|
+
|
|
198
|
+
# Export
|
|
199
|
+
exporter = ArchitectureExporter()
|
|
200
|
+
code = exporter.to_pytorch(best.graph, "{name}_model")
|
|
201
|
+
with open("{name}_model.py", 'w') as f:
|
|
202
|
+
f.write(code)
|
|
203
|
+
|
|
204
|
+
print(f"\\n✓ Best architecture exported!")
|
|
205
|
+
print(f" Fitness: {{best.fitness:.4f}}")
|
|
206
|
+
print(f" Parameters: {{best.graph.estimate_parameters():,}}")
|
|
207
|
+
""",
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@click.command()
|
|
212
|
+
@click.option(
|
|
213
|
+
"--template",
|
|
214
|
+
"-t",
|
|
215
|
+
type=click.Choice(["cnn_classification", "simple_nas", "advanced_nas"]),
|
|
216
|
+
default="cnn_classification",
|
|
217
|
+
help="Template type",
|
|
218
|
+
)
|
|
219
|
+
@click.option("--name", "-n", default="my_nas", help="Project name")
|
|
220
|
+
@click.option("--output", "-o", default=None, help="Output file path")
|
|
221
|
+
@click.option("--num-classes", default=10, help="Number of output classes")
|
|
222
|
+
@click.option("--max-params", default=1000000, help="Maximum parameters constraint")
|
|
223
|
+
@click.option("--population-size", default=20, help="Population size")
|
|
224
|
+
@click.option("--num-generations", default=50, help="Number of generations")
|
|
225
|
+
@click.option("--interactive", "-i", is_flag=True, help="Interactive mode")
|
|
226
|
+
def quickstart(
|
|
227
|
+
template: str,
|
|
228
|
+
name: str,
|
|
229
|
+
output: Optional[str],
|
|
230
|
+
num_classes: int,
|
|
231
|
+
max_params: int,
|
|
232
|
+
population_size: int,
|
|
233
|
+
num_generations: int,
|
|
234
|
+
interactive: bool,
|
|
235
|
+
):
|
|
236
|
+
"""
|
|
237
|
+
Generate a quick-start NAS project from template.
|
|
238
|
+
|
|
239
|
+
Examples:
|
|
240
|
+
morphml quickstart
|
|
241
|
+
morphml quickstart -t simple_nas -n my_project
|
|
242
|
+
morphml quickstart -i # Interactive mode
|
|
243
|
+
"""
|
|
244
|
+
# Interactive mode
|
|
245
|
+
if interactive:
|
|
246
|
+
click.echo("🧬 MorphML Quick Start - Interactive Mode\n")
|
|
247
|
+
|
|
248
|
+
template = click.prompt(
|
|
249
|
+
"Select template",
|
|
250
|
+
type=click.Choice(["cnn_classification", "simple_nas", "advanced_nas"]),
|
|
251
|
+
default="cnn_classification",
|
|
252
|
+
)
|
|
253
|
+
name = click.prompt("Project name", default="my_nas")
|
|
254
|
+
num_classes = click.prompt("Number of classes", default=10, type=int)
|
|
255
|
+
|
|
256
|
+
if template in ["cnn_classification", "advanced_nas"]:
|
|
257
|
+
max_params = click.prompt("Max parameters", default=1000000, type=int)
|
|
258
|
+
population_size = click.prompt("Population size", default=20, type=int)
|
|
259
|
+
num_generations = click.prompt("Number of generations", default=50, type=int)
|
|
260
|
+
|
|
261
|
+
# Generate code from template
|
|
262
|
+
template_code = TEMPLATES[template]
|
|
263
|
+
code = template_code.format(
|
|
264
|
+
name=name,
|
|
265
|
+
num_classes=num_classes,
|
|
266
|
+
max_params=max_params,
|
|
267
|
+
population_size=population_size,
|
|
268
|
+
num_generations=num_generations,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Determine output file
|
|
272
|
+
if output is None:
|
|
273
|
+
output = f"{name}.py"
|
|
274
|
+
|
|
275
|
+
output_path = Path(output)
|
|
276
|
+
|
|
277
|
+
# Check if file exists
|
|
278
|
+
if output_path.exists():
|
|
279
|
+
if not click.confirm(f"File {output} already exists. Overwrite?"):
|
|
280
|
+
click.echo("Aborted.")
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
# Write file
|
|
284
|
+
output_path.write_text(code)
|
|
285
|
+
|
|
286
|
+
# Success message
|
|
287
|
+
click.echo(f"\n✓ Created {output}")
|
|
288
|
+
click.echo(f"\nTemplate: {template}")
|
|
289
|
+
click.echo(f"Project: {name}")
|
|
290
|
+
click.echo(f"Classes: {num_classes}")
|
|
291
|
+
|
|
292
|
+
if template in ["cnn_classification", "advanced_nas"]:
|
|
293
|
+
click.echo(f"Max params: {max_params:,}")
|
|
294
|
+
click.echo(f"Population: {population_size}")
|
|
295
|
+
click.echo(f"Generations: {num_generations}")
|
|
296
|
+
|
|
297
|
+
click.echo("\nNext steps:")
|
|
298
|
+
click.echo(f" 1. Review and customize {output}")
|
|
299
|
+
click.echo(f" 2. Run: python {output}")
|
|
300
|
+
click.echo(" 3. Check exported model file")
|
|
301
|
+
|
|
302
|
+
# Show template description
|
|
303
|
+
descriptions = {
|
|
304
|
+
"cnn_classification": "Full CNN classification with constraints and export",
|
|
305
|
+
"simple_nas": "Minimal NAS example for quick testing",
|
|
306
|
+
"advanced_nas": "Advanced NAS with adaptive operators and visualization",
|
|
307
|
+
}
|
|
308
|
+
click.echo(f"\nTemplate: {descriptions[template]}")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == "__main__":
|
|
312
|
+
quickstart()
|
morphml/config.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Configuration management for MorphML."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from morphml.exceptions import ConfigurationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OptimizerConfig(BaseModel):
|
|
14
|
+
"""Configuration for optimizers."""
|
|
15
|
+
|
|
16
|
+
name: str = Field(description="Optimizer name (genetic, bayesian, darts, etc.)")
|
|
17
|
+
population_size: int = Field(default=50, ge=1, description="Population size")
|
|
18
|
+
num_generations: int = Field(default=100, ge=1, description="Number of generations")
|
|
19
|
+
mutation_rate: float = Field(default=0.1, ge=0.0, le=1.0, description="Mutation probability")
|
|
20
|
+
crossover_rate: float = Field(default=0.8, ge=0.0, le=1.0, description="Crossover probability")
|
|
21
|
+
|
|
22
|
+
class Config:
|
|
23
|
+
extra = "allow" # Allow additional fields for optimizer-specific config
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EvaluationConfig(BaseModel):
|
|
27
|
+
"""Configuration for architecture evaluation."""
|
|
28
|
+
|
|
29
|
+
num_epochs: int = Field(default=50, ge=1, description="Training epochs")
|
|
30
|
+
batch_size: int = Field(default=128, ge=1, description="Batch size")
|
|
31
|
+
learning_rate: float = Field(default=0.001, gt=0.0, description="Learning rate")
|
|
32
|
+
device: str = Field(default="cuda", description="Device (cuda or cpu)")
|
|
33
|
+
num_workers: int = Field(default=4, ge=0, description="Data loader workers")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LoggingConfig(BaseModel):
|
|
37
|
+
"""Configuration for logging."""
|
|
38
|
+
|
|
39
|
+
level: str = Field(default="INFO", description="Log level")
|
|
40
|
+
format: str = Field(
|
|
41
|
+
default="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
42
|
+
description="Log format",
|
|
43
|
+
)
|
|
44
|
+
file: Optional[str] = Field(default=None, description="Log file path")
|
|
45
|
+
console: bool = Field(default=True, description="Enable console logging")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ExperimentConfig(BaseModel):
|
|
49
|
+
"""Main experiment configuration."""
|
|
50
|
+
|
|
51
|
+
name: str = Field(description="Experiment name")
|
|
52
|
+
seed: int = Field(default=42, description="Random seed for reproducibility")
|
|
53
|
+
output_dir: Path = Field(default=Path("./experiments"), description="Output directory")
|
|
54
|
+
|
|
55
|
+
optimizer: OptimizerConfig
|
|
56
|
+
evaluation: EvaluationConfig = Field(default_factory=EvaluationConfig)
|
|
57
|
+
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
|
58
|
+
|
|
59
|
+
class Config:
|
|
60
|
+
arbitrary_types_allowed = True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConfigManager:
|
|
64
|
+
"""
|
|
65
|
+
Manage MorphML configuration.
|
|
66
|
+
|
|
67
|
+
Supports loading from:
|
|
68
|
+
- YAML files
|
|
69
|
+
- Environment variables
|
|
70
|
+
- Python dictionaries
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
config = ConfigManager.from_yaml("config.yaml")
|
|
74
|
+
config.get("optimizer.population_size")
|
|
75
|
+
config.set("optimizer.mutation_rate", 0.2)
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, config_dict: Optional[Dict[str, Any]] = None):
|
|
79
|
+
"""
|
|
80
|
+
Initialize configuration manager.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
config_dict: Configuration dictionary
|
|
84
|
+
"""
|
|
85
|
+
self._config = config_dict or {}
|
|
86
|
+
self._experiment_config: Optional[ExperimentConfig] = None
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_yaml(cls, config_path: str) -> "ConfigManager":
|
|
90
|
+
"""
|
|
91
|
+
Load configuration from YAML file.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
config_path: Path to YAML file
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
ConfigManager instance
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ConfigurationError: If file cannot be loaded
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
with open(config_path, "r") as f:
|
|
104
|
+
config_dict = yaml.safe_load(f)
|
|
105
|
+
return cls(config_dict)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise ConfigurationError(f"Failed to load config from {config_path}: {e}") from e
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def from_dict(cls, config_dict: Dict[str, Any]) -> "ConfigManager":
|
|
111
|
+
"""Create configuration from dictionary."""
|
|
112
|
+
return cls(config_dict)
|
|
113
|
+
|
|
114
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
115
|
+
"""
|
|
116
|
+
Get configuration value by dot-separated key.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
key: Dot-separated key (e.g., "optimizer.population_size")
|
|
120
|
+
default: Default value if key not found
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Configuration value
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> config.get("optimizer.mutation_rate")
|
|
127
|
+
0.1
|
|
128
|
+
"""
|
|
129
|
+
keys = key.split(".")
|
|
130
|
+
value: Any = self._config
|
|
131
|
+
|
|
132
|
+
for k in keys:
|
|
133
|
+
if isinstance(value, dict):
|
|
134
|
+
value = value.get(k)
|
|
135
|
+
if value is None:
|
|
136
|
+
return default
|
|
137
|
+
else:
|
|
138
|
+
return default
|
|
139
|
+
|
|
140
|
+
return value
|
|
141
|
+
|
|
142
|
+
def set(self, key: str, value: Any) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Set configuration value by dot-separated key.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
key: Dot-separated key
|
|
148
|
+
value: Value to set
|
|
149
|
+
"""
|
|
150
|
+
keys = key.split(".")
|
|
151
|
+
config = self._config
|
|
152
|
+
|
|
153
|
+
for k in keys[:-1]:
|
|
154
|
+
if k not in config:
|
|
155
|
+
config[k] = {}
|
|
156
|
+
config = config[k]
|
|
157
|
+
|
|
158
|
+
config[keys[-1]] = value
|
|
159
|
+
|
|
160
|
+
def validate(self) -> ExperimentConfig:
|
|
161
|
+
"""
|
|
162
|
+
Validate configuration using Pydantic model.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Validated ExperimentConfig
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ConfigurationError: If validation fails
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
self._experiment_config = ExperimentConfig(**self._config)
|
|
172
|
+
return self._experiment_config
|
|
173
|
+
except Exception as e:
|
|
174
|
+
raise ConfigurationError(f"Configuration validation failed: {e}") from e
|
|
175
|
+
|
|
176
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
177
|
+
"""Export configuration as dictionary."""
|
|
178
|
+
return self._config.copy()
|
|
179
|
+
|
|
180
|
+
def save(self, output_path: str) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Save configuration to YAML file.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
output_path: Output file path
|
|
186
|
+
"""
|
|
187
|
+
with open(output_path, "w") as f:
|
|
188
|
+
yaml.dump(self._config, f, default_flow_style=False, sort_keys=False)
|
|
189
|
+
|
|
190
|
+
def merge(self, other_config: Dict[str, Any]) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Merge another configuration into this one.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
other_config: Configuration dictionary to merge
|
|
196
|
+
"""
|
|
197
|
+
self._deep_merge(self._config, other_config)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _deep_merge(base: Dict, update: Dict) -> None:
|
|
201
|
+
"""Deep merge update dict into base dict."""
|
|
202
|
+
for key, value in update.items():
|
|
203
|
+
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
|
204
|
+
ConfigManager._deep_merge(base[key], value)
|
|
205
|
+
else:
|
|
206
|
+
base[key] = value
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def load_config_from_env() -> Dict[str, Any]:
|
|
210
|
+
"""
|
|
211
|
+
Load configuration from environment variables.
|
|
212
|
+
|
|
213
|
+
Environment variables should be prefixed with MORPHML_
|
|
214
|
+
Example: MORPHML_OPTIMIZER_POPULATION_SIZE=100
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Configuration dictionary
|
|
218
|
+
"""
|
|
219
|
+
config: Dict[str, Any] = {}
|
|
220
|
+
prefix = "MORPHML_"
|
|
221
|
+
|
|
222
|
+
for key, value in os.environ.items():
|
|
223
|
+
if key.startswith(prefix):
|
|
224
|
+
# Remove prefix and convert to lowercase with dots
|
|
225
|
+
config_key = key[len(prefix) :].lower().replace("_", ".")
|
|
226
|
+
|
|
227
|
+
# Try to convert to int/float
|
|
228
|
+
try:
|
|
229
|
+
if "." in value:
|
|
230
|
+
value = float(value) # type: ignore
|
|
231
|
+
else:
|
|
232
|
+
value = int(value) # type: ignore
|
|
233
|
+
except ValueError:
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
# Set in config
|
|
237
|
+
keys = config_key.split(".")
|
|
238
|
+
current = config
|
|
239
|
+
for k in keys[:-1]:
|
|
240
|
+
if k not in current:
|
|
241
|
+
current[k] = {}
|
|
242
|
+
current = current[k]
|
|
243
|
+
current[keys[-1]] = value
|
|
244
|
+
|
|
245
|
+
return config
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Default configuration template
|
|
249
|
+
DEFAULT_CONFIG = {
|
|
250
|
+
"name": "morphml_experiment",
|
|
251
|
+
"seed": 42,
|
|
252
|
+
"output_dir": "./experiments",
|
|
253
|
+
"optimizer": {
|
|
254
|
+
"name": "genetic",
|
|
255
|
+
"population_size": 50,
|
|
256
|
+
"num_generations": 100,
|
|
257
|
+
"mutation_rate": 0.1,
|
|
258
|
+
"crossover_rate": 0.8,
|
|
259
|
+
},
|
|
260
|
+
"evaluation": {
|
|
261
|
+
"num_epochs": 50,
|
|
262
|
+
"batch_size": 128,
|
|
263
|
+
"learning_rate": 0.001,
|
|
264
|
+
"device": "cuda",
|
|
265
|
+
"num_workers": 4,
|
|
266
|
+
},
|
|
267
|
+
"logging": {"level": "INFO", "console": True, "file": None},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_config() -> ConfigManager:
|
|
272
|
+
"""
|
|
273
|
+
Get default configuration manager.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
ConfigManager instance with default configuration
|
|
277
|
+
"""
|
|
278
|
+
return ConfigManager.from_dict(DEFAULT_CONFIG)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Constraint handling for architecture search."""
|
|
2
|
+
|
|
3
|
+
from morphml.constraints.handler import ConstraintHandler
|
|
4
|
+
from morphml.constraints.predicates import (
|
|
5
|
+
DepthConstraint,
|
|
6
|
+
MaxParametersConstraint,
|
|
7
|
+
MinParametersConstraint,
|
|
8
|
+
OperationConstraint,
|
|
9
|
+
WidthConstraint,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ConstraintHandler",
|
|
14
|
+
"MaxParametersConstraint",
|
|
15
|
+
"MinParametersConstraint",
|
|
16
|
+
"DepthConstraint",
|
|
17
|
+
"WidthConstraint",
|
|
18
|
+
"OperationConstraint",
|
|
19
|
+
]
|