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.

Files changed (158) hide show
  1. morphml/__init__.py +14 -0
  2. morphml/api/__init__.py +26 -0
  3. morphml/api/app.py +326 -0
  4. morphml/api/auth.py +193 -0
  5. morphml/api/client.py +338 -0
  6. morphml/api/models.py +132 -0
  7. morphml/api/rate_limit.py +192 -0
  8. morphml/benchmarking/__init__.py +36 -0
  9. morphml/benchmarking/comparison.py +430 -0
  10. morphml/benchmarks/__init__.py +56 -0
  11. morphml/benchmarks/comparator.py +409 -0
  12. morphml/benchmarks/datasets.py +280 -0
  13. morphml/benchmarks/metrics.py +199 -0
  14. morphml/benchmarks/openml_suite.py +201 -0
  15. morphml/benchmarks/problems.py +289 -0
  16. morphml/benchmarks/suite.py +318 -0
  17. morphml/cli/__init__.py +5 -0
  18. morphml/cli/commands/experiment.py +329 -0
  19. morphml/cli/main.py +457 -0
  20. morphml/cli/quickstart.py +312 -0
  21. morphml/config.py +278 -0
  22. morphml/constraints/__init__.py +19 -0
  23. morphml/constraints/handler.py +205 -0
  24. morphml/constraints/predicates.py +285 -0
  25. morphml/core/__init__.py +3 -0
  26. morphml/core/crossover.py +449 -0
  27. morphml/core/dsl/README.md +359 -0
  28. morphml/core/dsl/__init__.py +72 -0
  29. morphml/core/dsl/ast_nodes.py +364 -0
  30. morphml/core/dsl/compiler.py +318 -0
  31. morphml/core/dsl/layers.py +368 -0
  32. morphml/core/dsl/lexer.py +336 -0
  33. morphml/core/dsl/parser.py +455 -0
  34. morphml/core/dsl/search_space.py +386 -0
  35. morphml/core/dsl/syntax.py +199 -0
  36. morphml/core/dsl/type_system.py +361 -0
  37. morphml/core/dsl/validator.py +386 -0
  38. morphml/core/graph/__init__.py +40 -0
  39. morphml/core/graph/edge.py +124 -0
  40. morphml/core/graph/graph.py +507 -0
  41. morphml/core/graph/mutations.py +409 -0
  42. morphml/core/graph/node.py +196 -0
  43. morphml/core/graph/serialization.py +361 -0
  44. morphml/core/graph/visualization.py +431 -0
  45. morphml/core/objectives/__init__.py +20 -0
  46. morphml/core/search/__init__.py +33 -0
  47. morphml/core/search/individual.py +252 -0
  48. morphml/core/search/parameters.py +453 -0
  49. morphml/core/search/population.py +375 -0
  50. morphml/core/search/search_engine.py +340 -0
  51. morphml/distributed/__init__.py +76 -0
  52. morphml/distributed/fault_tolerance.py +497 -0
  53. morphml/distributed/health_monitor.py +348 -0
  54. morphml/distributed/master.py +709 -0
  55. morphml/distributed/proto/README.md +224 -0
  56. morphml/distributed/proto/__init__.py +74 -0
  57. morphml/distributed/proto/worker.proto +170 -0
  58. morphml/distributed/proto/worker_pb2.py +79 -0
  59. morphml/distributed/proto/worker_pb2_grpc.py +423 -0
  60. morphml/distributed/resource_manager.py +416 -0
  61. morphml/distributed/scheduler.py +567 -0
  62. morphml/distributed/storage/__init__.py +33 -0
  63. morphml/distributed/storage/artifacts.py +381 -0
  64. morphml/distributed/storage/cache.py +366 -0
  65. morphml/distributed/storage/checkpointing.py +329 -0
  66. morphml/distributed/storage/database.py +459 -0
  67. morphml/distributed/worker.py +549 -0
  68. morphml/evaluation/__init__.py +5 -0
  69. morphml/evaluation/heuristic.py +237 -0
  70. morphml/exceptions.py +55 -0
  71. morphml/execution/__init__.py +5 -0
  72. morphml/execution/local_executor.py +350 -0
  73. morphml/integrations/__init__.py +28 -0
  74. morphml/integrations/jax_adapter.py +206 -0
  75. morphml/integrations/pytorch_adapter.py +530 -0
  76. morphml/integrations/sklearn_adapter.py +206 -0
  77. morphml/integrations/tensorflow_adapter.py +230 -0
  78. morphml/logging_config.py +93 -0
  79. morphml/meta_learning/__init__.py +66 -0
  80. morphml/meta_learning/architecture_similarity.py +277 -0
  81. morphml/meta_learning/experiment_database.py +240 -0
  82. morphml/meta_learning/knowledge_base/__init__.py +19 -0
  83. morphml/meta_learning/knowledge_base/embedder.py +179 -0
  84. morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
  85. morphml/meta_learning/knowledge_base/meta_features.py +265 -0
  86. morphml/meta_learning/knowledge_base/vector_store.py +271 -0
  87. morphml/meta_learning/predictors/__init__.py +27 -0
  88. morphml/meta_learning/predictors/ensemble.py +221 -0
  89. morphml/meta_learning/predictors/gnn_predictor.py +552 -0
  90. morphml/meta_learning/predictors/learning_curve.py +231 -0
  91. morphml/meta_learning/predictors/proxy_metrics.py +261 -0
  92. morphml/meta_learning/strategy_evolution/__init__.py +27 -0
  93. morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
  94. morphml/meta_learning/strategy_evolution/bandit.py +276 -0
  95. morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
  96. morphml/meta_learning/transfer.py +581 -0
  97. morphml/meta_learning/warm_start.py +286 -0
  98. morphml/optimizers/__init__.py +74 -0
  99. morphml/optimizers/adaptive_operators.py +399 -0
  100. morphml/optimizers/bayesian/__init__.py +52 -0
  101. morphml/optimizers/bayesian/acquisition.py +387 -0
  102. morphml/optimizers/bayesian/base.py +319 -0
  103. morphml/optimizers/bayesian/gaussian_process.py +635 -0
  104. morphml/optimizers/bayesian/smac.py +534 -0
  105. morphml/optimizers/bayesian/tpe.py +411 -0
  106. morphml/optimizers/differential_evolution.py +220 -0
  107. morphml/optimizers/evolutionary/__init__.py +61 -0
  108. morphml/optimizers/evolutionary/cma_es.py +416 -0
  109. morphml/optimizers/evolutionary/differential_evolution.py +556 -0
  110. morphml/optimizers/evolutionary/encoding.py +426 -0
  111. morphml/optimizers/evolutionary/particle_swarm.py +449 -0
  112. morphml/optimizers/genetic_algorithm.py +486 -0
  113. morphml/optimizers/gradient_based/__init__.py +22 -0
  114. morphml/optimizers/gradient_based/darts.py +550 -0
  115. morphml/optimizers/gradient_based/enas.py +585 -0
  116. morphml/optimizers/gradient_based/operations.py +474 -0
  117. morphml/optimizers/gradient_based/utils.py +601 -0
  118. morphml/optimizers/hill_climbing.py +169 -0
  119. morphml/optimizers/multi_objective/__init__.py +56 -0
  120. morphml/optimizers/multi_objective/indicators.py +504 -0
  121. morphml/optimizers/multi_objective/nsga2.py +647 -0
  122. morphml/optimizers/multi_objective/visualization.py +427 -0
  123. morphml/optimizers/nsga2.py +308 -0
  124. morphml/optimizers/random_search.py +172 -0
  125. morphml/optimizers/simulated_annealing.py +181 -0
  126. morphml/plugins/__init__.py +35 -0
  127. morphml/plugins/custom_evaluator_example.py +81 -0
  128. morphml/plugins/custom_optimizer_example.py +63 -0
  129. morphml/plugins/plugin_system.py +454 -0
  130. morphml/reports/__init__.py +30 -0
  131. morphml/reports/generator.py +362 -0
  132. morphml/tracking/__init__.py +7 -0
  133. morphml/tracking/experiment.py +309 -0
  134. morphml/tracking/logger.py +301 -0
  135. morphml/tracking/reporter.py +357 -0
  136. morphml/utils/__init__.py +6 -0
  137. morphml/utils/checkpoint.py +189 -0
  138. morphml/utils/comparison.py +390 -0
  139. morphml/utils/export.py +407 -0
  140. morphml/utils/progress.py +392 -0
  141. morphml/utils/validation.py +392 -0
  142. morphml/version.py +7 -0
  143. morphml/visualization/__init__.py +50 -0
  144. morphml/visualization/analytics.py +423 -0
  145. morphml/visualization/architecture_diagrams.py +353 -0
  146. morphml/visualization/architecture_plot.py +223 -0
  147. morphml/visualization/convergence_plot.py +174 -0
  148. morphml/visualization/crossover_viz.py +386 -0
  149. morphml/visualization/graph_viz.py +338 -0
  150. morphml/visualization/pareto_plot.py +149 -0
  151. morphml/visualization/plotly_dashboards.py +422 -0
  152. morphml/visualization/population.py +309 -0
  153. morphml/visualization/progress.py +260 -0
  154. morphml-1.0.0.dist-info/METADATA +434 -0
  155. morphml-1.0.0.dist-info/RECORD +158 -0
  156. morphml-1.0.0.dist-info/WHEEL +4 -0
  157. morphml-1.0.0.dist-info/entry_points.txt +3 -0
  158. morphml-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,486 @@
