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,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)
|