بهره گیری از ویژگی های زمینه

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در GitHub دانلود دفترچه یادداشت

در آموزش ویژگی‌سازی، ما چندین ویژگی را فراتر از شناسه‌های کاربر و فیلم در مدل‌های خود گنجانده‌ایم، اما بررسی نکرده‌ایم که آیا این ویژگی‌ها دقت مدل را بهبود می‌بخشند یا خیر.

عوامل زیادی بر مفید بودن ویژگی‌های فراتر از شناسه‌ها در مدل توصیه‌گر تأثیر می‌گذارند:

  1. اهمیت زمینه : اگر تنظیمات برگزیده کاربر در زمینه ها و زمان نسبتاً پایدار باشد، ویژگی های زمینه ممکن است فایده زیادی نداشته باشند. با این حال، اگر ترجیحات کاربران بسیار متناوب باشد، افزودن زمینه، مدل را به طور قابل توجهی بهبود می بخشد. برای مثال، روز هفته ممکن است یکی از ویژگی‌های مهم در تصمیم‌گیری برای توصیه یک کلیپ کوتاه یا یک فیلم باشد: کاربران ممکن است فقط در طول هفته زمان تماشای محتوای کوتاه داشته باشند، اما می‌توانند استراحت کنند و در آخر هفته از یک فیلم کامل لذت ببرند. . به طور مشابه، مُهرهای زمان پرس و جو ممکن است نقش مهمی در مدل‌سازی پویایی محبوبیت داشته باشند: یک فیلم ممکن است در زمان انتشار بسیار محبوب باشد، اما پس از آن به سرعت تحلیل می‌رود. برعکس، دیگر فیلم‌ها ممکن است همیشه سبز باشند که با خوشحالی بارها و بارها تماشا می‌شوند.
  2. پراکندگی داده ها : اگر داده ها پراکنده باشند، استفاده از ویژگی های غیر شناسه ممکن است حیاتی باشد. با مشاهدات کمی که برای یک کاربر یا آیتم خاص در دسترس است، مدل ممکن است با برآورد یک نمایش خوب برای هر کاربر یا هر مورد مشکل داشته باشد. برای ساخت یک مدل دقیق، ویژگی‌های دیگری مانند دسته‌بندی آیتم‌ها، توضیحات و تصاویر باید مورد استفاده قرار گیرد تا به تعمیم مدل فراتر از داده‌های آموزشی کمک کند. این امر به ویژه در موقعیت‌های شروع سرد ، که در آن داده‌های نسبتا کمی در مورد برخی از آیتم‌ها یا کاربران در دسترس است، مرتبط است.

در این آموزش، استفاده از ویژگی‌هایی فراتر از عناوین فیلم و شناسه‌های کاربری در مدل MovieLens خود را آزمایش خواهیم کرد.

مقدماتی

ابتدا بسته های لازم را وارد می کنیم.

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

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

import tensorflow_recommenders as tfrs

ما آموزش ویژگی‌سازی را دنبال می‌کنیم و شناسه کاربر، مهر زمان و ویژگی‌های عنوان فیلم را حفظ می‌کنیم.

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"],
    "timestamp": x["timestamp"],
})
movies = movies.map(lambda x: x["movie_title"])

ما همچنین خانه داری را برای تهیه واژگان ویژگی انجام می دهیم.

timestamps = np.concatenate(list(ratings.map(lambda x: x["timestamp"]).batch(100)))

max_timestamp = timestamps.max()
min_timestamp = timestamps.min()

timestamp_buckets = np.linspace(
    min_timestamp, max_timestamp, num=1000,
)

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"]))))

تعریف مدل

مدل پرس و جو

ما با مدل کاربر تعریف شده در آموزش ویژگی‌سازی به عنوان اولین لایه مدل خود شروع می‌کنیم که وظیفه تبدیل نمونه‌های ورودی خام به تعبیه‌های ویژگی را دارد. با این حال، ما آن را کمی تغییر می دهیم تا به ما امکان دهد ویژگی های مهر زمانی را روشن یا خاموش کنیم. این به ما امکان می‌دهد تا تأثیری را که ویژگی‌های مهر زمانی روی مدل می‌گذارند را آسان‌تر نشان دهیم. در کد زیر، پارامتر use_timestamps به ما این امکان را می دهد که از ویژگی های timestamp استفاده کنیم یا خیر.

