Film consigliati: classifica

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino

I sistemi di raccomandazione del mondo reale sono spesso composti da due fasi:

  1. La fase di recupero è responsabile della selezione di una serie iniziale di centinaia di candidati tra tutti i possibili candidati. L'obiettivo principale di questo modello è eliminare in modo efficiente tutti i candidati a cui l'utente non è interessato. Poiché il modello di recupero può avere a che fare con milioni di candidati, deve essere efficiente dal punto di vista computazionale.
  2. La fase di classificazione prende i risultati del modello di recupero e li perfeziona per selezionare la migliore manciata possibile di raccomandazioni. Il suo compito è restringere l'insieme di elementi a cui l'utente potrebbe essere interessato a una rosa di probabili candidati.

Ci concentreremo sulla seconda fase, la classifica. Se siete interessati nella fase di recupero, hanno uno sguardo al nostro recupero tutorial.

In questo tutorial, andremo a:

  1. Ottieni i nostri dati e dividili in un set di allenamento e test.
  2. Implementa un modello di classificazione.
  3. Adattalo e valutalo.

Importazioni

Per prima cosa togliamo di mezzo le nostre importazioni.

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

Preparazione del set di dati

Stiamo andando a utilizzare gli stessi dati come il recupero dei tutorial. Questa volta manterremo anche gli ascolti: questi sono gli obiettivi che cerchiamo di prevedere.

ratings = tfds.load("movielens/100k-ratings", split="train")

ratings = ratings.map(lambda x: {
    "movie_title": x["movie_title"],
    "user_id": x["user_id"],
    "user_rating": x["user_rating"]
})
2021-10-02 11:04:25.388548: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

Come prima, divideremo i dati inserendo l'80% delle valutazioni nel set di treni e il 20% nel set di prova.

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)

Scopriamo anche gli ID utente univoci e i titoli dei film presenti nei dati.

Questo è importante perché dobbiamo essere in grado di mappare i valori grezzi delle nostre caratteristiche categoriche per incorporare i vettori nei nostri modelli. Per fare ciò, abbiamo bisogno di un vocabolario che associ un valore di caratteristica non elaborato a un numero intero in un intervallo contiguo: questo ci consente di cercare gli incorporamenti corrispondenti nelle nostre tabelle di incorporamento.

movie_titles = ratings.batch(1_000_000).map(lambda x: x["movie_title"])
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)))

Implementazione di un modello

Architettura

I modelli di classificazione non devono affrontare gli stessi vincoli di efficienza dei modelli di recupero, quindi abbiamo un po' più di libertà nella scelta delle architetture.

Un modello composto da più strati densi impilati è un'architettura relativamente comune per la classificazione delle attività. Possiamo implementarlo come segue:

class RankingModel(tf.keras.Model):

  def __init__(self):
    super().__init__()
    embedding_dimension = 32

    # Compute embeddings for users.
    self.user_embeddings = tf.keras.Sequential([
      tf.keras.layers.StringLookup(
        vocabulary=unique_user_ids, mask_token=None),
      tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
    ])

    # Compute embeddings for movies.
    self.movie_embeddings = 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)
    ])

    # Compute predictions.
    self.ratings = tf.keras.Sequential([
      # Learn multiple dense layers.
      tf.keras.layers.Dense(256, activation="relu"),
      tf.keras.layers.Dense(64, activation="relu"),
      # Make rating predictions in the final layer.
      tf.keras.layers.Dense(1)
  ])

  def call(self, inputs):

    user_id, movie_title = inputs

    user_embedding = self.user_embeddings(user_id)
    movie_embedding = self.movie_embeddings(movie_title)

    return self.ratings(tf.concat([user_embedding, movie_embedding], axis=1))

Questo modello prende gli ID utente e i titoli dei film e genera una valutazione prevista:

RankingModel()((["42"], ["One Flew Over the Cuckoo's Nest (1975)"]))
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['42']
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['42']
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ["One Flew Over the Cuckoo's Nest (1975)"]
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ["One Flew Over the Cuckoo's Nest (1975)"]
Consider rewriting this model with the Functional API.
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[0.03740937]], dtype=float32)>

Perdita e metriche

Il componente successivo è la perdita utilizzata per addestrare il nostro modello. TFRS ha diversi livelli di perdita e attività per renderlo facile.

In questo caso, faremo uso del Ranking oggetto compito: un wrapper convenienza che impacchetta insieme la funzione di perdita e computo metrico.

Useremo insieme al MeanSquaredError perdita Keras al fine di prevedere i feedback.

task = tfrs.tasks.Ranking(
  loss = tf.keras.losses.MeanSquaredError(),
  metrics=[tf.keras.metrics.RootMeanSquaredError()]
)

L'attività stessa è un livello Keras che accetta come argomenti vero e previsto e restituisce la perdita calcolata. Lo useremo per implementare il ciclo di addestramento del modello.

Il modello completo

