Tensorflow.js Error: Unknown layer: Functional

Viewed 4330

I'm loading a simple Tensorflow.js model using tf.loadLayersModel(), but the model is not building. I am using the Functional API to build the Model, but only consisting of Dense Layers. A similar error seems to arise with Lambda layers, but I only use 2 Dense Layers and functional layers are supported in Tf.js.

Full Error:

Error: Unknown layer: Functional. This may be due to one of the following reasons:
1. The layer is defined in Python, in which case it needs to be ported to TensorFlow.js or your JavaScript code.
2. The custom layer is defined in JavaScript, but is not registered properly with tf.serialization.registerClass()

The JS code that triggers it:

const http = tf.io.http

tf.loadLayersModel(http(url)).then((model) => {
    console.log('Loaded model.')
    console.log(model)
})

url's fetched content (aka the model.json file)

{"format": "layers-model", "generatedBy": "keras v2.4.0", "convertedBy": "TensorFlow.js Converter v2.0.1.post1", "modelTopology": {"keras_version": "2.4.0", "backend": "tensorflow", "model_config": {"class_name": "Functional", "config": {"name": "my_model", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 10], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": []}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 20, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 20, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_1", "inbound_nodes": [[["dense", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 10, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_2", "inbound_nodes": [[["dense_1", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense_2", 0, 0]]}}, "training_config": {"loss": "mse", "metrics": "accuracy", "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "RMSprop", "config": {"name": "RMSprop", "learning_rate": 0.001, "decay": 0.0, "rho": 0.9, "momentum": 0.0, "epsilon": 1e-07, "centered": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "dense/kernel", "shape": [10, 20], "dtype": "float32"}, {"name": "dense/bias", "shape": [20], "dtype": "float32"}, {"name": "dense_1/kernel", "shape": [20, 20], "dtype": "float32"}, {"name": "dense_1/bias", "shape": [20], "dtype": "float32"}, {"name": "dense_2/kernel", "shape": [20, 10], "dtype": "float32"}, {"name": "dense_2/bias", "shape": [10], "dtype": "float32"}]}]}

Want to reproduce the model? Here's the python code:

import keras
import keras.layers as layers
import tensorflowjs as tfjs

inputs = keras.Input(shape=(10,))
dense = layers.Dense(20, activation="relu")
x = dense(inputs)
x = layers.Dense(20, activation="relu")(x)
outputs = layers.Dense(10)(x)

# Create the model
model = keras.Model(inputs=inputs, outputs=outputs, name="my_model")

KEY = 'sampleid'
MDL = 'mymodel'

model.compile(loss='mse',metrics='accuracy')

tfjs.converters.save_keras_model(model, MDL)

NOTE: The URL is a bit verbose (it's a Firebase Storage downloadURL) and I'm not confident the IOHandler (http) can parse the weightPathPrefix perfectly. I am not sure this is the issue or even an issue, but it could create problems if it was incorrect and I don't know how to check it's calculated value.

Versions:

JS:  Tensorflow.js : 2.0.1
Py:  Tensorflowjs  : 2.0.1.post1
Py:  Keras         : 2.4.3

Update 7/29/20:

The issue seems to be in the parsing of the model weights (see NOTE). I added this example to a GitHub ticket about the tf.loadLayersModel() function earlier, which contains a lot of details about attempted solutions.

2 Answers

Python tensorflow uses Functional as a class name of functional models, but tfjs use a different name for them internally.

Try changing modelTopology.model_config.class_name in model.json to Model.

Based on what you already wrote I tried to write the model using the sequential rather than functional API:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import tensorflowjs as tfjs

# create sequential model
model = keras.Sequential(
    [
        layers.Dense(2, activation="relu", name="layer1"),
        layers.Dense(3, activation="relu", name="layer2"),
        layers.Dense(4, name="layer3"),
    ]
)
# Call model on a test input
KEY = 'sampleid'
MDL = 'mymodel'

model.compile(loss='mse',metrics='accuracy')

tfjs.converters.save_keras_model(model, MDL)

Loading the file as:

model = await tf.loadLayersModel('./mymodel/model.json');
model.summary();

seems to be working then. But I agree, the functional API should also work. Some more information maybe found here.

Related