Classifica in base alla lista

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

Nel tutorial classifica base , ci siamo allenati un modello in grado di prevedere valutazioni per coppie utente / film. Il modello è stato addestrato per ridurre al minimo l'errore quadratico medio delle valutazioni previste.

Tuttavia, l'ottimizzazione delle previsioni del modello sui singoli filmati non è necessariamente il metodo migliore per addestrare i modelli di ranking. Non abbiamo bisogno di modelli di ranking per prevedere i punteggi con grande precisione. Invece, ci preoccupiamo maggiormente della capacità del modello di generare un elenco ordinato di elementi che corrisponda all'ordinamento delle preferenze dell'utente.

Invece di ottimizzare le previsioni del modello su singole coppie di query/elemento, possiamo ottimizzare la classifica del modello di un elenco nel suo insieme. Questo metodo viene chiamato listwise classifica.

In questo tutorial, utilizzeremo TensorFlow Recommenders per creare modelli di ranking a lista. Per farlo, ci si avvarrà di classifica perdite e le metriche fornite da tensorflow classifica , un pacchetto tensorflow che si concentra sulla imparare a rango .

Preliminari

Se tensorflow Ranking non è disponibile nel proprio ambiente di runtime, è possibile installarlo tramite pip :

pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets
pip install -q tensorflow-ranking

Possiamo quindi importare tutti i pacchetti necessari:

import pprint

import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py:119: PkgResourcesDeprecationWarning: 0.18ubuntu0.18.04.1 is an invalid version and will not be supported in a future release
  PkgResourcesDeprecationWarning,
import tensorflow_ranking as tfr
import tensorflow_recommenders as tfrs
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_addons/utils/ensure_tf_install.py:67: UserWarning: Tensorflow Addons supports using Python ops for all Tensorflow versions above or equal to 2.4.0 and strictly below 2.7.0 (nightly versions are not supported). 
 The versions of TensorFlow you are currently using is 2.7.0 and is not supported. 
Some things might work, some things might not.
If you were to encounter a bug, do not file an issue.
If you want to make sure you're using a tested and supported configuration, either change the TensorFlow version or the TensorFlow Addons's version. 
You can find the compatibility matrix in TensorFlow Addon's readme:
https://github.com/tensorflow/addons
  UserWarning,

Continueremo a utilizzare il set di dati MovieLens 100K. Come prima, carichiamo i set di dati e manteniamo solo l'ID utente, il titolo del film e le funzionalità di valutazione degli utenti per questo tutorial. Facciamo anche alcune pulizie per preparare i nostri vocabolari.

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

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

unique_movie_titles = np.unique(np.concatenate(list(movies.batch(1000))))
unique_user_ids = np.unique(np.concatenate(list(ratings.batch(1_000).map(
    lambda x: x["user_id"]))))

Preelaborazione dei dati

Tuttavia, non possiamo utilizzare direttamente il set di dati MovieLens per l'ottimizzazione degli elenchi. Per eseguire l'ottimizzazione per elenco, è necessario avere accesso a un elenco di film classificati da ciascun utente, ma ogni esempio nel set di dati MovieLens 100K contiene solo la classificazione di un singolo film.

Per aggirare questo problema, trasformiamo il set di dati in modo che ogni esempio contenga un ID utente e un elenco di film valutati da quell'utente. Alcuni film nell'elenco saranno classificati più in alto di altri; l'obiettivo del nostro modello sarà quello di fare previsioni che corrispondano a questo ordinamento.

Per fare questo, usiamo la tfrs.examples.movielens.movielens_to_listwise funzione di supporto. Prende il set di dati MovieLens 100K e genera un set di dati contenente esempi di elenchi come discusso in precedenza. I dettagli di implementazione possono essere trovati nel codice sorgente .

tf.random.set_seed(42)

# Split between train and tests sets, as before.
shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)

train = shuffled.take(80_000)
test = shuffled.skip(80_000).take(20_000)

