Skip to content

Instantly share code, notes, and snippets.

@AbhimanyuAryan
Forked from zcaceres/node-python-fastai.md
Created February 24, 2019 12:08
Show Gist options
  • Save AbhimanyuAryan/1a3e4db4f3403b1fe03afb2fc3839c04 to your computer and use it in GitHub Desktop.
Save AbhimanyuAryan/1a3e4db4f3403b1fe03afb2fc3839c04 to your computer and use it in GitHub Desktop.
Starter code to use NodeJS with a Python layer for the model.

Deploying a Deep Learning Image Classification Model with NodeJS, Python, and Fastai

TL|DR: Use this to easily deploy a FastAI Python model using NodeJS.

You've processed your data and trained your model and now it's time to move it to the cloud.

If you've used a Python-based framework like fastai to build your model, there are several excellent solutions for deployment like Django or Starlette. But many web devs prefer to work in NodeJS, especially if your model is only part of a broader application.

My friend Navjot pointed out that NodeJS and Python could run together if we could send remote procedure calls from NodeJS to Python.

I extended his shared NodeJS/Python environment into a simple, minimal boilerplate for a NodeJS deployment of an image classification model. The deep learning model was made with the fastai library. Although fastai and our model were built in Python, we can expose the model to users from NodeJS.

Here's how.

How does it work?

The deployment works using three main modules: server.js, PythonConnector.js, and PythonServer.py.

Our Express server in server.js provides a standard, RESTful API to the outside world. Nothing new here!

But our Express server also references our PythonConnector, which serves as a middle man between the worlds of NodeJS and Python.

const PythonConnector = require('./PythonConnector.js');

Specifically, on startup PythonConnector spawns a python3 process that sets up our PythonServer. It also negotiates and maintains a socket connection to PythonServer through zerorpc.

class PythonConnector {
    static server() {
        if (!PythonConnector.connected) {
            console.log('PythonConnector – making a new connection to the python layer');
            PythonConnector.zerorpcProcess = spawn('python3', ['-u', path.join(__dirname, 'PythonServer.py')]);
            PythonConnector.zerorpcProcess.stdout.on('data', function(data) {
                console.info('python:', data.toString());
            });
            PythonConnector.zerorpcProcess.stderr.on('data', function(data) {
                console.error('python:', data.toString());
            });
            PythonConnector.zerorpc = new zerorpc.Client({'timeout': TIMEOUT, 'heartbeatInterval': TIMEOUT*1000});
            PythonConnector.zerorpc.connect('tcp://' + IP + ':' + PORT);
            PythonConnector.connected = true;
        }
        return PythonConnector.zerorpc;
    }
  ...
}

When the client makes a request to a given endpoint from our server.js, such as /predict, our Express server commands the PythonConnector middleman to invoke a function in our Python environment via zerorpc.

Our Python environment returns some JSON, which can be processed and forwarded along to our client.

Here's how that looks when a user sends an image for classification.

First our server gets the request...

...

// Our prediction endpoint (Receives an image as req.file)
app.post('/predict', upload.single('img'), async function (req, res) {
    const { path } = req.file
    try {
        const prediction = await PythonConnector.invoke('predict_from_img', path);
        res.json(prediction);
    }
    catch (e) {
        console.log(`error in ${req.url}`, e);
        res.sendStatus(404);
    }

    // delete the uploaded file (regardless whether prediction successful or not)
    fs.unlink(path, (err) => {
        if (err) console.error(err)
        console.log('Cleaned up', path)
    })
})

...

... and calls out to PythonConnector...

static async invoke(method, ...args) {
    try {
        const zerorpc = PythonConnector.server();
        return await Utils.promisify(zerorpc.invoke, zerorpc, method, ...args);
    }
    catch (e) {
        return Promise.reject(e)
    }
}

... which makes a prediction from our fastai model:

from model_fastai import FastaiImageClassifier

class PythonServer(object):
    def predict_from_img(self, img_path):
        model = FastaiImageClassifier()
        return model.predict(img_path)
...

You can use the boilerplate out of the box. The included model classifies black bears, teddy bears, and grizzly bears.

If you'd like to use the boilerplate for your own project, you can customize the model with instructions available on Github.

Thanks for reading!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment