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,426 @@
1
+ """Architecture encoding/decoding utilities for continuous optimizers.
2
+
3
+ This module provides methods to convert between discrete graph architectures
4
+ and continuous vector representations, enabling the use of continuous optimizers
5
+ like CMA-ES and PSO for neural architecture search.
6
+
7
+ Encoding Strategies:
8
+ - Positional encoding of operations
9
+ - Hyperparameter normalization
10
+ - Fixed-length vector representation
11
+ - Padding/truncation for variable architectures
12
+
13
+ Author: Eshan Roy <eshanized@proton.me>
14
+ Organization: TONMOY INFRASTRUCTURE & VISION
15
+ """
16
+
17
+ from typing import Dict, List, Optional, Tuple
18
+
19
+ import numpy as np
20
+
21
+ from morphml.core.dsl import SearchSpace
22
+ from morphml.core.graph import GraphNode, ModelGraph
23
+ from morphml.logging_config import get_logger
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ # Standard operation vocabulary for encoding
29
+ OPERATION_VOCABULARY = [
30
+ "input",
31
+ "output",
32
+ "conv2d",
33
+ "dense",
34
+ "relu",
35
+ "sigmoid",
36
+ "tanh",
37
+ "maxpool",
38
+ "avgpool",
39
+ "batchnorm",
40
+ "dropout",
41
+ "flatten",
42
+ "add",
43
+ "concat",
44
+ "identity",
45
+ ]
46
+
47
+
48
+ class ArchitectureEncoder:
49
+ """
50
+ Encoder/decoder for converting between graphs and continuous vectors.
51
+
52
+ Provides bidirectional mapping between ModelGraph (discrete) and
53
+ numpy arrays (continuous) for use with continuous optimizers.
54
+
55
+ Encoding Scheme:
56
+ For each node position (up to max_nodes):
57
+ - operation_id: [0, 1] normalized index
58
+ - param1: [0, 1] normalized hyperparameter
59
+ - param2: [0, 1] normalized hyperparameter
60
+
61
+ Total dimensions: max_nodes * 3
62
+
63
+ Example:
64
+ >>> encoder = ArchitectureEncoder(search_space, max_nodes=20)
65
+ >>> vector = encoder.encode(graph) # ModelGraph -> np.ndarray
66
+ >>> decoded_graph = encoder.decode(vector) # np.ndarray -> ModelGraph
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ search_space: SearchSpace,
72
+ max_nodes: int = 20,
73
+ operation_vocab: Optional[List[str]] = None,
74
+ ):
75
+ """
76
+ Initialize encoder.
77
+
78
+ Args:
79
+ search_space: SearchSpace for sampling/validation
80
+ max_nodes: Maximum number of nodes to encode
81
+ operation_vocab: List of operation names (None = use default)
82
+ """
83
+ self.search_space = search_space
84
+ self.max_nodes = max_nodes
85
+ self.operation_vocab = operation_vocab or OPERATION_VOCABULARY
86
+ self.vocab_size = len(self.operation_vocab)
87
+
88
+ # Encoding dimension: 3 features per node
89
+ self.dim = max_nodes * 3
90
+
91
+ logger.debug(
92
+ f"Initialized ArchitectureEncoder: "
93
+ f"max_nodes={max_nodes}, dim={self.dim}, vocab_size={self.vocab_size}"
94
+ )
95
+
96
+ def encode(self, graph: ModelGraph) -> np.ndarray:
97
+ """
98
+ Encode ModelGraph as continuous vector.
99
+
100
+ Args:
101
+ graph: ModelGraph to encode
102
+
103
+ Returns:
104
+ Continuous vector of shape (dim,)
105
+
106
+ Example:
107
+ >>> vector = encoder.encode(graph)
108
+ >>> print(vector.shape) # (60,) for max_nodes=20
109
+ """
110
+ vector = np.zeros(self.dim)
111
+
112
+ try:
113
+ # Get topological order
114
+ nodes = list(graph.topological_sort())
115
+ except Exception:
116
+ # Fallback: use nodes in any order
117
+ nodes = list(graph.nodes.values())
118
+
119
+ # Encode each node
120
+ for i, node in enumerate(nodes[: self.max_nodes]):
121
+ base_idx = i * 3
122
+
123
+ # Feature 1: Operation ID (normalized)
124
+ op_id = self._encode_operation(node.operation)
125
+ vector[base_idx] = op_id / self.vocab_size
126
+
127
+ # Features 2-3: Hyperparameters (normalized)
128
+ param1, param2 = self._encode_parameters(node)
129
+ vector[base_idx + 1] = param1
130
+ vector[base_idx + 2] = param2
131
+
132
+ # Padding is already zeros for nodes beyond graph length
133
+
134
+ return vector
135
+
136
+ def decode(self, vector: np.ndarray) -> ModelGraph:
137
+ """
138
+ Decode continuous vector to ModelGraph.
139
+
140
+ Args:
141
+ vector: Continuous vector of shape (dim,)
142
+
143
+ Returns:
144
+ Decoded ModelGraph
145
+
146
+ Note:
147
+ Decoding is approximate - the inverse mapping is not unique.
148
+ We sample a base architecture and modify it based on the vector.
149
+
150
+ Example:
151
+ >>> graph = encoder.decode(vector)
152
+ >>> assert graph.is_valid_dag()
153
+ """
154
+ # Start with a random architecture from search space
155
+ graph = self.search_space.sample()
156
+
157
+ try:
158
+ nodes = list(graph.topological_sort())
159
+ except Exception:
160
+ nodes = list(graph.nodes.values())
161
+
162
+ # Modify nodes based on vector
163
+ for i in range(min(len(nodes), self.max_nodes)):
164
+ base_idx = i * 3
165
+
166
+ if base_idx + 2 >= len(vector):
167
+ break
168
+
169
+ # Decode operation
170
+ op_id_norm = vector[base_idx]
171
+ op_id = int(op_id_norm * self.vocab_size) % self.vocab_size
172
+ operation = self.operation_vocab[op_id]
173
+
174
+ # Decode parameters
175
+ param1_norm = np.clip(vector[base_idx + 1], 0, 1)
176
+ param2_norm = np.clip(vector[base_idx + 2], 0, 1)
177
+
178
+ # Apply to node (if not input/output)
179
+ if i < len(nodes) and nodes[i].operation not in ["input", "output"]:
180
+ nodes[i].operation = operation
181
+ self._decode_parameters(nodes[i], param1_norm, param2_norm)
182
+
183
+ # Validate and return
184
+ if graph.is_valid_dag():
185
+ return graph
186
+ else:
187
+ # Fallback: return unmodified sample
188
+ logger.warning("Decoded graph invalid, returning sample")
189
+ return self.search_space.sample()
190
+
191
+ def _encode_operation(self, operation: str) -> int:
192
+ """
193
+ Encode operation name to integer ID.
194
+
195
+ Args:
196
+ operation: Operation name
197
+
198
+ Returns:
199
+ Operation ID (0 to vocab_size-1)
200
+ """
201
+ try:
202
+ return self.operation_vocab.index(operation)
203
+ except ValueError:
204
+ # Unknown operation -> map to first
205
+ return 0
206
+
207
+ def _encode_parameters(self, node: GraphNode) -> Tuple[float, float]:
208
+ """
209
+ Encode node hyperparameters to [0,1] range.
210
+
211
+ Args:
212
+ node: GraphNode
213
+
214
+ Returns:
215
+ (param1, param2) tuple of normalized values
216
+ """
217
+ param1, param2 = 0.0, 0.0
218
+
219
+ if node.operation == "conv2d":
220
+ # Normalize filters and kernel_size
221
+ filters = node.params.get("filters", 32)
222
+ kernel_size = node.params.get("kernel_size", 3)
223
+ param1 = np.clip(filters / 512.0, 0, 1)
224
+ param2 = np.clip(kernel_size / 7.0, 0, 1)
225
+
226
+ elif node.operation == "dense":
227
+ # Normalize units
228
+ units = node.params.get("units", 128)
229
+ param1 = np.clip(units / 1024.0, 0, 1)
230
+ param2 = 0.0
231
+
232
+ elif node.operation == "dropout":
233
+ # Normalize rate
234
+ rate = node.params.get("rate", 0.5)
235
+ param1 = np.clip(rate, 0, 1)
236
+ param2 = 0.0
237
+
238
+ elif node.operation in ["maxpool", "avgpool"]:
239
+ # Normalize pool_size
240
+ pool_size = node.params.get("pool_size", 2)
241
+ param1 = np.clip(pool_size / 4.0, 0, 1)
242
+ param2 = 0.0
243
+
244
+ return param1, param2
245
+
246
+ def _decode_parameters(self, node: GraphNode, param1: float, param2: float) -> None:
247
+ """
248
+ Decode normalized parameters and update node.
249
+
250
+ Args:
251
+ node: GraphNode to update
252
+ param1: First normalized parameter
253
+ param2: Second normalized parameter
254
+ """
255
+ if node.operation == "conv2d":
256
+ # Denormalize filters and kernel_size
257
+ filters = int(param1 * 512)
258
+ filters = max(16, min(512, filters))
259
+ kernel_size = int(param2 * 7)
260
+ kernel_size = max(1, min(7, kernel_size))
261
+ if kernel_size % 2 == 0:
262
+ kernel_size += 1 # Make odd
263
+
264
+ node.params["filters"] = filters
265
+ node.params["kernel_size"] = kernel_size
266
+
267
+ elif node.operation == "dense":
268
+ # Denormalize units
269
+ units = int(param1 * 1024)
270
+ units = max(32, min(1024, units))
271
+ node.params["units"] = units
272
+
273
+ elif node.operation == "dropout":
274
+ # Denormalize rate
275
+ rate = np.clip(param1, 0.1, 0.9)
276
+ node.params["rate"] = rate
277
+
278
+ elif node.operation in ["maxpool", "avgpool"]:
279
+ # Denormalize pool_size
280
+ pool_size = int(param1 * 4)
281
+ pool_size = max(2, min(4, pool_size))
282
+ node.params["pool_size"] = pool_size
283
+
284
+ def get_dimension(self) -> int:
285
+ """Get encoding dimension."""
286
+ return self.dim
287
+
288
+ def get_bounds(self) -> List[Tuple[float, float]]:
289
+ """
290
+ Get bounds for each dimension.
291
+
292
+ Returns:
293
+ List of (min, max) tuples
294
+ """
295
+ # All dimensions in [0, 1]
296
+ return [(0.0, 1.0) for _ in range(self.dim)]
297
+
298
+
299
+ class ContinuousArchitectureSpace:
300
+ """
301
+ Wrapper providing continuous interface to discrete architecture space.
302
+
303
+ Combines encoder with evaluation, providing a continuous objective
304
+ function for optimizers like CMA-ES and PSO.
305
+
306
+ Example:
307
+ >>> space = ContinuousArchitectureSpace(search_space, evaluator)
308
+ >>> fitness = space.evaluate(vector) # Evaluate continuous vector
309
+ """
310
+
311
+ def __init__(self, search_space: SearchSpace, evaluator: callable, max_nodes: int = 20):
312
+ """
313
+ Initialize continuous space.
314
+
315
+ Args:
316
+ search_space: Discrete architecture search space
317
+ evaluator: Function to evaluate ModelGraph -> fitness
318
+ max_nodes: Maximum nodes in encoding
319
+ """
320
+ self.search_space = search_space
321
+ self.evaluator = evaluator
322
+ self.encoder = ArchitectureEncoder(search_space, max_nodes)
323
+
324
+ self.evaluation_count = 0
325
+ self.cache: Dict[str, float] = {}
326
+
327
+ def evaluate(self, vector: np.ndarray) -> float:
328
+ """
329
+ Evaluate fitness of continuous vector.
330
+
331
+ Args:
332
+ vector: Continuous architecture encoding
333
+
334
+ Returns:
335
+ Fitness value
336
+ """
337
+ # Check cache
338
+ vector_key = vector.tobytes()
339
+ if vector_key in self.cache:
340
+ return self.cache[vector_key]
341
+
342
+ # Decode to graph
343
+ graph = self.encoder.decode(vector)
344
+
345
+ # Evaluate
346
+ fitness = self.evaluator(graph)
347
+
348
+ # Cache
349
+ self.cache[vector_key] = fitness
350
+ self.evaluation_count += 1
351
+
352
+ return fitness
353
+
354
+ def get_dimension(self) -> int:
355
+ """Get dimension of continuous space."""
356
+ return self.encoder.get_dimension()
357
+
358
+ def get_bounds(self) -> List[Tuple[float, float]]:
359
+ """Get bounds for optimization."""
360
+ return self.encoder.get_bounds()
361
+
362
+ def random_vector(self) -> np.ndarray:
363
+ """
364
+ Sample random vector in continuous space.
365
+
366
+ Returns:
367
+ Random vector
368
+ """
369
+ return np.random.rand(self.encoder.get_dimension())
370
+
371
+
372
+ def test_encoding_invertibility(search_space: SearchSpace, n_samples: int = 100) -> float:
373
+ """
374
+ Test encoding/decoding invertibility.
375
+
376
+ Measures how well encoding->decoding preserves architecture properties.
377
+
378
+ Args:
379
+ search_space: SearchSpace to test
380
+ n_samples: Number of test samples
381
+
382
+ Returns:
383
+ Average similarity score (0-1)
384
+ """
385
+ encoder = ArchitectureEncoder(search_space)
386
+
387
+ similarities = []
388
+
389
+ for _ in range(n_samples):
390
+ # Sample architecture
391
+ graph1 = search_space.sample()
392
+
393
+ # Encode and decode
394
+ vector = encoder.encode(graph1)
395
+ graph2 = encoder.decode(vector)
396
+
397
+ # Measure similarity (simple: count matching operations)
398
+ ops1 = [n.operation for n in graph1.nodes.values()]
399
+ ops2 = [n.operation for n in graph2.nodes.values()]
400
+
401
+ matches = sum(1 for o1, o2 in zip(ops1, ops2) if o1 == o2)
402
+ similarity = matches / max(len(ops1), len(ops2))
403
+
404
+ similarities.append(similarity)
405
+
406
+ avg_similarity = np.mean(similarities)
407
+ logger.info(f"Encoding invertibility: {avg_similarity:.2%}")
408
+
409
+ return avg_similarity
410
+
411
+
412
+ # Convenience functions
413
+ def encode_architecture(
414
+ graph: ModelGraph, search_space: SearchSpace, max_nodes: int = 20
415
+ ) -> np.ndarray:
416
+ """Quick encoding of single architecture."""
417
+ encoder = ArchitectureEncoder(search_space, max_nodes)
418
+ return encoder.encode(graph)
419
+
420
+
421
+ def decode_architecture(
422
+ vector: np.ndarray, search_space: SearchSpace, max_nodes: int = 20
423
+ ) -> ModelGraph:
424
+ """Quick decoding of single vector."""
425
+ encoder = ArchitectureEncoder(search_space, max_nodes)
426
+ return encoder.decode(vector)