Skip to content

Commit

Permalink
Merge branch 'keras_interface' of github.com:ModECI/MDF into keras_in…
Browse files Browse the repository at this point in the history
…terface
  • Loading branch information
pgleeson committed Aug 1, 2023
2 parents 5e143f2 + 37dcbae commit 97883e0
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 0 deletions.
53 changes: 53 additions & 0 deletions examples/TensorFlow/Keras/keras_to_MDF.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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!")
3 changes: 3 additions & 0 deletions src/modeci_mdf/interfaces/keras/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Import and export code for `Keras <https://keras.io/>`_ models"""

from .importer import keras_to_mdf
199 changes: 199 additions & 0 deletions src/modeci_mdf/interfaces/keras/importer.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 97883e0

Please sign in to comment.