Hire the author: Lakshay G
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:
- It is solely based on the request/ response model
- The training data gets updated and does not wipe out even if the browser window is closed
- 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
- Basic programming fundamentals are a must
- Must have a little bit idea about JavaScript, React.js, Node.js and npm packages(brain.js)
Step by Step Tutorial
Backend
- The first task is to create the MongoDB schema for the training dataThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
const mongoose = require("mongoose"); const TrainingSchema = new mongoose.Schema({ input: [Number, Number, Number], output: [Number] }); module.exports = Train = mongoose.model("trainingdatas", TrainingSchema);
Now in this remember all the input and the output parameters should be Numbers - The second task is to create a function to connect to the Database.This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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; - The third task is to create the server file and the API’sThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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') })
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 - 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)
- Now the server is ready for deployment, follow this in order to deploy it for free on Heroku.
Frontend
- The first step is to create a react-app (use npx create-react-app appname)
- Now set up redux in the app (follow this short video for help)
- Now after everything is set, let’s start with the frontend designing and setting up the actions and reducers.
- First Lets code the App.js file first for the design and this is how it looksThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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);
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 - Let’s write the actions corresponding to each of the 5 routes at the backendThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
import axios from 'axios' // const domain = "http://localhost:4000" const domain = "https://apicontrastpicker.herokuapp.com" 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', }); } }; - After writing the actions, now our last task is to write the reducerThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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; } } - After this is done, import all the actions and the redux state variables in the App.js file (step 4)
- Now, the client application is ready for deployment (follow this for help) and it will finally look like



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
- Don’t write messy code, everything should be well understood. Doing this will automatically help you when it comes to debugging.
- 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.
- 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.
Hi,
Nice blog explaining brain.js in detail. However, I would request you to add comments in the code so that user can understand the code better. Eg. URI needs mongoDB URI, or the numbers to create DB schema.
That will be helpful.
Also, If you can elaborate how the neural network is training the model, that will be helpful.
Thank you.