Recommander des films: récupération

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher la source sur GitHub Télécharger le cahier

Les systèmes de recommandation du monde réel sont souvent composés de deux étapes:

  1. L'étape de récupération est responsable de la sélection d'un ensemble initial de centaines de candidats parmi tous les candidats possibles. L'objectif principal de ce modèle est d'éliminer efficacement tous les candidats qui ne intéressent pas l'utilisateur. Étant donné que le modèle d'extraction peut traiter des millions de candidats, il doit être efficace en termes de calcul.
  2. L'étape de classement prend les sorties du modèle d'extraction et les affine pour sélectionner la meilleure poignée possible de recommandations. Sa tâche est de restreindre l'ensemble des éléments susceptibles d'intéresser l'utilisateur à une liste restreinte de candidats potentiels.

Dans ce tutoriel, nous allons nous concentrer sur la première étape, la récupération. Si vous êtes intéressé par l'étape de classement, jetez un œil à notre tutoriel de classement .

Les modèles d'extraction sont souvent composés de deux sous-modèles:

  1. Un modèle de requête calculant la représentation de requête (normalement un vecteur d'incorporation à dimensionnalité fixe) à l'aide des fonctionnalités de requête.
  2. Un modèle candidat calculant la représentation candidate (un vecteur de taille égale) à l'aide des caractéristiques candidates

Les sorties des deux modèles sont ensuite multipliées ensemble pour donner un score d'affinité candidat-requête, des scores plus élevés exprimant une meilleure correspondance entre le candidat et la requête.

Dans ce didacticiel, nous allons créer et entraîner un tel modèle à deux tours à l'aide du jeu de données Movielens.

Nous allons:

  1. Obtenez nos données et divisez-les en un ensemble de formation et de test.
  2. Implémentez un modèle de récupération.
  3. Ajustez-le et évaluez-le.
  4. Exportez-le pour un service efficace en créant un indice des voisins les plus proches approximatifs (ANN).

Le jeu de données

L'ensemble de données Movielens est un ensemble de données classique du groupe de recherche GroupLens de l'Université du Minnesota. Il contient un ensemble de notes attribuées aux films par un ensemble d'utilisateurs et constitue un outil de recherche sur le système de recommandation.

Les données peuvent être traitées de deux manières:

  1. Il peut être interprété comme exprimant les films que les utilisateurs ont regardés (et notés) et ceux qu'ils n'ont pas. Il s'agit d'une forme de rétroaction implicite, où les montres des utilisateurs nous disent quelles choses ils préfèrent voir et celles qu'ils préfèrent ne pas voir.
  2. Il peut également être considéré comme exprimant à quel point les utilisateurs ont aimé les films qu'ils ont regardés. Il s'agit d'une forme de rétroaction explicite: étant donné qu'un utilisateur a regardé un film, nous pouvons dire à peu près combien il a aimé en regardant la note qu'il a donnée.

Dans ce tutoriel, nous nous concentrons sur un système de récupération: un modèle qui prédit un ensemble de films du catalogue que l'utilisateur est susceptible de regarder. Souvent, les données implicites sont plus utiles ici, et nous allons donc traiter Movielens comme un système implicite. Cela signifie que chaque film qu'un utilisateur a regardé est un exemple positif et que chaque film qu'il n'a pas vu est un exemple négatif implicite.

Importations

Commençons par éliminer nos importations.

pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets
pip install -q scann
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

Préparation du jeu de données

Jetons d'abord un coup d'œil aux données.

Nous utilisons l'ensemble de données MovieLens des ensembles de données Tensorflow . Le chargement de movielens/100k_ratings produit un objet tf.data.Dataset contenant les données de classement et le chargement de movielens/100k_movies donne un objet tf.data.Dataset contenant uniquement les données de films.

Notez que puisque le jeu de données MovieLens n'a pas de fractionnement prédéfini, toutes les données sont sous fractionnement de train .

# Ratings data.
ratings = tfds.load("movielens/100k-ratings", split="train")
# Features of all the available movies.
movies = tfds.load("movielens/100k-movies", split="train")

L'ensemble de données de classification renvoie un dictionnaire d'ID de film, d'ID utilisateur, de classification attribuée, d'horodatage, d'informations sur le film et d'informations utilisateur:

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'}

L'ensemble de données des films contient l'ID du film, le titre du film et des données sur les genres auxquels il appartient. Notez que les genres sont encodés avec des étiquettes entières.

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)'}

Dans cet exemple, nous allons nous concentrer sur les données de notation. D'autres didacticiels explorent également comment utiliser les données d'informations du film pour améliorer la qualité du modèle.

Nous conservons uniquement les champs user_id et movie_title dans l'ensemble de données.

ratings = ratings.map(lambda x: {
    "movie_title": x["movie_title"],
    "user_id": x["user_id"],
})
movies = movies.map(lambda x: x["movie_title"])

Pour adapter et évaluer le modèle, nous devons le diviser en un ensemble de formation et d'évaluation. Dans un système de recommandation industriel, cela se ferait très probablement par le temps: les données jusqu'à temps $ T $ seraient utilisées pour prédire les interactions après $ T $.

Dans cet exemple simple, cependant, utilisons une répartition aléatoire, mettant 80% des cotes dans l'ensemble de trains et 20% dans l'ensemble de test.

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)

Découvrons également les identifiants utilisateur uniques et les titres de films présents dans les données.

Ceci est important car nous devons être en mesure de mapper les valeurs brutes de nos caractéristiques catégorielles pour incorporer des vecteurs dans nos modèles. Pour ce faire, nous avons besoin d'un vocabulaire qui mappe une valeur de caractéristique brute à un entier dans une plage contiguë: cela nous permet de rechercher les plongements correspondants dans nos tables d'intégration.

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]
array([b"'Til There Was You (1997)", b'1-900 (1994)',
       b'101 Dalmatians (1996)', b'12 Angry Men (1957)', b'187 (1997)',
       b'2 Days in the Valley (1996)',
       b'20,000 Leagues Under the Sea (1954)',
       b'2001: A Space Odyssey (1968)',
       b'3 Ninjas: High Noon At Mega Mountain (1998)',
       b'39 Steps, The (1935)'], dtype=object)

Mettre en œuvre un modèle

Le choix de l'architecture de notre modèle est un élément clé de la modélisation.

Parce que nous construisons un modèle de récupération à deux tours, nous pouvons construire chaque tour séparément, puis les combiner dans le modèle final.

La tour de requête

Commençons par la tour de requêtes.

La première étape consiste à décider de la dimensionnalité de la requête et des représentations candidates:

embedding_dimension = 32

Des valeurs plus élevées correspondront à des modèles qui peuvent être plus précis, mais qui seront également plus lents à ajuster et plus sujets au surajustement.

La seconde consiste à définir le modèle lui-même. Ici, nous allons utiliser les couches de prétraitement Keras pour d'abord convertir les identifiants utilisateur en entiers, puis les convertir en incorporations utilisateur via une couche d' Embedding . Notez que nous utilisons la liste des identifiants d'utilisateurs uniques que nous avons calculés précédemment comme vocabulaire:

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)
])

Un modèle simple comme celui-ci correspond exactement à une approche de factorisation matricielle classique. Lors de la définition d' une sous - classe de tf.keras.Model pour ce modèle simple pourrait être surpuissant, nous pouvons facilement l' étendre à un modèle arbitrairement complexe en utilisant des composants standards KERAS, aussi longtemps que nous revenons d' une embedding_dimension sortie -Wide à la fin.

La tour candidate

On peut faire de même avec la tour candidate.

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)
])

Métrique

Dans nos données d'entraînement, nous avons des paires positives (utilisateur, film). Pour déterminer la qualité de notre modèle, nous devons comparer le score d'affinité que le modèle calcule pour cette paire aux scores de tous les autres candidats possibles: si le score de la paire positive est plus élevé que pour tous les autres candidats, notre modèle est très précis.

Pour ce faire, nous pouvons utiliser la métrique tfrs.metrics.FactorizedTopK . La métrique a un argument obligatoire: l'ensemble de données des candidats qui sont utilisés comme négatifs implicites pour l'évaluation.

