לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

ממליצים על ריבוי משימות

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

במדריך האחזור הבסיסי בנינו מערכת אחזור באמצעות שעוני סרטים כאותות אינטראקציה חיוביים.

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

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

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

במדריך זה, אנו הולכים לבנות ממליץ רב-אובייקטיבי עבור Movielens, תוך שימוש בסימנים מרומזים (שעוני סרטים) ובאותות מפורשים (רייטינג).

יבוא

בואו קודם נסתלק מהיבוא שלנו.

pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets
import os
import pprint
import tempfile

from typing import Dict, Text

import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
import tensorflow_recommenders as tfrs

הכנת מערך הנתונים

אנו נשתמש במערך הנתונים Movielens 100K.

ratings = tfds.load('movielens/100k-ratings', split="train")
movies = tfds.load('movielens/100k-movies', split="train")

# Select the basic features.
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"])

וחזור על ההכנות שלנו לבניית אוצר מילים ולפיצול הנתונים לרכבת ולערכת מבחנים:

# Randomly shuffle data and split between train and test.
tf.random.set_seed(42)
shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)

train = shuffled.take(80_000)
test = shuffled.skip(80_000).take(20_000)

movie_titles = movies.batch(1_000)
user_ids = ratings.batch(1_000_000).map(lambda x: x["user_id"])

unique_movie_titles = np.unique(np.concatenate(list(movie_titles)))
unique_user_ids = np.unique(np.concatenate(list(user_ids)))

מודל רב משימתי

לממליצים מרובי משימות ישנם שני חלקים קריטיים:

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

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

דגמי המשתמשים והסרטים הם כבעבר:

user_model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.StringLookup(
      vocabulary=unique_user_ids, mask_token=None),
  # We add 1 to account for the unknown token.
  tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
])

movie_model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.StringLookup(
      vocabulary=unique_movie_titles, mask_token=None),
  tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
])

עם זאת, כעת יהיו לנו שתי משימות. הראשונה היא משימת הדירוג:

tfrs.tasks.Ranking(
    loss=tf.keras.losses.MeanSquaredError(),
    metrics=[tf.keras.metrics.RootMeanSquaredError()],
)

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

השנייה היא משימת האחזור:

tfrs.tasks.Retrieval(
    metrics=tfrs.metrics.FactorizedTopK(
        candidates=movies.batch(128)
    )
)

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

מרכיבים אותו

הרכבנו את הכל בכיתת דוגמניות.

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

class MovielensModel(tfrs.models.Model):

  def __init__(self, rating_weight: float, retrieval_weight: float) -> None:
    # We take the loss weights in the constructor: this allows us to instantiate
    # several model objects with different loss weights.

    super().__init__()

    embedding_dimension = 32

    # User and movie models.
    self.movie_model: tf.keras.layers.Layer = tf.keras.Sequential([
      tf.keras.layers.experimental.preprocessing.StringLookup(
        vocabulary=unique_movie_titles, mask_token=None),
      tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
    ])
    self.user_model: tf.keras.layers.Layer = tf.keras.Sequential([
      tf.keras.layers.experimental.preprocessing.StringLookup(
        vocabulary=unique_user_ids, mask_token=None),
      tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
    ])

    # A small model to take in user and movie embeddings and predict ratings.
    # We can make this as complicated as we want as long as we output a scalar
    # as our prediction.
    self.rating_model = tf.keras.Sequential([
        tf.keras.layers.Dense(256, activation="relu"),
        tf.keras.layers.Dense(128, activation="relu"),
        tf.keras.layers.Dense(1),
    ])

    # The tasks.
    self.rating_task: tf.keras.layers.Layer = tfrs.tasks.Ranking(
        loss=tf.keras.losses.MeanSquaredError(),
        metrics=[tf.keras.metrics.RootMeanSquaredError()],
    )
    self.retrieval_task: tf.keras.layers.Layer = tfrs.tasks.Retrieval(
        metrics=tfrs.metrics.FactorizedTopK(
            candidates=movies.batch(128).map(self.movie_model)
        )
    )

    # The loss weights.
    self.rating_weight = rating_weight
    self.retrieval_weight = retrieval_weight

  def call(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:
    # We pick out the user features and pass them into the user model.
    user_embeddings = self.user_model(features["user_id"])
    # And pick out the movie features and pass them into the movie model.
    movie_embeddings = self.movie_model(features["movie_title"])

    return (
        user_embeddings,
        movie_embeddings,
        # We apply the multi-layered rating model to a concatentation of
        # user and movie embeddings.
        self.rating_model(
            tf.concat([user_embeddings, movie_embeddings], axis=1)
        ),
    )

  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:

    ratings = features.pop("user_rating")

    user_embeddings, movie_embeddings, rating_predictions = self(features)

    # We compute the loss for each task.
    rating_loss = self.rating_task(
        labels=ratings,
        predictions=rating_predictions,
    )
    retrieval_loss = self.retrieval_task(user_embeddings, movie_embeddings)

    # And combine them using the loss weights.
    return (self.rating_weight * rating_loss

            + self.retrieval_weight * retrieval_loss)

מודל המתמחה בדירוג

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

model = MovielensModel(rating_weight=1.0, retrieval_weight=0.0)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
cached_train = train.shuffle(100_000).batch(8192).cache()
cached_test = test.batch(4096).cache()
model.fit(cached_train, epochs=3)
metrics = model.evaluate(cached_test, return_dict=True)

print(f"Retrieval top-100 accuracy: {metrics['factorized_top_k/top_100_categorical_accuracy']:.3f}.")
print(f"Ranking RMSE: {metrics['root_mean_squared_error']:.3f}.")
Epoch 1/3
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
10/10 [==============================] - 2s 235ms/step - root_mean_squared_error: 2.0903 - factorized_top_k/top_1_categorical_accuracy: 4.0000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0025 - factorized_top_k/top_10_categorical_accuracy: 0.0055 - factorized_top_k/top_50_categorical_accuracy: 0.0296 - factorized_top_k/top_100_categorical_accuracy: 0.0590 - loss: 4.0315 - regularization_loss: 0.0000e+00 - total_loss: 4.0315
Epoch 2/3
10/10 [==============================] - 2s 219ms/step - root_mean_squared_error: 1.1531 - factorized_top_k/top_1_categorical_accuracy: 3.8750e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0026 - factorized_top_k/top_10_categorical_accuracy: 0.0055 - factorized_top_k/top_50_categorical_accuracy: 0.0298 - factorized_top_k/top_100_categorical_accuracy: 0.0593 - loss: 1.3189 - regularization_loss: 0.0000e+00 - total_loss: 1.3189
Epoch 3/3
10/10 [==============================] - 2s 219ms/step - root_mean_squared_error: 1.1198 - factorized_top_k/top_1_categorical_accuracy: 3.8750e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0026 - factorized_top_k/top_10_categorical_accuracy: 0.0055 - factorized_top_k/top_50_categorical_accuracy: 0.0302 - factorized_top_k/top_100_categorical_accuracy: 0.0598 - loss: 1.2479 - regularization_loss: 0.0000e+00 - total_loss: 1.2479
5/5 [==============================] - 1s 107ms/step - root_mean_squared_error: 1.1130 - factorized_top_k/top_1_categorical_accuracy: 5.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0030 - factorized_top_k/top_10_categorical_accuracy: 0.0053 - factorized_top_k/top_50_categorical_accuracy: 0.0298 - factorized_top_k/top_100_categorical_accuracy: 0.0595 - loss: 1.2336 - regularization_loss: 0.0000e+00 - total_loss: 1.2336
Retrieval top-100 accuracy: 0.060.
Ranking RMSE: 1.113.

המודל עושה אישור בחיזוי דירוגים (עם RMSE של סביב 1.11), אך מתפקד בצורה גרועה בניבוי אילו סרטים נצפו או לא: הדיוק שלו ב 100 הוא כמעט פי 4 גרוע יותר מדגם שאומן רק לחזות שעונים.

מודל המתמחה בשליפה

בואו ננסה כעת מודל המתמקד בשליפה בלבד.

model = MovielensModel(rating_weight=0.0, retrieval_weight=1.0)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
model.fit(cached_train, epochs=3)
metrics = model.evaluate(cached_test, return_dict=True)

print(f"Retrieval top-100 accuracy: {metrics['factorized_top_k/top_100_categorical_accuracy']:.3f}.")
print(f"Ranking RMSE: {metrics['root_mean_squared_error']:.3f}.")
Epoch 1/3
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
10/10 [==============================] - 2s 219ms/step - root_mean_squared_error: 3.7237 - factorized_top_k/top_1_categorical_accuracy: 0.0014 - factorized_top_k/top_5_categorical_accuracy: 0.0101 - factorized_top_k/top_10_categorical_accuracy: 0.0208 - factorized_top_k/top_50_categorical_accuracy: 0.1012 - factorized_top_k/top_100_categorical_accuracy: 0.1795 - loss: 69818.0284 - regularization_loss: 0.0000e+00 - total_loss: 69818.0284
Epoch 2/3
10/10 [==============================] - 2s 217ms/step - root_mean_squared_error: 3.7495 - factorized_top_k/top_1_categorical_accuracy: 0.0028 - factorized_top_k/top_5_categorical_accuracy: 0.0189 - factorized_top_k/top_10_categorical_accuracy: 0.0385 - factorized_top_k/top_50_categorical_accuracy: 0.1691 - factorized_top_k/top_100_categorical_accuracy: 0.2929 - loss: 67473.2876 - regularization_loss: 0.0000e+00 - total_loss: 67473.2876
Epoch 3/3
10/10 [==============================] - 2s 218ms/step - root_mean_squared_error: 3.7648 - factorized_top_k/top_1_categorical_accuracy: 0.0032 - factorized_top_k/top_5_categorical_accuracy: 0.0223 - factorized_top_k/top_10_categorical_accuracy: 0.0456 - factorized_top_k/top_50_categorical_accuracy: 0.1879 - factorized_top_k/top_100_categorical_accuracy: 0.3148 - loss: 66329.2514 - regularization_loss: 0.0000e+00 - total_loss: 66329.2514
5/5 [==============================] - 1s 108ms/step - root_mean_squared_error: 3.7730 - factorized_top_k/top_1_categorical_accuracy: 0.0011 - factorized_top_k/top_5_categorical_accuracy: 0.0098 - factorized_top_k/top_10_categorical_accuracy: 0.0218 - factorized_top_k/top_50_categorical_accuracy: 0.1253 - factorized_top_k/top_100_categorical_accuracy: 0.2353 - loss: 31085.0697 - regularization_loss: 0.0000e+00 - total_loss: 31085.0697
Retrieval top-100 accuracy: 0.235.
Ranking RMSE: 3.773.

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

מודל משותף

בואו נכשיר כעת מודל שמקצה משקל חיובי לשתי המשימות.

model = MovielensModel(rating_weight=1.0, retrieval_weight=1.0)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
model.fit(cached_train, epochs=3)
metrics = model.evaluate(cached_test, return_dict=True)

print(f"Retrieval top-100 accuracy: {metrics['factorized_top_k/top_100_categorical_accuracy']:.3f}.")
print(f"Ranking RMSE: {metrics['root_mean_squared_error']:.3f}.")
Epoch 1/3
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
10/10 [==============================] - 2s 216ms/step - root_mean_squared_error: 2.5007 - factorized_top_k/top_1_categorical_accuracy: 0.0015 - factorized_top_k/top_5_categorical_accuracy: 0.0100 - factorized_top_k/top_10_categorical_accuracy: 0.0207 - factorized_top_k/top_50_categorical_accuracy: 0.1009 - factorized_top_k/top_100_categorical_accuracy: 0.1788 - loss: 69811.8239 - regularization_loss: 0.0000e+00 - total_loss: 69811.8239
Epoch 2/3
10/10 [==============================] - 2s 217ms/step - root_mean_squared_error: 1.2097 - factorized_top_k/top_1_categorical_accuracy: 0.0023 - factorized_top_k/top_5_categorical_accuracy: 0.0184 - factorized_top_k/top_10_categorical_accuracy: 0.0367 - factorized_top_k/top_50_categorical_accuracy: 0.1638 - factorized_top_k/top_100_categorical_accuracy: 0.2875 - loss: 67481.2727 - regularization_loss: 0.0000e+00 - total_loss: 67481.2727
Epoch 3/3
10/10 [==============================] - 2s 216ms/step - root_mean_squared_error: 1.1200 - factorized_top_k/top_1_categorical_accuracy: 0.0032 - factorized_top_k/top_5_categorical_accuracy: 0.0223 - factorized_top_k/top_10_categorical_accuracy: 0.0443 - factorized_top_k/top_50_categorical_accuracy: 0.1862 - factorized_top_k/top_100_categorical_accuracy: 0.3150 - loss: 66297.9304 - regularization_loss: 0.0000e+00 - total_loss: 66297.9304
5/5 [==============================] - 1s 111ms/step - root_mean_squared_error: 1.1312 - factorized_top_k/top_1_categorical_accuracy: 8.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0087 - factorized_top_k/top_10_categorical_accuracy: 0.0219 - factorized_top_k/top_50_categorical_accuracy: 0.1248 - factorized_top_k/top_100_categorical_accuracy: 0.2344 - loss: 31062.8213 - regularization_loss: 0.0000e+00 - total_loss: 31062.8213
Retrieval top-100 accuracy: 0.234.
Ranking RMSE: 1.131.

התוצאה היא מודל המבצע בערך באותה מידה בשתי המשימות כמו כל מודל מיוחד.

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