Ora possiamo mettere tutto insieme in un modello. TFRs espone una classe del modello di base ( tfrs.models.Model ), che semplifica modelli bulding: tutto quello che dobbiamo fare è impostare i componenti del __init__ metodo, e implementare il compute_loss metodo, prendendo le caratteristiche prime e restituire un valore di perdita .

Il modello base si occuperà quindi di creare il ciclo di addestramento appropriato per adattarsi al nostro modello.

class MovielensModel(tfrs.models.Model):

  def __init__(self):
    super().__init__()
    self.ranking_model: tf.keras.Model = RankingModel()
    self.task: tf.keras.layers.Layer = tfrs.tasks.Ranking(
      loss = tf.keras.losses.MeanSquaredError(),
      metrics=[tf.keras.metrics.RootMeanSquaredError()]
    )

  def call(self, features: Dict[str, tf.Tensor]) -> tf.Tensor:
    return self.ranking_model(
        (features["user_id"], features["movie_title"]))

  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:
    labels = features.pop("user_rating")

    rating_predictions = self(features)

    # The task computes the loss and the metrics.
    return self.task(labels=labels, predictions=rating_predictions)

Adattamento e valutazione

Dopo aver definito il modello, possiamo utilizzare le routine di adattamento e valutazione standard di Keras per adattare e valutare il modello.

Istanziamo prima il modello.

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

Quindi mescola, raggruppa e memorizza nella cache i dati di formazione e valutazione.

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

Quindi addestrare il modello:

model.fit(cached_train, epochs=3)
Epoch 1/3
10/10 [==============================] - 2s 26ms/step - root_mean_squared_error: 2.1718 - loss: 4.3303 - regularization_loss: 0.0000e+00 - total_loss: 4.3303
Epoch 2/3
10/10 [==============================] - 0s 8ms/step - root_mean_squared_error: 1.1227 - loss: 1.2602 - regularization_loss: 0.0000e+00 - total_loss: 1.2602
Epoch 3/3
10/10 [==============================] - 0s 8ms/step - root_mean_squared_error: 1.1162 - loss: 1.2456 - regularization_loss: 0.0000e+00 - total_loss: 1.2456
<keras.callbacks.History at 0x7f28389eaa90>

Mentre il modello si allena, la perdita sta diminuendo e la metrica RMSE sta migliorando.

Infine, possiamo valutare il nostro modello sul test set:

model.evaluate(cached_test, return_dict=True)
5/5 [==============================] - 2s 14ms/step - root_mean_squared_error: 1.1108 - loss: 1.2287 - regularization_loss: 0.0000e+00 - total_loss: 1.2287
{'root_mean_squared_error': 1.1108061075210571,
 'loss': 1.2062578201293945,
 'regularization_loss': 0,
 'total_loss': 1.2062578201293945}

Più bassa è la metrica RMSE, più accurato è il nostro modello nel prevedere le valutazioni.

Testare il modello di ranking

Ora possiamo testare il modello di classificazione calcolando le previsioni per una serie di film e quindi classificare questi film in base alle previsioni:

test_ratings = {}
test_movie_titles = ["M*A*S*H (1970)", "Dances with Wolves (1990)", "Speed (1994)"]
for movie_title in test_movie_titles:
  test_ratings[movie_title] = model({
      "user_id": np.array(["42"]),
      "movie_title": np.array([movie_title])
  })

print("Ratings:")
for title, score in sorted(test_ratings.items(), key=lambda x: x[1], reverse=True):
  print(f"{title}: {score}")
Ratings:
M*A*S*H (1970): [[3.584712]]
Dances with Wolves (1990): [[3.551556]]
Speed (1994): [[3.5215874]]

Esportare per servire

Il modello può essere facilmente esportato per servire:

tf.saved_model.save(model, "export")
2021-10-02 11:04:38.235611: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
WARNING:absl:Found untraced functions such as ranking_1_layer_call_and_return_conditional_losses, ranking_1_layer_call_fn, ranking_1_layer_call_fn, ranking_1_layer_call_and_return_conditional_losses, ranking_1_layer_call_and_return_conditional_losses while saving (showing 5 of 5). These functions will not be directly callable after loading.
INFO:tensorflow:Assets written to: export/assets
INFO:tensorflow:Assets written to: export/assets

Ora possiamo ricaricarlo ed eseguire previsioni:

loaded = tf.saved_model.load("export")

loaded({"user_id": np.array(["42"]), "movie_title": ["Speed (1994)"]}).numpy()
array([[3.5215874]], dtype=float32)

Prossimi passi

Il modello sopra ci dà un inizio decente verso la costruzione di un sistema di classificazione.

Naturalmente, creare un sistema di classificazione pratico richiede uno sforzo molto maggiore.

Nella maggior parte dei casi, un modello di classificazione può essere sostanzialmente migliorato utilizzando più funzionalità anziché solo identificatori di utenti e candidati. Per vedere come fare, avere uno sguardo al lato caratteristiche tutorial.

È inoltre necessaria un'attenta comprensione degli obiettivi da ottimizzare. Per iniziare sulla costruzione di una di raccomandazione che ottimizza molteplici obiettivi, dare un'occhiata alla nostra multitask tutorial.