class UserModel(tf.keras.Model):

  def __init__(self, use_timestamps):
    super().__init__()

    self._use_timestamps = use_timestamps

    self.user_embedding = tf.keras.Sequential([
        tf.keras.layers.StringLookup(
            vocabulary=unique_user_ids, mask_token=None),
        tf.keras.layers.Embedding(len(unique_user_ids) + 1, 32),
    ])

    if use_timestamps:
      self.timestamp_embedding = tf.keras.Sequential([
          tf.keras.layers.Discretization(timestamp_buckets.tolist()),
          tf.keras.layers.Embedding(len(timestamp_buckets) + 1, 32),
      ])
      self.normalized_timestamp = tf.keras.layers.Normalization(
          axis=None
      )

      self.normalized_timestamp.adapt(timestamps)

  def call(self, inputs):
    if not self._use_timestamps:
      return self.user_embedding(inputs["user_id"])

    return tf.concat([
        self.user_embedding(inputs["user_id"]),
        self.timestamp_embedding(inputs["timestamp"]),
        tf.reshape(self.normalized_timestamp(inputs["timestamp"]), (-1, 1)),
    ], axis=1)

توجه داشته باشید که استفاده ما از ویژگی‌های مهر زمانی در این آموزش با انتخاب تقسیم آموزشی-آزمون به روشی نامطلوب در تعامل است. از آنجایی که ما داده‌های خود را به‌جای ترتیب زمانی به‌صورت تصادفی تقسیم کرده‌ایم (برای اطمینان از اینکه رویدادهایی که به مجموعه داده‌های آزمایشی تعلق دارند دیرتر از موارد موجود در مجموعه آموزشی رخ می‌دهند)، مدل ما می‌تواند به طور مؤثر از آینده یاد بگیرد. این غیر واقعی است: به هر حال، ما امروز نمی‌توانیم مدلی را روی داده‌های فردا آموزش دهیم.

این بدان معناست که افزودن ویژگی‌های زمانی به مدل به آن امکان می‌دهد الگوهای تعامل آینده را بیاموزد. ما این کار را فقط برای مصورسازی انجام می دهیم: مجموعه داده MovieLens خود بسیار متراکم است و بر خلاف بسیاری از مجموعه داده های دنیای واقعی، از ویژگی های فراتر از شناسه های کاربر و عناوین فیلم بهره زیادی نمی برد.

گذشته از این اخطار، مدل‌های دنیای واقعی ممکن است از دیگر ویژگی‌های مبتنی بر زمان مانند زمان روز یا روز هفته بهره ببرند، به‌خصوص اگر داده‌ها دارای الگوهای فصلی قوی باشند.

مدل کاندید

برای سادگی، مدل کاندید را ثابت نگه می داریم. دوباره، ما آن را از آموزش ویژگی‌سازی کپی می‌کنیم:

class MovieModel(tf.keras.Model):

  def __init__(self):
    super().__init__()

    max_tokens = 10_000

    self.title_embedding = tf.keras.Sequential([
      tf.keras.layers.StringLookup(
          vocabulary=unique_movie_titles, mask_token=None),
      tf.keras.layers.Embedding(len(unique_movie_titles) + 1, 32)
    ])

    self.title_vectorizer = tf.keras.layers.TextVectorization(
        max_tokens=max_tokens)

    self.title_text_embedding = tf.keras.Sequential([
      self.title_vectorizer,
      tf.keras.layers.Embedding(max_tokens, 32, mask_zero=True),
      tf.keras.layers.GlobalAveragePooling1D(),
    ])

    self.title_vectorizer.adapt(movies)

  def call(self, titles):
    return tf.concat([
        self.title_embedding(titles),
        self.title_text_embedding(titles),
    ], axis=1)

مدل ترکیبی

با تعریف UserModel و MovieModel ، می‌توانیم یک مدل ترکیبی را کنار هم قرار دهیم و منطق تلفات و معیارهای خود را پیاده‌سازی کنیم.

