Introduction to Diversity in Recommendation Systems

Welcome to today's lesson on diversity in recommendation systems. In previous lessons, we explored coverage and novelty metrics. Now, we will dive into diversity, an equally important concept that is crucial for enhancing user satisfaction and engagement with recommendation systems. By ensuring that users receive a diverse range of recommendations, we maintain their interest and cater to varied tastes, which ultimately leads to a richer user experience.

Setup

Before we dive into the code, let's ensure we have the necessary setup in place. For this lesson, we need user predictions and item vectors. In C++, we will use std::map to store user predictions and item vectors, and we will use Eigen's VectorXd to represent item characteristics in a multi-dimensional space.

Here is a brief setup using C++ data structures:

#include <map>
#include <vector>
#include <string>
#include <Eigen/Dense>

using Eigen::VectorXd;

// Example user predictions: each user receives a list of recommended items
std::map<std::string, std::vector<std::string>> user_predictions = {
    {"user1", {"item1", "item2", "item3"}},
    {"user2", {"item2", "item3", "item4"}},
    {"user3", {"item1", "item4", "item5"}}
};

// Example item vectors representing characteristics of items in a multi-dimensional space
std::map<std::string, VectorXd> item_vectors;
item_vectors["item1"] = VectorXd(3); item_vectors["item1"] << 1, 0, 0;
item_vectors["item2"] = VectorXd(3); item_vectors["item2"] << 0, 1, 0;
item_vectors["item3"] = VectorXd(3); item_vectors["item3"] << 0, 0, 1;
item_vectors["item4"] = VectorXd(3); item_vectors["item4"] << 1, 1, 0;
item_vectors["item5"] = VectorXd(3); item_vectors["item5"] << 0, 1, 1;

These data structures are essential for calculating diversity and should be initialized in your environment beforehand.

Cosine Similarity Revisit
Theoretical Foundation of Diversity

Diversity in recommendation systems measures how dissimilar the recommended items are from each other within a user's recommendation list. A diverse recommendation set contains items that cover different categories, genres, or characteristics, preventing the system from showing only very similar items to users.

Mathematical Formulation
Interpretation of Diversity Scores
  • High Diversity (close to 1.0): Items in recommendation lists are very different from each other. Users receive varied recommendations spanning different categories or characteristics.

  • Low Diversity (close to 0.0): Items in recommendation lists are very similar to each other. Users receive homogeneous recommendations that may lead to monotony.

  • Moderate Diversity (around 0.5): A balanced mix of similar and dissimilar items, which often provides a good user experience.

Why Diversity Matters
  1. User Engagement: Diverse recommendations prevent user boredom and maintain engagement over time.

  2. Exploration: Diversity encourages users to discover new types of content they might not have considered.

  3. Avoiding Filter Bubbles: High similarity can trap users in narrow content bubbles, limiting their exposure to varied options.

  4. Business Value: Diverse recommendations can lead to increased sales across different product categories.

Step-by-Step Code Walkthrough: Part 1

Let's break down the diversity function and understand its components. We process each user individually, transforming their list of recommended items into vectors using the item_vectors map, and then immediately calculate similarities for that user.

In C++, we use a loop to iterate over each user, and for each user, we collect the corresponding Eigen vectors:

for (const auto& pair : predictions) {
    std::vector<VectorXd> user_item_vectors;
    for (const std::string& item_name : pair.second) {
        if (item_vectors.count(item_name)) {
            user_item_vectors.push_back(item_vectors.at(item_name));
        }
    }
    // Process this user's vectors immediately (shown in next section)
}

This approach processes one user at a time, creating a vector of Eigen representations for each user's recommended items and then immediately calculating similarities before moving to the next user.

Calculating Similarities

After collecting each user's recommended items into vectors, we immediately calculate the pairwise cosine similarity for those vectors. In C++, we do this by iterating over all unique pairs of item vectors for the current user and computing their cosine similarity.

To exclude self-similarity (where an item is compared to itself), we only consider pairs where the indices are different. For each user, we sum the similarities for all unique pairs and keep track of the total number of such pairs across all users.

Here is how you can perform this calculation within the user loop in C++:

double total_similarity = 0.0;
int count = 0;

for (const auto& pair : predictions) {
    std::vector<VectorXd> user_item_vectors;
    for (const std::string& item_name : pair.second) {
        if (item_vectors.count(item_name)) {
            user_item_vectors.push_back(item_vectors.at(item_name));
        }
    }
    
    if (user_item_vectors.size() < 2) continue;
    
    for (size_t i = 0; i < user_item_vectors.size(); ++i) {
        for (size_t j = i + 1; j < user_item_vectors.size(); ++j) {
            double sim = cosine_similarity(user_item_vectors[i], user_item_vectors[j]);
            total_similarity += sim;
            count++;
        }
    }
}

By only considering pairs where i < j, we avoid self-similarity and double-counting.

Step-by-Step Code Walkthrough: Part 2

