توصيات متعددة المهام

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

في البرنامج التعليمي استرجاع الأساسي قمنا ببناء نظام استرجاع باستخدام الساعات فيلم كإشارات التفاعل الإيجابي.

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

يعد دمج كل هذه الأشكال المختلفة من التغذية الراجعة أمرًا بالغ الأهمية لبناء الأنظمة التي يحب المستخدمون استخدامها ، والتي لا تعمل على تحسين أي مقياس واحد على حساب الأداء العام.

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

في هذا البرنامج التعليمي ، سنقوم ببناء مقترح متعدد الأغراض لـ 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.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.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.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.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
10/10 [==============================] - 7s 331ms/step - root_mean_squared_error: 2.0903 - factorized_top_k/top_1_categorical_accuracy: 2.7500e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0024 - factorized_top_k/top_10_categorical_accuracy: 0.0054 - factorized_top_k/top_50_categorical_accuracy: 0.0294 - factorized_top_k/top_100_categorical_accuracy: 0.0589 - loss: 4.0315 - regularization_loss: 0.0000e+00 - total_loss: 4.0315
Epoch 2/3
10/10 [==============================] - 3s 321ms/step - root_mean_squared_error: 1.1531 - factorized_top_k/top_1_categorical_accuracy: 1.8750e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0024 - factorized_top_k/top_10_categorical_accuracy: 0.0054 - factorized_top_k/top_50_categorical_accuracy: 0.0297 - factorized_top_k/top_100_categorical_accuracy: 0.0591 - loss: 1.3189 - regularization_loss: 0.0000e+00 - total_loss: 1.3189
Epoch 3/3
10/10 [==============================] - 3s 316ms/step - root_mean_squared_error: 1.1198 - factorized_top_k/top_1_categorical_accuracy: 1.6250e-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.0300 - factorized_top_k/top_100_categorical_accuracy: 0.0597 - loss: 1.2479 - regularization_loss: 0.0000e+00 - total_loss: 1.2479
5/5 [==============================] - 3s 194ms/step - root_mean_squared_error: 1.1130 - factorized_top_k/top_1_categorical_accuracy: 4.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0028 - factorized_top_k/top_10_categorical_accuracy: 0.0052 - factorized_top_k/top_50_categorical_accuracy: 0.0295 - factorized_top_k/top_100_categorical_accuracy: 0.0597 - 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
10/10 [==============================] - 4s 313ms/step - root_mean_squared_error: 3.7238 - factorized_top_k/top_1_categorical_accuracy: 7.5000e-05 - factorized_top_k/top_5_categorical_accuracy: 0.0014 - factorized_top_k/top_10_categorical_accuracy: 0.0041 - factorized_top_k/top_50_categorical_accuracy: 0.0473 - factorized_top_k/top_100_categorical_accuracy: 0.1135 - loss: 69818.0298 - regularization_loss: 0.0000e+00 - total_loss: 69818.0298
Epoch 2/3
10/10 [==============================] - 3s 326ms/step - root_mean_squared_error: 3.7495 - factorized_top_k/top_1_categorical_accuracy: 0.0011 - factorized_top_k/top_5_categorical_accuracy: 0.0116 - factorized_top_k/top_10_categorical_accuracy: 0.0268 - factorized_top_k/top_50_categorical_accuracy: 0.1425 - factorized_top_k/top_100_categorical_accuracy: 0.2658 - loss: 67473.2884 - regularization_loss: 0.0000e+00 - total_loss: 67473.2884
Epoch 3/3
10/10 [==============================] - 3s 314ms/step - root_mean_squared_error: 3.7648 - factorized_top_k/top_1_categorical_accuracy: 0.0014 - factorized_top_k/top_5_categorical_accuracy: 0.0180 - factorized_top_k/top_10_categorical_accuracy: 0.0388 - factorized_top_k/top_50_categorical_accuracy: 0.1773 - factorized_top_k/top_100_categorical_accuracy: 0.3050 - loss: 66329.2543 - regularization_loss: 0.0000e+00 - total_loss: 66329.2543
5/5 [==============================] - 1s 193ms/step - root_mean_squared_error: 3.7730 - factorized_top_k/top_1_categorical_accuracy: 0.0012 - factorized_top_k/top_5_categorical_accuracy: 0.0097 - 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.2352 - 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
10/10 [==============================] - 4s 299ms/step - root_mean_squared_error: 2.5007 - factorized_top_k/top_1_categorical_accuracy: 3.7500e-05 - factorized_top_k/top_5_categorical_accuracy: 0.0014 - factorized_top_k/top_10_categorical_accuracy: 0.0043 - factorized_top_k/top_50_categorical_accuracy: 0.0450 - factorized_top_k/top_100_categorical_accuracy: 0.1102 - loss: 69811.8274 - regularization_loss: 0.0000e+00 - total_loss: 69811.8274
Epoch 2/3
10/10 [==============================] - 3s 312ms/step - root_mean_squared_error: 1.2097 - factorized_top_k/top_1_categorical_accuracy: 9.8750e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0110 - factorized_top_k/top_10_categorical_accuracy: 0.0255 - factorized_top_k/top_50_categorical_accuracy: 0.1385 - factorized_top_k/top_100_categorical_accuracy: 0.2605 - loss: 67481.2713 - regularization_loss: 0.0000e+00 - total_loss: 67481.2713
Epoch 3/3
10/10 [==============================] - 3s 305ms/step - root_mean_squared_error: 1.1200 - factorized_top_k/top_1_categorical_accuracy: 0.0011 - factorized_top_k/top_5_categorical_accuracy: 0.0175 - factorized_top_k/top_10_categorical_accuracy: 0.0380 - factorized_top_k/top_50_categorical_accuracy: 0.1758 - factorized_top_k/top_100_categorical_accuracy: 0.3040 - loss: 66297.9318 - regularization_loss: 0.0000e+00 - total_loss: 66297.9318
5/5 [==============================] - 1s 187ms/step - root_mean_squared_error: 1.1312 - factorized_top_k/top_1_categorical_accuracy: 9.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0083 - factorized_top_k/top_10_categorical_accuracy: 0.0220 - factorized_top_k/top_50_categorical_accuracy: 0.1248 - factorized_top_k/top_100_categorical_accuracy: 0.2347 - loss: 31062.8206 - regularization_loss: 0.0000e+00 - total_loss: 31062.8206
Retrieval top-100 accuracy: 0.235.
Ranking RMSE: 1.131.

والنتيجة هي نموذج يؤدي أداءً جيدًا تقريبًا في كلا المهمتين مثل كل نموذج متخصص.

صنع التنبؤ

يمكننا استخدام نموذج متعدد المهام المدرب للحصول على حفلات زفاف مدربة من قبل المستخدمين والأفلام ، بالإضافة إلى التصنيف المتوقع:

trained_movie_embeddings, trained_user_embeddings, predicted_rating = model({
      "user_id": np.array(["42"]),
      "movie_title": np.array(["Dances with Wolves (1990)"])
  })
print("Predicted rating:")
print(predicted_rating)
Predicted rating:
tf.Tensor([[3.4021819]], shape=(1, 1), dtype=float32)

في حين أن النتائج هنا لا تظهر فائدة دقيقة من النموذج المشترك في هذه الحالة ، يعد التعلم متعدد المهام بشكل عام أداة مفيدة للغاية. يمكننا أن نتوقع نتائج أفضل عندما نتمكن من نقل المعرفة من مهمة وفيرة بالبيانات (مثل النقرات) إلى مهمة قليلة البيانات وثيقة الصلة (مثل المشتريات).