Introduction

In this tutorial, I’ll cover how to build a color contrast picker using brain.js. To build this I will be using Reactjs, redux, brain.js MongoDB, and node.js. It includes building a neural network and then training it to predict accurate results.Brain.js is a great library to start with machine learning or neural networks. The unique thing about this project that differentiates it from other articles is that:

  1. It is solely based on the request/ response model
  2. The training data gets updated and does not wipe out even if the browser window is closed
  3. It can be used to simultaneously train the neural network and predict results 

NOTE: I will be using a standard neural network, that will be approximating the XOR function over the input parameters to predict results.

Motivation

Google says “Machine Learning is the future,” and the future of Machine Learning is going to be very bright. I wanted to start with machine learning from a very basic level and javascript is the scripting language I prefer to code in. And as a result, I came across this amazing library brain.js. The best part is you don’t need to know the advanced fundamentals of machine learning to use this. Brain.js is the best library for beginners.

Goal

My goal with this project is to make the readers successfully implement it with the greatest ease and then deploy it online for free.

Glossary

neural network: A neural network is a series of algorithms that endeavors to recognize underlying relationships in a set of data through a process that mimics the way the human brain operates

AI: It stands for artificial intelligence (intelligence demonstrated by machines).

NaN: In computing, NaN, standing for Not a Number, is a member of a numeric data type that can be interpreted as a value that is undefined or unrepresentable, especially in floating-point arithmetic.

Project Requirements

  1. Basic programming fundamentals are a must
  2. Must have a little bit idea about JavaScript, React.js, Node.js and npm packages(brain.js)

Step by Step Tutorial

Backend

  1. The first task is to create the MongoDB schema for the training data
    const mongoose = require("mongoose");
    const TrainingSchema = new mongoose.Schema({
    input: [Number, Number, Number],
    output: [Number]
    });
    module.exports = Train = mongoose.model("trainingdatas", TrainingSchema);
    view raw model.js hosted with ❤ by GitHub

    Now in this remember all the input and the output parameters should be Numbers
  2. The second task is to create a function to connect to the Database.
    const mongoose = require("mongoose");
    const URI = "URL"
    const connectDB = async () => {
    try {
    await mongoose.connect(URI, {
    useUnifiedTopology: true,
    useNewUrlParser: true
    })
    console.log("MongoDB Connected")
    } catch (err) {
    console.log("MongoDB Authentication failed ")
    }
    }
    module.exports = connectDB;
    view raw gistfile1.txt hosted with ❤ by GitHub
  3. The third task is to create the server file and the API’s
    var express = require('express')
    var app = express();
    const Train = require("./models/Train");
    const db = require('./mongoConnect.js')
    const brain = require('brain.js')
    const cors = require('cors')
    const net = new brain.NeuralNetwork()
    db()
    app.use(cors());
    app.get('/', async (req, res) => {
    res.send('Ping-Pong')
    })
    app.get('/predict/:r/:g/:b', async (req, res) => {
    var red = req.params.r;
    var green = req.params.g;
    var blue = req.params.b;
    res.json({
    'result': net.run([red, green, blue])[0],
    'r': parseFloat(red),
    'g': parseFloat(green),
    'b': parseFloat(blue)
    })
    })
    app.get('/train', async (req, res) => {
    // var tg = new Train({
    // input:[0,0,0],output:[1]
    // })
    // var ty = new Train({
    // input:[1,1,1], output:[0]
    // })
    // await tg.save()
    // await ty.save()
    var t = await Train.find({}).select('-_id -__v')
    net.train(t)
    res.json({
    'success': 'true'
    });
    })
    app.get('/result/:r/:g/:b', async (req, res) => {
    try {
    var r = req.params.r;
    var g = req.params.g;
    var b = req.params.b;
    res.json({
    'result': net.run([r, g, b])[0]
    })
    } catch (err) {
    console.log('Error')
    }
    })
    app.get('/white/:r/:g/:b', async (req, res) => {
    var r = req.params.r;
    var g = req.params.g;
    var b = req.params.b;
    if ((r >= 0 && r <= 1) && (g >= 0 && g <= 1) && (b >= 0 && b <= 1)) {
    var t = new Train({
    input: [r, g, b],
    output: [1]
    })
    await t.save();
    res.json({
    r: Math.random(),
    g: Math.random(),
    b: Math.random(),
    result: net.run([r, g, b])[0]
    })
    } else {
    res.json({
    'success': 'failed'
    })
    }
    })
    try {
    app.get('/black/:r/:g/:b', async (req, res) => {
    var r = req.params.r;
    var g = req.params.g;
    var b = req.params.b;
    if ((r >= 0 && r <= 1) && (g >= 0 && g <= 1) && (b >= 0 && b <= 1)) {
    var t = new Train({
    input: [r, g, b],
    output: [0]
    })
    await t.save();
    res.json({
    r: Math.random(),
    g: Math.random(),
    b: Math.random(),
    result: net.run([r, g, b])[0]
    })
    } else {
    res.json({
    'success': 'failed'
    })
    }
    })
    } catch (err) {
    }
    app.listen(process.env.PORT || 4000, function() {
    console.log('Started')
    })
    view raw server.js hosted with ❤ by GitHub

    There are 5 main routes that will be used in our client application :
    1. To train our neural network (/train)
    2. To predict the output (/predict/:r/:g/:b and /result/:r/:g/:b but both have different use-cases)
    3. To instruct the neural network (/white/:r/:g/:b and /black/:r/:g/:b)

    Note: Before running this file keep in mind to separately run the statements commented from line 28 to 35 (because we need to store some starting data for our neural network) and then comment them back
  4. After all these steps are successfully executed, run the server (it is going to show some warnings related to brain.js but those will not affect our application in any way)
  5. Now the server is ready for deployment, follow this in order to deploy it for free on Heroku.

Frontend

  1. The first step is to create a react-app (use npx create-react-app appname)
  2. Now set up redux in the app (follow this short video for help)
  3. Now after everything is set, let’s start with the frontend designing and setting up the actions and reducers.
  4. First Lets code the App.js file first for the design and this is how it looks
    import React, {
    useEffect,
    useState,
    Fragment
    } from "react";
    import loading from './loading.gif'
    import {
    connect
    } from "react-redux";
    import {
    PropTypes
    } from 'prop-types'
    import {
    trainNetwork,
    result,
    white,
    black,
    predict
    } from './actions/contrastpicker.js'
    const App = ({
    trainNetwork,
    training,
    result,
    resultN,
    white,
    red,
    green,
    blue,
    whiteReturning,
    blackReturning,
    black,
    predict,
    closeResult
    }) => {
    useEffect(() => {
    console.log('Hello')
    trainNetwork();
    result(red, green, blue)
    }, []);
    const handleWhite = async (e) => {
    console.log('Handling white')
    white(red, green, blue);
    //result(red,green,blue)
    };
    const handleBlack = async (e) => {
    console.log('Handling black')
    black(red, green, blue);
    //result(red,green,blue)
    };
    const [data, setData] = useState({
    r: 0,
    g: 0,
    b: 0
    })
    const {
    r,
    g,
    b
    } = data;
    const handleRed = async (e) => {
    setData({
    ...data,
    r: e.target.value / 255
    })
    }
    const handleGreen = async (e) => {
    setData({
    ...data,
    g: e.target.value / 255
    })
    }
    const handleBlue = async (e) => {
    setData({
    ...data,
    b: e.target.value / 255
    })
    }
    const predictResults = async (e) => {
    if (b >= 0 && b <= 1 && r >= 0 && r <= 1 && g >= 0 && g <= 1) {
    predict(r, g, b)
    }
    }
    return ( <
    React.Fragment > {
    training ? < div style = {
    {
    'textAlign': 'center',
    'fontSize': '50px',
    'marginTop': '15%'
    }
    } > Training Network <
    div > < img src = {
    loading
    }
    /></div >
    <
    /div>:
    <
    div style = {
    {
    'backgroundColor': 'rgb(' + red * 255 + ',' + green * 255 + ',' + blue * 255 + ')',
    'width': window.innerWidth,
    'height': window.innerHeight
    }
    } >
    <
    div style = {
    {
    'textAlign': 'center',
    'fontSize': '20px'
    }
    } >
    <
    div style = {
    {
    'color': 'white'
    }
    } > This is white on this page < /div> <
    div style = {
    {
    'color': 'black'
    }
    } > This is black on this page < /div> <
    br / >
    <
    div style = {
    {
    'color': resultN > 0.5 ? 'white' : 'black'
    }
    } > The font - color of this text is predicted by the neural network < /div>
    <
    /div>
    <
    div style = {
    {
    'textAlign': 'center',
    'fontSize': '20px'
    }
    } >
    <
    p style = {
    {
    'color': resultN > 0.5 ? 'white' : 'black'
    }
    } > Which one is more readable ? < /p> <
    button className = "btn btn-light"
    style = {
    {
    'marginRight': '5px'
    }
    }
    onClick = {
    handleBlack
    } > BLACK < /button> <
    button className = "btn btn-dark"
    onClick = {
    handleWhite
    }
    >
    WHITE < /button> <
    /div> <
    br / >
    <
    form >
    <
    div className = "form-group" >
    <
    div className = "row"
    style = {
    {
    'padding': '10px',
    'textAlign': 'center',
    'fontSize': '5px',
    'backgroundColor': 'white',
    'width': 'fit-content',
    'marginLeft': 'auto',
    'marginRight': 'auto',
    'marginTop': '20px'
    }
    } >
    <
    span > < input required className = "form-control"
    type = "number"
    min = "0"
    max = "255"
    placeholder = "Red"
    style = {
    {
    'margin': '5px',
    'width': 'fit-content'
    }
    }
    onChange = {
    handleRed
    } > < /input></span >
    <
    span > < input required className = "form-control"
    type = "number"
    min = "0"
    max = "255"
    placeholder = "Green"
    style = {
    {
    'margin': '5px',
    'width': 'fit-content'
    }
    }
    onChange = {
    handleGreen
    } > < /input></span >
    <
    span > < input required className = "form-control"
    type = "number"
    min = "0"
    max = "255"
    placeholder = "Blue"
    style = {
    {
    'margin': '5px',
    'width': 'fit-content'
    }
    }
    onChange = {
    handleBlue
    } > < /input></span >
    <
    button type = "button"
    className = "btn btn-success"
    onClick = {
    predictResults
    } > Set Background Color and Predict < /button> <
    /div> <
    /div> <
    /form>
    <
    div style = {
    {
    'textAlign': 'center',
    'fontSize': '80px',
    'color': resultN > 0.5 ? 'white' : 'black',
    'marginTop': '20px'
    }
    } > {
    !closeResult ? resultN > 0.5 ? 'WHITE' : 'BLACK' : null
    } <
    div style = {
    {
    'bottom': '40px',
    'marginLeft': 'auto',
    'marginRight': 'auto',
    'left': '0',
    'right': '0',
    'width': '100%',
    'textAlign': 'center',
    'fontSize': '0.7rem',
    'position': 'absolute'
    }
    } > The training data is frequently getting updated and the neural network 's learnings are not wiped out even on closing the browser window <
    div > Simultaneously update the training data and predict results < /div>
    <
    /div> <
    /div> <
    /div>
    } <
    /React.Fragment>
    )
    }
    App.propTypes = {
    training: PropTypes.bool.isRequired,
    trainNetwork: PropTypes.func.isRequired,
    result: PropTypes.func.isRequired,
    resultN: PropTypes.number.isRequired,
    red: PropTypes.number.isRequired,
    green: PropTypes.number.isRequired,
    blue: PropTypes.number.isRequired,
    whiteReturning: PropTypes.bool.isRequired,
    blackReturning: PropTypes.bool.isRequired,
    predict: PropTypes.func.isRequired,
    closeResult: PropTypes.bool.isRequired
    }
    const mapStateToProps = (state) => ({
    training: state.contrastpicker.training,
    resultN: state.contrastpicker.resultN,
    red: state.contrastpicker.red,
    green: state.contrastpicker.green,
    blue: state.contrastpicker.blue,
    whiteReturning: state.contrastpicker.whiteReturning,
    blackReturning: state.contrastpicker.blackReturning,
    closeResult: state.contrastpicker.closeResult
    });
    export default connect(mapStateToProps, {
    trainNetwork,
    result,
    white,
    black,
    predict
    })(App);
    view raw app.js hosted with ❤ by GitHub

    Don’t worry if you didn’t get meaning of the functions used, it will be way easier to understand once the actions and reducers are set
  5. Let’s write the actions corresponding to each of the 5 routes at the backend
    import axios from 'axios'
    // const domain = "http://localhost:4000&quot;
    const domain = "https://apicontrastpicker.herokuapp.com&quot;
    export const trainNetwork = () => async (dispatch) => {
    try {
    dispatch({
    type: 'TRAINING'
    })
    console.log('Dispatching')
    const result = await axios.get(`${domain}/train`)
    console.log(result.data)
    dispatch({
    type: 'NETWORK_TRAINED',
    payload: {
    trained: result.data
    },
    });
    } catch (error) {
    if (error.response) {
    console.log(error.response.data.errors);
    console.log(error.response.status);
    console.log(error.response.headers);
    } else if (error.request) {
    /*
    * The request was made but no response was received, `error.request`
    * is an instance of XMLHttpRequest in the browser and an instance
    * of http.ClientRequest in Node.js
    */
    console.log(error.request);
    } else {
    console.log("Error", error.message);
    }
    console.log(error);
    dispatch({
    type: 'TRAINING_ERROR',
    });
    }
    };
    export const result = (red, green, blue) => async (dispatch) => {
    try {
    dispatch({
    type: 'FINDING'
    })
    //console.log('Dispatching')
    const result = await axios.get(`${domain}/result/${red}/${green}/${blue}`)
    console.log(result.data)
    dispatch({
    type: 'RESULT_FOUND',
    payload: {
    trained: result.data.result
    },
    });
    console.log(result.data.result)
    } catch (error) {
    if (error.response) {
    console.log(error.response.data.errors);
    console.log(error.response.status);
    console.log(error.response.headers);
    } else if (error.request) {
    /*
    * The request was made but no response was received, `error.request`
    * is an instance of XMLHttpRequest in the browser and an instance
    * of http.ClientRequest in Node.js
    */
    console.log(error.request);
    } else {
    console.log("Error", error.message);
    }
    console.log(error);
    dispatch({
    type: 'FINDING_ERROR',
    });
    }
    };
    export const white = (red, green, blue) => async (dispatch) => {
    try {
    dispatch({
    type: 'WHITE_RETURNING'
    });
    const result = await axios.get(`${domain}/white/${red}/${green}/${blue}`)
    console.log(result.data)
    dispatch({
    type: 'WHITE_RETURNED',
    payload: result.data,
    });
    console.log(result.data)
    } catch (error) {
    if (error.response) {
    console.log(error.response.data.errors);
    console.log(error.response.status);
    console.log(error.response.headers);
    } else if (error.request) {
    /*
    * The request was made but no response was received, `error.request`
    * is an instance of XMLHttpRequest in the browser and an instance
    * of http.ClientRequest in Node.js
    */
    console.log(error.request);
    } else {
    console.log("Error", error.message);
    }
    console.log(error);
    dispatch({
    type: 'WHITE_ERROR',
    });
    }
    };
    export const black = (red, green, blue) => async (dispatch) => {
    try {
    dispatch({
    type: 'BLACK_RETURNING'
    });
    const result = await axios.get(`${domain}/black/${red}/${green}/${blue}`)
    console.log(result.data)
    dispatch({
    type: 'BLACK_RETURNED',
    payload: result.data,
    });
    console.log(result.data)
    } catch (error) {
    if (error.response) {
    console.log(error.response.data.errors);
    console.log(error.response.status);
    console.log(error.response.headers);
    } else if (error.request) {
    /*
    * The request was made but no response was received, `error.request`
    * is an instance of XMLHttpRequest in the browser and an instance
    * of http.ClientRequest in Node.js
    */
    console.log(error.request);
    } else {
    console.log("Error", error.message);
    }
    console.log(error);
    dispatch({
    type: 'BLACK_ERROR',
    });
    }
    };
    export const predict = (red, green, blue) => async (dispatch) => {
    try {
    dispatch({
    type: 'PREDICTING'
    });
    console.log(red + ' ' + green + ' ' + blue)
    const result = await axios.get(`${domain}/predict/${red}/${green}/${blue}`)
    console.log(result.data)
    dispatch({
    type: 'PREDICTED',
    payload: result.data,
    });
    console.log(result.data)
    } catch (error) {
    if (error.response) {
    console.log(error.response.data.errors);
    console.log(error.response.status);
    console.log(error.response.headers);
    } else if (error.request) {
    /*
    * The request was made but no response was received, `error.request`
    * is an instance of XMLHttpRequest in the browser and an instance
    * of http.ClientRequest in Node.js
    */
    console.log(error.request);
    } else {
    console.log("Error", error.message);
    }
    console.log(error);
    dispatch({
    type: 'PREDICT_ERROR',
    });
    }
    };
    view raw action.js hosted with ❤ by GitHub
  6. After writing the actions, now our last task is to write the reducer
    const initialState = {
    training: false,
    founding: false,
    resultN: 0,
    red: Math.random(),
    green: Math.random(),
    blue: Math.random(),
    whiteReturning: false,
    blackReturning: false,
    predicting: false,
    closeResult: true
    };
    export default function(state = initialState, action) {
    const {
    type,
    payload
    } = action;
    switch (type) {
    case 'NETWORK_TRAINED':
    return {
    ...state,
    training: false
    }
    case 'TRAINING_ERROR':
    return {
    ...state,
    training: false
    }
    case 'TRAINING':
    return {
    ...state,
    training: true
    }
    case 'FOUNDING':
    return {
    ...state,
    founding: true
    }
    case 'RESULT_FOUND':
    return {
    ...state,
    founding: false,
    resultN: payload.trained
    }
    case 'FINDING_ERROR':
    return {
    ...state,
    founding: false
    }
    case 'WHITE_RETURNED':
    return {
    ...state,
    red: payload.r,
    green: payload.g,
    blue: payload.b,
    resultN: payload.result,
    whiteReturning: false,
    closeResult: true
    }
    case 'WHITE_ERROR':
    return {
    ...state,
    whiteReturning: false,
    closeResult: true
    }
    case 'WHITE_RETURNING':
    return {
    ...state,
    whiteReturning: true
    }
    case 'BLACK_RETURNED':
    return {
    ...state,
    red: payload.r,
    green: payload.g,
    blue: payload.b,
    resultN: payload.result,
    blackReturning: false,
    closeResult: true
    }
    case 'BLACK_ERROR':
    return {
    ...state,
    blackReturning: false,
    closeResult: true
    }
    case 'BLACK_RETURNING':
    return {
    ...state,
    blackReturning: true
    }
    case 'PREDICTED':
    return {
    ...state,
    red: payload.r,
    green: payload.g,
    blue: payload.b,
    resultN: payload.result,
    predicting: false,
    closeResult: false
    }
    case 'PREDICT_ERROR':
    return {
    ...state,
    predicting: false,
    closeResult: true
    }
    case 'PREDICTING':
    return {
    ...state,
    predicting: true
    }
    default:
    return state;
    }
    }
    view raw reducer.js hosted with ❤ by GitHub
  7. After this is done, import all the actions and the redux state variables in the App.js file (step 4)
  8. Now, the client application is ready for deployment (follow this for help) and it will finally look like
