MACHINE LEARNING

How to Improve Recommendations Using User Feedback and Retraining with Neural Collaborative Filtering

Learn how to build a smarter recommendation engine using user ratings, feedback loops, and model retraining with Neural Collaborative Filtering in JavaScript.

Awesome! Let’s implement a feedback loop so users can rate movies, and the system will retrain the model to improve future recommendations.

🎯 Goal

Allow users to:

  1. View movie recommendations.
  2. Rate recommended movies.
  3. Store ratings in a database or file.
  4. Periodically retrain the model using the updated ratings data.

🧱 Architecture Overview

[React UI] ⇄ [Node.js API] ⇄ [ratings.json or DB] ⇄ [TF.js Training Script]
                             ↑
                      User feedback loop

1️⃣ React UI: Add Rating Input

client/src/App.jsx (add feedback UI)

function App() {
  const [userId, setUserId] = useState("");
  const [recommendations, setRecommendations] = useState([]);

  const getRecommendations = async () => {
    const res = await axios.get(`http://localhost:5000/api/recommend/${userId}`);
    setRecommendations(res.data);
  };

  const sendRating = async (movieId, rating) => {
    await axios.post(`http://localhost:5000/api/rate`, {
      userId: parseInt(userId),
      movieId,
      rating,
    });
    alert("Rating submitted!");
  };

  return (
    <div className="p-6 max-w-md mx-auto">
      <h1 className="text-2xl mb-4 font-bold">Movie Recommendations</h1>
      <input
        type="number"
        value={userId}
        onChange={(e) => setUserId(e.target.value)}
        placeholder="Enter user ID"
        className="border p-2 w-full"
      />
      <button onClick={getRecommendations} className="mt-2 bg-blue-500 text-white px-4 py-2">
        Recommend
      </button>

      <ul className="mt-4">
        {recommendations.map((rec) => (
          <li key={rec.id}>
            <div>{rec.title} — Score: {rec.score.toFixed(2)}</div>
            <select onChange={(e) => sendRating(rec.id, e.target.value)}>
              <option value="">Rate</option>
              {[1,2,3,4,5].map((r) => <option key={r} value={r}>{r}</option>)}
            </select>
          </li>
        ))}
      </ul>
    </div>
  );
}

2️⃣ Node.js API: Handle Ratings

server/routes/ratings.js

const express = require('express');
const fs = require('fs');
const router = express.Router();

const RATINGS_PATH = 'data/user_ratings.json';

router.post('/rate', (req, res) => {
  const { userId, movieId, rating } = req.body;

  let ratings = [];
  if (fs.existsSync(RATINGS_PATH)) {
    ratings = JSON.parse(fs.readFileSync(RATINGS_PATH));
  }

  ratings.push({ userId, movieId, rating, timestamp: Date.now() });
  fs.writeFileSync(RATINGS_PATH, JSON.stringify(ratings, null, 2));

  res.sendStatus(200);
});

module.exports = router;

Update server/index.js

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { loadModel, recommendForUser } = require('./model/recommender');
const ratingsRoute = require('./routes/ratings');

const app = express();
app.use(cors());
app.use(bodyParser.json());

app.use('/api', ratingsRoute);

app.get('/api/recommend/:userId', async (req, res) => {
  const userId = parseInt(req.params.userId);
  const recommendations = await recommendForUser(userId);
  res.json(recommendations);
});

3️⃣ Periodic Retraining Script

Create a script to retrain the model from both:

  • u.data
  • user_ratings.json

server/model/retrain.js

const tf = require('@tensorflow/tfjs-node');
const fs = require('fs');

async function loadData() {
  const original = fs.readFileSync('data/u.data', 'utf8')
    .split('\n')
    .filter(Boolean)
    .map(l => {
      const [user, item, rating] = l.split('\t');
      return { user: +user, item: +item, rating: +rating };
    });

  const feedback = fs.existsSync('data/user_ratings.json')
    ? JSON.parse(fs.readFileSync('data/user_ratings.json'))
    : [];

  return original.concat(feedback);
}

async function retrain() {
  const data = await loadData();
  const numUsers = Math.max(...data.map(d => d.user)) + 1;
  const numItems = Math.max(...data.map(d => d.item)) + 1;

  const userTensor = tf.tensor1d(data.map(d => d.user), 'int32');
  const itemTensor = tf.tensor1d(data.map(d => d.item), 'int32');
  const ratingTensor = tf.tensor2d(data.map(d => d.rating), [data.length, 1]);

  const embeddingDim = 8;
  const model = createModel(numUsers, numItems, embeddingDim);
  model.compile({ optimizer: tf.train.adam(0.01), loss: 'meanSquaredError' });

  await model.fit([userTensor, itemTensor], ratingTensor, {
    epochs: 10,
    batchSize: 64,
  });

  await model.save('file://server/model');
  console.log("Model retrained and saved.");
}

function createModel(numUsers, numItems, embeddingDim) {
  const userInput = tf.input({ shape: [1] });
  const itemInput = tf.input({ shape: [1] });

  const userEmbed = tf.layers.embedding({ inputDim: numUsers, outputDim: embeddingDim }).apply(userInput);
  const itemEmbed = tf.layers.embedding({ inputDim: numItems, outputDim: embeddingDim }).apply(itemInput);

  const merged = tf.layers.concatenate().apply([
    tf.layers.flatten().apply(userEmbed),
    tf.layers.flatten().apply(itemEmbed),
  ]);

  const dense = tf.layers.dense({ units: 32, activation: 'relu' }).apply(merged);
  const output = tf.layers.dense({ units: 1 }).apply(dense);

  return tf.model({ inputs: [userInput, itemInput], outputs: output });
}

retrain();

Run with:

node server/model/retrain.js

Or schedule it with a cron job or trigger manually from admin UI.

✅ Summary

You now have:

  • A UI where users rate movies.
  • Ratings saved via API to user_ratings.json.
  • A script to retrain the neural model with feedback.

🔜 Next Ideas

  • ✅ Store feedback in SQLite/PostgreSQL
  • ✅ Add UI feedback summary (rated movies)
  • ✅ Retrain automatically after N ratings
  • ✅ Add user profile features (age, genre preference)

Leave a Reply

Your email address will not be published. Required fields are marked *