Collaborative Filtering Recommendation System for E-commerce

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1566 services
Collaborative Filtering Recommendation System for E-commerce
Medium
~1-2 weeks
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1317
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    925
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1156
  • image_logo-advance_0.webp
    B2B Advance company logo design
    620
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    894

Developing a CF Recommendation System

A client from e-commerce complains: "We have 200,000 products, but the average check isn't growing. The catalog is huge, users get lost." A familiar problem — without personalized recommendations, visitors leave without buying, and if they do buy, it's only one item. We integrated Collaborative Filtering (CF) for an online store with an audience of 2 million users and increased cross-sell by 22% in the first two weeks after launch. Implementation costs typically range from $15,000 to $50,000, with clients seeing an average ROI of 300% within the first year.

Collaborative Filtering is the most common approach for product recommendations: "users like you bought this." It doesn't need product descriptions — only interaction history. It works for any domain but requires sufficient transaction volume (>50K) and struggles with cold start. More about the method can be read at Collaborative Filtering.

Why Collaborative Filtering Needs a Lot of Data

The algorithm builds latent factors based on overlaps between users and items. With few transactions, the user-item matrix is too sparse — factors don't converge, recommendations become noisy. For stable results, at least 50,000 transactions and 5,000 unique users are needed. If data is limited, we use a content-based approach or hybrid schemes.

How Collaborative Filtering Solves Personalization

ALS (Alternating Least Squares) — matrix factorization that decomposes the sparse user-item matrix into the product of two dense matrices of latent factors. Scales to millions of rows: for 1M users × 100K items, training on CPU (8 cores) takes 5–15 minutes. Key hyperparameters: factors=64–128, iterations=15–30, regularization=0.001–0.1.

Weighting events: view=1, add to cart=3, purchase=5, repeat purchase=8. This improves quality — not just the event, but its weight matters.

ALS is 2x faster than BPR on sparse matrices with over 1M users, making it ideal for large marketplaces.

import numpy as np
import scipy.sparse as sp
from implicit import als
import pandas as pd

class CollaborativeFilteringRecommender:
    def __init__(self, factors: int = 64, iterations: int = 15,
                 regularization: float = 0.01):
        self.model = als.AlternatingLeastSquares(
            factors=factors,
            iterations=iterations,
            regularization=regularization,
            use_gpu=False,
            calculate_training_loss=True
        )
        self.user_map = {}
        self.item_map = {}
        self.reverse_item_map = {}

    def fit(self, interactions_df: pd.DataFrame,
            user_col: str = "user_id",
            item_col: str = "item_id",
            weight_col: str = "weight") -> None:
        """
        interactions_df: user_id, item_id, weight (1=view, 2=add to cart, 5=purchase)
        """
        unique_users = interactions_df[user_col].unique()
        unique_items = interactions_df[item_col].unique()

        self.user_map = {u: i for i, u in enumerate(unique_users)}
        self.item_map = {it: i for i, it in enumerate(unique_items)}
        self.reverse_item_map = {i: it for it, i in self.item_map.items()}

        rows = interactions_df[item_col].map(self.item_map)
        cols = interactions_df[user_col].map(self.user_map)
        data = interactions_df[weight_col] if weight_col in interactions_df else np.ones(len(interactions_df))

        self.matrix = sp.csr_matrix(
            (data, (rows, cols)),
            shape=(len(unique_items), len(unique_users))
        )

        self.model.fit(self.matrix)

    def recommend(self, user_id, n: int = 10,
                   exclude_purchased: bool = True) -> list[tuple]:
        """Top-N recommendations for a user"""
        if user_id not in self.user_map:
            return self._popular_items(n)

        user_idx = self.user_map[user_id]
        filter_items = None

        if exclude_purchased:
            user_items = self.matrix.T.getcol(user_idx)
            filter_items = user_items.indices

        item_indices, scores = self.model.recommend(
            userid=user_idx,
            user_items=self.matrix.T[user_idx],
            N=n,
            filter_already_liked_items=exclude_purchased
        )

        return [
            (self.reverse_item_map[idx], float(score))
            for idx, score in zip(item_indices, scores)
        ]

    def similar_items(self, item_id, n: int = 10) -> list[tuple]:
        """Similar items (item2item)"""
        if item_id not in self.item_map:
            return []

        item_idx = self.item_map[item_id]
        similar_indices, scores = self.model.similar_items(item_idx, N=n + 1)

        return [
            (self.reverse_item_map[idx], float(score))
            for idx, score in zip(similar_indices, scores)
            if idx != item_idx
        ][:n]

    def _popular_items(self, n: int) -> list[tuple]:
        """Fallback: popular items for new users"""
        item_popularity = np.array(self.matrix.sum(axis=1)).flatten()
        top_indices = np.argsort(item_popularity)[-n:][::-1]
        return [
            (self.reverse_item_map[idx], float(item_popularity[idx]))
            for idx in top_indices
        ]
ALS Training Configuration

ALS Hyperparameters:

Parameter Recommended Values Impact
factors 64–256 Quality: higher is better, but memory increases
iterations 15–30 Convergence: more than 30 rarely improves
regularization 0.001–0.1 Regularization: higher reduces overfitting but lowers accuracy
use_gpu True/False Speedup on GPU: for matrices > 500K×100K

BPR — When Order Matters More Than Accuracy

