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/utils/export.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Export architectures to framework-specific code.
|
|
2
|
+
|
|
3
|
+
Example:
|
|
4
|
+
>>> from morphml.utils import ArchitectureExporter
|
|
5
|
+
>>>
|
|
6
|
+
>>> exporter = ArchitectureExporter()
|
|
7
|
+
>>> pytorch_code = exporter.to_pytorch(graph)
|
|
8
|
+
>>> print(pytorch_code)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from morphml.core.graph import ModelGraph
|
|
14
|
+
from morphml.logging_config import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ArchitectureExporter:
|
|
20
|
+
"""
|
|
21
|
+
Export neural architectures to executable code.
|
|
22
|
+
|
|
23
|
+
Generates framework-specific code (PyTorch, Keras) from
|
|
24
|
+
ModelGraph representations. Supports custom layer handlers.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
>>> exporter = ArchitectureExporter()
|
|
28
|
+
>>>
|
|
29
|
+
>>> # Add custom layer handler
|
|
30
|
+
>>> def custom_handler(node, shapes):
|
|
31
|
+
... return f"nn.CustomLayer({node.params})"
|
|
32
|
+
>>> exporter.add_custom_layer_handler("custom_op", custom_handler)
|
|
33
|
+
>>>
|
|
34
|
+
>>> # PyTorch
|
|
35
|
+
>>> pytorch_code = exporter.to_pytorch(graph)
|
|
36
|
+
>>> with open('model.py', 'w') as f:
|
|
37
|
+
... f.write(pytorch_code)
|
|
38
|
+
>>>
|
|
39
|
+
>>> # Keras
|
|
40
|
+
>>> keras_code = exporter.to_keras(graph)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self):
|
|
44
|
+
"""Initialize exporter with custom layer handlers."""
|
|
45
|
+
self.custom_pytorch_handlers = {}
|
|
46
|
+
self.custom_keras_handlers = {}
|
|
47
|
+
logger.debug("Initialized ArchitectureExporter")
|
|
48
|
+
|
|
49
|
+
def add_custom_layer_handler(
|
|
50
|
+
self,
|
|
51
|
+
operation_name: str,
|
|
52
|
+
pytorch_handler=None,
|
|
53
|
+
keras_handler=None,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Add custom handler for a layer type.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
operation_name: Name of the operation (e.g., "custom_conv")
|
|
60
|
+
pytorch_handler: Function(node, shapes) -> str for PyTorch code
|
|
61
|
+
keras_handler: Function(node, shapes) -> str for Keras code
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> def my_pytorch_handler(node, shapes):
|
|
65
|
+
... return f"nn.MyCustomLayer({node.params['size']})"
|
|
66
|
+
>>> exporter.add_custom_layer_handler("my_op", pytorch_handler=my_pytorch_handler)
|
|
67
|
+
"""
|
|
68
|
+
if pytorch_handler:
|
|
69
|
+
self.custom_pytorch_handlers[operation_name] = pytorch_handler
|
|
70
|
+
logger.info(f"Added custom PyTorch handler for '{operation_name}'")
|
|
71
|
+
|
|
72
|
+
if keras_handler:
|
|
73
|
+
self.custom_keras_handlers[operation_name] = keras_handler
|
|
74
|
+
logger.info(f"Added custom Keras handler for '{operation_name}'")
|
|
75
|
+
|
|
76
|
+
def remove_custom_layer_handler(self, operation_name: str):
|
|
77
|
+
"""Remove custom handler for a layer type."""
|
|
78
|
+
self.custom_pytorch_handlers.pop(operation_name, None)
|
|
79
|
+
self.custom_keras_handlers.pop(operation_name, None)
|
|
80
|
+
logger.info(f"Removed custom handlers for '{operation_name}'")
|
|
81
|
+
|
|
82
|
+
def _infer_shape(self, node, shapes):
|
|
83
|
+
"""Infer shape of node based on predecessors."""
|
|
84
|
+
if node.operation == "input":
|
|
85
|
+
return node.get_param("shape", (32, 32, 3))
|
|
86
|
+
|
|
87
|
+
pred_node = list(node.predecessors)[0]
|
|
88
|
+
pred_shape = shapes.get(pred_node.id)
|
|
89
|
+
|
|
90
|
+
if pred_shape is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
op = node.operation
|
|
94
|
+
params = node.params
|
|
95
|
+
|
|
96
|
+
if op == "conv2d":
|
|
97
|
+
filters = params.get("filters", 64)
|
|
98
|
+
kernel_size = params.get("kernel_size", 3)
|
|
99
|
+
padding = params.get("padding", "same")
|
|
100
|
+
kernel_size // 2 if padding == "same" else 0
|
|
101
|
+
|
|
102
|
+
if len(pred_shape) == 3:
|
|
103
|
+
return (filters, pred_shape[1], pred_shape[2])
|
|
104
|
+
elif len(pred_shape) == 1:
|
|
105
|
+
return (filters, pred_shape[0])
|
|
106
|
+
else:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
elif op == "dense":
|
|
110
|
+
units = params.get("units", 128)
|
|
111
|
+
return (units,)
|
|
112
|
+
|
|
113
|
+
elif op == "maxpool":
|
|
114
|
+
pool_size = params.get("pool_size", 2)
|
|
115
|
+
if len(pred_shape) == 3:
|
|
116
|
+
return (pred_shape[0], pred_shape[1] // pool_size, pred_shape[2] // pool_size)
|
|
117
|
+
elif len(pred_shape) == 1:
|
|
118
|
+
return (pred_shape[0],)
|
|
119
|
+
else:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
elif op == "avgpool":
|
|
123
|
+
pool_size = params.get("pool_size", 2)
|
|
124
|
+
if len(pred_shape) == 3:
|
|
125
|
+
return (pred_shape[0], pred_shape[1] // pool_size, pred_shape[2] // pool_size)
|
|
126
|
+
elif len(pred_shape) == 1:
|
|
127
|
+
return (pred_shape[0],)
|
|
128
|
+
else:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
elif op == "flatten":
|
|
132
|
+
return (int(np.prod(pred_shape)),)
|
|
133
|
+
|
|
134
|
+
elif op in ["relu", "sigmoid", "tanh", "softmax"]:
|
|
135
|
+
return pred_shape
|
|
136
|
+
|
|
137
|
+
elif op == "dropout":
|
|
138
|
+
return pred_shape
|
|
139
|
+
|
|
140
|
+
elif op == "batchnorm":
|
|
141
|
+
return pred_shape
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def to_pytorch(self, graph: ModelGraph, class_name: str = "GeneratedModel") -> str:
|
|
147
|
+
"""
|
|
148
|
+
Generate PyTorch code.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
graph: Architecture to export
|
|
152
|
+
class_name: Name for generated class
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
PyTorch code as string
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
>>> code = exporter.to_pytorch(graph, 'MyModel')
|
|
159
|
+
>>> exec(code) # Defines MyModel class
|
|
160
|
+
"""
|
|
161
|
+
code = []
|
|
162
|
+
code.append("import torch")
|
|
163
|
+
code.append("import torch.nn as nn")
|
|
164
|
+
code.append("import torch.nn.functional as F")
|
|
165
|
+
code.append("")
|
|
166
|
+
code.append("")
|
|
167
|
+
code.append(f"class {class_name}(nn.Module):")
|
|
168
|
+
code.append(" def __init__(self):")
|
|
169
|
+
code.append(" super().__init__()")
|
|
170
|
+
code.append("")
|
|
171
|
+
|
|
172
|
+
# Get topological order
|
|
173
|
+
try:
|
|
174
|
+
sorted_nodes = graph.topological_sort()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Failed to sort graph: {e}")
|
|
177
|
+
sorted_nodes = list(graph.nodes.values())
|
|
178
|
+
|
|
179
|
+
shapes = {}
|
|
180
|
+
for node in sorted_nodes:
|
|
181
|
+
shapes[node.id] = self._infer_shape(node, shapes)
|
|
182
|
+
|
|
183
|
+
# Generate layers
|
|
184
|
+
layer_names = {}
|
|
185
|
+
for i, node in enumerate(sorted_nodes):
|
|
186
|
+
layer_name = f"layer_{i}"
|
|
187
|
+
layer_names[node.id] = layer_name
|
|
188
|
+
|
|
189
|
+
op = node.operation
|
|
190
|
+
params = node.params
|
|
191
|
+
|
|
192
|
+
if op == "conv2d":
|
|
193
|
+
filters = params.get("filters", 64)
|
|
194
|
+
kernel_size = params.get("kernel_size", 3)
|
|
195
|
+
padding = params.get("padding", "same")
|
|
196
|
+
padding_val = kernel_size // 2 if padding == "same" else 0
|
|
197
|
+
|
|
198
|
+
# Try to infer input channels
|
|
199
|
+
in_channels = "?"
|
|
200
|
+
if node.predecessors:
|
|
201
|
+
pred_node = list(node.predecessors)[0]
|
|
202
|
+
pred_shape = shapes.get(pred_node.id)
|
|
203
|
+
if pred_shape and len(pred_shape) >= 3:
|
|
204
|
+
in_channels = pred_shape[0]
|
|
205
|
+
|
|
206
|
+
code.append(
|
|
207
|
+
f" self.{layer_name} = nn.Conv2d("
|
|
208
|
+
f"in_channels={in_channels}, out_channels={filters}, "
|
|
209
|
+
f"kernel_size={kernel_size}, padding={padding_val})"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
elif op == "dense":
|
|
213
|
+
units = params.get("units", 128)
|
|
214
|
+
|
|
215
|
+
# Try to infer input features
|
|
216
|
+
in_features = "?"
|
|
217
|
+
if node.predecessors:
|
|
218
|
+
pred_node = list(node.predecessors)[0]
|
|
219
|
+
pred_shape = shapes.get(pred_node.id)
|
|
220
|
+
if pred_shape:
|
|
221
|
+
if len(pred_shape) == 1:
|
|
222
|
+
in_features = pred_shape[0]
|
|
223
|
+
else:
|
|
224
|
+
# Need flattening
|
|
225
|
+
in_features = int(np.prod(pred_shape))
|
|
226
|
+
|
|
227
|
+
code.append(
|
|
228
|
+
f" self.{layer_name} = nn.Linear(in_features={in_features}, out_features={units})"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
elif op == "maxpool":
|
|
232
|
+
pool_size = params.get("pool_size", 2)
|
|
233
|
+
code.append(f" self.{layer_name} = nn.MaxPool2d(kernel_size={pool_size})")
|
|
234
|
+
|
|
235
|
+
elif op == "avgpool":
|
|
236
|
+
pool_size = params.get("pool_size", 2)
|
|
237
|
+
code.append(f" self.{layer_name} = nn.AvgPool2d(kernel_size={pool_size})")
|
|
238
|
+
|
|
239
|
+
elif op == "dropout":
|
|
240
|
+
rate = params.get("rate", 0.5)
|
|
241
|
+
code.append(f" self.{layer_name} = nn.Dropout(p={rate})")
|
|
242
|
+
|
|
243
|
+
elif op == "batchnorm":
|
|
244
|
+
# Try to infer number of features
|
|
245
|
+
num_features = "?"
|
|
246
|
+
if node.predecessors:
|
|
247
|
+
pred_node = list(node.predecessors)[0]
|
|
248
|
+
pred_shape = shapes.get(pred_node.id)
|
|
249
|
+
if pred_shape and len(pred_shape) >= 1:
|
|
250
|
+
num_features = pred_shape[0] # First dimension is channels
|
|
251
|
+
|
|
252
|
+
code.append(
|
|
253
|
+
f" self.{layer_name} = nn.BatchNorm2d(num_features={num_features})"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
elif op == "flatten":
|
|
257
|
+
code.append(f" self.{layer_name} = nn.Flatten()")
|
|
258
|
+
|
|
259
|
+
elif op in ["relu", "sigmoid", "tanh", "softmax"]:
|
|
260
|
+
# Functional, no layer definition needed
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
elif op == "input":
|
|
264
|
+
# No layer needed
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
else:
|
|
268
|
+
# Check for custom handler
|
|
269
|
+
if op in self.custom_pytorch_handlers:
|
|
270
|
+
handler = self.custom_pytorch_handlers[op]
|
|
271
|
+
custom_code = handler(node, shapes)
|
|
272
|
+
code.append(f" self.{layer_name} = {custom_code}")
|
|
273
|
+
else:
|
|
274
|
+
code.append(f" # {layer_name}: {op} (custom - no handler defined)")
|
|
275
|
+
|
|
276
|
+
code.append("")
|
|
277
|
+
code.append(" def forward(self, x):")
|
|
278
|
+
|
|
279
|
+
# Generate forward pass
|
|
280
|
+
for i, node in enumerate(sorted_nodes):
|
|
281
|
+
layer_name = layer_names[node.id]
|
|
282
|
+
op = node.operation
|
|
283
|
+
|
|
284
|
+
if op == "input":
|
|
285
|
+
code.append(" # x = input")
|
|
286
|
+
elif op == "relu":
|
|
287
|
+
code.append(" x = F.relu(x)")
|
|
288
|
+
elif op == "sigmoid":
|
|
289
|
+
code.append(" x = torch.sigmoid(x)")
|
|
290
|
+
elif op == "tanh":
|
|
291
|
+
code.append(" x = torch.tanh(x)")
|
|
292
|
+
elif op == "softmax":
|
|
293
|
+
code.append(" x = F.softmax(x, dim=1)")
|
|
294
|
+
else:
|
|
295
|
+
code.append(f" x = self.{layer_name}(x)")
|
|
296
|
+
|
|
297
|
+
code.append(" return x")
|
|
298
|
+
code.append("")
|
|
299
|
+
code.append("")
|
|
300
|
+
code.append("# Usage:")
|
|
301
|
+
code.append(f"# model = {class_name}()")
|
|
302
|
+
code.append("# output = model(input_tensor)")
|
|
303
|
+
|
|
304
|
+
return "\n".join(code)
|
|
305
|
+
|
|
306
|
+
def to_keras(self, graph: ModelGraph, model_name: str = "generated_model") -> str:
|
|
307
|
+
"""
|
|
308
|
+
Generate Keras/TensorFlow code.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
graph: Architecture to export
|
|
312
|
+
model_name: Name for generated model
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Keras code as string
|
|
316
|
+
"""
|
|
317
|
+
code = []
|
|
318
|
+
code.append("import tensorflow as tf")
|
|
319
|
+
code.append("from tensorflow import keras")
|
|
320
|
+
code.append("from tensorflow.keras import layers")
|
|
321
|
+
code.append("")
|
|
322
|
+
code.append("")
|
|
323
|
+
code.append(f"def {model_name}():")
|
|
324
|
+
code.append(' """Generated Keras model."""')
|
|
325
|
+
|
|
326
|
+
# Get topological order
|
|
327
|
+
try:
|
|
328
|
+
sorted_nodes = graph.topological_sort()
|
|
329
|
+
except Exception:
|
|
330
|
+
sorted_nodes = list(graph.nodes.values())
|
|
331
|
+
|
|
332
|
+
# Find input shape
|
|
333
|
+
input_shape = None
|
|
334
|
+
for node in sorted_nodes:
|
|
335
|
+
if node.operation == "input":
|
|
336
|
+
input_shape = node.get_param("shape", (32, 32, 3))
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
if input_shape is None:
|
|
340
|
+
input_shape = (32, 32, 3)
|
|
341
|
+
|
|
342
|
+
code.append(f" inputs = keras.Input(shape={input_shape})")
|
|
343
|
+
code.append(" x = inputs")
|
|
344
|
+
code.append("")
|
|
345
|
+
|
|
346
|
+
# Generate layers
|
|
347
|
+
for node in sorted_nodes:
|
|
348
|
+
op = node.operation
|
|
349
|
+
params = node.params
|
|
350
|
+
|
|
351
|
+
if op == "input":
|
|
352
|
+
continue
|
|
353
|
+
elif op == "conv2d":
|
|
354
|
+
filters = params.get("filters", 64)
|
|
355
|
+
kernel_size = params.get("kernel_size", 3)
|
|
356
|
+
padding = params.get("padding", "same")
|
|
357
|
+
code.append(
|
|
358
|
+
f" x = layers.Conv2D({filters}, {kernel_size}, " f"padding='{padding}')(x)"
|
|
359
|
+
)
|
|
360
|
+
elif op == "dense":
|
|
361
|
+
units = params.get("units", 128)
|
|
362
|
+
code.append(f" x = layers.Dense({units})(x)")
|
|
363
|
+
elif op == "maxpool":
|
|
364
|
+
pool_size = params.get("pool_size", 2)
|
|
365
|
+
code.append(f" x = layers.MaxPooling2D({pool_size})(x)")
|
|
366
|
+
elif op == "avgpool":
|
|
367
|
+
pool_size = params.get("pool_size", 2)
|
|
368
|
+
code.append(f" x = layers.AveragePooling2D({pool_size})(x)")
|
|
369
|
+
elif op == "dropout":
|
|
370
|
+
rate = params.get("rate", 0.5)
|
|
371
|
+
code.append(f" x = layers.Dropout({rate})(x)")
|
|
372
|
+
elif op == "batchnorm":
|
|
373
|
+
code.append(" x = layers.BatchNormalization()(x)")
|
|
374
|
+
elif op == "flatten":
|
|
375
|
+
code.append(" x = layers.Flatten()(x)")
|
|
376
|
+
elif op == "relu":
|
|
377
|
+
code.append(" x = layers.Activation('relu')(x)")
|
|
378
|
+
elif op == "sigmoid":
|
|
379
|
+
code.append(" x = layers.Activation('sigmoid')(x)")
|
|
380
|
+
elif op == "tanh":
|
|
381
|
+
code.append(" x = layers.Activation('tanh')(x)")
|
|
382
|
+
elif op == "softmax":
|
|
383
|
+
code.append(" x = layers.Activation('softmax')(x)")
|
|
384
|
+
|
|
385
|
+
code.append("")
|
|
386
|
+
code.append(" model = keras.Model(inputs=inputs, outputs=x)")
|
|
387
|
+
code.append(" return model")
|
|
388
|
+
code.append("")
|
|
389
|
+
code.append("")
|
|
390
|
+
code.append("# Usage:")
|
|
391
|
+
code.append(f"# model = {model_name}()")
|
|
392
|
+
code.append("# model.compile(optimizer='adam', loss='categorical_crossentropy')")
|
|
393
|
+
code.append("# model.fit(x_train, y_train, epochs=10)")
|
|
394
|
+
|
|
395
|
+
return "\n".join(code)
|
|
396
|
+
|
|
397
|
+
def to_json(self, graph: ModelGraph) -> str:
|
|
398
|
+
"""
|
|
399
|
+
Export architecture as JSON.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
graph: Architecture to export
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
JSON string
|
|
406
|
+
"""
|
|
407
|
+
return graph.to_json()
|