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
morphml/cli/main.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""Main CLI entry point for MorphML."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.progress import (
|
|
11
|
+
BarColumn,
|
|
12
|
+
Progress,
|
|
13
|
+
SpinnerColumn,
|
|
14
|
+
TaskProgressColumn,
|
|
15
|
+
TextColumn,
|
|
16
|
+
TimeElapsedColumn,
|
|
17
|
+
)
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
|
|
20
|
+
from morphml import __version__
|
|
21
|
+
from morphml.evaluation import HeuristicEvaluator
|
|
22
|
+
from morphml.logging_config import get_logger, setup_logging
|
|
23
|
+
from morphml.optimizers import (
|
|
24
|
+
DARTS,
|
|
25
|
+
ENAS,
|
|
26
|
+
DifferentialEvolution,
|
|
27
|
+
GaussianProcessOptimizer,
|
|
28
|
+
GeneticAlgorithm,
|
|
29
|
+
HillClimbing,
|
|
30
|
+
NSGA2Optimizer,
|
|
31
|
+
RandomSearch,
|
|
32
|
+
SimulatedAnnealing,
|
|
33
|
+
SMACOptimizer,
|
|
34
|
+
TPEOptimizer,
|
|
35
|
+
)
|
|
36
|
+
from morphml.utils import ArchitectureExporter, Checkpoint
|
|
37
|
+
|
|
38
|
+
console = Console()
|
|
39
|
+
logger = get_logger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@click.group()
|
|
43
|
+
@click.version_option(version=__version__)
|
|
44
|
+
def cli():
|
|
45
|
+
"""
|
|
46
|
+
MorphML - Evolutionary AutoML Construction Kit
|
|
47
|
+
|
|
48
|
+
Use 'morphml COMMAND --help' for more information on a command.
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@cli.command()
|
|
54
|
+
@click.argument("experiment_file", type=click.Path(exists=True))
|
|
55
|
+
@click.option("--output-dir", "-o", default="./results", help="Output directory")
|
|
56
|
+
@click.option("--checkpoint-dir", "-c", default=None, help="Checkpoint directory")
|
|
57
|
+
@click.option("--resume", "-r", type=click.Path(exists=True), help="Resume from checkpoint")
|
|
58
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose logging")
|
|
59
|
+
@click.option(
|
|
60
|
+
"--export-format",
|
|
61
|
+
"-e",
|
|
62
|
+
type=click.Choice(["pytorch", "keras", "both"]),
|
|
63
|
+
default="both",
|
|
64
|
+
help="Export format",
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"--optimizer",
|
|
68
|
+
type=click.Choice(
|
|
69
|
+
["ga", "rs", "hc", "sa", "de", "gp", "tpe", "smac", "nsga2", "darts", "enas"]
|
|
70
|
+
),
|
|
71
|
+
default="ga",
|
|
72
|
+
help="Optimizer to use (ga=Genetic Algorithm, rs=Random Search, hc=Hill Climbing, "
|
|
73
|
+
"sa=Simulated Annealing, de=Differential Evolution, gp=Gaussian Process, "
|
|
74
|
+
"tpe=Tree-structured Parzen Estimator, smac=SMAC, nsga2=NSGA-II, "
|
|
75
|
+
"darts=DARTS, enas=ENAS)",
|
|
76
|
+
)
|
|
77
|
+
def run(
|
|
78
|
+
experiment_file: str,
|
|
79
|
+
output_dir: str,
|
|
80
|
+
checkpoint_dir: str,
|
|
81
|
+
resume: str,
|
|
82
|
+
verbose: bool,
|
|
83
|
+
export_format: str,
|
|
84
|
+
optimizer: str,
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Run an experiment from a Python file.
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
morphml run experiment.py --output-dir ./results
|
|
91
|
+
"""
|
|
92
|
+
# Setup logging
|
|
93
|
+
setup_logging(level="DEBUG" if verbose else "INFO")
|
|
94
|
+
|
|
95
|
+
console.print(f"[bold cyan]MorphML v{__version__}[/bold cyan]")
|
|
96
|
+
console.print("[cyan]Author:[/cyan] Eshan Roy <eshanized@proton.me>")
|
|
97
|
+
console.print("[cyan]Organization:[/cyan] TONMOY INFRASTRUCTURE & VISION\n")
|
|
98
|
+
console.print(f"Running experiment: [yellow]{experiment_file}[/yellow]\n")
|
|
99
|
+
|
|
100
|
+
# Load experiment definition
|
|
101
|
+
try:
|
|
102
|
+
spec = importlib.util.spec_from_file_location("experiment", experiment_file)
|
|
103
|
+
module = importlib.util.module_from_spec(spec)
|
|
104
|
+
spec.loader.exec_module(module)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
console.print(f"[bold red]Error loading experiment:[/bold red] {e}")
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
# Extract components
|
|
110
|
+
if not hasattr(module, "search_space"):
|
|
111
|
+
console.print("[bold red]Error:[/bold red] experiment file must define 'search_space'")
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
search_space = module.search_space
|
|
115
|
+
optimizer_config = getattr(
|
|
116
|
+
module,
|
|
117
|
+
"optimizer_config",
|
|
118
|
+
{
|
|
119
|
+
"population_size": 20,
|
|
120
|
+
"num_generations": 50,
|
|
121
|
+
"elite_size": 2,
|
|
122
|
+
"mutation_rate": 0.15,
|
|
123
|
+
"crossover_rate": 0.7,
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
getattr(module, "max_evaluations", None)
|
|
127
|
+
|
|
128
|
+
# Create output directory
|
|
129
|
+
output_path = Path(output_dir)
|
|
130
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
|
|
132
|
+
# Initialize components
|
|
133
|
+
optimizer_instance = _create_optimizer(optimizer, search_space, optimizer_config)
|
|
134
|
+
evaluator = HeuristicEvaluator()
|
|
135
|
+
|
|
136
|
+
# Run experiment with progress bar
|
|
137
|
+
try:
|
|
138
|
+
console.print("[bold green]Starting optimization...[/bold green]\n")
|
|
139
|
+
|
|
140
|
+
with Progress(
|
|
141
|
+
SpinnerColumn(),
|
|
142
|
+
TextColumn("[progress.description]{task.description}"),
|
|
143
|
+
BarColumn(),
|
|
144
|
+
TaskProgressColumn(),
|
|
145
|
+
TimeElapsedColumn(),
|
|
146
|
+
console=console,
|
|
147
|
+
) as progress:
|
|
148
|
+
task = progress.add_task(
|
|
149
|
+
"[cyan]Evolving architectures...", total=optimizer_config.get("num_generations", 50)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Optimize with callback
|
|
153
|
+
def callback(generation, population):
|
|
154
|
+
progress.update(task, advance=1)
|
|
155
|
+
if hasattr(population, "get_statistics"):
|
|
156
|
+
stats = population.get_statistics()
|
|
157
|
+
progress.console.print(
|
|
158
|
+
f" Gen {generation}: best={stats['best_fitness']:.4f}, "
|
|
159
|
+
f"mean={stats['mean_fitness']:.4f}, "
|
|
160
|
+
f"diversity={population.get_diversity():.3f}"
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
progress.console.print(f" Iteration {generation}")
|
|
164
|
+
|
|
165
|
+
# Checkpoint
|
|
166
|
+
if checkpoint_dir and generation % 10 == 0:
|
|
167
|
+
cp_path = Path(checkpoint_dir) / f"checkpoint_gen_{generation}.json"
|
|
168
|
+
cp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
Checkpoint.save(optimizer_instance, str(cp_path))
|
|
170
|
+
|
|
171
|
+
best = optimizer_instance.optimize(evaluator, callback=callback)
|
|
172
|
+
|
|
173
|
+
console.print("\n[bold green]✓ Optimization complete![/bold green]\n")
|
|
174
|
+
|
|
175
|
+
except KeyboardInterrupt:
|
|
176
|
+
console.print("\n[yellow]Interrupted by user[/yellow]")
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
|
|
179
|
+
# Save results
|
|
180
|
+
_save_results(best, optimizer_instance, output_path, export_format)
|
|
181
|
+
|
|
182
|
+
# Display summary
|
|
183
|
+
_display_results_summary(best, optimizer_instance, output_path)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _create_optimizer(optimizer_name: str, search_space, config: dict):
|
|
187
|
+
"""Factory function to create optimizer instances.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
optimizer_name: Short name of optimizer (ga, rs, hc, etc.)
|
|
191
|
+
search_space: SearchSpace to optimize
|
|
192
|
+
config: Configuration dict from experiment file
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Optimizer instance
|
|
196
|
+
"""
|
|
197
|
+
optimizer_map = {
|
|
198
|
+
"ga": GeneticAlgorithm,
|
|
199
|
+
"rs": RandomSearch,
|
|
200
|
+
"hc": HillClimbing,
|
|
201
|
+
"sa": SimulatedAnnealing,
|
|
202
|
+
"de": DifferentialEvolution,
|
|
203
|
+
"gp": GaussianProcessOptimizer,
|
|
204
|
+
"tpe": TPEOptimizer,
|
|
205
|
+
"smac": SMACOptimizer,
|
|
206
|
+
"nsga2": NSGA2Optimizer,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# GPU-dependent optimizers
|
|
210
|
+
if optimizer_name == "darts":
|
|
211
|
+
if DARTS is None:
|
|
212
|
+
console.print(
|
|
213
|
+
"[red]Error:[/red] DARTS requires PyTorch. Install with: pip install 'morphml[gpu]'"
|
|
214
|
+
)
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
console.print("[yellow]Warning:[/yellow] DARTS requires GPU for optimal performance")
|
|
217
|
+
return DARTS(search_space, config)
|
|
218
|
+
|
|
219
|
+
if optimizer_name == "enas":
|
|
220
|
+
if ENAS is None:
|
|
221
|
+
console.print(
|
|
222
|
+
"[red]Error:[/red] ENAS requires PyTorch. Install with: pip install 'morphml[gpu]'"
|
|
223
|
+
)
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
console.print("[yellow]Warning:[/yellow] ENAS requires GPU for optimal performance")
|
|
226
|
+
return ENAS(search_space, config)
|
|
227
|
+
|
|
228
|
+
# Standard optimizers
|
|
229
|
+
optimizer_class = optimizer_map.get(optimizer_name)
|
|
230
|
+
if optimizer_class is None:
|
|
231
|
+
console.print(f"[red]Error:[/red] Unknown optimizer: {optimizer_name}")
|
|
232
|
+
sys.exit(1)
|
|
233
|
+
|
|
234
|
+
# Bayesian optimizers use different config structure
|
|
235
|
+
if optimizer_name in ["gp", "tpe", "smac"]:
|
|
236
|
+
return optimizer_class(search_space, config=config)
|
|
237
|
+
|
|
238
|
+
# NSGA2 uses different config structure
|
|
239
|
+
if optimizer_name == "nsga2":
|
|
240
|
+
return optimizer_class(search_space, config=config)
|
|
241
|
+
|
|
242
|
+
# Phase 1 optimizers use **kwargs
|
|
243
|
+
return optimizer_class(search_space, **config)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _save_results(best, optimizer, output_path: Path, export_format: str):
|
|
247
|
+
"""Save experiment results."""
|
|
248
|
+
console.print("[cyan]Saving results...[/cyan]")
|
|
249
|
+
|
|
250
|
+
# Save best model graph
|
|
251
|
+
best_model_path = output_path / "best_model.json"
|
|
252
|
+
with open(best_model_path, "w") as f:
|
|
253
|
+
json.dump(best.graph.to_dict(), f, indent=2)
|
|
254
|
+
console.print(f" ✓ Best model: [yellow]{best_model_path}[/yellow]")
|
|
255
|
+
|
|
256
|
+
# Export architecture
|
|
257
|
+
exporter = ArchitectureExporter()
|
|
258
|
+
|
|
259
|
+
if export_format in ("pytorch", "both"):
|
|
260
|
+
pytorch_path = output_path / "best_model_pytorch.py"
|
|
261
|
+
pytorch_code = exporter.to_pytorch(best.graph, "BestModel")
|
|
262
|
+
with open(pytorch_path, "w") as f:
|
|
263
|
+
f.write(pytorch_code)
|
|
264
|
+
console.print(f" ✓ PyTorch export: [yellow]{pytorch_path}[/yellow]")
|
|
265
|
+
|
|
266
|
+
if export_format in ("keras", "both"):
|
|
267
|
+
keras_path = output_path / "best_model_keras.py"
|
|
268
|
+
keras_code = exporter.to_keras(best.graph, "best_model")
|
|
269
|
+
with open(keras_path, "w") as f:
|
|
270
|
+
f.write(keras_code)
|
|
271
|
+
console.print(f" ✓ Keras export: [yellow]{keras_path}[/yellow]")
|
|
272
|
+
|
|
273
|
+
# Save history if available
|
|
274
|
+
if hasattr(optimizer, "get_history"):
|
|
275
|
+
history = optimizer.get_history()
|
|
276
|
+
history_path = output_path / "history.json"
|
|
277
|
+
with open(history_path, "w") as f:
|
|
278
|
+
json.dump(history, f, indent=2)
|
|
279
|
+
console.print(f" ✓ History: [yellow]{history_path}[/yellow]")
|
|
280
|
+
|
|
281
|
+
# Save summary
|
|
282
|
+
summary = {
|
|
283
|
+
"best_fitness": best.fitness if hasattr(best, "fitness") else str(best),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# Add population stats if available
|
|
287
|
+
if hasattr(optimizer, "population"):
|
|
288
|
+
summary["final_generation"] = optimizer.population.generation
|
|
289
|
+
summary["population_size"] = optimizer.population.size()
|
|
290
|
+
summary["statistics"] = optimizer.population.get_statistics()
|
|
291
|
+
|
|
292
|
+
summary_path = output_path / "summary.json"
|
|
293
|
+
with open(summary_path, "w") as f:
|
|
294
|
+
json.dump(summary, f, indent=2)
|
|
295
|
+
console.print(f" ✓ Summary: [yellow]{summary_path}[/yellow]\n")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _display_results_summary(best, optimizer, output_path: Path):
|
|
299
|
+
"""Display results in terminal."""
|
|
300
|
+
table = Table(title="Experiment Results", show_header=True, header_style="bold magenta")
|
|
301
|
+
table.add_column("Metric", style="cyan", width=30)
|
|
302
|
+
table.add_column("Value", style="green", width=20)
|
|
303
|
+
|
|
304
|
+
# Add fitness
|
|
305
|
+
table.add_row("Best Fitness", f"{best.fitness:.6f}" if hasattr(best, "fitness") else str(best))
|
|
306
|
+
|
|
307
|
+
# Add population stats if available
|
|
308
|
+
if hasattr(optimizer, "population"):
|
|
309
|
+
stats = optimizer.population.get_statistics()
|
|
310
|
+
table.add_row("Mean Fitness", f"{stats['mean_fitness']:.6f}")
|
|
311
|
+
table.add_row("Final Generation", str(optimizer.population.generation))
|
|
312
|
+
table.add_row("Population Size", str(optimizer.population.size()))
|
|
313
|
+
table.add_row("Best Model Nodes", str(len(best.graph.nodes)))
|
|
314
|
+
table.add_row("Best Model Depth", str(best.graph.get_depth()))
|
|
315
|
+
table.add_row("Est. Parameters", f"{best.graph.estimate_parameters():,}")
|
|
316
|
+
table.add_row("Output Directory", str(output_path))
|
|
317
|
+
|
|
318
|
+
console.print(table)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@cli.command()
|
|
322
|
+
@click.argument("results_dir", type=click.Path(exists=True))
|
|
323
|
+
def status(results_dir: str):
|
|
324
|
+
"""Show status of an experiment."""
|
|
325
|
+
results_path = Path(results_dir)
|
|
326
|
+
summary_path = results_path / "summary.json"
|
|
327
|
+
|
|
328
|
+
if not summary_path.exists():
|
|
329
|
+
console.print(f"[red]No results found in {results_dir}[/red]")
|
|
330
|
+
sys.exit(1)
|
|
331
|
+
|
|
332
|
+
with open(summary_path) as f:
|
|
333
|
+
summary = json.load(f)
|
|
334
|
+
|
|
335
|
+
table = Table(
|
|
336
|
+
title=f"Experiment Status: {results_dir}", show_header=True, header_style="bold magenta"
|
|
337
|
+
)
|
|
338
|
+
table.add_column("Metric", style="cyan", width=30)
|
|
339
|
+
table.add_column("Value", style="green", width=30)
|
|
340
|
+
|
|
341
|
+
for key, value in summary.items():
|
|
342
|
+
if isinstance(value, dict):
|
|
343
|
+
table.add_row(key, json.dumps(value, indent=2))
|
|
344
|
+
elif isinstance(value, float):
|
|
345
|
+
table.add_row(key, f"{value:.6f}")
|
|
346
|
+
else:
|
|
347
|
+
table.add_row(key, str(value))
|
|
348
|
+
|
|
349
|
+
console.print(table)
|
|
350
|
+
|
|
351
|
+
# Show best model info if available
|
|
352
|
+
best_model_path = results_path / "best_model.json"
|
|
353
|
+
if best_model_path.exists():
|
|
354
|
+
console.print(f"\n[cyan]Best model saved at:[/cyan] [yellow]{best_model_path}[/yellow]")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@cli.command()
|
|
358
|
+
@click.option("--key", "-k", default=None, help="Show specific config key")
|
|
359
|
+
def config(key: str):
|
|
360
|
+
"""Show current configuration."""
|
|
361
|
+
from morphml.config import get_config
|
|
362
|
+
|
|
363
|
+
cfg = get_config()
|
|
364
|
+
|
|
365
|
+
if key:
|
|
366
|
+
value = cfg.get(key)
|
|
367
|
+
console.print(f"[cyan]{key}:[/cyan] [green]{value}[/green]")
|
|
368
|
+
else:
|
|
369
|
+
table = Table(title="MorphML Configuration", show_header=True, header_style="bold magenta")
|
|
370
|
+
table.add_column("Key", style="cyan", width=40)
|
|
371
|
+
table.add_column("Value", style="green", width=40)
|
|
372
|
+
|
|
373
|
+
# Show some default configurations
|
|
374
|
+
default_config = {
|
|
375
|
+
"version": __version__,
|
|
376
|
+
"logging.level": "INFO",
|
|
377
|
+
"execution.max_workers": 4,
|
|
378
|
+
"search.population_size": 50,
|
|
379
|
+
"search.max_generations": 100,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for k, v in default_config.items():
|
|
383
|
+
table.add_row(k, str(v))
|
|
384
|
+
|
|
385
|
+
console.print(table)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@cli.command()
|
|
389
|
+
@click.argument("architecture_file", type=click.Path(exists=True))
|
|
390
|
+
@click.option(
|
|
391
|
+
"--format",
|
|
392
|
+
"-f",
|
|
393
|
+
type=click.Choice(["pytorch", "keras", "both"]),
|
|
394
|
+
default="pytorch",
|
|
395
|
+
help="Export format",
|
|
396
|
+
)
|
|
397
|
+
@click.option("--output", "-o", default=None, help="Output file path")
|
|
398
|
+
def export(architecture_file: str, format: str, output: str):
|
|
399
|
+
"""Export architecture to framework-specific code."""
|
|
400
|
+
console.print(f"[cyan]Exporting architecture from:[/cyan] {architecture_file}\n")
|
|
401
|
+
|
|
402
|
+
# Load architecture
|
|
403
|
+
with open(architecture_file, "r") as f:
|
|
404
|
+
arch_data = json.load(f)
|
|
405
|
+
|
|
406
|
+
from morphml.core.graph import ModelGraph
|
|
407
|
+
|
|
408
|
+
graph = ModelGraph.from_dict(arch_data)
|
|
409
|
+
|
|
410
|
+
exporter = ArchitectureExporter()
|
|
411
|
+
|
|
412
|
+
if format in ("pytorch", "both"):
|
|
413
|
+
code = exporter.to_pytorch(graph, "ExportedModel")
|
|
414
|
+
if output:
|
|
415
|
+
with open(output, "w") as f:
|
|
416
|
+
f.write(code)
|
|
417
|
+
console.print(f"[green]✓ PyTorch code exported to:[/green] [yellow]{output}[/yellow]")
|
|
418
|
+
else:
|
|
419
|
+
console.print("[bold]PyTorch Code:[/bold]\n")
|
|
420
|
+
console.print(code)
|
|
421
|
+
|
|
422
|
+
if format in ("keras", "both") and format != "pytorch":
|
|
423
|
+
code = exporter.to_keras(graph, "exported_model")
|
|
424
|
+
output_keras = output.replace(".py", "_keras.py") if output else None
|
|
425
|
+
if output_keras:
|
|
426
|
+
with open(output_keras, "w") as f:
|
|
427
|
+
f.write(code)
|
|
428
|
+
console.print(
|
|
429
|
+
f"[green]✓ Keras code exported to:[/green] [yellow]{output_keras}[/yellow]"
|
|
430
|
+
)
|
|
431
|
+
else:
|
|
432
|
+
console.print("\n[bold]Keras Code:[/bold]\n")
|
|
433
|
+
console.print(code)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@cli.command()
|
|
437
|
+
def version():
|
|
438
|
+
"""Show MorphML version and system info."""
|
|
439
|
+
import platform
|
|
440
|
+
import sys
|
|
441
|
+
|
|
442
|
+
table = Table(title="MorphML System Information", show_header=True, header_style="bold magenta")
|
|
443
|
+
table.add_column("Item", style="cyan", width=30)
|
|
444
|
+
table.add_column("Value", style="green", width=50)
|
|
445
|
+
|
|
446
|
+
table.add_row("MorphML Version", __version__)
|
|
447
|
+
table.add_row("Author", "Eshan Roy <eshanized@proton.me>")
|
|
448
|
+
table.add_row("Organization", "TONMOY INFRASTRUCTURE & VISION")
|
|
449
|
+
table.add_row("Repository", "https://github.com/TIVerse/MorphML")
|
|
450
|
+
table.add_row("Python Version", sys.version.split()[0])
|
|
451
|
+
table.add_row("Platform", platform.platform())
|
|
452
|
+
|
|
453
|
+
console.print(table)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
if __name__ == "__main__":
|
|
457
|
+
cli()
|