# We sample 50 lists for each user for the training data. For each list we
# sample 5 movies from the movies the user rated.
train = tfrs.examples.movielens.sample_listwise(
    train,
    num_list_per_user=50,
    num_examples_per_list=5,
    seed=42
)
test = tfrs.examples.movielens.sample_listwise(
    test,
    num_list_per_user=1,
    num_examples_per_list=5,
    seed=42
)

Possiamo esaminare un esempio dai dati di addestramento. L'esempio include un ID utente, un elenco di 10 ID film e le relative valutazioni da parte dell'utente.

for example in train.take(1):
  pprint.pprint(example)
{'movie_title': <tf.Tensor: shape=(5,), dtype=string, numpy=
array([b'Postman, The (1997)', b'Liar Liar (1997)', b'Contact (1997)',
       b'Welcome To Sarajevo (1997)',
       b'I Know What You Did Last Summer (1997)'], dtype=object)>,
 'user_id': <tf.Tensor: shape=(), dtype=string, numpy=b'681'>,
 'user_rating': <tf.Tensor: shape=(5,), dtype=float32, numpy=array([4., 5., 1., 4., 1.], dtype=float32)>}

Definizione del modello

Addestreremo lo stesso modello con tre diverse perdite:

  • errore quadratico medio,
  • perdita della cerniera a coppie, e
  • una perdita ListMLE listwise.

Queste tre perdite corrispondono all'ottimizzazione puntuale, a coppie e a lista.

Per valutare il modello che utilizziamo normalizzato scontata crescita cumulata (NDCG) . NDCG misura una classifica prevista prendendo una somma ponderata della valutazione effettiva di ciascun candidato. Le valutazioni dei film che sono classificate più in basso dal modello sarebbero scontate di più. Di conseguenza, un buon modello che classifica i film più apprezzati in cima avrebbe un risultato NDCG elevato. Poiché questa metrica tiene conto della posizione classificata di ciascun candidato, è una metrica a lista.

class RankingModel(tfrs.Model):

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

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

    # Compute embeddings for movies.
    self.movie_embeddings = tf.keras.Sequential([
      tf.keras.layers.StringLookup(
        vocabulary=unique_movie_titles),
      tf.keras.layers.Embedding(len(unique_movie_titles) + 2, embedding_dimension)
    ])

    # Compute predictions.
    self.score_model = 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)
    ])

    self.task = tfrs.tasks.Ranking(
      loss=loss,
      metrics=[
        tfr.keras.metrics.NDCGMetric(name="ndcg_metric"),
        tf.keras.metrics.RootMeanSquaredError()
      ]
    )

  def call(self, features):
    # We first convert the id features into embeddings.
    # User embeddings are a [batch_size, embedding_dim] tensor.
    user_embeddings = self.user_embeddings(features["user_id"])

    # Movie embeddings are a [batch_size, num_movies_in_list, embedding_dim]
    # tensor.
    movie_embeddings = self.movie_embeddings(features["movie_title"])

    # We want to concatenate user embeddings with movie emebeddings to pass
    # them into the ranking model. To do so, we need to reshape the user
    # embeddings to match the shape of movie embeddings.
    list_length = features["movie_title"].shape[1]
    user_embedding_repeated = tf.repeat(
        tf.expand_dims(user_embeddings, 1), [list_length], axis=1)

    # Once reshaped, we concatenate and pass into the dense layers to generate
    # predictions.
    concatenated_embeddings = tf.concat(
        [user_embedding_repeated, movie_embeddings], 2)

    return self.score_model(concatenated_embeddings)

  def compute_loss(self, features, training=False):
    labels = features.pop("user_rating")

    scores = self(features)

    return self.task(
        labels=labels,
        predictions=tf.squeeze(scores, axis=-1),
    )

Allenare i modelli

Ora possiamo addestrare ciascuno dei tre modelli.

epochs = 30

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

Modello di errore quadratico medio

Questo modello è molto simile al modello di cui tutorial classifica base . Formiamo il modello per ridurre al minimo l'errore quadratico medio tra le valutazioni effettive e le valutazioni previste. Pertanto, questa perdita viene calcolata individualmente per ogni film e l'allenamento è puntuale.