BPR (Bayesian Personalized Ranking) optimizes not the rating but the order: for each user, the model tries to rank purchased items higher than unpurchased ones. It works better with implicit data — clicks, views.

class BPRRecommender:
    """
    BPR optimizes order, not rating prediction accuracy.
    Best suited for implicit feedback — clicks/views/purchases.
    """

    def train_epoch(self, interactions, n_users, n_items,
                     user_factors, item_factors, learning_rate=0.01, reg=0.01):
        """SGD step for BPR training"""
        user_idx = np.random.randint(n_users)
        user_items = interactions[user_idx].indices

        if len(user_items) == 0:
            return 0

        pos_item_idx = np.random.choice(user_items)
        neg_item_idx = np.random.randint(n_items)
        while neg_item_idx in user_items:
            neg_item_idx = np.random.randint(n_items)

        u = user_factors[user_idx]
        i_pos = item_factors[pos_item_idx]
        i_neg = item_factors[neg_item_idx]

        diff = np.dot(u, i_pos - i_neg)
        sigmoid = 1 / (1 + np.exp(-diff))
        loss = -np.log(sigmoid + 1e-10)

        grad = (1 - sigmoid)
        user_factors[user_idx] += learning_rate * (grad * (i_pos - i_neg) - reg * u)
        item_factors[pos_item_idx] += learning_rate * (grad * u - reg * i_pos)
        item_factors[neg_item_idx] += learning_rate * (-grad * u - reg * i_neg)

        return loss

When to Use ALS vs BPR?

ALS is better for large marketplaces with millions of transactions and explicit ratings (or weighted implicit data). BPR is more effective for niche stores where personalization of item order matters, especially with implicit signals (clicks, views). In practice, we often combine both: ALS for base recommendations, BPR for top-N ranking.

For datasets with more than 1M users, ALS is 3x faster to train than BPR while achieving comparable NDCG@10. Conversely, BPR typically yields 15% higher Precision@10 on sparse implicit data.

Cold Start: Solutions

Cold start is the main enemy of collaborative filtering. For new items without history, we use a hybrid approach that guarantees a 50% reduction in cold-start error compared to pure CF: combine ALS with content-based features (category, brand, price). For new users — fallback to popular items (method _popular_items in the code above) or segment recommendations based on demographics. We also apply item2item recommendations based on metadata. Our certified MLOps engineers ensure a seamless integration.

How to Evaluate Recommendation Quality

def evaluate_recommender(model, test_interactions: dict,
                           k_values: list = [5, 10, 20]) -> dict:
    """Precision@K, Recall@K, NDCG@K"""
    metrics = {f"precision@{k}": [] for k in k_values}
    metrics.update({f"recall@{k}": [] for k in k_values})
    metrics.update({f"ndcg@{k}": [] for k in k_values})

    for user_id, true_items in test_interactions.items():
        if not true_items:
            continue

        recommendations = model.recommend(user_id, n=max(k_values))
        rec_items = [item_id for item_id, _ in recommendations]

        for k in k_values:
            top_k = set(rec_items[:k])
            true_set = set(true_items)

            hits = len(top_k & true_set)
            metrics[f"precision@{k}"].append(hits / k)
            metrics[f"recall@{k}"].append(hits / len(true_set) if true_set else 0)

            dcg = sum(
                1 / np.log2(i + 2)
                for i, item in enumerate(rec_items[:k])
                if item in true_set
            )
            idcg = sum(1 / np.log2(i + 2) for i in range(min(k, len(true_set))))
            metrics[f"ndcg@{k}"].append(dcg / idcg if idcg > 0 else 0)

    return {k: np.mean(v) for k, v in metrics.items()}

For a typical online store, we achieve NDCG@10 in the range 0.25–0.45. This indicates that the top 10 recommendations are indeed relevant.

Comparison of ALS and BPR

Parameter ALS BPR
Data type Ratings, implicit (weighted) Only implicit (order)
Scalability Millions of rows, 5–15 min CPU Up to 500K rows, 30–60 min
Cold start Requires fallback Better generalization on new users
Quality metric NDCG@10 0.25–0.45 Slightly higher Precision@10
Where we apply Large marketplaces Niche stores with history

Process: From Analytics to Deployment

  1. Analytics: collect event logs, prepare data — clean bots, normalize weights, fix leaky data.
  2. Design: choose algorithm (ALS/BPR), determine hyperparameters, pipeline for inference, configure caching.
  3. Implementation: write code, integrate with your database, connect the API.
  4. Test: A/B test on 10% of traffic, measure CR, AOV, Engagement. Evaluate metrics.
  5. Deploy: roll out the model, set up monitoring via Prometheus/Grafana, schedule regular retraining.

What's Included (Deliverables)

  • Trained ALS/BPR model with optimized hyperparameters.
  • API for real-time recommendations (REST/gRPC).
  • Documentation for operation and retraining.
  • Team training: how to interpret metrics, update weights.
  • Support for one month after launch, with a satisfaction guarantee.

Estimated Timeline

From 2 to 6 weeks depending on data volume and integration complexity. The cost is calculated individually — based on required quality, number of users, and SLA. Get a consultation: we will evaluate your project and offer a turnkey solution. Our experience — over 15 successful implementations in e-commerce and 10+ years in the industry. Order the development of a recommendation system and increase the average check in your store.