Rekomendowanie filmów: ranking

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Rzeczywiste systemy rekomendujące często składają się z dwóch etapów:

  1. Etap wyszukiwania odpowiada za wybór początkowego zestawu setek kandydatów spośród wszystkich możliwych kandydatów. Głównym celem tego modelu jest skuteczne wyeliminowanie wszystkich kandydatów, którymi użytkownik nie jest zainteresowany. Ponieważ model wyszukiwania może dotyczyć milionów kandydatów, musi być wydajny obliczeniowo.
  2. Etap rankingu obejmuje dane wyjściowe modelu wyszukiwania i dostraja je, aby wybrać najlepszą możliwą garść rekomendacji. Jego zadaniem jest zawężenie zbioru pozycji, którymi użytkownik może być zainteresowany, do krótkiej listy prawdopodobnych kandydatów.

Skupimy się na drugim etapie, rankingu. Jeśli jesteś zainteresowany w etapie odzyskiwania, przyjrzeć się naszym pobierania tutoriala.

W tym samouczku zamierzamy:

  1. Pobierz nasze dane i podziel je na zestaw treningowy i testowy.
  2. Implementuj model rankingowy.
  3. Dopasuj i oceń to.

Import

Najpierw usuńmy nasz import.

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

Przygotowanie zbioru danych

Zamierzamy wykorzystać te same dane jak odzyskiwania tutoriala. Tym razem również utrzymamy oceny: to są cele, które staramy się przewidzieć.

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

Tak jak poprzednio, podzielimy dane, umieszczając 80% ocen w zestawie pociągu i 20% w zestawie testowym.

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)

Przeanalizujmy również unikalne identyfikatory użytkowników i tytuły filmów obecne w danych.

Jest to ważne, ponieważ musimy być w stanie odwzorować surowe wartości naszych cech kategorycznych na wektory osadzenia w naszych modelach. Aby to zrobić, potrzebujemy słownika, który odwzorowuje surową wartość cechy na liczbę całkowitą w ciągłym zakresie: pozwala nam to wyszukać odpowiednie osadzenia w naszych tabelach osadzania.

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

Wdrażanie modelu

Architektura

Modele rankingowe nie borykają się z takimi samymi ograniczeniami wydajności, jak modele wyszukiwania, dzięki czemu mamy nieco więcej swobody w wyborze architektur.

Model składający się z wielu ułożonych w stos gęstych warstw jest stosunkowo powszechną architekturą do zadań rankingowych. Możemy to zaimplementować w następujący sposób:

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

Ten model pobiera identyfikatory użytkowników i tytuły filmów i generuje przewidywaną ocenę:

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

Strata i metryki

Kolejnym składnikiem jest strata wykorzystywana do trenowania naszego modelu. TFRS ma kilka warstw strat i zadań, aby to ułatwić.

W tym przypadku będziemy korzystać z Ranking obiektu zadanie: owijki wygody który łączy ze sobą funkcję strat oraz metrykę obliczeń.

Użyjemy go wraz z MeanSquaredError utraty Keras w celu przewidzenia ocen.

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

Samo zadanie jest warstwą Keras, która przyjmuje prawdę i przewidywaną jako argumenty i zwraca obliczoną stratę. Wykorzystamy to do zaimplementowania pętli szkoleniowej modelu.

Pełny model

Teraz możemy to wszystko złożyć w model. TFRS naraża klasę modelu podstawowego ( tfrs.models.Model ), który usprawnia modele bulding: wszystko, co musisz zrobić, to założyć składników w __init__ metody i wdrożenie compute_loss sposób, biorąc w surowych cech i powrocie wartość strat .

Model bazowy zajmie się następnie stworzeniem odpowiedniej pętli treningowej, która będzie pasować do naszego modelu.

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)

Dopasowanie i ocena

Po zdefiniowaniu modelu możemy użyć standardowych procedur dopasowania i oceny Keras, aby dopasować i ocenić model.

Najpierw stwórzmy egzemplarz modelu.

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

Następnie przetasuj, wsadowo i buforuj dane uczące i oceniające.

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

Następnie wytrenuj model:

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>

W miarę trenowania modelu straty spadają, a metryka RMSE poprawia się.

Na koniec możemy ocenić nasz model na zestawie testowym:

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}

Im niższy wskaźnik RMSE, tym dokładniejszy jest nasz model w przewidywaniu ocen.

Testowanie modelu rankingowego

Teraz możemy przetestować model rankingowy, obliczając prognozy dla zestawu filmów, a następnie uszeregować te filmy na podstawie prognoz:

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

Eksportowanie do serwowania

Model można łatwo wyeksportować do serwowania:

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

Możemy teraz załadować go z powrotem i wykonać prognozy:

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

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

Następne kroki

Powyższy model daje nam przyzwoity początek w budowaniu systemu rankingowego.

Oczywiście stworzenie praktycznego systemu rankingowego wymaga znacznie więcej wysiłku.

W większości przypadków model rankingowy można znacznie ulepszyć, używając większej liczby funkcji, a nie tylko identyfikatorów użytkowników i kandydatów. Aby zobaczyć, jak to zrobić, zajrzyj na stronę funkcje samouczek.

Niezbędne jest również dokładne zrozumienie celów, które warto zoptymalizować. Aby zacząć budowę dzia rekomendacji, która optymalizuje wiele celów, rzucić okiem na naszej wielozadaniowej tutoriala.