How to make a Machine Learning Recommendation System from scratch

How to make a Machine Learning Recommendation System using JavaScript

Are you interested in machine learning and its applications? If you’re looking to learn something new and great, how about a machine learning recommendation system?

Photo by Franck V. on Unsplash

Well, don’t be scared or blown away with those massive terms and words, they just tell someone what exactly some code does.

Machine learning has always been in the news, whether for taking away jobs or making a robot understand and reply to a human. But what exactly can we do with machine learning?

The answer is, almost everything.

Whatever a human can do, machine learning model can do better.

Now before anyone who has seen such models failing drastically starts commenting furiously, let me rephrase that statement a bit.

Whatever a human can do, machine learning model can do better; given time and effort.

Well enough of ML vs human, let’s get into what we’re making here. So here we would be building a machine learning recommendation system.

What is a machine learning Recommendation System?

A Recommendation System is a machine learning model that recommends new options to choose from and learns from your previous actions on a topic. This means that the model would understand your selection and recommend choices that you “might” like. You might have seen Spotify, Netflix or Amazon recommended items. This is similar to that, but maybe a little less efficient.

What exactly are we going to recommend?

After giving it a bit of thought, movies and songs require a lot of other code and data that would make the learning harder, so I came up with the code/text editor theme.

Everybody (read almost everybody) loves customisable themes for their IDE or text editors and spend a lot of time looking for themes they would like to use.

This problem is what we would be targeting in this example because it makes learning the concept of machine learning recommendation system easy to understand, and makes up an example that would be usable in the real world.

How would it look?

Here’s how it’ll look (for best results, open this in private/incognito window).

Let’s get into some code now. We will be using JavaScript, CSS, and HTML along with the ‘brain.js’ machine learning library to make this model.

The complete code can be found in this GitHub repository.

Now we’ll discuss the JavaScript code that we are using to make this model work. First let’s write the code for instantiating some variables, libraries and code that will make up the UI for our webpage.

const editorWrapper = document.querySelector(".editor-wrapper")const colorOne = document.querySelectorAll(".color-1")const colorTwo = document.querySelectorAll(".color-2")const colorThree = document.querySelectorAll(".color-3")const stars = document.querySelectorAll(".star")const themes = document.getElementById("themes")window.localStorage.trainingData = window.localStorage.trainingData || JSON.stringify([])// our current voting combinationconst currentColors = {  back: {},  one: {},  two: {},  three: {},}

These are some of those variables that we will be using to change the text colour and theme of the text in our webpage.

We will now generate a random theme on load of the webpage. Here we also give the user the choice to vote the themes and use that data to recommend more themes that would appeal to the user.

// kick it off by creating a random theme for you to vote on/*   as you vote, saveTrainingData  - saves to localstorage  - runs predictThemeCombinations*//*  predictThemeCombinations  - trains a neural network from your vote history  - creates 100,000 random themes  - runs the themes through the network, getting a score for each  - sorts and returns the top 20 scored themes*/   generateRandomTheme()predictThemeCombinations()

We will be writing code for generateRandomTheme() and predictThemeCombinations() later.

Also, we need to write code for stars that user can use to rate the theme. We will be making it a bit interactive by making the star golden when the user hovers over it or clicks on it. The theme is rated and that data is used by our model to predict further choices for the user.

stars.forEach((star, i) => {  const score = i / 4  star.addEventListener("mouseenter", setStars.bind(setStars, i))  star.addEventListener("mouseleave", clearStars)  star.addEventListener("click", saveTrainingData.bind(saveTrainingData, score))})

We will also be writing code for setStars() and clearStars() functions.

Now let’s write the code for the function saveTrainingData() for the score that the user generates when he rates the theme. This method saves training data according to the score and uses it to train our model.