Dans notre cas, c'est le jeu de données des movies , converti en plongements via notre modèle de film:

metrics = tfrs.metrics.FactorizedTopK(
  candidates=movies.batch(128).map(movie_model)
)

Perte

Le composant suivant est la perte utilisée pour entraîner notre modèle. TFRS a plusieurs couches de pertes et tâches pour rendre cela facile.

Dans cet exemple, nous utiliserons l'objet de tâche Retrieval : un wrapper pratique qui regroupe la fonction de perte et le calcul de la métrique:

task = tfrs.tasks.Retrieval(
  metrics=metrics
)

La tâche elle-même est une couche Keras qui prend la requête et les incorporations des candidats comme arguments, et renvoie la perte calculée: nous l'utiliserons pour implémenter la boucle d'apprentissage du modèle.

Le modèle complet

Nous pouvons maintenant rassembler tout cela dans un modèle. TFRS expose une classe de modèle de base ( tfrs.models.Model ) qui rationalise la construction de modèles: tout ce que nous devons faire est de configurer les composants dans la méthode __init__ , et d'implémenter la méthode compute_loss , en prenant les caractéristiques brutes et en renvoyant une valeur de perte .

Le modèle de base se chargera ensuite de créer la boucle d'entraînement appropriée pour s'adapter à notre modèle.

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)

La classe de base tfrs.Model est une classe simplement pratique: elle nous permet de calculer à la fois les pertes d'entraînement et de test en utilisant la même méthode.

Sous le capot, c'est toujours un simple modèle Keras. Vous pouvez obtenir la même fonctionnalité en héritant de tf.keras.Model et en test_step fonctions train_step et test_step (voir le guide pour plus de détails):

class NoBaseClassMovielensModel(tf.keras.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 train_step(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:

    # Set up a gradient tape to record gradients.
    with tf.GradientTape() as tape:

      # 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)

      # Handle regularization losses as well.
      regularization_loss = sum(self.losses)

      total_loss = loss + regularization_loss

    gradients = tape.gradient(total_loss, self.trainable_variables)
    self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))

    metrics = {metric.name: metric.result() for metric in self.metrics}
    metrics["loss"] = loss
    metrics["regularization_loss"] = regularization_loss
    metrics["total_loss"] = total_loss

    return metrics

  def test_step(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:

    # 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)

    # Handle regularization losses as well.
    regularization_loss = sum(self.losses)

    total_loss = loss + regularization_loss

    metrics = {metric.name: metric.result() for metric in self.metrics}
    metrics["loss"] = loss
    metrics["regularization_loss"] = regularization_loss
    metrics["total_loss"] = total_loss

    return metrics

Dans ces didacticiels, cependant, nous nous en tenons à l'utilisation de la classe de base tfrs.Model pour nous concentrer sur la modélisation et abstraire une partie du passe-partout.

Montage et évaluation

Après avoir défini le modèle, nous pouvons utiliser des routines d'ajustement et d'évaluation Keras standard pour ajuster et évaluer le modèle.

Commençons par instancier le modèle.

model = MovielensModel(user_model, movie_model)
model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))

Ensuite, mélangez, regroupez et mettez en cache les données d'entraînement et d'évaluation.

cached_train = train.shuffle(100_000).batch(8192).cache()
cached_test = test.batch(4096).cache()

Puis entraînez le modèle:

model.fit(cached_train, epochs=3)
Epoch 1/3
10/10 [==============================] - 5s 298ms/step - factorized_top_k/top_1_categorical_accuracy: 1.6250e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0029 - factorized_top_k/top_10_categorical_accuracy: 0.0082 - factorized_top_k/top_50_categorical_accuracy: 0.0680 - factorized_top_k/top_100_categorical_accuracy: 0.1419 - loss: 69885.1072 - regularization_loss: 0.0000e+00 - total_loss: 69885.1072
Epoch 2/3
10/10 [==============================] - 3s 289ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0020 - factorized_top_k/top_5_categorical_accuracy: 0.0150 - factorized_top_k/top_10_categorical_accuracy: 0.0315 - factorized_top_k/top_50_categorical_accuracy: 0.1565 - factorized_top_k/top_100_categorical_accuracy: 0.2807 - loss: 67523.3700 - regularization_loss: 0.0000e+00 - total_loss: 67523.3700
Epoch 3/3
10/10 [==============================] - 3s 278ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0029 - factorized_top_k/top_5_categorical_accuracy: 0.0211 - factorized_top_k/top_10_categorical_accuracy: 0.0437 - factorized_top_k/top_50_categorical_accuracy: 0.1842 - factorized_top_k/top_100_categorical_accuracy: 0.3126 - loss: 66302.9609 - regularization_loss: 0.0000e+00 - total_loss: 66302.9609
<tensorflow.python.keras.callbacks.History at 0x7f16a7780898>

