Recommendation Engines
Practical Techniques: Content-Based Filtering, Collaborative Filtering and Advanced Systems (Hybrid).
According to Mordor Intelligence, the Recommendation Engine market was estimated to be worth 4 billion USD in 2022, and it is expected to grow more than four times in the next five years, reaching around 21 billion USD by 2028.
With an overload of data, and numerous products and items for users to choose from, recommendation engines assist users in decision-making by providing options that best suit their interests.
To build a recommendation engine, there are three major modelling techniques:
1. Content based Filtering: This technique collects data from past activities or products you have used, as well as information about the user to make recommendations.
The disadvantage of content-based filtering is that, in business problems where there is little or no information about the user or products, using this technique results in inefficient recommender systems.
In an effort to fix this problem, another widely used technique is the collaborative filtering system, which does not rely on information about the user or the product.
2. Collaborative Filtering: This technique filters products based on the similarities between users and items. Its a dynamic approach that finds the relationship between users and items, and recommends preferences of similar users, or similar items or both.
3. Advanced systems(Hybrid): This is any technique that makes use of Content-based Filtering, Collaborative Filtering, and other kinds of modelling techniques to improve the recommendation performance.
Please note that every recommendation engine business solution has a preferred technique that best fits its specific problem. While this article models movie recommendation engines using all three techniques for practical purposes, it’s important to choose the technique that best suits your particular business needs.
Practical Examples
We will build movie recommender systems using Python programming language.
Relevant Libraries for this project:
# Relevant Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import nltk
from nltk.corpus import stopwords
from tensorflow.keras import models
from tensorflow.keras import layers
from tensorflow.keras import optimizers
from tensorflow.keras import losses
from tensorflow.keras import regularizers
from tensorflow.keras import metrics
from tensorflow.keras.utils import plot_modelContent based filtering
The “Content” we will be using to make recommendations are the movie; Overview, Genre, Cast, Crew, and Keywords.
Click this link to download the data used for this project.
- Overview: This contains a short summary of the movie.
- Cast: This contains the names of the groups of actors that makeup the movie.
- Crew: This contains the names of the group of people responsible for producing the movie.
- Keywords: This contains the movie plot keywords which describe motifs, themes, character types, plot details, etc.
- Genres: This contains the name(s) of the movie category.
Methodology:
- Get the data
- Data Pre-processing:
- Create data frame with relevant features.
- Merge all “content” into one text.
- Convert the text inputs to a matrix. - Calculate the similarity score of all movies against each other, with cosine similarity.
- Make Recommendation.
- Data Pre-processing:
Load the data.
# Create a variable for the movie file directory.
data_dir = '/Downloads/data/imdb/'
# load metadata csv file
metadata = pd.read_csv(imdb_data_dir + 'movies_metadata.csv')
# Load the keywords and credits data
credits = pd.read_csv(imdb_data_dir + 'credits.csv')
keywords = pd.read_csv(imdb_data_dir + 'keywords.csv')The content of interest are in three different csv files, so we will merge them together.
# Drop rows that disrupts the data from merging
metadata = metadata.drop([19730, 29503, 35587])
# Convert IDs to integers Required for merging
keywords['id'] = keywords['id'].astype('int')
credits['id'] = credits['id'].astype('int')
metadata['id'] = metadata['id'].astype('int')
# Merge keywords and credits into your main metadata dataframe
metadata = metadata.merge(credits, on='id')
metadata = metadata.merge(keywords, on='id')Some of the relevant content is stored in fields that contain lists of python dictionaries. To help extract the exact “content” needed, we first convert these string representation of Python objects to their actual Python object format. Then, we create self-defined functions to get the desired content.
# Parse the string features into their corresponding python objects
from ast import literal_eval
features = ['cast', 'crew', 'keywords', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(literal_eval)
# Function to get the director's name from the crew feature. If director is not listed, return NaN
def get_director(x):
for i in x:
if i['job'] == 'Director':
return i['name']
return np.nan
# A list is passed into this function as "X", and returns the values of that list, but limits it to a maximum of three items.
# If X is not a list, or is empty, the function returns an empty list.
def get_list(x):
if isinstance(x, list):
names = [i['name'] for i in x]
#Check if more than 3 elements exist. If yes, return only first three. If no, return entire list.
if len(names) > 3:
names = names[:3]
return names
#Return empty list in case of missing/malformed data
return []Create a data frame with relevant features: Add the desired content as features in the metadata data frame.
# Defines new director, cast, genres and keywords features in the metadata dataframe.
metadata['director'] = metadata['crew'].apply(get_director)
features = ['cast', 'keywords', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(get_list)Clean the data: Convert all strings to lower case, and remove spaces from names, so multiple names are seen as one variable.
# Function to convert all strings to lower case and strip names of spaces
def clean_data(x):
if isinstance(x, list):
return [str.lower(i.replace(" ", "")) for i in x]
else:
#Check if director exists. If not, return empty string
if isinstance(x, str):
return str.lower(x.replace(" ", ""))
else:
return ''
# Apply clean_data function to the features.
features = ['cast', 'keywords', 'director', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(clean_data)Merge all content into one text: Create a new feature that contains the text of all the content relevant to each movie.
# "Soup" represents a combination of all the text features, relevant to build the recommendation engine
def create_soup(x):
return ' '.join(x['keywords']) + ' ' + ' '.join(x['cast']) + ' ' + x['director'] + ' ' + ' '.join(x['genres'])
# Create a new soup feature in the metadata dataframe
metadata['soup'] = metadata.apply(create_soup, axis=1)Now that we have our input ready, it is time to vectorize the text inputs, so we have a matrix ready for analysis.
To do this we will use the TF-IDF Vectorizer. It is considered one of the most efficient vectorizer algorithm. It helps transform text into a vector, used to train machine learning algorithms.
Mathematical Formula:
Convert the text input to a matrix: Define the tf-idf vectorizer, transform the soup feature, and create the tf-idf matrix.
#Define a TF-IDF Vectorizer Object. Remove all english stop words such as 'the', 'a'
tfidf = TfidfVectorizer(stop_words='english')
#Construct the required TF-IDF matrix by fitting and transforming the data
tfidf_matrix = tfidf.fit_transform(metadata['soup'])
#Output the shape of tfidf_matrix
tfidf_matrix.shape2. Calculate similarity score: Compute the pair-wise cosine similarity between all movies. This finds the similarity between each movie.
# Compute the cosine Similarity matrix
cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)3. Make Recommendations: Create a recommendation function that takes a movie title as input, and outputs the top 10 recommendations using the cosine similarity score.
# Function that takes in movie title as input and outputs most similar movies using the tf-idf scores
def get_recommendations(title, cosine_sim=cosine_sim):
# Get the index of the movie that matches the title
idx = indices[title]
# Get the pairwsie similarity scores of all movies with that movie
sim_scores = list(enumerate(cosine_sim[idx]))
# Sort the movies based on the similarity scores
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
# Get the scores of the 10 most similar movies
sim_scores = sim_scores[1:11]
# Get the movie indices
movie_indices = [i[0] for i in sim_scores]
# Return the top 10 most similar movies
return metadata['title'].iloc[movie_indices]Collaborative filtering
A simple user-based collaborative filtering system gets the movie(s) a user likes. It then finds similar users who have liked the same movie(s) and identifies other movies that these similar users have all liked. Finally, it recommends the top n movies to the user, using either the ratings metric or any other business metric to rank the movies.
In this practical example, we will model an advanced collaborative filtering movie recommender system, that uses deep learning to predict movie ratings based on user and movie embeddings.
The idea behind this approach is to train the data on a neural network and recommend movies to users, using the embeddings learned for each user and movie. Which will be used to make ratings predictions on movies that a user has not yet watched, and recommend the highly rated movies.
Embeddings: This is a process of representing words or texts in a lower-dimensional space, which learns the semantic and syntactic relationship between words.
We would use the famous movie lens dataset for this project.
Get Ovbude Ehi’s stories in your inbox
Join Medium for free to get updates from this writer.
Click this link to download the data.
Methodology:
- Get the data
- Data Pre-processing:
- Create data frame with relevant features.
- Encode the data - Develop the deep learning model
- Train and Evaluate the model
- Compile the model.
- Split the data into a train, test and validation dataset.
- Train the model.
1. Get the data
# Read movies data as csv
movies = pd.read_csv('ml-latest-small/movies.csv')
# Read Ratings data as csv
ratings = pd.read_csv('ml-latest-small/ratings.csv')2. Data Pre-processing:
Create data frame with relevant features, by merging the “top users” (users with the highest ratings), and the “top movies” (movies with the highest rating) into one data frame.
# k variable holds the number of top "k" users and movies
k = 15
# count the number of ratings by each user
g = ratings.groupby('userId')['rating'].count()
# select the top "k" users
top_users = g.sort_values(ascending=False)[:k]
# groups the ratings dataframe by movieId
g = ratings.groupby('movieId')['rating'].count()
# Selects the top "k" movies with the highest number of ratings
top_movies = g.sort_values(ascending=False)[:k]
# Join the ratings dataframe with the top_users and top_movies dataframe
top_r = ratings.join(top_users, rsuffix='_r', how='inner', on='userId')
top_r = top_r.join(top_movies, rsuffix='_r', how='inner', on='movieId')Result:
Although the userId and movieId features are numeric, they are still categorical values, so we encode them in preparation for the deep learning model.
# Encode the userId column
user_enc = LabelEncoder()
ratings['user'] = user_enc.fit_transform(ratings.userId.values)
n_users = ratings['user'].nunique()
# Encode the movieId column
item_enc = LabelEncoder()
ratings['movie'] = user_enc.fit_transform(ratings.movieId.values)
n_movies = ratings['movie'].nunique()Convert the ratings column from float64 to float32 to save memory.
# convert the rating column to float
ratings['rating'] = ratings['rating'].values.astype(np.float32)Calculate the minimum and maximum ratings, which will be used to scale the output of the neural network later.
# Find the minimum and maximum rating
min_rating = min(ratings['rating'])
max_rating = max(ratings['rating'])3. Develop the deep learning model.
Note: The predicted ratings is calculated by multiplying the user and movie embeddings, then adding the user and movie bias.
Define the number of “latent factors” (The number of dimensions for the embedding vectors)
# Number of latent factors
emb_sz = 50Define the user embedding.
# User embeddings
# Input layer
user = layers.Input(shape=(1,))
# Embedding layer
user_emb = layers.Embedding(n_users, emb_sz, embeddings_regularizer=regularizers.l2(1e-6))(user)
# Reshape layer to flatten the embedding vector.
user_emb = layers.Reshape((emb_sz,))(user_emb)To get the user bias, define an embedding layer for the user bias, and reshape it.
# User bias
# Embedding layer
user_bias = layers.Embedding(n_users, 1, embeddings_regularizer=regularizers.l2(1e-6))(user)
# Reshape layer
user_bias = layers.Reshape((1,))(user_bias)Define the movie embedding.
# Movie embeddings
# Input layer
movie = layers.Input(shape=(1,))
# Embedding layer
movie_emb = layers.Embedding(n_movies, emb_sz, embeddings_regularizer=regularizers.l2(1e-6))(movie)
# Reshape layer
movie_emb = layers.Reshape((emb_sz,))(movie_emb)To get the user bias, define an embedding layer for the user bias, and reshape it.
# Movie bias
# Embedding layer
movie_bias = layers.Embedding(n_movies, 1, embeddings_regularizer=regularizers.l2(1e-6))(movie)
# Reshape layer
movie_bias = layers.Reshape((1,))(movie_bias)Calculate the dot product of the user and movie embeddings, to get the predicted ratings.
# Dot product
rating = layers.Concatenate()([user_emb, movie_emb])To get a more accurate ratings prediction, we add the user and movie bias to the ratings.
# Add biases to the ratings
# Adds the user and movie embeddig to the dot product of the embeddings
rating = layers.Add()([rating, user_bias, movie_bias])
# Activation function
rating = layers.Dense(10, activation='relu')(rating)
# Applies dropout regularization to the ratings to reduce overfitting
rating = layers.Dropout(0.5)(rating)
# Produces a rating between 0 and 1
rating = layers.Dense(1, activation='sigmoid')(rating)
# Scales the predicted ratings to a range of 1 - 5
rating = layers.Lambda(lambda x:x*(max_rating - min_rating) + min_rating)(rating)4. Train and Evaluate the model.
Compile the model.
# Model
model = models.Model([user, movie], rating)
# Compile the model
model.compile(loss='mse', metrics=metrics.RootMeanSquaredError(),
optimizer=optimizers.Adam(lr=0.001))Split the data into a train and test dataset.
X = ratings[['user', 'movie']].values
y = ratings['rating'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)Train the model.
# Train the model
model.fit(x=[X_train[:,0], X_train[:,1]], y=y_train,
batch_size=64, epochs=5, verbose=1,
validation_data=([X_test[:,0], X_test[:,1]], y_test))Result:
From the validation result, using the root mean square error (RMSE) evaluation metric, we can say that our deep learning model predicts user ratings with 88% accuracy.
Advanced Systems (Hybrid)
Modern hybrid recommender systems consist of two other techniques, in addition to the two major techniques I mentioned earlier. These techniques are:
- Demographic based recommender systems: This is a method of recommendation, that recommends based on the demographic attributes and characteristics of users.
- Knowledge based recommender systems: This technique recommends products based on domain knowledge. These systems analyze a user’s profile, and identifies their knowledge base.
There are seven well-known approaches for implementing a hybrid recommendation system. Among them, I will briefly highlight three major methodologies that are commonly used.
1. Weighted Hybrid Recommender System:
This approach takes output ratings from different recommender systems, and combines the result to produce a final ratings score.
2. Meta-Level Recommender system:
This approach provides a ‘contributing recommender system’ whose output serves as input to the main recommender system.
3. Switching Hybrid Recommender System:
This approach uses a switching criteria to choose which recommendation system should be employed.
Conclusion
As Artificial Intelligence continues to advance, we can expect more sophisticated methodologies and techniques, using machine learning and natural language processing algorithms, to learn more about the behavior and relationships between people and products. As a result, provide more accurate recommendations for users, leading to increased sales for businesses.
So the next time you get online recommendations, I hope you now have a better understanding of what is happening behind the algorithms.