1
+ """Genetic Algorithm optimizer for neural architecture search.
2
+
3
+ A complete implementation of evolutionary NAS using genetic algorithms.
4
+
5
+ Example:
6
+ >>> from morphml.optimizers import GeneticAlgorithm
7
+ >>> from morphml.core.dsl import create_cnn_space
8
+ >>>
9
+ >>> # Define search space
10
+ >>> space = create_cnn_space(num_classes=10)
11
+ >>>
12
+ >>> # Create optimizer
13
+ >>> ga = GeneticAlgorithm(
14
+ ... search_space=space,
15
+ ... population_size=50,
16
+ ... num_generations=100,
17
+ ... mutation_rate=0.2,
18
+ ... crossover_rate=0.8,
19
+ ... elitism=5
20
+ ... )
21
+ >>>
22
+ >>> # Run optimization
23
+ >>> best_individual = ga.optimize(evaluator=my_evaluator)
24
+ """
25
+
26
+ from typing import Any, Callable, Dict, List, Optional
27
+
28
+ from morphml.core.dsl.search_space import SearchSpace
29
+ from morphml.core.graph import GraphMutator, ModelGraph
30
+ from morphml.core.search import Individual, Population
31
+ from morphml.exceptions import OptimizerError
32
+ from morphml.logging_config import get_logger
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ class GeneticAlgorithm:
38
+ """
39
+ Genetic Algorithm optimizer for neural architecture search.
40
+
41
+ Implements a complete evolutionary algorithm with:
42
+ - Population initialization from search space
43
+ - Selection (tournament, roulette, rank, random)
44
+ - Crossover (graph-level)
45
+ - Mutation (using GraphMutator)
46
+ - Elitism preservation
47
+ - Convergence tracking
48
+ - History recording
49
+
50
+ Attributes:
51
+ search_space: SearchSpace to sample from
52
+ population: Current population
53
+ mutator: GraphMutator for mutations
54
+ config: Algorithm configuration
55
+ history: Optimization history
56
+ best_individual: Best individual found so far
57
+
58
+ Example:
59
+ >>> ga = GeneticAlgorithm(
60
+ ... search_space=space,
61
+ ... population_size=50,
62
+ ... num_generations=100
63
+ ... )
64
+ >>> result = ga.optimize(evaluator=evaluate_func)
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ search_space: SearchSpace,
70
+ population_size: int = 50,
71
+ num_generations: int = 100,
72
+ mutation_rate: float = 0.2,
73
+ crossover_rate: float = 0.8,
74
+ elitism: int = 5,
75
+ selection_method: str = "tournament",
76
+ tournament_size: int = 3,
77
+ max_mutations: int = 3,
78
+ early_stopping_patience: Optional[int] = None,
79
+ **kwargs: Any,
80
+ ):
81
+ """
82
+ Initialize Genetic Algorithm optimizer.
83
+
84
+ Args:
85
+ search_space: SearchSpace to sample architectures from
86
+ population_size: Number of individuals in population
87
+ num_generations: Maximum number of generations
88
+ mutation_rate: Probability of mutating an offspring
89
+ crossover_rate: Probability of crossover
90
+ elitism: Number of best individuals to preserve
91
+ selection_method: Selection strategy ('tournament', 'roulette', 'rank')
92
+ tournament_size: Size of tournament for tournament selection
93
+ max_mutations: Maximum mutations per individual
94
+ early_stopping_patience: Stop if no improvement for N generations
95
+ **kwargs: Additional configuration
96
+ """
97
+ self.search_space = search_space
98
+ self.config = {
99
+ "population_size": population_size,
100
+ "num_generations": num_generations,
101
+ "mutation_rate": mutation_rate,
102
+ "crossover_rate": crossover_rate,
103
+ "elitism": elitism,
104
+ "selection_method": selection_method,
105
+ "tournament_size": tournament_size,
106
+ "max_mutations": max_mutations,
107
+ "early_stopping_patience": early_stopping_patience,
108
+ **kwargs,
109
+ }
110
+
111
+ # Initialize components
112
+ self.population = Population(max_size=population_size, elitism=elitism)
113
+ self.mutator = GraphMutator()
114
+
115
+ # State tracking
116
+ self.history: List[Dict[str, Any]] = []
117
+ self.best_individual: Optional[Individual] = None
118
+ self._generations_without_improvement = 0
119
+ self._initialized = False
120
+
121
+ logger.info(
122
+ f"Created GeneticAlgorithm: "
123
+ f"pop_size={population_size}, "
124
+ f"generations={num_generations}, "
125
+ f"mutation_rate={mutation_rate}"
126
+ )
127
+
128
+ def initialize_population(self) -> None:
129
+ """
130
+ Initialize population by sampling from search space.
131
+
132
+ Creates population_size individuals by sampling random
133
+ architectures from the search space.
134
+ """
135
+ logger.info(f"Initializing population of size {self.config['population_size']}")
136
+
137
+ self.population.clear()
138
+
139
+ for i in range(self.config["population_size"]):
140
+ try:
141
+ graph = self.search_space.sample()
142
+ individual = Individual(graph)
143
+ self.population.add(individual)
144
+
145
+ if (i + 1) % 10 == 0:
146
+ logger.debug(
147
+ f"Initialized {i + 1}/{self.config['population_size']} individuals"
148
+ )
149
+
150
+ except Exception as e:
151
+ logger.warning(f"Failed to sample individual {i}: {e}")
152
+ continue
153
+
154
+ self._initialized = True
155
+ logger.info(f"Population initialized with {self.population.size()} individuals")
156
+
157
+ def evaluate_population(self, evaluator: Callable[[ModelGraph], float]) -> None:
158
+ """
159
+ Evaluate all unevaluated individuals in the population.
160
+
161
+ Args:
162
+ evaluator: Function that takes ModelGraph and returns fitness score
163
+
164
+ Example:
165
+ >>> def my_evaluator(graph):
166
+ ... # Your evaluation logic
167
+ ... return accuracy_score
168
+ >>> ga.evaluate_population(my_evaluator)
169
+ """
170
+ unevaluated = self.population.get_unevaluated()
171
+
172
+ if not unevaluated:
173
+ logger.debug("No unevaluated individuals")
174
+ return
175
+
176
+ logger.info(f"Evaluating {len(unevaluated)} individuals")
177
+
178
+ for i, individual in enumerate(unevaluated):
179
+ try:
180
+ # Evaluate architecture
181
+ fitness = evaluator(individual.graph)
182
+ individual.set_fitness(fitness)
183
+
184
+ # Track best
185
+ if (
186
+ self.best_individual is None
187
+ or self.best_individual.fitness is None
188
+ or fitness > self.best_individual.fitness
189
+ ):
190
+ self.best_individual = individual
191
+ self._generations_without_improvement = 0
192
+ logger.info(f"New best fitness: {fitness:.4f}")
193
+
194
+ if (i + 1) % 10 == 0:
195
+ logger.debug(f"Evaluated {i + 1}/{len(unevaluated)} individuals")
196
+
197
+ except Exception as e:
198
+ logger.error(f"Evaluation failed for individual {individual.id[:12]}: {e}")
199
+ # Assign low fitness on failure
200
+ individual.set_fitness(0.0)
201
+
202
+ def select_parents(self, n: int) -> List[Individual]:
203
+ """
204
+ Select parent individuals for breeding.
205
+
206
+ Args:
207
+ n: Number of parents to select
208
+
209
+ Returns:
210
+ List of selected parent individuals
211
+ """
212
+ return self.population.select(
213
+ n=n,
214
+ method=self.config["selection_method"],
215
+ k=self.config.get("tournament_size", 3),
216
+ )
217
+
218
+ def crossover(self, parent1: Individual, parent2: Individual) -> Individual:
219
+ """
220
+ Perform crossover between two parents.
221
+
222
+ Uses single-point crossover to combine parent graphs.
223
+
224
+ Args:
225
+ parent1: First parent
226
+ parent2: Second parent
227
+
228
+ Returns:
229
+ Offspring individual
230
+ """
231
+ import random
232
+
233
+ from morphml.core.graph.mutations import crossover as graph_crossover
234
+
235
+ # Perform graph crossover
236
+ offspring_graph1, offspring_graph2 = graph_crossover(parent1.graph, parent2.graph)
237
+
238
+ # Randomly select one of the two offspring
239
+ selected_graph = random.choice([offspring_graph1, offspring_graph2])
240
+
241
+ # Create new individual
242
+ offspring = Individual(selected_graph)
243
+ offspring.parent_ids = [parent1.id, parent2.id]
244
+ offspring.metadata["crossover"] = "single_point"
245
+
246
+ return offspring
247
+
248
+ def mutate(self, individual: Individual) -> Individual:
249
+ """
250
+ Mutate an individual's architecture.
251
+
252
+ Args:
253
+ individual: Individual to mutate
254
+
255
+ Returns:
256
+ Mutated individual (new instance)
257
+ """
258
+ mutated_graph = self.mutator.mutate(
259
+ individual.graph,
260
+ mutation_rate=self.config["mutation_rate"],
261
+ max_mutations=self.config.get("max_mutations", 3),
262
+ )
263
+
264
+ mutated_individual = Individual(mutated_graph, parent_ids=[individual.id])
265
+
266
+ return mutated_individual
267
+
268
+ def generate_offspring(self, num_offspring: int) -> List[Individual]:
269
+ """
270
+ Generate offspring through selection, crossover, and mutation.
271
+
272
+ Args:
273
+ num_offspring: Number of offspring to generate
274
+
275
+ Returns:
276
+ List of offspring individuals
277
+ """
278
+ offspring: List[Individual] = []
279
+
280
+ while len(offspring) < num_offspring:
281
+ # Select parents
282
+ parents = self.select_parents(n=2)
283
+
284
+ if len(parents) < 2:
285
+ logger.warning("Not enough parents for crossover")
286
+ break
287
+
288
+ # Crossover
289
+ import random
290
+
291
+ if random.random() < self.config["crossover_rate"]:
292
+ child = self.crossover(parents[0], parents[1])
293
+ else:
294
+ # No crossover, just clone
295
+ child = random.choice(parents).clone(keep_fitness=False)
296
+
297
+ # Mutation
298
+ if random.random() < self.config["mutation_rate"]:
299
+ child = self.mutate(child)
300
+
301
+ offspring.append(child)
302
+
303
+ return offspring
304
+
305
+ def evolve_generation(self) -> None:
306
+ """
307
+ Evolve one generation.
308
+
309
+ Steps:
310
+ 1. Generate offspring
311
+ 2. Add to population
312
+ 3. Trim to max size (keeps elite + best)
313
+ 4. Advance generation
314
+ """
315
+ # Generate offspring
316
+ num_offspring = self.config["population_size"] - self.config["elitism"]
317
+ offspring = self.generate_offspring(num_offspring)
318
+
319
+ logger.debug(f"Generated {len(offspring)} offspring")
320
+
321
+ # Add offspring to population
322
+ self.population.add_many(offspring)
323
+
324
+ # Trim to max size
325
+ self.population.trim()
326
+
327
+ # Advance generation
328
+ self.population.next_generation()
329
+
330
+ def check_convergence(self) -> bool:
331
+ """
332
+ Check if optimization should stop.
333
+
334
+ Returns:
335
+ True if converged (should stop), False otherwise
336
+ """
337
+ # Check generation limit
338
+ if self.population.generation >= self.config["num_generations"]:
339
+ logger.info(f"Reached max generations: {self.config['num_generations']}")
340
+ return True
341
+
342
+ # Check early stopping
343
+ patience = self.config.get("early_stopping_patience")
344
+ if patience and self._generations_without_improvement >= patience:
345
+ logger.info(
346
+ f"Early stopping: No improvement for "
347
+ f"{self._generations_without_improvement} generations"
348
+ )
349
+ return True
350
+
351
+ return False
352
+
353
+ def optimize(
354
+ self,
355
+ evaluator: Callable[[ModelGraph], float],
356
+ callback: Optional[Callable[[int, Population], None]] = None,
357
+ ) -> Individual:
358
+ """
359
+ Run the genetic algorithm optimization.
360
+
361
+ Args:
362
+ evaluator: Function to evaluate fitness of architectures
363
+ callback: Optional callback function called each generation
364
+ callback(generation: int, population: Population)
365
+
366
+ Returns:
367
+ Best individual found
368
+
369
+ Raises:
370
+ OptimizerError: If optimization fails
371
+
372
+ Example:
373
+ >>> def evaluate(graph):
374
+ ... # Your evaluation
375
+ ... return accuracy
376
+ >>>
377
+ >>> def progress_callback(gen, pop):
378
+ ... stats = pop.get_statistics()
379
+ ... print(f"Gen {gen}: {stats['best_fitness']:.4f}")
380
+ >>>
381
+ >>> best = ga.optimize(evaluate, callback=progress_callback)
382
+ """
383
+ try:
384
+ # Initialize if needed
385
+ if not self._initialized:
386
+ self.initialize_population()
387
+
388
+ # Evaluate initial population
389
+ logger.info("Evaluating initial population")
390
+ self.evaluate_population(evaluator)
391
+
392
+ # Record initial stats
393
+ self._record_generation()
394
+
395
+ # Evolution loop
396
+ while not self.check_convergence():
397
+ gen = self.population.generation
398
+
399
+ logger.info(f"Generation {gen + 1}/{self.config['num_generations']}")
400
+
401
+ # Evolve
402
+ self.evolve_generation()
403
+
404
+ # Evaluate new individuals
405
+ self.evaluate_population(evaluator)
406
+
407
+ # Record statistics
408
+ self._record_generation()
409
+
410
+ # Track improvement
411
+ current_best = self.population.get_best(n=1)[0]
412
+ if (
413
+ self.best_individual
414
+ and self.best_individual.fitness is not None
415
+ and current_best.fitness is not None
416
+ and current_best.fitness <= self.best_individual.fitness
417
+ ):
418
+ self._generations_without_improvement += 1
419
+ else:
420
+ self._generations_without_improvement = 0
421
+
422
+ # Callback
423
+ if callback:
424
+ callback(self.population.generation, self.population)
425
+
426
+ # Final results
427
+ logger.info("Optimization complete")
428
+ stats = self.population.get_statistics()
429
+ logger.info(
430
+ f"Final: Best={stats['best_fitness']:.4f}, "
431
+ f"Mean={stats['mean_fitness']:.4f}, "
432
+ f"Generation={self.population.generation}"
433
+ )
434
+
435
+ return self.best_individual or self.population.get_best(n=1)[0]
436
+
437
+ except Exception as e:
438
+ logger.error(f"Optimization failed: {e}")
439
+ raise OptimizerError(f"Genetic algorithm optimization failed: {e}") from e
440
+
441
+ def _record_generation(self) -> None:
442
+ """Record current generation statistics to history."""
443
+ stats = self.population.get_statistics()
444
+ stats["diversity"] = self.population.get_diversity()
445
+ stats["best_individual_id"] = self.best_individual.id[:12] if self.best_individual else None
446
+ self.history.append(stats)
447
+
448
+ def get_history(self) -> List[Dict[str, Any]]:
449
+ """
450
+ Get optimization history.
451
+
452
+ Returns:
453
+ List of dictionaries with statistics for each generation
454
+ """
455
+ return self.history
456
+
457
+ def get_best_n(self, n: int = 10) -> List[Individual]:
458
+ """
459
+ Get top N individuals from final population.
460
+
461
+ Args:
462
+ n: Number of individuals to return
463
+
464
+ Returns:
465
+ List of best individuals
466
+ """
467
+ return self.population.get_best(n=n)
468
+
469
+ def reset(self) -> None:
470
+ """Reset optimizer state."""
471
+ self.population.clear()
472
+ self.history.clear()
473
+ self.best_individual = None
474
+ self._generations_without_improvement = 0
475
+ self._initialized = False
476
+ logger.info("Optimizer reset")
477
+
478
+ def __repr__(self) -> str:
479
+ """String representation."""
480
+ return (
481
+ f"GeneticAlgorithm("
482
+ f"pop_size={self.config['population_size']}, "
483
+ f"generations={self.config['num_generations']}, "
484
+ f"mutation_rate={self.config['mutation_rate']}, "
485
+ f"current_gen={self.population.generation})"
486
+ )
@@ -0,0 +1,22 @@
1
+ """Gradient-based Neural Architecture Search algorithms.
2
+
3
+ This module implements differentiable NAS methods that use gradient descent:
4
+ - DARTS (Differentiable Architecture Search)
5
+ - ENAS (Efficient Neural Architecture Search with weight sharing)
6
+
7
+ These methods require GPU acceleration and PyTorch.
8
+
9
+ Example:
10
+ >>> from morphml.optimizers.gradient_based import DARTS
11
+ >>> optimizer = DARTS(
12
+ ... search_space=space,
13
+ ... epochs=50,
14
+ ... learning_rate=0.025
15
+ ... )
16
+ >>> best = optimizer.optimize(evaluator)
17
+ """
18
+
19
+ from morphml.optimizers.gradient_based.darts import DARTSOptimizer as DARTS
20
+ from morphml.optimizers.gradient_based.enas import ENASOptimizer as ENAS
21
+
22
+ __all__ = ["DARTS", "ENAS"]