Now, let's implement the full logic for calculating diversity. We will define a function to compute cosine similarity between two Eigen vectors, and then use the approach described above to accumulate the total similarity and count of pairs.

Here is the C++ code for these steps:

// Function to calculate cosine similarity between two Eigen vectors
double cosine_similarity(const VectorXd& a, const VectorXd& b) {
    if (a.norm() == 0 || b.norm() == 0) return 0.0;
    return a.dot(b) / (a.norm() * b.norm());
}

// Diversity calculation function
double diversity(const std::map<std::string, std::vector<std::string>>& predictions,
                 const std::map<std::string, VectorXd>& item_vectors) {
    double total_similarity = 0.0;
    int count = 0;

    for (const auto& pair : predictions) {
        std::vector<VectorXd> user_item_vectors;
        for (const std::string& item_name : pair.second) {
            if (item_vectors.count(item_name)) {
                user_item_vectors.push_back(item_vectors.at(item_name));
            }
        }

        if (user_item_vectors.size() < 2) continue;

        for (size_t i = 0; i < user_item_vectors.size(); ++i) {
            for (size_t j = i + 1; j < user_item_vectors.size(); ++j) {
                total_similarity += cosine_similarity(user_item_vectors[i], user_item_vectors[j]);
                count++;
            }
        }
    }

    double average_similarity = (count != 0) ? total_similarity / count : 0.0;
    return 1.0 - average_similarity;
}

This function processes each user's recommendations individually, computes pairwise similarities for each user, and returns the overall diversity score.

Full Code Snippet

Here is the complete code for calculating diversity in recommendation systems using C++ with Eigen:

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <iomanip>
#include <Eigen/Dense>

using Eigen::VectorXd;

// Function to calculate cosine similarity between two Eigen vectors
double cosine_similarity(const VectorXd& a, const VectorXd& b) {
    if (a.norm() == 0 || b.norm() == 0) return 0.0;
    return a.dot(b) / (a.norm() * b.norm());
}

// Diversity calculation
double diversity(const std::map<std::string, std::vector<std::string>>& predictions,
                 const std::map<std::string, VectorXd>& item_vectors) {
    double total_similarity = 0.0;
    int count = 0;

    for (const auto& pair : predictions) {
        std::vector<VectorXd> user_item_vectors;
        for (const std::string& item_name : pair.second) {
            if (item_vectors.count(item_name)) {
                user_item_vectors.push_back(item_vectors.at(item_name));
            }
        }

        if (user_item_vectors.size() < 2) {
            continue;
        }

        for (size_t i = 0; i < user_item_vectors.size(); ++i) {
            for (size_t j = i + 1; j < user_item_vectors.size(); ++j) {
                total_similarity += cosine_similarity(user_item_vectors[i], user_item_vectors[j]);
                count++;
            }
        }
    }

    double average_similarity = (count != 0) ? total_similarity / count : 0.0;
    return 1.0 - average_similarity;
}

int main() {
    // Example data
    std::map<std::string, std::vector<std::string>> user_predictions = {
        {"user1", {"item1", "item2", "item3"}},
        {"user2", {"item2", "item3", "item4"}},
        {"user3", {"item1", "item4", "item5"}}
    };

    std::map<std::string, VectorXd> item_vectors;
    item_vectors["item1"] = VectorXd(3); item_vectors["item1"] << 1, 0, 0;
    item_vectors["item2"] = VectorXd(3); item_vectors["item2"] << 0, 1, 0;
    item_vectors["item3"] = VectorXd(3); item_vectors["item3"] << 0, 0, 1;
    item_vectors["item4"] = VectorXd(3); item_vectors["item4"] << 1, 1, 0;
    item_vectors["item5"] = VectorXd(3); item_vectors["item5"] << 0, 1, 1;

    // Calculate the diversity score
    double diversity_score = diversity(user_predictions, item_vectors);
    std::cout << "Diversity: " << std::fixed << std::setprecision(2) << diversity_score << std::endl;

    return 0;
}

This code incorporates each step discussed previously and is ready to be compiled and executed.

Calculating and Interpreting the Diversity Score

After implementing the function, we can calculate the diversity score by calling the diversity function and printing the result using std::cout:

double diversity_score = diversity(user_predictions, item_vectors);
std::cout << "Diversity: " << std::fixed << std::setprecision(2) << diversity_score << std::endl;

Output:

Diversity: 0.67

A diversity score close to 1 indicates a high diversity level, meaning the recommended items are quite different. Conversely, a score near 0 indicates a lack of diversity.

Conclusion and Next Steps

In this lesson, we've explored the concept of diversity in recommendation systems, learned about cosine similarity, and understood how to calculate a diversity score with a practical C++ code example. Understanding diversity is essential, as it enhances the robustness and appeal of recommendation systems.

Now, you're encouraged to proceed to the practice exercises, where you can apply these concepts using different datasets and configurations. Congratulations on progressing through the lesson, and keep up the strong momentum in your learning journey!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal