Peringkat berdasarkan daftar

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Dalam tutorial peringkat dasar , kita dilatih model yang dapat memprediksi peringkat untuk pasangan user / film. Model dilatih untuk meminimalkan kesalahan kuadrat rata-rata dari peringkat yang diprediksi.

Namun, mengoptimalkan prediksi model pada masing-masing film belum tentu merupakan metode terbaik untuk melatih model peringkat. Kami tidak memerlukan model peringkat untuk memprediksi skor dengan akurasi tinggi. Sebaliknya, kami lebih peduli dengan kemampuan model untuk menghasilkan daftar item yang diurutkan yang cocok dengan pemesanan preferensi pengguna.

Alih-alih mengoptimalkan prediksi model pada pasangan kueri/item individual, kita dapat mengoptimalkan peringkat model dari daftar secara keseluruhan. Metode ini disebut listwise peringkat.

Dalam tutorial ini, kita akan menggunakan TensorFlow Recommenders untuk membangun model peringkat listwise. Untuk melakukannya, kita akan menggunakan peringkat kerugian dan metrik yang disediakan oleh TensorFlow Ranking , paket TensorFlow yang berfokus pada belajar untuk peringkat .

Persiapan

Jika TensorFlow Ranking tidak tersedia di lingkungan runtime, Anda dapat menginstalnya menggunakan pip :

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

Kami kemudian dapat mengimpor semua paket yang diperlukan:

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,

Kami akan terus menggunakan dataset MovieLens 100K. Seperti sebelumnya, kami memuat kumpulan data dan hanya menyimpan fitur id pengguna, judul film, dan peringkat pengguna untuk tutorial ini. Kami juga melakukan beberapa rumah tangga untuk mempersiapkan kosakata kami.

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

Pra-pemrosesan data

Namun, kami tidak dapat menggunakan dataset MovieLens untuk pengoptimalan daftar secara langsung. Untuk melakukan pengoptimalan daftar, kita perlu memiliki akses ke daftar film yang telah diberi peringkat oleh setiap pengguna, tetapi setiap contoh dalam kumpulan data MovieLens 100K hanya berisi peringkat satu film.

Untuk menyiasatinya, kami mengubah kumpulan data sehingga setiap contoh berisi id pengguna dan daftar film yang dinilai oleh pengguna tersebut. Beberapa film dalam daftar akan diberi peringkat lebih tinggi dari yang lain; tujuan model kami adalah membuat prediksi yang cocok dengan urutan ini.

Untuk melakukan hal ini, kita menggunakan tfrs.examples.movielens.movielens_to_listwise fungsi pembantu. Dibutuhkan kumpulan data MovieLens 100K dan menghasilkan kumpulan data yang berisi contoh daftar seperti yang dibahas di atas. Rincian pelaksanaan dapat ditemukan dalam kode sumber .

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
)

Kita dapat memeriksa contoh dari data pelatihan. Contohnya termasuk id pengguna, daftar 10 id film, dan peringkatnya oleh pengguna.

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

Definisi model

Kami akan melatih model yang sama dengan tiga kerugian berbeda:

  • kesalahan kuadrat rata-rata,
  • kehilangan engsel berpasangan, dan
  • kerugian ListMLE listwise.

Ketiga kerugian ini sesuai dengan optimasi pointwise, pairwise, dan listwise.

Untuk mengevaluasi model yang kita gunakan dinormalisasi diskon akumulasi keuntungan (NDCG) . NDCG mengukur peringkat yang diprediksi dengan mengambil jumlah tertimbang dari peringkat aktual setiap kandidat. Peringkat film yang diberi peringkat lebih rendah oleh model akan lebih didiskon. Akibatnya, model bagus yang memberi peringkat film berperingkat tinggi di atas akan memiliki hasil NDCG yang tinggi. Karena metrik ini memperhitungkan posisi peringkat setiap kandidat, ini adalah metrik daftar.

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

Melatih para model

Sekarang kita dapat melatih masing-masing dari ketiga model tersebut.

epochs = 30

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

Model kesalahan kuadrat rata-rata

Model ini sangat mirip dengan model tutorial peringkat dasar . Kami melatih model untuk meminimalkan kesalahan kuadrat rata-rata antara peringkat aktual dan peringkat yang diprediksi. Oleh karena itu, kerugian ini dihitung secara individual untuk setiap film dan pelatihannya tepat sasaran.

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 kehilangan engsel berpasangan

Dengan meminimalkan kerugian engsel berpasangan, model mencoba memaksimalkan perbedaan antara prediksi model untuk item berperingkat tinggi dan item berperingkat rendah: semakin besar perbedaannya, semakin rendah kerugian model. Namun, begitu perbedaannya cukup besar, kerugiannya menjadi nol, menghentikan model dari mengoptimalkan lebih lanjut pasangan tertentu ini dan membiarkannya fokus pada pasangan lain yang peringkatnya salah.

Kerugian ini tidak dihitung untuk masing-masing film, melainkan untuk pasangan film. Oleh karena itu pelatihan menggunakan kerugian ini adalah berpasangan.

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 bijaksana

The ListMLE kerugian dari TensorFlow mengekspresikan Ranking daftar estimasi kemungkinan maksimum. Untuk menghitung kerugian ListMLE, pertama-tama kami menggunakan peringkat pengguna untuk menghasilkan peringkat yang optimal. Kami kemudian menghitung kemungkinan setiap kandidat diungguli oleh item apa pun di bawahnya dalam peringkat optimal menggunakan skor yang diprediksi. Model tersebut mencoba untuk meminimalkan kemungkinan tersebut untuk memastikan kandidat dengan peringkat tinggi tidak diungguli oleh kandidat dengan peringkat rendah. Anda dapat mempelajari lebih lanjut tentang rincian ListMLE di bagian 2.2 dari kertas Posisi-sadar ListMLE: Proses A Sequential Learning .

Perhatikan bahwa karena kemungkinan dihitung sehubungan dengan seorang kandidat dan semua kandidat di bawahnya dalam peringkat optimal, kerugiannya tidak berpasangan tetapi berdasarkan daftar. Oleh karena itu pelatihan menggunakan optimasi daftar.

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>

Membandingkan model

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

Dari ketiga model tersebut, model yang dilatih menggunakan ListMLE memiliki metrik NDCG tertinggi. Hasil ini menunjukkan bagaimana optimisasi listwise dapat digunakan untuk melatih model peringkat dan berpotensi menghasilkan model yang berperforma lebih baik daripada model yang dioptimalkan secara pointwise atau berpasangan.