המלצה על סרטים: דירוג

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

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

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

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

במדריך זה, אנו הולכים:

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

יבוא

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

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

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

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

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

ratings = ratings.map(lambda x: {
    "movie_title": x["movie_title"],
    "user_id": x["user_id"],
    "user_rating": x["user_rating"]
})
2021-10-02 11:04:25.388548: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

כמו קודם, נחלק את הנתונים על ידי הכנסת 80% מהדירוגים לקבוצת הרכבות ו-20% לקבוצת הבדיקות.

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 = ratings.batch(1_000_000).map(lambda x: x["movie_title"])
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)))

יישום מודל

ארכיטקטורה

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

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

class RankingModel(tf.keras.Model):

  def __init__(self):
    super().__init__()
    embedding_dimension = 32

    # Compute embeddings for users.
    self.user_embeddings = 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)
    ])

    # Compute embeddings for movies.
    self.movie_embeddings = 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)
    ])

    # Compute predictions.
    self.ratings = 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)
  ])

  def call(self, inputs):

    user_id, movie_title = inputs

    user_embedding = self.user_embeddings(user_id)
    movie_embedding = self.movie_embeddings(movie_title)

    return self.ratings(tf.concat([user_embedding, movie_embedding], axis=1))

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

RankingModel()((["42"], ["One Flew Over the Cuckoo's Nest (1975)"]))
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['42']
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['42']
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ["One Flew Over the Cuckoo's Nest (1975)"]
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ["One Flew Over the Cuckoo's Nest (1975)"]
Consider rewriting this model with the Functional API.
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[0.03740937]], dtype=float32)>

הפסד ומדדים

המרכיב הבא הוא ההפסד המשמש לאימון המודל שלנו. ל-TFRS יש כמה שכבות אובדן ומשימות כדי להקל על כך.

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

נשתמש ביחד זה עם MeanSquaredError אובדן Keras כדי לחזות את הרייטינג.

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

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

הדגם המלא

כעת נוכל לחבר את הכל יחד למודל. TFRs חושף מחלקת מודל הבסיס ( tfrs.models.Model ) אשר מייעלת דגמים bulding: כל מה שאנחנו צריכים לעשות זה להגדיר את הרכיבים __init__ השיטה, וליישם את compute_loss השיטה, לוקח את התכונות גלם והחזרת ערך הפסד .

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

class MovielensModel(tfrs.models.Model):

  def __init__(self):
    super().__init__()
    self.ranking_model: tf.keras.Model = RankingModel()
    self.task: tf.keras.layers.Layer = tfrs.tasks.Ranking(
      loss = tf.keras.losses.MeanSquaredError(),
      metrics=[tf.keras.metrics.RootMeanSquaredError()]
    )

  def call(self, features: Dict[str, tf.Tensor]) -> tf.Tensor:
    return self.ranking_model(
        (features["user_id"], features["movie_title"]))

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

    rating_predictions = self(features)

    # The task computes the loss and the metrics.
    return self.task(labels=labels, predictions=rating_predictions)

התאמה והערכה

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

בואו נראה תחילה את הדגם.

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

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

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

לאחר מכן אמן את הדגם:

model.fit(cached_train, epochs=3)
Epoch 1/3
10/10 [==============================] - 2s 26ms/step - root_mean_squared_error: 2.1718 - loss: 4.3303 - regularization_loss: 0.0000e+00 - total_loss: 4.3303
Epoch 2/3
10/10 [==============================] - 0s 8ms/step - root_mean_squared_error: 1.1227 - loss: 1.2602 - regularization_loss: 0.0000e+00 - total_loss: 1.2602
Epoch 3/3
10/10 [==============================] - 0s 8ms/step - root_mean_squared_error: 1.1162 - loss: 1.2456 - regularization_loss: 0.0000e+00 - total_loss: 1.2456
<keras.callbacks.History at 0x7f28389eaa90>

ככל שהדגם מתאמן, ההפסד יורד ומדד ה-RMSE משתפר.

לבסוף, נוכל להעריך את המודל שלנו במערך הבדיקות:

model.evaluate(cached_test, return_dict=True)
5/5 [==============================] - 2s 14ms/step - root_mean_squared_error: 1.1108 - loss: 1.2287 - regularization_loss: 0.0000e+00 - total_loss: 1.2287
{'root_mean_squared_error': 1.1108061075210571,
 'loss': 1.2062578201293945,
 'regularization_loss': 0,
 'total_loss': 1.2062578201293945}

ככל שמדד ה-RMSE נמוך יותר, כך המודל שלנו מדויק יותר בחיזוי דירוגים.

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

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

test_ratings = {}
test_movie_titles = ["M*A*S*H (1970)", "Dances with Wolves (1990)", "Speed (1994)"]
for movie_title in test_movie_titles:
  test_ratings[movie_title] = model({
      "user_id": np.array(["42"]),
      "movie_title": np.array([movie_title])
  })

print("Ratings:")
for title, score in sorted(test_ratings.items(), key=lambda x: x[1], reverse=True):
  print(f"{title}: {score}")
Ratings:
M*A*S*H (1970): [[3.584712]]
Dances with Wolves (1990): [[3.551556]]
Speed (1994): [[3.5215874]]

מייצא להגשה

ניתן לייצא את הדגם בקלות להגשה:

tf.saved_model.save(model, "export")
2021-10-02 11:04:38.235611: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
WARNING:absl:Found untraced functions such as ranking_1_layer_call_and_return_conditional_losses, ranking_1_layer_call_fn, ranking_1_layer_call_fn, ranking_1_layer_call_and_return_conditional_losses, ranking_1_layer_call_and_return_conditional_losses while saving (showing 5 of 5). These functions will not be directly callable after loading.
INFO:tensorflow:Assets written to: export/assets
INFO:tensorflow:Assets written to: export/assets

כעת נוכל לטעון אותו בחזרה ולבצע תחזיות:

loaded = tf.saved_model.load("export")

loaded({"user_id": np.array(["42"]), "movie_title": ["Speed (1994)"]}).numpy()
array([[3.5215874]], dtype=float32)

הצעדים הבאים

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

כמובן שהכנת מערכת דירוג מעשית דורשת הרבה יותר מאמץ.

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

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