Рейтинг по спискам

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

В основном рейтинге учебника , мы тренировались модель , которая может предсказывать рейтинги для пар пользователя / кино. Модель была обучена минимизировать среднеквадратичную ошибку прогнозируемых оценок.

Однако оптимизация прогнозов модели для отдельных фильмов не обязательно является лучшим методом обучения моделей ранжирования. Нам не нужны модели ранжирования, чтобы предсказывать оценки с большой точностью. Вместо этого мы больше заботимся о способности модели генерировать упорядоченный список элементов, который соответствует порядку предпочтений пользователя.

Вместо оптимизации прогнозов модели по отдельным парам запрос / элемент, мы можем оптимизировать рейтинг модели для списка в целом. Этот метод называется listwise ранжирования.

В этом руководстве мы будем использовать рекомендатели TensorFlow для построения моделей ранжирования по спискам. Для этого мы будем использовать рейтинг потери и показатели , предоставляемые TensorFlow Ранжирование , пакет TensorFlow , который фокусируется на обучение ранга .

Предварительные мероприятия

Если TensorFlow Рейтинг не доступен в вашей среде выполнения, вы можете установить его с помощью pip :

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

Затем мы можем импортировать все необходимые пакеты:

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,

Мы продолжим использовать набор данных MovieLens 100K. Как и раньше, мы загружаем наборы данных и сохраняем для этого руководства только идентификатор пользователя, название фильма и рейтинг пользователей. Мы также занимаемся подготовкой словаря.

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

Предварительная обработка данных

Однако мы не можем напрямую использовать набор данных MovieLens для оптимизации списка. Чтобы выполнить оптимизацию по списку, нам нужен доступ к списку фильмов, которые оценил каждый пользователь, но каждый пример в наборе данных MovieLens 100K содержит только рейтинг одного фильма.

Чтобы обойти это, мы трансформируем набор данных так, чтобы каждый пример содержал идентификатор пользователя и список фильмов, оцененных этим пользователем. Некоторые фильмы в списке будут иметь более высокий рейтинг, чем другие; цель нашей модели будет заключаться в том, чтобы делать прогнозы, соответствующие этому порядку.

Для этого мы используем tfrs.examples.movielens.movielens_to_listwise вспомогательную функцию. Он берет набор данных MovieLens 100K и генерирует набор данных, содержащий примеры списков, как описано выше. Детали реализации можно найти в исходном коде .

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
)

Мы можем проверить пример на основе данных обучения. Пример включает идентификатор пользователя, список из 10 идентификаторов фильмов и их оценки пользователем.

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

Определение модели

Мы будем обучать одну и ту же модель с тремя разными потерями:

  • среднеквадратическая ошибка,
  • попарная потеря шарнира, и
  • потеря списка ListMLE.

Эти три потери соответствуют поточечной, попарной и списочной оптимизации.

Для оценки модели мы используем нормированную дисконтированные накопленную прибыль (NDCG) . NDCG измеряет прогнозируемый рейтинг, взяв взвешенную сумму фактического рейтинга каждого кандидата. Рейтинги фильмов, которые оцениваются моделью ниже, будут дисконтированы больше. В результате хорошая модель, которая ставит фильмы с высоким рейтингом на первое место, будет иметь высокий результат NDCG. Поскольку этот показатель учитывает ранжированное положение каждого кандидата, это показатель по списку.

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

Обучение моделей

Теперь мы можем обучить каждую из трех моделей.

epochs = 30

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

Модель среднеквадратичной ошибки

Эта модель очень похожа на модель в основном рейтинге учебника . Мы обучаем модель минимизировать среднеквадратичную ошибку между фактическими и прогнозируемыми рейтингами. Следовательно, этот проигрыш рассчитывается индивидуально для каждого фильма, и обучение является точечным.

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>

Модель парных потерь в шарнире

Минимизируя попарные потери шарниров, модель пытается максимизировать разницу между прогнозами модели для элемента с высоким и низким рейтингом: чем больше эта разница, тем меньше потери модели. Однако, как только разница становится достаточно большой, потери становятся равными нулю, останавливая дальнейшую оптимизацию модели этой конкретной пары и позволяя ей сосредоточиться на других парах, которые неправильно ранжированы.

Эти потери рассчитываются не для отдельных фильмов, а для пар фильмов. Следовательно, обучение с использованием этой потери является попарным.

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>

Списочная модель

ListMLE потери от TensorFlow Рейтинге экспрессов список максимального правдоподобия. Чтобы вычислить потерю ListMLE, мы сначала используем рейтинги пользователей для создания оптимального ранжирования. Затем мы вычисляем вероятность того, что каждый кандидат будет превосходит любой элемент ниже его в оптимальном рейтинге, используя прогнозируемые оценки. Модель пытается минимизировать такую ​​вероятность, чтобы гарантировать, что кандидаты с высоким рейтингом не уступят в рейтинге кандидатам с низким рейтингом. Вы можете узнать больше о деталях ListMLE в разделе 2.2 бумагоделательной Position-Aware ListMLE: Последовательный процесс обучения .

Обратите внимание: поскольку вероятность вычисляется по отношению к кандидату и всем кандидатам, находящимся ниже его в оптимальном рейтинге, потеря происходит не попарно, а по списку. Следовательно, в обучении используется оптимизация списка.

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>

Сравнение моделей

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

Из трех моделей модель, обученная с помощью ListMLE, имеет наивысший показатель NDCG. Этот результат показывает, как списочную оптимизацию можно использовать для обучения моделей ранжирования и потенциально создавать модели, которые работают лучше, чем модели, оптимизированные точечно или попарно.