How to build color contrast picker using brain.js How to build color contrast picker using brain.js How to build color contrast picker using brain.js

Note: The Black/ White buttons are used to train the neural network whereas the “Set Background and Predict” button is used to predict the results

Learning Tools and Strategies

  1. Don’t write messy code, everything should be well understood. Doing this will automatically help you when it comes to debugging.
  2. Do a console.log() at each major step you feel is important, or that you feel may throw some kind of error. This is so that you can get the result of each major code-snippet.
  3. The last most important thing is to make sure that the training data consists of only numbers and not strings, otherwise, brain.js neural network will not train it and throw an error NaN which means Not a Number.

Search Terms

color contrast picker, brain.js, neural network, machine learning

Reflective Analysis

This is my first hands-on on any machine learning related project. After implementing this project my theoretical knowledge related to neural networks has increased because I was visually able to see what was happening. Explore more of this brain.js documentation here. The most common mistake people are expected to make is to insert strings in training data rather than numbers and as a result, the neural network is not able to train the data and throws NaN error.

Conclusions and Future Directions

It is a basic and very easy implementation of color contrast picker using brain.js by training the neural network to predict accurate results and keeping the learnings of our neural network persistent even if the browser window is closed. The server application and the client application are deployed separately, so I would suggest trying deploying them both together as a single application.

Citations

I used https://blog.frankfurt-school.de/neural-networks-vs-random-forests-does-it-always-have-to-be-deep-learning/ for the featured image.

The project is available here and the deployed version is available here.