در اینجا ما در حال ساخت یک مدل بازیابی هستیم. برای تجدید نظر در مورد نحوه عملکرد، به آموزش بازیابی اولیه مراجعه کنید.

توجه داشته باشید که ما همچنین باید مطمئن شویم که مدل پرس و جو و مدل کاندید خروجی جاسازی با اندازه سازگار هستند. از آنجایی که با افزودن ویژگی‌های بیشتر، اندازه‌های آن‌ها را تغییر خواهیم داد، ساده‌ترین راه برای انجام این کار استفاده از لایه‌ای متراکم بعد از هر مدل است:

class MovielensModel(tfrs.models.Model):

  def __init__(self, use_timestamps):
    super().__init__()
    self.query_model = tf.keras.Sequential([
      UserModel(use_timestamps),
      tf.keras.layers.Dense(32)
    ])
    self.candidate_model = tf.keras.Sequential([
      MovieModel(),
      tf.keras.layers.Dense(32)
    ])
    self.task = tfrs.tasks.Retrieval(
        metrics=tfrs.metrics.FactorizedTopK(
            candidates=movies.batch(128).map(self.candidate_model),
        ),
    )

  def compute_loss(self, features, training=False):
    # We only pass the user id and timestamp features into the query model. This
    # is to ensure that the training inputs would have the same keys as the
    # query inputs. Otherwise the discrepancy in input structure would cause an
    # error when loading the query model after saving it.
    query_embeddings = self.query_model({
        "user_id": features["user_id"],
        "timestamp": features["timestamp"],
    })
    movie_embeddings = self.candidate_model(features["movie_title"])

    return self.task(query_embeddings, movie_embeddings)

آزمایش

داده ها را آماده کنید

ابتدا داده ها را به یک مجموعه آموزشی و یک مجموعه آزمایشی تقسیم می کنیم.

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)

cached_train = train.shuffle(100_000).batch(2048)
cached_test = test.batch(4096).cache()

خط پایه: هیچ ویژگی مهر زمانی وجود ندارد

ما آماده ایم اولین مدل خود را امتحان کنیم: بیایید با عدم استفاده از ویژگی های مهر زمانی برای ایجاد خط پایه خود شروع کنیم.

model = MovielensModel(use_timestamps=False)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))

model.fit(cached_train, epochs=3)

train_accuracy = model.evaluate(
    cached_train, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]
test_accuracy = model.evaluate(
    cached_test, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]

print(f"Top-100 accuracy (train): {train_accuracy:.2f}.")
print(f"Top-100 accuracy (test): {test_accuracy:.2f}.")
Epoch 1/3
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
40/40 [==============================] - 10s 169ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0092 - factorized_top_k/top_5_categorical_accuracy: 0.0172 - factorized_top_k/top_10_categorical_accuracy: 0.0256 - factorized_top_k/top_50_categorical_accuracy: 0.0824 - factorized_top_k/top_100_categorical_accuracy: 0.1473 - loss: 14579.4628 - regularization_loss: 0.0000e+00 - total_loss: 14579.4628
Epoch 2/3
40/40 [==============================] - 9s 173ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0020 - factorized_top_k/top_5_categorical_accuracy: 0.0126 - factorized_top_k/top_10_categorical_accuracy: 0.0251 - factorized_top_k/top_50_categorical_accuracy: 0.1129 - factorized_top_k/top_100_categorical_accuracy: 0.2133 - loss: 14136.2137 - regularization_loss: 0.0000e+00 - total_loss: 14136.2137
Epoch 3/3
40/40 [==============================] - 9s 174ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0021 - factorized_top_k/top_5_categorical_accuracy: 0.0155 - factorized_top_k/top_10_categorical_accuracy: 0.0307 - factorized_top_k/top_50_categorical_accuracy: 0.1389 - factorized_top_k/top_100_categorical_accuracy: 0.2535 - loss: 13939.9265 - regularization_loss: 0.0000e+00 - total_loss: 13939.9265
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
40/40 [==============================] - 10s 189ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0036 - factorized_top_k/top_5_categorical_accuracy: 0.0226 - factorized_top_k/top_10_categorical_accuracy: 0.0427 - factorized_top_k/top_50_categorical_accuracy: 0.1729 - factorized_top_k/top_100_categorical_accuracy: 0.2944 - loss: 13711.3802 - regularization_loss: 0.0000e+00 - total_loss: 13711.3802
5/5 [==============================] - 3s 267ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0010 - factorized_top_k/top_5_categorical_accuracy: 0.0078 - factorized_top_k/top_10_categorical_accuracy: 0.0184 - factorized_top_k/top_50_categorical_accuracy: 0.1051 - factorized_top_k/top_100_categorical_accuracy: 0.2126 - loss: 30995.8988 - regularization_loss: 0.0000e+00 - total_loss: 30995.8988
Top-100 accuracy (train): 0.29.
Top-100 accuracy (test): 0.21.

