توصیه کنندگان چند کاره

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در 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.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)

در حالی که نتایج در اینجا مزیت دقت روشنی را از یک مدل مشترک در این مورد نشان نمی‌دهند، یادگیری چند کاره به طور کلی ابزار بسیار مفیدی است. زمانی می‌توانیم انتظار نتایج بهتری داشته باشیم که بتوانیم دانش را از یک کار با داده‌های فراوان (مانند کلیک‌ها) به یک کار کم داده مرتبط (مانند خرید) منتقل کنیم.