Ranking listowy

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

W podstawowej tutorialu rankingu , ćwiczyliśmy model, który można przewidzieć ocen dla par użytkownika / filmowych. Model został wytrenowany w celu zminimalizowania błędu średniokwadratowego przewidywanych ocen.

Jednak optymalizacja prognoz modelu na poszczególnych filmach niekoniecznie jest najlepszą metodą uczenia modeli rankingowych. Nie potrzebujemy modeli rankingowych, aby przewidywać wyniki z dużą dokładnością. Zamiast tego bardziej dbamy o zdolność modelu do generowania uporządkowanej listy elementów, która odpowiada kolejności preferencji użytkownika.

Zamiast optymalizować predykcje modelu dla poszczególnych par zapytanie/element, możemy zoptymalizować ranking modelu listy jako całości. Metoda ta nazywana jest listwise rankingu.

W tym samouczku użyjemy Rekomendatorów TensorFlow do zbudowania modeli rankingowych typu listwise. Aby to zrobić, będziemy wykorzystywać rankingu straty i metryki dostarczonych przez TensorFlow Ranking , pakiet TensorFlow która koncentruje się na nauce do rankingu .

Czynności wstępne

Jeśli TensorFlow Ranking nie jest dostępna w środowisku wykonawczym, można go zainstalować przy użyciu pip :

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

Możemy wtedy zaimportować wszystkie potrzebne pakiety:

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,

Będziemy nadal używać zestawu danych MovieLens 100K. Tak jak poprzednio, w tym samouczku ładujemy zestawy danych i zachowujemy tylko identyfikator użytkownika, tytuł filmu i funkcje oceny użytkowników. Zajmujemy się również sprzątaniem, aby przygotować nasze słownictwo.

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

Wstępne przetwarzanie danych

Nie możemy jednak użyć zestawu danych MovieLens bezpośrednio do optymalizacji listy. Aby przeprowadzić optymalizację listową, musimy mieć dostęp do listy filmów ocenionych przez każdego użytkownika, ale każdy przykład w zestawie danych MovieLens 100K zawiera ocenę tylko jednego filmu.

Aby obejść ten problem, przekształcamy zbiór danych tak, aby każdy przykład zawierał identyfikator użytkownika i listę filmów ocenionych przez tego użytkownika. Niektóre filmy na liście będą miały wyższą pozycję w rankingu niż inne; celem naszego modelu będzie dokonywanie prognoz zgodnych z tą kolejnością.

Aby to zrobić, używamy tfrs.examples.movielens.movielens_to_listwise funkcji pomocnika. Pobiera zestaw danych MovieLens 100K i generuje zestaw danych zawierający przykłady list, jak omówiono powyżej. Szczegóły implementacji można znaleźć w kodzie źródłowym .

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
)

Możemy sprawdzić przykład z danych treningowych. Przykład zawiera identyfikator użytkownika, listę 10 identyfikatorów filmów i ich oceny przez użytkownika.

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

Definicja modelu

Będziemy trenować ten sam model z trzema różnymi stratami:

  • błąd średniokwadratowy,
  • utrata pary zawiasów, oraz
  • apatyczna strata ListMLE.

Te trzy straty odpowiadają optymalizacji punktowej, parami i listowej.

Aby ocenić modelu używamy znormalizowaną dyskontowane skumulowany zysk (NDCG) . NDCG mierzy przewidywany ranking, biorąc ważoną sumę rzeczywistej oceny każdego kandydata. Oceny filmów, które zostały sklasyfikowane niżej przez model, byłyby bardziej dyskontowane. W rezultacie dobry model, który plasuje wysoko oceniane filmy na szczycie, miałby wysoki wynik NDCG. Ponieważ ta metryka uwzględnia pozycję każdego kandydata w rankingu, jest to metryka listwise.

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

Szkolenie modeli

Możemy teraz trenować każdy z trzech modeli.

epochs = 30

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

Model błędu średniokwadratowego

Model ten jest bardzo podobny do modelu w podstawowej tutorialu rankingowej . Uczymy model, aby zminimalizować błąd średniokwadratowy między ocenami rzeczywistymi a ocenami przewidywanymi. Dlatego strata ta jest wyliczana indywidualnie dla każdego filmu, a trening jest punktowy.

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>

Model utraty zawiasów parami

Minimalizując utratę pary zawiasów, model stara się zmaksymalizować różnicę między przewidywaniami modelu dla pozycji wysoko ocenianej i pozycji nisko ocenianej: im większa jest ta różnica, tym niższa strata modelu. Jednak gdy różnica jest wystarczająco duża, strata staje się zerowa, powstrzymując model przed dalszą optymalizacją tej konkretnej pary i pozwalając mu skupić się na innych parach, które są niewłaściwie sklasyfikowane

Ta strata nie jest obliczana dla pojedynczych filmów, ale raczej dla par filmów. Stąd trening wykorzystujący tę stratę odbywa się w parach.

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>

Model listwise

ListMLE strata z TensorFlow wyraża, listę rankingową największej wiarygodności. Aby obliczyć stratę ListMLE, najpierw używamy ocen użytkowników, aby wygenerować optymalny ranking. Następnie obliczamy prawdopodobieństwo, że każdy kandydat zostanie przewyższony przez dowolny element znajdujący się poniżej w rankingu optymalnym, korzystając z przewidywanych wyników. Model stara się zminimalizować takie prawdopodobieństwo, aby zapewnić, że kandydaci wysoko oceniani nie zostaną przewyższeni przez kandydatów nisko ocenianych. Możesz dowiedzieć się więcej o szczegółach ListMLE w punkcie 2.2 papieru Position-aware ListMLE: proces sekwencyjny Learning .

Zauważ, że ponieważ prawdopodobieństwo jest obliczane w odniesieniu do kandydata i wszystkich kandydatów poniżej niego w optymalnym rankingu, strata nie jest parami, ale listami. Stąd szkolenie wykorzystuje optymalizację list.

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>

Porównanie modeli

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

Spośród trzech modeli model wytrenowany przy użyciu ListMLE ma najwyższą metrykę NDCG. Ten wynik pokazuje, w jaki sposób optymalizacja listowa może być używana do trenowania modeli rankingowych i może potencjalnie generować modele, które działają lepiej niż modele zoptymalizowane w sposób punktowy lub parami.