این به ما یک دقت بالای 100 پایه در حدود 0.2 می دهد.

ثبت پویایی زمان با ویژگی های زمان

اگر ویژگی های زمانی را اضافه کنیم نتیجه تغییر می کند؟

model = MovielensModel(use_timestamps=True)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))

model.fit(cached_train, epochs=3)

train_accuracy = model.evaluate(
    cached_train, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]
test_accuracy = model.evaluate(
    cached_test, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]

print(f"Top-100 accuracy (train): {train_accuracy:.2f}.")
print(f"Top-100 accuracy (test): {test_accuracy:.2f}.")
Epoch 1/3
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
40/40 [==============================] - 10s 175ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0057 - factorized_top_k/top_5_categorical_accuracy: 0.0148 - factorized_top_k/top_10_categorical_accuracy: 0.0238 - factorized_top_k/top_50_categorical_accuracy: 0.0812 - factorized_top_k/top_100_categorical_accuracy: 0.1487 - loss: 14606.0927 - regularization_loss: 0.0000e+00 - total_loss: 14606.0927
Epoch 2/3
40/40 [==============================] - 9s 176ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0026 - factorized_top_k/top_5_categorical_accuracy: 0.0153 - factorized_top_k/top_10_categorical_accuracy: 0.0304 - factorized_top_k/top_50_categorical_accuracy: 0.1375 - factorized_top_k/top_100_categorical_accuracy: 0.2512 - loss: 13958.5635 - regularization_loss: 0.0000e+00 - total_loss: 13958.5635
Epoch 3/3
40/40 [==============================] - 9s 177ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0026 - factorized_top_k/top_5_categorical_accuracy: 0.0189 - factorized_top_k/top_10_categorical_accuracy: 0.0393 - factorized_top_k/top_50_categorical_accuracy: 0.1713 - factorized_top_k/top_100_categorical_accuracy: 0.3015 - loss: 13696.8511 - regularization_loss: 0.0000e+00 - total_loss: 13696.8511
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
40/40 [==============================] - 9s 172ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0050 - factorized_top_k/top_5_categorical_accuracy: 0.0323 - factorized_top_k/top_10_categorical_accuracy: 0.0606 - factorized_top_k/top_50_categorical_accuracy: 0.2254 - factorized_top_k/top_100_categorical_accuracy: 0.3637 - loss: 13382.7869 - regularization_loss: 0.0000e+00 - total_loss: 13382.7869
5/5 [==============================] - 1s 237ms/step - 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.0214 - factorized_top_k/top_50_categorical_accuracy: 0.1259 - factorized_top_k/top_100_categorical_accuracy: 0.2468 - loss: 30699.8529 - regularization_loss: 0.0000e+00 - total_loss: 30699.8529
Top-100 accuracy (train): 0.36.
Top-100 accuracy (test): 0.25.

این کاملاً بهتر است: نه تنها دقت تمرین بسیار بالاتر است، بلکه دقت تست نیز به طور قابل توجهی بهبود یافته است.

مراحل بعدی

این آموزش نشان می‌دهد که حتی مدل‌های ساده نیز می‌توانند با ترکیب ویژگی‌های بیشتر دقیق‌تر شوند. با این حال، برای استفاده بیشتر از ویژگی‌های خود، اغلب لازم است مدل‌های بزرگ‌تر و عمیق‌تر بسازید. برای کشف جزئیات بیشتر، به آموزش بازیابی عمیق نگاهی بیندازید.