Neural Network VS Algorithm

Neural networks are pretty amazing, and now you can just have one run right in your browser with brain.js.  I wanted to learn about this whole neural network thing, so I decided to see how accurately I could get a neural network to replicate an algorithm.

Here I use the BMR algorithm which takes a height, weight, age, and sex; and returns a calorie amount.  Can a neural network predict a calorie amount?  Sure it can.  Just to note, this is really just a proof of concept, an algorithm already exists for this, so there’s no reason to spend time training a neural network.

You can grab the entire project here on github: https://github.com/roblouie/bmrAI

In order to learn, neural networks need training data.  The training data needs to include both the input and the matching output, so in this case our training data will have some basic patient information for the input and a drug dose for the output.  The neural network will then iterate through this data and continually adjust itself until it’s output closely matches the output from the sample data.

For this example I use brain-0.6.3.js, so that’s the version of brain.js to use if you want to follow along.  I fire up yeoman and scaffold out a standard angular webapp.

First in order to build our sample data, we need the dose algorithm.  We create an algorithm-service to do this:

'use strict';

//BMR = 10 * weight(kg) + 6.25 * height(cm) - 5 * age(y) + 5         (man)
//BMR = 10 * weight(kg) + 6.25 * height(cm) - 5 * age(y) - 161     (woman)

angular.module('bmrAiApp')
  .service('algorithmService', function () {
        var WEIGHT_MULTIPLIER = 10;
        var HEIGHT_MULTIPLIER = 6.25;
        var AGE_MULTIPLIER = 5;
        var MALE_MODIFIER = 5;
        var FEMALE_MODIFIER = -161;

        return {
            calculateBasalMetabolicRate: calculateBasalMetabolicRate
        };

        function calculateBasalMetabolicRate(weight, height, age, isMale) {
            var BMR = WEIGHT_MULTIPLIER * weight + HEIGHT_MULTIPLIER * height - AGE_MULTIPLIER * age + (isMale == 1 ? MALE_MODIFIER : FEMALE_MODIFIER);
            return Math.round(BMR);
        }
  });

The heart of the algorithm is in calculateBasalMetablolicRate, where a BMR is calculated based on passed in information.

Now, it’s important to note that brain.js wants it’s input as either a map or an array of numbers between zero and one, so all of our inputs need to be between zero and one.  This is easily achieved by dividing each input by that inputs max, so for instance we set a max age of 75, and any age is simply divided by 75.

First we set up helper functions to get random numbers between zero and one, the work by accepting a max and a min, generating a random number between the max and min, and then dividing by the max.

function getRandomNumberAndBetweenOneAndZero(min, max) {
    var randomInt = getRandomInt(min, max);

    return {originalNumber: randomInt, betweenZeroAndOne: +(randomInt / max).toFixed(2)};
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getZeroOrOne() {
    return Math.round(getRandomInt(0, 1));
}

Now we make specific helper functions:

function transformHeightToFraction(height) {
    return +(height / 215).toFixed(2);
}

function transformWeightToFraction(weight) {
    return +(weight / 135).toFixed(2);
}

function transformAgeToFraction(age) {
    return +(age / 75).toFixed(2);
}

Populating the Sample Data Set and Training the Network

With the algorithm and helper functions in place, we can populate our training data.  Here I populate 30,000 sample patients.

function buildTestData() {
    var personDataList = [];

    for (var i = 0; i < 20000; i ++) {
        var age = getRandomNumberAndBetweenOneAndZero(18, 75);
        var height = getRandomNumberAndBetweenOneAndZero(120, 215);
        var weight = getRandomNumberAndBetweenOneAndZero(45, 135);
        var isMale = getZeroOrOne();

        personDataList.push({
            input: {
                age: age.betweenZeroAndOne,
                height: height.betweenZeroAndOne,
                weight: weight.betweenZeroAndOne,
                isMale: isMale
            },
            output: {bmr: (algorithmService.calculateBasalMetabolicRate(weight.originalNumber, height.originalNumber, age.originalNumber, isMale) / 2324)}
        });
    }

    return personDataList;
}

Here I choose random numbers for all the information, put it into the input map, then calculate the output for that person and put it in the output map. We now have 30,000 random people and their BMRs that the neural network can use to learn.

First the neural network is created

var bmrAI = new brain.NeuralNetwork({
    hiddenLayers: [4, 4]
});

By default, brain.js creates a single hidden layer with a size equal to the input array. The hidden layers are how the network adjusts itself, and I found a single layer did not allow enough adjustment, so I added a second one with 4 nodes. More brain power!

With our neural net initialized, it’s time to train. This will take some time, but you can watch the console output its margin of error as it trains.

function trainAIForBmr() {
    var options = {
        errorThresh: 0.00001,  // error threshold to reach
        iterations: 20000,   // maximum training iterations
        log: true,           // console.log() progress periodically
        logPeriod: 50,       // number of iterations between logging
        learningRate: 0.3    // learning rate
    };

    var data = buildTestData();

    bmrAI.train(data, options);
}

The error threshold tells the neural network to stop training if it hits a low enough margin of error. The iterations puts a cap on how long it trains, regardless of the error threshold being reached, log and logPeriod will log cause it to log to the console after it goes through every 50 patients. The learningRate is a number from zero to one, the higher the number, the faster it learns, but the less accurate it is.

It's Learning!

When training is complete, you can save the current trained state of the neural net to json by calling toJson() on the neural net.  You can then load that state back in by calling fromJson() on a fresh instance.  This means you can train once and then just load the trained state instantly every time after that.

The neural net can now make predictions for you by calling run on the neural net and passing in an input map.

function calculateBmr(weight, height, age, isMale) {
    return bmrAI.run({
        age: transformAgeToFraction(age),
        height: transformHeightToFraction(height),
        weight: transformWeightToFraction(weight),
        isMale: isMale
    }).bmr;
}

I have a very simple front end to test out the intelligence of the neural network.  Please check out the project on github and experiment!  Spoiler alert: with these settings, with only about a half hour of training, the margin of error drops to one percent!

Know of any other free and easy to use neural networks? Have a cool idea for applying a neural network to an existing problem? Let me know in the comments below.