Machine Learning In Node.js With TensorFlow.js
Tags: node, machinelearning, tensorflow, javascript
Published: 2018-08-09 16:41:32

---
title: "Machine Learning In Node.js With TensorFlow.js"
published: true
description: "Using TensorFlow.js for Machine Learning on Node.js. Use MobileNet models for image classification and visual recognition on local image files."
keywords: "node.js machine learning tensorflow javascript"
tags: [node.js, machine learning, tensorflow, javascript]
canonical_url: http://jamesthom.as/blog/2018/08/07/machine-learning-in-node-dot-js-with-tensorflow-dot-js/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/wjpl17jrjdv6b0oru14h.png
---

[TensorFlow.js](https://js.tensorflow.org/) is a new version of the popular open-source library which brings deep learning to JavaScript. Developers can now define, train, and run machine learning models using the [high-level library API](https://js.tensorflow.org/api/0.12.0/).

[Pre-trained models](https://github.com/tensorflow/tfjs-models/) mean developers can now easily perform complex tasks like [visual recognition](https://emojiscavengerhunt.withgoogle.com/), [generating music](https://magenta.tensorflow.org/demos/performance_rnn/index.html#2|2,0,1,0,1,1,0,1,0,1,0,1|1,1,1,1,1,1,1,1,1,1,1,1|1,1,1,1,1,1,1,1,1,1,1,1|false) or [detecting human poses](https://storage.googleapis.com/tfjs-models/demos/posenet/camera.html) with just a few lines of JavaScript.

Having started as a front-end library for web browsers, recent updates added [experimental support](https://github.com/tensorflow/tfjs-node) for Node.js. This allows TensorFlow.js to be used in backend JavaScript applications without having to use Python.

*Reading about the library, I wanted to test it out with a simple task...* 🧐

> **Use TensorFlow.js to perform visual recognition on images using JavaScript from Node.js**

Unfortunately, most of the [documentation](https://js.tensorflow.org/#getting-started) and [example code](https://js.tensorflow.org/tutorials/webcam-transfer-learning.html) provided uses the library in a browser. [Project utilities](https://github.com/tensorflow/tfjs-models/tree/master/mobilenet) provided to simplify loading and using pre-trained models have not yet been extended with Node.js support. Getting this working did end up with me spending a lot of time reading the Typescript source files for the library. 👎

However, after a few days' hacking, I managed to get [this completed](https://gist.github.com/jthomas/145610bdeda2638d94fab9a397eb1f1d)! Hurrah! 🤩

*Before we dive into the code, let's start with an overview of the different TensorFlow libraries.*

## TensorFlow

[TensorFlow](https://www.tensorflow.org/) is an open-source software library for machine learning applications. TensorFlow can be used to implement neural networks and other deep learning algorithms.

Released by Google in November 2015, TensorFlow was originally a [Python library](https://www.tensorflow.org/api_docs/python/). It used either CPU or GPU-based computation for training and evaluating machine learning models. The library was initially designed to run on high-performance servers with expensive GPUs. 

Recent updates have extended the software to run in resource-constrained environments like mobile devices and web browsers.

### TensorFlow Lite

[Tensorflow Lite](https://www.tensorflow.org/mobile/tflite/), a lightweight version of the library for mobile and embedded devices, was released in May 2017. This was accompanied by a new series of pre-trained deep learning models for vision recognition tasks, called [MobileNet](https://ai.googleblog.com/2017/06/mobilenets-open-source-models-for.html). MobileNet models were designed to work efficiently in resource-constrained environments like mobile devices.

### TensorFlow.js

Following Tensorflow Lite, [TensorFlow.js](https://medium.com/tensorflow/introducing-tensorflow-js-machine-learning-in-javascript-bf3eab376db) was announced in March 2018. This version of the library was designed to run in the browser, building on an earlier project called [deeplearn.js](https://twitter.com/deeplearnjs). WebGL provides GPU access to the library. Developers use a JavaScript API to train, load and run models.

TensorFlow.js was recently extended to run on Node.js, using an [extension library](https://github.com/tensorflow/tfjs-node) called `tfjs-node`.

*The Node.js extension is an alpha release and still under active development.*

#### Importing Existing Models Into TensorFlow.js

Existing TensorFlow and Keras models can be executed using the TensorFlow.js library. Models need converting to a new format [using this tool](https://github.com/tensorflow/tfjs-converter) before execution. Pre-trained and converted models for image classification, pose detection and k-nearest neighbours are [available on Github](https://github.com/tensorflow/tfjs-models).

## Using TensorFlow.js in Node.js

### Installing TensorFlow Libraries

TensorFlow.js can be installed from the [NPM registry](https://www.npmjs.com/). 

- `@tensorflow/tfjs` - [Core TensorFlow.js library](https://www.npmjs.com/package/@tensorflow/tfjs)
- `@tensorflow/tfjs-node` - [TensorFlow.js Node.js extension](https://www.npmjs.com/package/@tensorflow/tfjs-node)
- `@tensorflow/tfjs-node-gpu` - [TensorFlow.js Node.js extension with GPU support](https://www.npmjs.com/package/@tensorflow/tfjs-node-gpu)

```
npm install @tensorflow/tfjs @tensorflow/tfjs-node
// or...
npm install @tensorflow/tfjs @tensorflow/tfjs-node-gpu
```

Both Node.js extensions use native dependencies which will be compiled on demand.

### Loading TensorFlow Libraries

TensorFlow's [JavaScript API](https://js.tensorflow.org/api/0.12.0/) is exposed from the core library. Extension modules to enable Node.js support do not expose additional APIs.

```javascript
const tf = require('@tensorflow/tfjs')
// Load the binding (CPU computation)
require('@tensorflow/tfjs-node')
// Or load the binding (GPU computation)
require('@tensorflow/tfjs-node-gpu')
```

### Loading TensorFlow Models

TensorFlow.js provides an [NPM library](https://github.com/tensorflow/tfjs-models) (`tfjs-models`) to ease loading pre-trained & converted models for [image classification](https://github.com/tensorflow/tfjs-models/tree/master/mobilenet), [pose detection](https://github.com/tensorflow/tfjs-models/tree/master/posenet) and [k-nearest neighbours](https://github.com/tensorflow/tfjs-models/tree/master/knn-classifier). 

The [MobileNet model](https://github.com/tensorflow/tfjs-models/tree/master/mobilenet) used for image classification is a deep neural network trained to [identify 1000 different classes](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/imagenet_classes.ts). 

In the project's README, the [following example code](https://github.com/tensorflow/tfjs-models/tree/master/mobilenet#via-npm) is used to load the model.

```javascript
import * as mobilenet from '@tensorflow-models/mobilenet';

// Load the model.
const model = await mobilenet.load();
```

**One of the first challenges I encountered was that this does not work on Node.js.**

```
Error: browserHTTPRequest is not supported outside the web browser.
```

Looking at the [source code](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L27), the `mobilenet` library is a wrapper around the underlying `tf.Model` class. When the `load()` method is called, it automatically downloads the correct model files from an external HTTP address and instantiates the TensorFlow model. 

The Node.js extension does not yet support HTTP requests to dynamically retrieve models. Instead, models must be manually loaded from the filesystem.

*After reading the source code for the library, I managed to create a work-around...*

#### Loading Models From a Filesystem

Rather than calling the module's `load` method, if the `MobileNet` class is created manually, the auto-generated `path` variable which contains the HTTP address of the model can be overwritten with a local filesystem path. Having done this, calling the `load` method on the class instance will trigger the [filesystem loader class](https://js.tensorflow.org/tutorials/model-save-load.html), rather than trying to use the browser-based HTTP loader.

```javascript
const path = "mobilenet/model.json"
const mn = new mobilenet.MobileNet(1, 1);
mn.path = `file://${path}`
await mn.load()
```

**Awesome, it works!**

*But how where do the models files come from?*

### MobileNet Models

Models for TensorFlow.js consist of two file types, a model configuration file stored in JSON and model weights in a binary format. Model weights are often sharded into multiple files for better caching by browsers.

Looking at the [automatic loading code](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L68-L76) for MobileNet models, models configuration and weight shards are retrieved from a public storage bucket at this address. 

```
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v${version}_${alpha}_${size}/
```

The template parameters in the URL refer to the model versions listed [here](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md#pre-trained-models). Classification accuracy results for each version are also shown on that page.

*According to the [source code](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L36), only MobileNet v1 models can be loaded using the `tensorflow-models/mobilenet` library.*

The HTTP retrieval code loads the `model.json` file from this location and then recursively fetches all referenced model weights shards. These files are in the format `groupX-shard1of1`.

#### Downloading Models Manually

Saving all model files to a filesystem can be achieved by retrieving the model configuration file, parsing out the referenced weight files and downloading each weight file manually.

**I want to use the MobileNet V1 Module with 1.0 alpha value and image size of 224 pixels.** This gives me the [following URL](https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/model.json) for the model configuration file.

```
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/model.json
```

Once this file has been downloaded locally, I can use the [`jq` tool](https://stedolan.github.io/jq/) to parse all the weight file names.

```
$ cat model.json | jq -r ".weightsManifest[].paths[0]"
group1-shard1of1
group2-shard1of1
group3-shard1of1
...
```

Using the `sed` tool, I can prefix these names with the HTTP URL to generate URLs for each weight file.

```
$ cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//'
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group1-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group2-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group3-shard1of1
...
```

Using the `parallel` and `curl` commands, I can then download all of these files to my local directory.

```
cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//' |  parallel curl -O
```

### Classifying Images

[This example code](https://github.com/tensorflow/tfjs-models/tree/master/mobilenet#via-npm) is provided by TensorFlow.js to demonstrate returning classifications for an image.

```javascript
const img = document.getElementById('img');

// Classify the image.
const predictions = await model.classify(img);
```

**This does not work on Node.js due to the lack of a DOM.**

The `classify` [method](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L143-L155) accepts numerous DOM elements (`canvas`, `video`, `image`) and will automatically retrieve and convert image bytes from these elements into a [`tf.Tensor3D` class](https://js.tensorflow.org/api/latest/index.html#tensor3d) which is used as the input to the model. Alternatively, the `tf.Tensor3D` input can be passed directly. 

**Rather than trying to use an external package to simulate a DOM element in Node.js, I found it easier to construct the `tf.Tensor3D` manually.**

#### Generating Tensor3D from an Image

Reading the [source code](https://github.com/tensorflow/tfjs-core/blob/master/src/kernels/backend_cpu.ts#L126-L140) for the method used to turn DOM elements into Tensor3D classes, the following input parameters are used to generate the Tensor3D class.

```javascript
const values = new Int32Array(image.height * image.width * numChannels);
// fill pixels with pixel channel bytes from image
const outShape = [image.height, image.width, numChannels];
const input = tf.tensor3d(values, outShape, 'int32');
```

`pixels` is a 2D array of type (Int32Array) which contains a sequential list of channel values for each pixel. `numChannels` is the number of channel values per pixel.

#### Creating Input Values For JPEGs

The [`jpeg-js` library](https://www.npmjs.com/package/jpeg-js) is a pure javascript JPEG encoder and decoder for Node.js. Using this library the RGB values for each pixel can be extracted.

```javascript
const pixels = jpeg.decode(buffer, true);
```

This will return a `Uint8Array` with four channel values (`RGBA`) for each pixel (`width * height`). The MobileNet model only uses the three colour channels (`RGB`) for classification, ignoring the alpha channel. This code converts the four channel array into the correct three channel version.

```javascript
const numChannels = 3;
const numPixels = image.width * image.height;
const values = new Int32Array(numPixels * numChannels);

for (let i = 0; i < numPixels; i++) {
  for (let channel = 0; channel < numChannels; ++channel) {
    values[i * numChannels + channel] = pixels[i * 4 + channel];
  }
}
```

#### MobileNet Models Input Requirements

The [MobileNet model](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md#mobilenet_v1) being used classifies images of width and height 224 pixels. Input tensors must contain float values, between -1 and 1, for each of the three channels pixel values.

Input values for images of different dimensions needs to be re-sized before classification. Additionally, pixels values from the JPEG decoder are in the range *0 - 255*, rather than *-1 to 1*. These values also need converting prior to classification.

**TensorFlow.js has library methods to make this process easier but, fortunately for us, the `tfjs-models/mobilenet` library [automatically handles](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L103-L114) this issue!** 👍

Developers can pass in Tensor3D inputs of type `int32`  and different dimensions to the  `classify` method and it converts the input to the correct format prior to classification. Which means there's nothing to do... Super 🕺🕺🕺.

#### Obtaining Predictions

MobileNet models in Tensorflow are trained to recognise entities from the [top 1000 classes](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/imagenet_classes.ts) in the [ImageNet](http://image-net.org/) dataset. The models output the probabilities that each of those entities is in the image being classified.

*The full list of trained classes for the model being used can be found in [this file](https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/imagenet_classes.ts).*

The `tfjs-models/mobilenet` library exposes a `classify` method on the `MobileNet` class to return the top X classes with highest probabilities from an image input.

```javascript
const predictions = await mn_model.classify(input, 10);
```

`predictions` is an array of X classes and probabilities in the following format.

```javascript
{
  className: 'panda',
  probability: 0.9993536472320557
}
```

## Example

Having worked how to use the TensorFlow.js library and MobileNet models on Node.js, [this script](https://gist.github.com/jthomas/145610bdeda2638d94fab9a397eb1f1d) will classify an image given as a command-line argument.

### source code

- Save this script file and package descriptor to local files.

<script src="https://gist.github.com/jthomas/145610bdeda2638d94fab9a397eb1f1d.js"></script>

### testing it out

- Download the model files to a `mobilenet` directory using the instructions above.
- Install the project dependencies using NPM

```
npm install
```

- Download a sample JPEG file to classify

```
wget http://bit.ly/2JYSal9 -O panda.jpg
```

![Panda](https://thepracticaldev.s3.amazonaws.com/i/su9xscczsrjvwgcjlpyx.jpg)

- Run the script with the model file and input image as arguments.

```
node script.js mobilenet/model.json panda.jpg
```

**If everything worked, the following output should be printed to the console.**

```javascript
classification results: [ {
    className: 'giant panda, panda, panda bear, coon bear',
    probability: 0.9993536472320557 
} ]
```

The image is correctly classified as containing a Panda with 99.93% probability! 🐼🐼🐼

## Conclusion

TensorFlow.js brings the power of deep learning to JavaScript developers. Using pre-trained models with the TensorFlow.js library makes it simple to extend JavaScript applications with complex machine learning tasks with minimal effort and code.

Having been released as a browser-based library, TensorFlow.js has now been extended to work on Node.js, although not all of the tools and utilities support the new runtime. With a few days' hacking, I was able to use the library with the MobileNet models for visual recognition on images from a local file.

Getting this working in the Node.js runtime means I now move on to my next idea... making this run inside a serverless function! Come back soon to read about my next adventure with TensorFlow.js. 👋