Au fur et à mesure que le modèle s'entraîne, la perte diminue et un ensemble de mesures d'extraction top-k est mis à jour. Ceux-ci nous indiquent si le vrai positif se trouve dans les k éléments les plus récupérés de l'ensemble de candidats. Par exemple, une métrique d'exactitude catégorielle des 5 premiers de 0,2 nous indiquerait qu'en moyenne, le vrai positif se trouve dans les 5 premiers éléments récupérés 20% du temps.

Notez que, dans cet exemple, nous évaluons les métriques lors de la formation ainsi que lors de l'évaluation. Étant donné que cela peut être assez lent avec de grands ensembles de candidats, il peut être prudent de désactiver le calcul métrique dans la formation et de l'exécuter uniquement lors de l'évaluation.

Enfin, nous pouvons évaluer notre modèle sur l'ensemble de test:

model.evaluate(cached_test, return_dict=True)
5/5 [==============================] - 1s 164ms/step - factorized_top_k/top_1_categorical_accuracy: 9.0000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0099 - factorized_top_k/top_10_categorical_accuracy: 0.0221 - factorized_top_k/top_50_categorical_accuracy: 0.1248 - factorized_top_k/top_100_categorical_accuracy: 0.2327 - loss: 31079.0628 - regularization_loss: 0.0000e+00 - total_loss: 31079.0628
{'factorized_top_k/top_1_categorical_accuracy': 0.0008999999845400453,
 'factorized_top_k/top_5_categorical_accuracy': 0.009850000031292439,
 'factorized_top_k/top_10_categorical_accuracy': 0.022050000727176666,
 'factorized_top_k/top_50_categorical_accuracy': 0.12475000321865082,
 'factorized_top_k/top_100_categorical_accuracy': 0.23274999856948853,
 'loss': 28244.76953125,
 'regularization_loss': 0,
 'total_loss': 28244.76953125}

Les performances de l'ensemble de test sont bien pires que celles de l'entraînement. Cela est dû à deux facteurs:

  1. Notre modèle est susceptible de mieux fonctionner sur les données qu'il a vues, simplement parce qu'il peut les mémoriser. Ce phénomène de surajustement est particulièrement fort lorsque les modèles ont de nombreux paramètres. Elle peut être médiée par la régularisation du modèle et l'utilisation de fonctionnalités utilisateur et vidéo qui aident le modèle à mieux se généraliser aux données invisibles.
  2. Le modèle recommande à nouveau certains des films déjà regardés par les utilisateurs. Ces montres connues comme positives peuvent évincer les films de test des principales recommandations K.

Le deuxième phénomène peut être abordé en excluant les films déjà vus des recommandations de test. Cette approche est relativement courante dans la littérature sur les systèmes de recommandation, mais nous ne la suivons pas dans ces didacticiels. S'il est important de ne pas recommander les montres passées, nous devrions nous attendre à ce que les modèles correctement spécifiés apprennent automatiquement ce comportement à partir de l'historique des utilisateurs et des informations contextuelles. De plus, il est souvent approprié de recommander le même article plusieurs fois (par exemple, une série télévisée à feuilles persistantes ou un article régulièrement acheté).

Faire des prédictions

Maintenant que nous avons un modèle, nous aimerions pouvoir faire des prédictions. Nous pouvons utiliser la couche tfrs.layers.factorized_top_k.BruteForce pour ce faire.

