דירוג ברמה

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

בשנת ההדרכה הבסיסית בדירוג , אנחנו מאומנים מודל שיכול לנבא דירוגים עבור זוגות משתמש / סרט. המודל הוכשר כדי למזער את השגיאה הממוצעת בריבוע של דירוגים חזויים.

עם זאת, אופטימיזציה של תחזיות המודל על סרטים בודדים אינה בהכרח השיטה הטובה ביותר לאימון מודלים לדירוג. אנחנו לא צריכים מודלים של דירוג כדי לחזות ציונים בדיוק רב. במקום זאת, אכפת לנו יותר מהיכולת של המודל ליצור רשימה מסודרת של פריטים התואמת את ההזמנה המועדפת של המשתמש.

במקום לבצע אופטימיזציה של תחזיות המודל על צמדי שאילתה/פריטים בודדים, נוכל לייעל את דירוג המודל של רשימה כולה. שיטה זו נקראת listwise הדירוג.

במדריך זה, נשתמש ב- TensorFlow Recommenders כדי לבנות מודלים של דירוג רשימות. לשם כך, נוכל לעשות שימוש בדירוג הפסדים ומדדים שמספקים 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 של נייר ListMLE Position-מודע: תהליך סדרתית למידה .

שימו לב שמכיוון שהסבירות מחושבת ביחס למועמד ולכל המועמדים מתחתיו בדירוג האופטימלי, ההפסד אינו בזוגיות אלא ברשימה. מכאן שהאימון משתמש באופטימיזציה של רשימות.

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 הגבוה ביותר. תוצאה זו מראה כיצד ניתן להשתמש באופטימיזציה רשימתית כדי להכשיר מודלים לדירוג ויכולה לייצר מודלים עם ביצועים טובים יותר ממודלים שעברו אופטימיזציה באופן נקודתי או זוגי.