الترتيب بالقائمة

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

في البرنامج التعليمي التصنيف الأساسي ، قمنا بتدريب النموذج الذي يمكن التنبؤ التصنيفات لأزواج المستخدم / الفيلم. تم تدريب النموذج لتقليل متوسط ​​الخطأ التربيعي للتصنيفات المتوقعة.

ومع ذلك ، فإن تحسين تنبؤات النموذج على الأفلام الفردية ليس بالضرورة أفضل طريقة لتدريب نماذج التصنيف. لا نحتاج إلى نماذج ترتيب للتنبؤ بالنتائج بدقة كبيرة. بدلاً من ذلك ، نحن نهتم أكثر بقدرة النموذج على إنشاء قائمة مرتبة من العناصر التي تطابق ترتيب تفضيلات المستخدم.

بدلاً من تحسين تنبؤات النموذج على أزواج الاستعلام / العناصر الفردية ، يمكننا تحسين ترتيب النموذج للقائمة ككل. وتسمى هذه الطريقة 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>

نموذج Listwise

و ListMLE خسارة من TensorFlow يعبر عن الترتيب تسرد أقصى تقدير احتمال. لحساب خسارة ListMLE ، نستخدم أولاً تصنيفات المستخدم لإنشاء الترتيب الأمثل. نحسب بعد ذلك احتمالية تفوق كل مرشح في الترتيب بأي عنصر أدناه في الترتيب الأمثل باستخدام الدرجات المتوقعة. يحاول النموذج تقليل هذه الاحتمالية إلى الحد الأدنى لضمان عدم تفوق المرشحين ذوي التصنيف العالي على المرشحين ذوي التصنيف المنخفض. يمكنك معرفة المزيد عن تفاصيل ListMLE في القسم 2.2 من ورقة 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. توضح هذه النتيجة كيف يمكن استخدام تحسين القائمة لتدريب نماذج الترتيب ويمكن أن ينتج نماذج تقدم أداءً أفضل من النماذج المحسّنة بطريقة نقطية أو ثنائية الاتجاه.