mse_model = RankingModel(tf.keras.losses.MeanSquaredError())
mse_model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
mse_model.fit(cached_train, epochs=epochs, verbose=False)
<keras.callbacks.History at 0x7f64791a5d10>

Modello di perdita della cerniera a coppie

Riducendo al minimo la perdita di cerniera a coppie, il modello cerca di massimizzare la differenza tra le previsioni del modello per un articolo con valutazione elevata e un articolo con valutazione bassa: maggiore è la differenza, minore è la perdita del modello. Tuttavia, una volta che la differenza è abbastanza grande, la perdita diventa zero, impedendo al modello di ottimizzare ulteriormente questa particolare coppia e permettendogli di concentrarsi su altre coppie che sono classificate in modo errato

Questa perdita non viene calcolata per i singoli film, ma per le coppie di film. Quindi l'allenamento che utilizza questa perdita è a coppie.

hinge_model = RankingModel(tfr.keras.losses.PairwiseHingeLoss())
hinge_model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
hinge_model.fit(cached_train, epochs=epochs, verbose=False)
<keras.callbacks.History at 0x7f647914f190>

Modello Listwise

Il ListMLE perdite da tensorflow esprime Ranking List stima di massima verosimiglianza. Per calcolare la perdita ListMLE, utilizziamo prima le valutazioni degli utenti per generare una classifica ottimale. Calcoliamo quindi la probabilità che ogni candidato venga superato da qualsiasi elemento al di sotto di esso nella classifica ottimale utilizzando i punteggi previsti. Il modello cerca di ridurre al minimo tale probabilità per garantire che i candidati con un punteggio elevato non siano superati dai candidati con un punteggio basso. È possibile saperne di più sui dettagli di ListMLE nella sezione 2.2 della carta ListMLE Position-aware: Un processo di apprendimento sequenziale .

Si noti che poiché la probabilità è calcolata rispetto a un candidato e a tutti i candidati al di sotto di essa nella graduatoria ottimale, la perdita non è a coppie ma a lista. Quindi la formazione utilizza l'ottimizzazione dell'elenco.

listwise_model = RankingModel(tfr.keras.losses.ListMLELoss())
listwise_model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
listwise_model.fit(cached_train, epochs=epochs, verbose=False)
<keras.callbacks.History at 0x7f647b35f350>

Confrontando i modelli

mse_model_result = mse_model.evaluate(cached_test, return_dict=True)
print("NDCG of the MSE Model: {:.4f}".format(mse_model_result["ndcg_metric"]))
1/1 [==============================] - 0s 405ms/step - ndcg_metric: 0.9053 - root_mean_squared_error: 0.9671 - loss: 0.9354 - regularization_loss: 0.0000e+00 - total_loss: 0.9354
NDCG of the MSE Model: 0.9053
hinge_model_result = hinge_model.evaluate(cached_test, return_dict=True)
print("NDCG of the pairwise hinge loss model: {:.4f}".format(hinge_model_result["ndcg_metric"]))
1/1 [==============================] - 0s 457ms/step - ndcg_metric: 0.9058 - root_mean_squared_error: 3.8330 - loss: 1.0180 - regularization_loss: 0.0000e+00 - total_loss: 1.0180
NDCG of the pairwise hinge loss model: 0.9058
listwise_model_result = listwise_model.evaluate(cached_test, return_dict=True)
print("NDCG of the ListMLE model: {:.4f}".format(listwise_model_result["ndcg_metric"]))
1/1 [==============================] - 0s 432ms/step - ndcg_metric: 0.9071 - root_mean_squared_error: 2.7224 - loss: 4.5401 - regularization_loss: 0.0000e+00 - total_loss: 4.5401
NDCG of the ListMLE model: 0.9071

Dei tre modelli, il modello addestrato utilizzando ListMLE ha la metrica NDCG più alta. Questo risultato mostra come l'ottimizzazione listwise può essere utilizzata per addestrare modelli di ranking e può potenzialmente produrre modelli con prestazioni migliori rispetto ai modelli ottimizzati in modo puntuale o a coppie.