function saveTrainingData(score) {  const data = JSON.parse(window.localStorage.trainingData)  data.push({    input: [      Math.round(currentColors.back.r/2.55) / 100, // divide by 255 and round to 2 decimal places      Math.round(currentColors.back.g/2.55) / 100,      Math.round(currentColors.back.b/2.55) / 100,      Math.round(currentColors.one.r/2.55) / 100,      Math.round(currentColors.one.g/2.55) / 100,      Math.round(currentColors.one.b/2.55) / 100,      Math.round(currentColors.two.r/2.55) / 100,      Math.round(currentColors.two.g/2.55) / 100,      Math.round(currentColors.two.b/2.55) / 100,      Math.round(currentColors.three.r/2.55) / 100,      Math.round(currentColors.three.g/2.55) / 100,      Math.round(currentColors.three.b/2.55) / 100,    ],    output: [score]  })  window.localStorage.trainingData = JSON.stringify(data)  predictThemeCombinations()  clearStars()  generateRandomTheme()}

Here we also have used the method clearStars() which basically removes the star rating from the page if the user has not clicked on it and removes the cursor.

The setStars() method does exactly what we discussed earlier.

function setStars(whichStar) {  for (let i = 0; i < stars.length; i++) {    stars[i].classList.add("gold")    if (i >= whichStar) {      break;    }  }}function clearStars() {  for (const star of stars) {    star.classList.remove("gold")  }}

Now let’s write code for generateRandomTheme() that we used at the very beginning of the webpage. This method just gets a random background colour and 3 other random colours to set the theme for the first time.

function generateRandomTheme() {  currentColors.back = getRandomBackgroundRgb()  currentColors.one = getRandomRgb()  currentColors.two = getRandomRgb()  currentColors.three = getRandomRgb()  editorWrapper.style.background = `rgb(${currentColors.back.r},${currentColors.back.g},${currentColors.back.b})`  for (let color of colorOne) {    color.style.color = `rgb(${currentColors.one.r},${currentColors.one.g},${currentColors.one.b})`  }  for (let color of colorTwo) {    color.style.color = `rgb(${currentColors.two.r},${currentColors.two.g},${currentColors.two.b})`  }  for (let color of colorThree) {    color.style.color = `rgb(${currentColors.three.r},${currentColors.three.g},${currentColors.three.b})`  }}

And the supplement methods (functions if you call them) that we used above.

function getRandomRgb() {  return {    r: Math.round(Math.random()*205 + 50), // number between 50 and 255    g: Math.round(Math.random()*205 + 50),    b: Math.round(Math.random()*205 + 50),  }}function getRandomBackgroundRgb() {  return {    r: Math.round(Math.random()*50), // number between 0 and 50    g: Math.round(Math.random()*50),    b: Math.round(Math.random()*50),  }}

And the method that does it all. The predictThemeCombinations() function uses the user’s liked history trained network to get colours and themes that they would probably like. Take some time to understand the working of this method. It’s simple but clever.

function predictThemeCombinations() {  const data = JSON.parse(window.localStorage.trainingData)  if (!data.length) {    return;  }  themes.innerHTML = ""  const net = new brain.NeuralNetwork({activation: "leaky-relu"});  const results = []  net.train(data)  for (let i = 0; i < 100000; i++) {    const back = getRandomBackgroundRgb()    const one = getRandomRgb()    const two = getRandomRgb()    const three = getRandomRgb()    const colors = [      Math.round(back.r/2.55) / 100, // divide by 255 and round to 2 decimal places      Math.round(back.g/2.55) / 100,      Math.round(back.b/2.55) / 100,      Math.round(one.r/2.55) / 100,      Math.round(one.g/2.55) / 100,      Math.round(one.b/2.55) / 100,      Math.round(two.r/2.55) / 100,      Math.round(two.g/2.55) / 100,      Math.round(two.b/2.55) / 100,      Math.round(three.r/2.55) / 100,      Math.round(three.g/2.55) / 100,      Math.round(three.b/2.55) / 100,    ]    const [ score ] = net.run(colors)    results.push({ back, one, two, three, score})  }

Also, we have 100,000 results and we don’t just want to show the user the first one. A top 20 recommendations would be better. So we just sort the results and show the top 20 results from them.

// sort results  const sortedResults = results.sort(function(a, b) {    var a = a.score    var b = b.score    return b - a  })  // keep the top 20 results after sorting  for (let i = 0; i < 20; i++) {    addNewTheme(sortedResults[i])  }}

addNewTheme()? Well, this method would also contain a lot of HTML and would be a bit ugly. But in essence, we are just using the colour themes in the HTML components, using this.

function addNewTheme({back, one, two, three, score}) {  const newTheme = document.createElement("div")  newTheme.classList.add("predicted-theme")  newTheme.innerHTML = `  <div class="editor-wrapper" style="background:rgb(${back.r}, ${back.g}, ${back.b})">    <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">import</span> React <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">from</span> <span style="color: rgb(${two.r}, ${two.g}, ${two.b})">"react"</span><br/>    <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">import</span> ReactDOM <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">from</span> <span style="color: rgb(${two.r}, ${two.g}, ${two.b})">"react-dom"</span><br/>    <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">import {</span> Provider <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">} from</span> <span style="color: rgb(${two.r}, ${two.g}, ${two.b})">"react-redux"</span><br/>    <br/>    <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">import</span> Layout <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">from</span> <span style="color: rgb(${two.r}, ${two.g}, ${two.b})">"./components/Layout"</span><br/>    <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">import</span> store <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">from</span> <span style="color: rgb(${two.r}, ${two.g}, ${two.b})">"./store"</span><br/>    <br/>    <span style="color:rgb(${one.r}, ${one.g}, ${one.b})">const</span> app<span style="color:rgb(${one.r}, ${one.g}, ${one.b})"> = </span>document.<span style="color: rgb(${three.r}, ${three.g}, ${three.b})">getElementById</span><span style="color:rgb(${one.r}, ${one.g}, ${one.b})">(</span><span style="color: rgb(${two.r}, ${two.g}, ${two.b})">'app'</span><span style="color:rgb(${one.r}, ${one.g}, ${one.b})">)</span><br/>    <br/>    ReactDOM.render<span style="color:rgb(${one.r}, ${one.g}, ${one.b})">(</span>&lt;<span style="color: rgb(${three.r}, ${three.g}, ${three.b})">Provider store={</span>store<span style="color: rgb(${three.r}, ${three.g}, ${three.b})">}</span>&gt;<br/>    &nbsp;&nbsp;&lt;<span style="color: rgb(${three.r}, ${three.g}, ${three.b})">Layout </span>/&gt;<br/>    &lt;/<span style="color: rgb(${three.r}, ${three.g}, ${three.b})">Provider</span>&gt;, app<span style="color:rgb(${one.r}, ${one.g}, ${one.b})"">)</span>  </div>  <li>Score ${score}</li>  <li>Background rgb(${back.r}, ${back.g}, ${back.b})</li>  <li>Color 1 rgb(${one.r}, ${one.g}, ${one.b})</li>  <li>Color 2 rgb(${two.r}, ${two.g}, ${two.b})</li>  <li>Color 3 rgb(${three.r}, ${three.g}, ${three.b})</li>  `  themes.appendChild(newTheme)}

Did I say a bit ugly? Well, what can I say…

And believe me, this method does nothing but set colours that we get in our Top 20 list and sets them up in the components.

Again, the complete code can be found in this GitHub repository.

So in nutshell

We gave the user the choices to choose a background colour and text colour with its variants. Then the user rated the combination of that choice shown as a theme. We took that choice to train our model to present themes that align with the user’s choices.

The model simply makes more combinations of the colours the users choose or the colours that are similar and is trained with that. This lets it make a list of other possible choices for the user. Then we get the top choices using sorting and display the top 20.

And the result?

In case we’re meeting for the first time here, I am Pradyuman Dixit and I mostly write about Machine learning, Android Development and sometimes about Web Development.

You can read my other Machine Learning posts here:

How to make a simple Machine Learning Website from Scratch

How to understand machine learning with simple code examples

How to make a Machine Learning Recommendation System from scratch was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.

Publication date: 
05/16/2019 - 20:56