Recommending Movies - Retrieval - TensorFlow Recommenders
Recommending Movies - Retrieval - TensorFlow Recommenders
Recommending Movies - Retrieval - TensorFlow Recommenders
| TensorFlow Recommenders
and (https://io.google/2023/program/?utm_source=devsite-hpp&utm_medium=embedded_marketing&utm_campaign=tf_hpp_ondemand_card
1. The retrieval stage is responsible for selecting an initial set of hundreds of candidates from all possible candidates.
The main objective of this model is to efficiently weed out all candidates that the user is not interested in. Because the
retrieval model may be dealing with millions of candidates, it has to be computationally efficient.
2. The ranking stage takes the outputs of the retrieval model and fine-tunes them to select the best possible handful of
recommendations. Its task is to narrow down the set of items the user may be interested in to a shortlist of likely
candidates.
In this tutorial, we're going to focus on the first stage, retrieval. If you are interested in the ranking stage, have a look at our
ranking (/recommenders/examples/basic_ranking) tutorial.
1. A query model computing the query representation (normally a fixed-dimensionality embedding vector) using query
features.
2. A candidate model computing the candidate representation (an equally-sized vector) using the candidate features
The outputs of the two models are then multiplied together to give a query-candidate affinity score, with higher scores
expressing a better match between the candidate and the query.
In this tutorial, we're going to build and train such a two-tower model using the Movielens dataset.
1. Get our data and split it into a training and test set.
4. Export it for efficient serving by building an approximate nearest neighbours (ANN) index.
The dataset
The Movielens dataset is a classic dataset from the GroupLens (https://grouplens.org/datasets/movielens/) research group at
the University of Minnesota. It contains a set of ratings given to movies by a set of users, and is a workhorse of
recommender system research.
1. It can be interpreted as expressesing which movies the users watched (and rated), and which they did not. This is a
form of implicit feedback, where users' watches tell us which things they prefer to see and which they'd rather not see.
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 1/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
2. It can also be seen as expressesing how much the users liked the movies they did watch. This is a form of explicit
feedback: given that a user watched a movie, we can tell roughly how much they liked by looking at the rating they
have given.
In this tutorial, we are focusing on a retrieval system: a model that predicts a set of movies from the catalogue that the user
is likely to watch. Often, implicit data is more useful here, and so we are going to treat Movielens as an implicit system. This
means that every movie a user watched is a positive example, and every movie they have not seen is an implicit negative
example.
Imports
Let's first get our imports out of the way.
import os
import pprint
import tempfile
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
Note that since the MovieLens dataset does not have predefined splits, all data are under train split.
# Ratings data.
ratings = tfds.load("movielens/100k-ratings", split="train")
# Features of all the available movies.
movies = tfds.load("movielens/100k-movies", split="train")
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 2/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
The ratings dataset returns a dictionary of movie id, user id, the assigned rating, timestamp, movie information, and user
information:
for x in ratings.take(1).as_numpy_iterator():
pprint.pprint(x)
{'bucketized_user_age': 45.0,
'movie_genres': array([7]),
'movie_id': b'357',
'movie_title': b"One Flew Over the Cuckoo's Nest (1975)",
'raw_user_age': 46.0,
'timestamp': 879024327,
'user_gender': True,
'user_id': b'138',
'user_occupation_label': 4,
'user_occupation_text': b'doctor',
'user_rating': 4.0,
'user_zip_code': b'53211'}
2022-12-14 12:14:51.221818: W tensorflow/core/kernels/data/cache_dataset_ops.cc:856] The calling iterato
The movies dataset contains the movie id, movie title, and data on what genres it belongs to. Note that the genres are
encoded with integer labels.
for x in movies.take(1).as_numpy_iterator():
pprint.pprint(x)
{'movie_genres': array([4]),
'movie_id': b'1681',
'movie_title': b'You So Crazy (1994)'}
2022-12-14 12:14:51.385630: W tensorflow/core/kernels/data/cache_dataset_ops.cc:856] The calling iterator
In this example, we're going to focus on the ratings data. Other tutorials explore how to use the movie information data as
well to improve the model quality.
ratings = ratings.map(lambda x: {
"movie_title": x["movie_title"],
"user_id": x["user_id"],
})
movies = movies.map(lambda x: x["movie_title"])
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/autograph/py
Instructions for updating:
Lambda fuctions will be no more assumed to be used in the statement where they are used, or at least in t
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/autograph/py
Instructions for updating:
Lambda fuctions will be no more assumed to be used in the statement where they are used, or at least in t
To fit and evaluate the model, we need to split it into a training and evaluation set. In an industrial recommender system, this
would most likely be done by time: the data up to time \(T\) would be used to predict interactions after \(T\).
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 3/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
In this simple example, however, let's use a random split, putting 80% of the ratings in the train set, and 20% in the test set.
tf.random.set_seed(42)
shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)
train = shuffled.take(80_000)
test = shuffled.skip(80_000).take(20_000)
Let's also figure out unique user ids and movie titles present in the data.
This is important because we need to be able to map the raw values of our categorical features to embedding vectors in our
models. To do that, we need a vocabulary that maps a raw feature value to an integer in a contiguous range: this allows us
to look up the corresponding embeddings in our embedding tables.
movie_titles = movies.batch(1_000)
user_ids = ratings.batch(1_000_000).map(lambda x: x["user_id"])
unique_movie_titles = np.unique(np.concatenate(list(movie_titles)))
unique_user_ids = np.unique(np.concatenate(list(user_ids)))
unique_movie_titles[:10]
Implementing a model
Choosing the architecture of our model is a key part of modelling.
Because we are building a two-tower retrieval model, we can build each tower separately and then combine them in the final
model.
The first step is to decide on the dimensionality of the query and candidate representations:
embedding_dimension = 32
Higher values will correspond to models that may be more accurate, but will also be slower to fit and more prone to
overfitting.
The second is to define the model itself. Here, we're going to use Keras preprocessing layers to first convert user ids to
integers, and then convert those to user embeddings via an Embedding layer. Note that we use the list of unique user ids we
computed earlier as a vocabulary:
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 4/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
user_model = tf.keras.Sequential([
tf.keras.layers.StringLookup(
vocabulary=unique_user_ids, mask_token=None),
# We add an additional embedding to account for unknown tokens.
tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
])
movie_model = tf.keras.Sequential([
tf.keras.layers.StringLookup(
vocabulary=unique_movie_titles, mask_token=None),
tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
])
Metrics
In our training data we have positive (user, movie) pairs. To figure out how good our model is, we need to compare the
affinity score that the model calculates for this pair to the scores of all the other possible candidates: if the score for the
positive pair is higher than for all other candidates, our model is highly accurate.
In our case, that's the movies dataset, converted into embeddings via our movie model:
metrics = tfrs.metrics.FactorizedTopK(
candidates=movies.batch(128).map(movie_model)
)
Loss
The next component is the loss used to train our model. TFRS has several loss layers and tasks to make this easy.
In this instance, we'll make use of the Retrieval task object: a convenience wrapper that bundles together the loss function
and metric computation:
task = tfrs.tasks.Retrieval(
metrics=metrics
)
The task itself is a Keras layer that takes the query and candidate embeddings as arguments, and returns the computed
loss: we'll use that to implement the model's training loop.
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 5/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
The base model will then take care of creating the appropriate training loop to fit our model.
class MovielensModel(tfrs.Model):
Under the hood, it's still a plain Keras model. You could achieve the same functionality by inheriting from tf.keras.Model
(https://www.tensorflow.org/api_docs/python/tf/keras/Model) and overriding the train_step and test_step functions (see the
guide (https://www.tensorflow.org/guide/keras/customizing_what_happens_in_fit) for details):
class NoBaseClassMovielensModel(tf.keras.Model):
# Loss computation.
user_embeddings = self.user_model(features["user_id"])
positive_movie_embeddings = self.movie_model(features["movie_title"])
loss = self.task(user_embeddings, positive_movie_embeddings)
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 6/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
metrics["loss"] = loss
metrics["regularization_loss"] = regularization_loss
metrics["total_loss"] = total_loss
return metrics
# Loss computation.
user_embeddings = self.user_model(features["user_id"])
positive_movie_embeddings = self.movie_model(features["movie_title"])
loss = self.task(user_embeddings, positive_movie_embeddings)
return metrics
Then shuffle, batch, and cache the training and evaluation data.
cached_train = train.shuffle(100_000).batch(8192).cache()
cached_test = test.batch(4096).cache()
model.fit(cached_train, epochs=3)
Epoch 1/3
10/10 [==============================] - 6s 309ms/step - factorized_top_k/top_1_categorical_accuracy: 7.2
Epoch 2/3
10/10 [==============================] - 3s 302ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0
Epoch 3/3
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 7/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
If you want to monitor the training process with TensorBoard, you can add a TensorBoard callback to fit() function and then
start TensorBoard using %tensorboard --logdir logs/fit. Please refer to TensorBoard documentation
(https://www.tensorflow.org/tensorboard/get_started) for more details.
As the model trains, the loss is falling and a set of top-k retrieval metrics is updated. These tell us whether the true positive
is in the top-k retrieved items from the entire candidate set. For example, a top-5 categorical accuracy metric of 0.2 would
tell us that, on average, the true positive is in the top 5 retrieved items 20% of the time.
Note that, in this example, we evaluate the metrics during training as well as evaluation. Because this can be quite slow with
large candidate sets, it may be prudent to turn metric calculation off in training, and only run it in evaluation.
model.evaluate(cached_test, return_dict=True)
Test set performance is much worse than training performance. This is due to two factors:
1. Our model is likely to perform better on the data that it has seen, simply because it can memorize it. This overfitting
phenomenon is especially strong when models have many parameters. It can be mediated by model regularization and
use of user and movie features that help the model generalize better to unseen data.
2. The model is re-recommending some of users' already watched movies. These known-positive watches can crowd out
test movies out of top K recommendations.
The second phenomenon can be tackled by excluding previously seen movies from test recommendations. This approach is
relatively common in the recommender systems literature, but we don't follow it in these tutorials. If not recommending past
watches is important, we should expect appropriately specified models to learn this behaviour automatically from past user
history and contextual information. Additionally, it is often appropriate to recommend the same item multiple times (say, an
evergreen TV series or a regularly purchased item).
Making predictions
Now that we have a model, we would like to be able to make predictions. We can use the
tfrs.layers.factorized_top_k.BruteForce
(https://www.tensorflow.org/recommenders/api_docs/python/tfrs/layers/factorized_top_k/BruteForce) layer to do this.
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 8/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
tf.data.Dataset.zip((movies.batch(100), movies.batch(100).map(model.movie_model)))
)
# Get recommendations.
_, titles = index(tf.constant(["42"]))
print(f"Recommendations for user 42: {titles[0, :3]}")
Of course, the BruteForce layer is going to be too slow to serve a model with many possible candidates. The following
sections shows how to speed this up by using an approximate retrieval index.
Model serving
After the model is trained, we need a way to deploy it.
a serving query model, taking in features of the query and transforming them into a query embedding, and
a serving candidate model. This most often takes the form of an approximate nearest neighbours (ANN) index which
allows fast approximate lookup of candidates in response to a query produced by the query model.
In TFRS, both components can be packaged into a single exportable model, giving us a model that takes the raw user id and
returns the titles of top movies for that user. This is done via exporting the model to a SavedModel format, which makes it
possible to serve using TensorFlow Serving (https://www.tensorflow.org/tfx/guide/serving).
To deploy a model like this, we simply export the BruteForce layer we created above:
print(f"Recommendations: {titles[0][:3]}")
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These
INFO:tensorflow:Assets written to: /tmpfs/tmp/tmptfkkd57q/model/assets
INFO:tensorflow:Assets written to: /tmpfs/tmp/tmptfkkd57q/model/assets
Recommendations: [b'Christmas Carol, A (1938)' b'Rudy (1993)'
b'Bridges of Madison County, The (1995)']
We can also export an approximate retrieval index to speed up predictions. This will make it possible to efficiently surface
recommendations from sets of tens of millions of candidates.
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 9/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
To do so, we can use the scann package. This is an optional dependency of TFRS, and we installed it separately at the
beginning of this tutorial by calling !pip install -q scann.
scann_index = tfrs.layers.factorized_top_k.ScaNN(model.user_model)
scann_index.index_from_dataset(
tf.data.Dataset.zip((movies.batch(100), movies.batch(100).map(model.movie_model)))
)
<tensorflow_recommenders.layers.factorized_top_k.ScaNN at 0x7f5fa01ff130>
This layer will perform approximate lookups: this makes retrieval slightly less accurate, but orders of magnitude faster on
large candidate sets.
# Get recommendations.
_, titles = scann_index(tf.constant(["42"]))
print(f"Recommendations for user 42: {titles[0, :3]}")
Recommendations for user 42: [b'Little Big League (1994)' b'Miracle on 34th Street (1994)'
b'Cinderella (1950)']
print(f"Recommendations: {titles[0][:3]}")
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These
INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpxpt22mi0/model/assets
INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpxpt22mi0/model/assets
Recommendations: [b'Little Big League (1994)' b'Miracle on 34th Street (1994)'
b'Cinderella (1950)']
To learn more about using and tuning fast approximate retrieval models, have a look at our efficient serving
(https://tensorflow.org/recommenders/examples/efficient_serving) tutorial.
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 10/11
23/05/2023, 17:13 Recommending movies: retrieval | TensorFlow Recommenders
Item-to-item recommendation
In this model, we created a user-movie model. However, for some applications (for example, product detail pages) it's
common to perform item-to-item (for example, movie-to-movie or product-to-product) recommendations.
Training models like this would follow the same pattern as shown in this tutorial, but with different training data. Here, we
had a user and a movie tower, and used (user, movie) pairs to train them. In an item-to-item model, we would have two item
towers (for the query and candidate item), and train the model using (query item, candidate item) pairs. These could be
constructed from clicks on product detail pages.
Next steps
This concludes the retrieval tutorial.
2. Using movie metadata: building a more complex movie model to alleviate cold-start.
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License
(https://creativecommons.org/licenses/by/4.0/), and code samples are licensed under the Apache 2.0 License
(https://www.apache.org/licenses/LICENSE-2.0). For details, see the Google Developers Site Policies
(https://developers.google.com/site-policies). Java is a registered trademark of Oracle and/or its affiliates.
https://www.tensorflow.org/recommenders/examples/basic_retrieval#building_a_candidate_ann_index 11/11