From 1dfde96012fbed343752d943833ce52afb5f3de1 Mon Sep 17 00:00:00 2001 From: Onoyiza Date: Fri, 28 Jul 2023 16:40:04 +0100 Subject: [PATCH 1/3] Add keras_to_mdf helper function --- src/modeci_mdf/interfaces/keras/__init__.py | 3 + src/modeci_mdf/interfaces/keras/importer.py | 199 ++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/modeci_mdf/interfaces/keras/__init__.py create mode 100644 src/modeci_mdf/interfaces/keras/importer.py diff --git a/src/modeci_mdf/interfaces/keras/__init__.py b/src/modeci_mdf/interfaces/keras/__init__.py new file mode 100644 index 000000000..036b31943 --- /dev/null +++ b/src/modeci_mdf/interfaces/keras/__init__.py @@ -0,0 +1,3 @@ +"""Import and export code for `Keras `_ models""" + +from .importer import keras_to_mdf diff --git a/src/modeci_mdf/interfaces/keras/importer.py b/src/modeci_mdf/interfaces/keras/importer.py new file mode 100644 index 000000000..1266f72f6 --- /dev/null +++ b/src/modeci_mdf/interfaces/keras/importer.py @@ -0,0 +1,199 @@ +from inspect import Parameter +from pyclbr import Function +import random + +from typing import Union, Dict, Any, Tuple, List, Callable +from xml.dom import Node + +import tensorflow as tf +from tensorflow import keras +from tensorflow.keras.layers import Dense, Flatten, Input + +from modeci_mdf.execution_engine import EvaluableGraph +from modeci_mdf.mdf import * +from modeci_mdf.utils import simple_connect + +import numpy as np + + +def get_weights_and_activation(layers, model): + + params = {} + activations = [] + for layer in layers: + n = {} + lyr = model.get_layer(layer) + if lyr.weights != []: + wgts, bias = lyr.weights + n["weights"], n["bias"] = wgts.numpy(), bias.numpy() + params[layer] = n + activations.append(str(lyr.activation).split()[1]) + return params, activations + + +def init_model_with_graph(model_id, graph_id): + mod = Model(id=model_id) + mod_graph = Graph(id=graph_id) + mod.graphs.append(mod_graph) + return mod, mod_graph + + +def create_input_node(node_id, value): # , reshape=False): + # if reshape == True: + # value = np.array(value).flatten() + # else: + # value = np.array(value) + input_node = Node(id=node_id) + input_node.parameters.append( + Parameter(id=f"{node_id}_in", value=np.array(value).tolist()) + ) + input_node.output_ports.append( + OutputPort(id=f"{node_id}_out", value=f"{node_id}_in") + ) + return input_node + + +def create_flatten_node(node_id): + flatten_node = Node(id=node_id) + flatten_node.input_ports.append(InputPort(id=f"{node_id}_in")) + + # args for onnx::reshape function + args = {"data": f"{node_id}_in", "shape": np.array([-1], dtype=np.int64)} + + # application of the onnx::reshape function to the input + flatten_node.functions.append( + Function(id="onnx_Reshape", function="onnx::Reshape", args=args) + ) + flatten_node.output_ports.append( + OutputPort(id=f"{node_id}_out", value="onnx_Reshape") + ) + return flatten_node + + +def create_dense_node(node_id, weights, bias): + node = Node(id=node_id) + node.input_ports.append(InputPort(id=f"{node_id}_in")) + # Weights + node.parameters.append(Parameter(id="wgts", value=weights)) + # bias + node.parameters.append(Parameter(id="bias", value=bias)) + # Value Weights + bias + node.parameters.append( + Parameter(id="Output", value=f"({node_id}_in @ wgts) + bias") + ) + + node.output_ports.append(Parameter(id=f"{node_id}_out", value="Output")) + return node + + +def create_activation_node(node_id, activation_name): + activation = Node(id=node_id) + activation.input_ports.append(InputPort(id=f"{node_id}_in")) + + # Functionality of relu + if activation_name == "relu": + # Value of relu function + relu_ = f"({node_id}_in * ({node_id}_in > 0 ))" + activation.parameters.append(Parameter(id="Output", value=relu_)) + + # Functionality of sigmoid + elif activation_name == "sigmoid": + # args for exponential function + args = {"variable0": "pos_in", "scale": 1, "rate": 1, "bias": 0, "offset": 0} + + # this will make x => x + activation.parameters.append(Parameter(id="pos_in", value=f"{node_id}_in")) + # value of e^x + activation.functions.append( + Function(id="exp", function="exponential", args=args) + ) + # value of sigmoid + activation.functions.append(Function(id="Output", value="1 / (1 + exp)")) + + elif activation_name == "softmax": + # args for exponential function + args = { + "variable0": f"{node_id}_in", + "scale": 1, + "rate": 1, + "bias": 0, + "offset": 0, + } + + # exponential of each value + activation.functions.append( + Function(id="exp", function="exponential", args=args) + ) + # sum of all exponentials + activation.functions.append(Function(id="exp_sum", value="sum(exp)")) + # normalizing results + activation.functions.append(Function(id="Output", value="exp / exp_sum")) + + activation.output_ports.append(OutputPort(id=f"{node_id}_out", value="Output")) + return activation + + +def keras_to_mdf( + model: Union[Callable, tf.keras.Model, tf.Module], + args: Union[None, np.ndarray, tf.Tensor] = None, + # trace: bool = False, +) -> Union[Model, Graph]: + r""" + Convert a Keras model to an MDF model. + Args: + model: The model to translate into MDF. + args: The input arguments for this model. + + Returns: + The translated MDF model + """ + + # create mdf model and graph + mdf_model, mdf_model_graph = init_model_with_graph( + f"{model.name}".capitalize(), f"{model.name}_Graph".capitalize() + ) + + # create the input node + input_node = create_input_node("Input_0", args) + mdf_model_graph.nodes.append(input_node) + + # create other nodes needed for mdf graph using the type of layers from the keras model + # get layers in the keras model + layers = [] + for layer in model.layers: + layers.append(layer.name) + + # get the parameters and activation in each dense layer + params, activations = get_weights_and_activation(layers, model) + + node_count = 1 + for layer in layers: + if layer == "flatten": + # input_node = create_input_node(f"{layer.capitalize()}_{node_count}", args, reshape=True) + flatten_node = create_flatten_node(f"{layer.capitalize()}_{node_count}") + mdf_model_graph.nodes.append(flatten_node) + node_count += 1 + + elif "dense" in layer: + weights = params[f"{layer}"]["weights"] + bias = params[f"{layer}"]["bias"] + dense_node = create_dense_node( + f"{layer[:5].capitalize()}_{node_count}", weights, bias + ) + mdf_model_graph.nodes.append(dense_node) + node_count += 1 + + activation = str(model.get_layer(layer).activation).split()[1] + if activation != "linear": + activation_node = create_activation_node( + f"{activation.capitalize()}_{node_count}", activation + ) + mdf_model_graph.nodes.append(activation_node) + node_count += 1 + + for i in range(len(mdf_model_graph.nodes) - 1): + e1 = simple_connect( + mdf_model_graph.nodes[i], mdf_model_graph.nodes[i + 1], mdf_model_graph + ) + + return mdf_model, params From 1c8fa88a02e38973ae9acfcc851cf54d9ac06d96 Mon Sep 17 00:00:00 2001 From: Onoyiza Date: Mon, 31 Jul 2023 18:13:16 +0100 Subject: [PATCH 2/3] Add Script to call keras_to_mdf function --- examples/TensorFlow/Keras/keras_to_MDF.py | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/TensorFlow/Keras/keras_to_MDF.py diff --git a/examples/TensorFlow/Keras/keras_to_MDF.py b/examples/TensorFlow/Keras/keras_to_MDF.py new file mode 100644 index 000000000..1aa3a8905 --- /dev/null +++ b/examples/TensorFlow/Keras/keras_to_MDF.py @@ -0,0 +1,55 @@ +import numpy as np +import tensorflow as tf + +from modeci_mdf.interfaces.keras import keras_to_mdf +from modelspec.utils import _val_info +from modeci_mdf.execution_engine import EvaluableGraph + +# load the keras model +model = tf.keras.models.load_model("kr_N_model.h5") + +# get one of the test images from the mnnist test dataset +_, (x_test, y_test) = tf.keras.datasets.mnist.load_data() +x_test = tf.keras.utils.normalize(x_test, axis=1) +one_x_test = x_test[0, :, :] + +# reshape the dimension of the test image to 2-D image +one_x_test = one_x_test.reshape(1, 28, 28) + +# get the output of predicting with the keras model +output = model.predict(one_x_test) + +# Convert the Keras model to MDF +mdf_model, params_dict = keras_to_mdf( + model=model, + args=one_x_test +) + +# Get mdf graph +mdf_graph = mdf_model.graphs[0] + +# visualize mdf graph-image +mdf_model.to_graph_image( + engine="dot", + output_format="png", + view_on_render=False, + level=1, + filename_root="keras_to_MDF", + is_horizontal=True, + solid_color=True +) +from IPython.display import Image +Image(filename="Keras_to_MDF.png") + +# Evaluate the model via the MDF scheduler +eg = EvaluableGraph(graph=mdf_graph, verbose=False) +eg.evaluate() +output_mdf = eg.output_enodes[0].get_output() +print("Evaluated the graph in Keras, output: %s" % (_val_info(output_mdf))) + +# Assert that the results are the same for Keras and MDF +assert np.allclose( + output, + output_mdf, +) +print("Passed all comparison tests!") \ No newline at end of file From b5c3cc8bed67d5bd0d51d4874dca2c4bb481dcb2 Mon Sep 17 00:00:00 2001 From: Onoyiza Date: Mon, 31 Jul 2023 18:17:37 +0100 Subject: [PATCH 3/3] pre-commit script for keras_to_mdf function call --- examples/TensorFlow/Keras/keras_to_MDF.py | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/examples/TensorFlow/Keras/keras_to_MDF.py b/examples/TensorFlow/Keras/keras_to_MDF.py index 1aa3a8905..10d00d327 100644 --- a/examples/TensorFlow/Keras/keras_to_MDF.py +++ b/examples/TensorFlow/Keras/keras_to_MDF.py @@ -6,7 +6,7 @@ from modeci_mdf.execution_engine import EvaluableGraph # load the keras model -model = tf.keras.models.load_model("kr_N_model.h5") +model = tf.keras.models.load_model("kr_N_model.h5") # get one of the test images from the mnnist test dataset _, (x_test, y_test) = tf.keras.datasets.mnist.load_data() @@ -20,25 +20,23 @@ output = model.predict(one_x_test) # Convert the Keras model to MDF -mdf_model, params_dict = keras_to_mdf( - model=model, - args=one_x_test -) +mdf_model, params_dict = keras_to_mdf(model=model, args=one_x_test) # Get mdf graph mdf_graph = mdf_model.graphs[0] # visualize mdf graph-image mdf_model.to_graph_image( - engine="dot", - output_format="png", - view_on_render=False, - level=1, - filename_root="keras_to_MDF", - is_horizontal=True, - solid_color=True + engine="dot", + output_format="png", + view_on_render=False, + level=1, + filename_root="keras_to_MDF", + is_horizontal=True, + solid_color=True, ) from IPython.display import Image + Image(filename="Keras_to_MDF.png") # Evaluate the model via the MDF scheduler @@ -52,4 +50,4 @@ output, output_mdf, ) -print("Passed all comparison tests!") \ No newline at end of file +print("Passed all comparison tests!")