Recommending movies: retrieval
Real-world recommender systems are often composed of two stages:
-
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.
-
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 tutorial.
Retrieval models are often composed of two sub-models:
-
A query model computing the query representation (normally a fixed-dimensionality embedding vector) using query features.
-
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.
We're going to:
-
Get our data and split it into a training and test set.
-
Implement a retrieval model.
-
Fit and evaluate it.
-
Export it for efficient serving by building an approximate nearest neighbours (ANN) index.
The dataset
It contains a set of ratings given to movies by a set of users, and is a workhorse of recommender system research
The data can be treated in two ways:
-
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.
-
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
import os import pprint import tempfile from typing import Dict, Text import numpy as np import tensorflow as tf import tensorflow_datasets as tfds import tensorflow_recommenders as tfrs
Preparing the dataset
We use the MovieLens dataset from Tensorflow Datasets. Loading movielens/100k_ratings yields a tf.data.Dataset object containing the ratings data and loading movielens/100k_movies yields a tf.data.Dataset object containing only the movies data.
# Ratings data.
ratings = tfds.load("movielens/100k-ratings", split="train")
# Features of all the available movies.
movies = tfds.load("movielens/100k-movies", split="train")
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)
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)
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.
We keep only the user_id, and movie_title fields in the dataset.
ratings = ratings.map(lambda x: {
"movie_title": x["movie_title"],
"user_id": x["user_id"],
})
movies = movies.map(lambda x: x["movie_title"])
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 query tower
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:
user_model = tf.keras.Sequential([
tf.keras.layers.experimental.preprocessing.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)
])
A simple model like this corresponds exactly to a classic matrix factorization approach. While defining a subclass of tf.keras.Model for this simple model might be overkill, we can easily extend it to an arbitrarily complex model using standard Keras components, as long as we return an embedding_dimension-wide output at the end.
The candidate tower
movie_model = tf.keras.Sequential([
tf.keras.layers.experimental.preprocessing.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.
To do this, we can use the tfrs.metrics.FactorizedTopK metric. The metric has one required argument: the dataset of candidates that are used as implicit negatives for evaluation.
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
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.
The full model
TFRS exposes a base model class (tfrs.models.Model) which streamlines building models: all we need to do is to set up the components in the init method, and implement the compute_loss method, taking in the raw features and returning a loss value.
The base model will then take care of creating the appropriate training loop to fit our model.
class MovielensModel(tfrs.Model):
def __init__(self, user_model, movie_model):
super().__init__()
self.movie_model: tf.keras.Model = movie_model
self.user_model: tf.keras.Model = user_model
self.task: tf.keras.layers.Layer = task
def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:
# We pick out the user features and pass them into the user model.
user_embeddings = self.user_model(features["user_id"])
# And pick out the movie features and pass them into the movie model,
# getting embeddings back.
positive_movie_embeddings = self.movie_model(features["movie_title"])
# The task computes the loss and the metrics.
return self.task(user_embeddings, positive_movie_embeddings)
Fitting and evaluating
Let's first instantiate the model.
model = MovielensModel(user_model, movie_model) model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))
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()
Then train the model:
model.fit(cached_train, epochs=3)
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.
Finally, we can evaluate our model on the test set:
model.evaluate(cached_test, return_dict=True)
Test set performance is much worse than training performance. This is due to two factors:
-
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.
-
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 layer to do this.
# Create a model that takes in raw query features, and
index = tfrs.layers.factorized_top_k.BruteForce(model.user_model)
# recommends movies out of the entire movies dataset.
index.index(movies.batch(100).map(model.movie_model), movies)
# 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
In a two-tower retrieval model, serving has two components:
-
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.
To deploy a model like this, we simply export the BruteForce layer we created above:
# Export the query model.
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "model")
# Save the index.
index.save(path)
# Load it back; can also be done in TensorFlow Serving.
loaded = tf.keras.models.load_model(path)
# Pass a user id in, get top predicted movie titles back.
scores, titles = loaded(["42"])
print(f"Recommendations: {titles[0][:3]}")
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.
scann_index = tfrs.layers.factorized_top_k.ScaNN(model.user_model) scann_index.index(movies.batch(100).map(model.movie_model), movies)
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]}")
Exporting it for serving is as easy as exporting the BruteForce layer:
# Export the query model.
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "model")
# Save the index.
scann_index.save(
path,
options=tf.saved_model.SaveOptions(namespace_whitelist=["Scann"])
)
# Load it back; can also be done in TensorFlow Serving.
loaded = tf.keras.models.load_model(path)
# Pass a user id in, get top predicted movie titles back.
scores, titles = loaded(["42"])
print(f"Recommendations: {titles[0][:3]}")
相关推荐
VDA 19.1_3rd edition_2026_English.rar
VDA 19.1_3rd edition_2026_English.rar
正交拉丁超立方(OLHC)分组与实验设计(DOE)评估的优化Python实现,移植自MATLA.rar
正交拉丁超立方(OLHC)分组与实验设计(DOE)评估的优化Python实现,移植自MATLA.rar
FCEMS燃料电池汽车能量管理控制、动力系统+驾驶员模型仿真、附说明文档及参考文献
内容概要:本文围绕燃料电池汽车能量管理控制系统(FCEMS)展开,系统阐述了其动力系统与驾驶员模型的仿真方法,并配套提供了详尽的说明文档及参考文献。研究深度融合线性车辆三自由度动力学模型与无迹卡尔曼滤波(UKF)技术,实现了对车辆横摆角速度、质心侧偏角及车速等关键状态的高精度估计,为优化能量管理策略奠定了坚实基础。整个仿真系统依托Carsim与Simulink联合仿真平台构建,有效保障了模型的真实性和工程实用性。此外,研究还涵盖了燃料电池供氢系统的精细化建模与先进控制策略,完整呈现了从核心部件建模到整车系统集成的全流程技术方案。; 适合人群:具备一定车辆工程、自动化或电力电子背景的研究生、科研人员及从事新能源汽车研发的工程师。; 使用场景及目标:①开展燃料电池汽车的能量管理控制策略研究与仿真验证;②进行车辆动力学状态估计与联合仿真平台搭建;③学习基于Simulink/Carsim的新能源汽车系统建模方法;④为硕士论文复现或科研项目提供全面的技术支持与参考。; 阅读建议:建议读者结合提供的仿真模型、PPT说明与参考文献,逐步复现仿真流程,重点关注UKF状态估计算法与能量管理逻辑的实现细节,同时利用所提供的网盘资源下载完整资料以便深入学习与扩展应用。
三相维也纳PFC整流器闭环仿真控制策略研究-中点电位平衡、无功补偿与双载波SVPWM(Simulink仿真实现)
内容概要:本文系统研究了三相维也纳PFC整流器的闭环仿真控制策略,聚焦于中点电位平衡、无功补偿以及双载波SVPWM调制三大关键技术问题。基于Simulink平台构建了30kW三相维也纳整流器的完整仿真模型,采用零序电流注入法实现直流侧中点电位的精确控制,有效抑制电压偏移,提升系统稳定性。结合无功电流补偿策略,显著改善输入功率因数,降低谐波畸变率。创新性地引入双载波SVPWM(马鞍波/羊角波)调制技术,优化开关调制过程,改善输出电压波形质量,减少开关损耗,提高整体转换效率。控制系统实现了对输入电流与输出电压的双闭环精确调控,仿真结果验证了该策略在动态响应速度、稳态控制精度及抗负载扰动能力方面的优越性能。; 适合人群:从事电力电子变换器设计、新能源发电系统、电动汽车充电技术及相关领域的高校研究生、科研人员以及具备电力电子与自动控制理论基础的工程技术人员。; 使用场景及目标:①为高功率密度、高效率PFC整流器的研发提供理论依据与仿真验证手段;②解决三电平整流器中常见的中点电位失衡难题;③探索先进SVPWM调制技术在实际系统中的应用潜力;④支撑新能源并网、工业电源、车载充电机等对电能质量要求严苛的应用场景下的核心电力电子装置开发。; 阅读建议:建议读者结合提供的Simulink仿真模型进行实践操作,深入理解控制器结构设计、零序注入实现方式、双载波生成逻辑及PI参数整定方法,并可通过修改负载条件或电网参数开展对比仿真,以全面掌握系统动态特性,后续可进一步推进至硬件原型实验验证。
汽车电动助力转向EPS系统建模仿真、二自由度+上下转向柱等、附参考文献
内容概要:本文围绕汽车电动助力转向(EPS)系统展开,重点介绍基于线性化模型的EPS系统建模仿真方法,涵盖二自由度车辆动力学模型与上下转向柱的动力学建模,深入探讨转向系统的控制策略设计与仿真验证。通过Matlab/Simulink工具实现系统建模与仿真分析,并结合Carsim与Matlab联合仿真手段,提升模型的真实性和工程参考价值。同时,研究中引入参考文献支撑,确保理论依据充分,内容兼具学术性与实用性。 适合人群:具备车辆工程或自动化相关背景,熟悉Matlab/Simulink仿真环境,从事汽车底盘控制、转向系统开发或智能驾驶研究的研发人员与研究生。 使用场景及目标:①开展电动助力转向系统(EPS)的建模与控制算法设计;②利用联合仿真平台(如Carsim与Matlab)进行车辆操纵稳定性分析;③研究转向柱动力学特性对转向手感的影响;④作为车辆动力学课程或科研项目的仿真案例参考。 阅读建议:建议读者结合文中提及的参考文献,系统学习EPS控制理论基础,并动手实践Matlab/Simulink仿真模型,通过调整控制参数与工况设置,深入理解系统响应特性,进一步拓展至多自由度整车模型与先进控制策略(如H∞、MPC)的应用研究。