# 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]}")
Recommendations for user 42: [b'Bridges of Madison County, The (1995)'
 b'Father of the Bride Part II (1995)' b'Rudy (1993)']

Bien sûr, la couche BruteForce sera trop lente pour servir un modèle avec de nombreux candidats possibles. Les sections suivantes montrent comment accélérer cela en utilisant un index de récupération approximatif.

Modèle servant

Une fois le modèle formé, nous avons besoin d'un moyen de le déployer.

Dans un modèle d'extraction à deux tours, le service comporte deux composants:

  • un modèle de requête de diffusion, prenant en compte les fonctionnalités de la requête et les transformant en une incorporation de requête, et
  • un modèle de candidat au service. Cela prend le plus souvent la forme d'un index des voisins les plus proches approximatifs (ANN) qui permet une recherche rapide et approximative des candidats en réponse à une requête produite par le modèle de requête.

Dans TFRS, les deux composants peuvent être regroupés dans un seul modèle exportable, ce qui nous donne un modèle qui prend l'identifiant brut de l'utilisateur et renvoie les titres des meilleurs films pour cet utilisateur. Cela se fait via l'exportation du modèle vers un format SavedModel , ce qui permet de servir à l'aide de TensorFlow Serving .

Pour déployer un modèle comme celui-ci, nous exportons simplement la couche BruteForce nous avons créée ci-dessus:

# 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]}")
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These functions will not be directly callable after loading.
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These functions will not be directly callable after loading.
INFO:tensorflow:Assets written to: /tmp/tmpedp1pofu/model/assets
INFO:tensorflow:Assets written to: /tmp/tmpedp1pofu/model/assets
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Recommendations: [b'Bridges of Madison County, The (1995)'
 b'Father of the Bride Part II (1995)' b'Rudy (1993)']

Nous pouvons également exporter un index de récupération approximatif pour accélérer les prédictions. Cela permettra de faire émerger efficacement les recommandations d'ensembles de dizaines de millions de candidats.

Pour ce faire, nous pouvons utiliser le package scann . Il s'agit d'une dépendance facultative de TFRS, et nous l'avons installée séparément au début de ce didacticiel en appelant !pip install -q scann .

Une fois installé, nous pouvons utiliser la couche TFRS ScaNN :

scann_index = tfrs.layers.factorized_top_k.ScaNN(model.user_model)
scann_index.index(movies.batch(100).map(model.movie_model), movies)
<tensorflow_recommenders.layers.factorized_top_k.ScaNN at 0x7f16c1480400>

Cette couche effectuera des recherches approximatives : cela rend la récupération légèrement moins précise, mais des ordres de grandeur plus rapides sur de grands ensembles de candidats.

# Get recommendations.
_, titles = scann_index(tf.constant(["42"]))
print(f"Recommendations for user 42: {titles[0, :3]}")
Recommendations for user 42: [b'Sleepless in Seattle (1993)' b'Father of the Bride Part II (1995)'
 b'Hunchback of Notre Dame, The (1996)']

L'exporter pour le servir est aussi simple que d'exporter la couche BruteForce :

# 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]}")
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These functions will not be directly callable after loading.
WARNING:absl:Found untraced functions such as query_with_exclusions while saving (showing 1 of 1). These functions will not be directly callable after loading.
INFO:tensorflow:Assets written to: /tmp/tmprglhaqg1/model/assets
INFO:tensorflow:Assets written to: /tmp/tmprglhaqg1/model/assets
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Recommendations: [b'Sleepless in Seattle (1993)' b'Father of the Bride Part II (1995)'
 b'Hunchback of Notre Dame, The (1996)']

Pour en savoir plus sur l'utilisation et le réglage des modèles de récupération approximative rapide, consultez notre didacticiel de diffusion efficace .

Prochaines étapes

Ceci conclut le didacticiel de récupération.

Pour développer ce qui est présenté ici, jetez un œil à:

  1. Apprentissage de modèles multi-tâches: optimisation conjointe des notes et des clics.
  2. Utilisation des métadonnées de film: création d'un modèle de film plus complexe pour atténuer le démarrage à froid.