Learn how to use matrix factorization and collaborative filtering to build a more personalized, scalable recommendation engine in JavaScript with TensorFlow.js.
🧠 Introduction
While content-based filtering relies on item features, collaborative filtering uses user-item interactions—ratings, views, clicks—to predict preferences.
One of the most powerful collaborative filtering techniques is Matrix Factorization, where we decompose a user-item matrix into latent factors that describe preferences and attributes.
We’ll use TensorFlow.js to implement a lightweight version directly in JavaScript.
🧾 What is Matrix Factorization?
You start with a user-item matrix:
Hobbit | 1984 | Dune | BNW | |
---|---|---|---|---|
UserA | 5 | ? | 3 | ? |
UserB | ? | 4 | ? | 5 |
Your goal is to predict the missing values (the “?”) using:
R ≈ U × Vᵗ
Where:
- R is the user-item matrix
- U is the user-factor matrix (preferences)
- V is the item-factor matrix (attributes)
🛠️ Step-by-Step: Collaborative Filtering in JavaScript
🔢 Step 1: Define Ratings Data
const ratings = [
{ user: 0, item: 0, rating: 5 },
{ user: 0, item: 2, rating: 3 },
{ user: 1, item: 1, rating: 4 },
{ user: 1, item: 3, rating: 5 }
];
const numUsers = 2;
const numItems = 4;
const numFactors = 3;
🧠 Step 2: Initialize User and Item Matrices
const tf = require('@tensorflow/tfjs-node'); // or use CDN in browser
let userMatrix = tf.variable(tf.randomNormal([numUsers, numFactors]));
let itemMatrix = tf.variable(tf.randomNormal([numItems, numFactors]));
🔁 Step 3: Training with Gradient Descent
const learningRate = 0.01;
const optimizer = tf.train.adam(learningRate);
const epochs = 200;
function loss() {
return tf.tidy(() => {
const predictions = tf.gather(userMatrix, ratings.map(r => r.user))
.mul(tf.gather(itemMatrix, ratings.map(r => r.item)))
.sum(1);
const labels = tf.tensor1d(ratings.map(r => r.rating));
return predictions.sub(labels).square().mean();
});
}
async function train() {
for (let i = 0; i < epochs; i++) {
optimizer.minimize(loss);
if (i % 50 === 0) {
const l = loss().dataSync()[0];
console.log(`Epoch ${i}: Loss = ${l.toFixed(4)}`);
}
}
}
🔍 Step 4: Make Predictions
async function recommend(userId, topN = 2) {
await train(); // ensure training is done
const userVec = userMatrix.slice([userId, 0], [1, numFactors]);
const scores = tf.matMul(userVec, itemMatrix.transpose()).dataSync();
const results = scores
.map((score, i) => ({ item: i, score }))
.sort((a, b) => b.score - a.score)
.slice(0, topN);
console.log(`Recommendations for User ${userId}:`, results);
}
📊 Sample Output
Epoch 0: Loss = 12.8423
Epoch 50: Loss = 1.9382
...
Recommendations for User 0:
[ { item: 1, score: 4.72 }, { item: 3, score: 3.96 } ]
✅ Benefits of Matrix Factorization
- Captures hidden relationships (e.g., “likes sci-fi” or “enjoys drama”)
- Learns user taste implicitly
- Scales to large datasets
🚀 Advanced Ideas
- 🧠 Add regularization to prevent overfitting
- 🗃️ Use real datasets like MovieLens
- 💾 Store model weights in browser
localStorage
or server - 🖼️ Visualize learned latent factors
- 🌐 Convert to TensorFlow Lite for edge deployment
🧩 Summary
By adding collaborative filtering with matrix factorization, your JavaScript recommendation engine becomes much smarter and more personalized. It’s a solid stepping stone toward full production-grade systems.
You’ve now built:
- A content-based recommender
- A feedback loop for personalization
- A collaborative filtering engine with real ML training