Deploy Tensoflow model on browser

For learning purpose, I deployed my trained model using Tensorflow trained on MNIST data set and deploy it on browser. The browser then can preprocess input image and make predictions without any extra calls to server. In practice this can be used with computer vision for lowering latancy. But we don’t really want to make our model publicly available to anyone.

This is how it looks like.



First, we build a simple model. Import some common libraries. We use tfjs to export model to json at the final step.

import os

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

We use MNIST data set for this example.

(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

train_labels = train_labels[:1000]
test_labels = test_labels[:1000]

train_images = train_images[:1000].reshape(-1, 28 * 28) / 255.0
test_images = test_images[:1000].reshape(-1, 28 * 28) / 255.0

Create a simple 3 layers NN model.

# Define a simple sequential model
def create_model():
  model = tf.keras.Sequential([
    keras.layers.Dense(512, activation='relu', input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation='sigmoid'),
  ])

  model.compile(optimizer='adam',
                loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])

  return model

# Create a basic model instance
model = create_model()

# Display the model's architecture
model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 512)               401920    
                                                                 
 dropout (Dropout)           (None, 512)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                5130      
                                                                 
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
____________________________
model.fit(train_images, train_labels, epochs=5)
tfjs.converters.save_keras_model(model, 'tfmnist_json')
Epoch 1/5
32/32 [==============================] - 0s 3ms/step - loss: 1.1469 - sparse_categorical_accuracy: 0.6740
Epoch 2/5
32/32 [==============================] - 0s 3ms/step - loss: 0.4375 - sparse_categorical_accuracy: 0.8780
Epoch 3/5
32/32 [==============================] - 0s 3ms/step - loss: 0.3037 - sparse_categorical_accuracy: 0.9270
Epoch 4/5
32/32 [==============================] - 0s 3ms/step - loss: 0.2133 - sparse_categorical_accuracy: 0.9410
Epoch 5/5
32/32 [==============================] - 0s 3ms/step - loss: 0.1592 - sparse_categorical_accuracy: 0.9700

After exporting the model, we get 2 files.

!ls tfmnist_json 
model.json group1-shard1of1.bin

The model.json will be load by tfjs library and then it will automatically download group1-shard1of1.bin.

We need to host the exported files some where so browser can download them. You can checkout my model at /tfmnist_json/model.json.

Here I use ES module feature to keep it simple. No build tools, just magic.

import * as tf from "https://cdnjs.cloudflare.com/ajax/libs/tensorflow/3.18.0/tf.fesm.js";
window.tf = tf;
const MODEL_DB = "tfmnist";
const MODEL_DB_URI = `indexeddb://${MODEL_DB}`;

let model;
try {
  model = await tf.loadLayersModel(MODEL_DB_URI);
  console.debug("Loaded model locally");
} catch (err) {
  console.error("Didn't find local model.", err);
  console.debug("Loading from remote");

  model = await tf.loadLayersModel("/tfmnist_json/model.json");
  console.debug("Loaded remote model and saving to local db.");
  model.save(MODEL_DB_URI);
}

After our model loaded successfully, we can start making predictions. Remember to preprocess the image so it can match input shape of our model.

  const image = tf.browser
    .fromPixels(context.getImageData(0, 0, canvas.height, canvas.width), 1)
    .resizeBilinear([28, 28])
    .div(255);
  const input = image.reshape([-1, 28 * 28]);
  const predictions = Object.entries(await model.predict([input]).data()).sort(
    (a, b) => b[1] - a[1]
  );

You can find the full code at main.mjs

You can also achive the same result without using Tensorflow. In fact, I trained an other model using Octave and export its weights to CSV. Loaded it in to a React app and calculate all matrix multiplications with the helps of mathjs. And it works like a charm.