Rekomendasi film: peringkat

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Sistem pemberi rekomendasi dunia nyata sering terdiri dari dua tahap:

  1. Tahap pengambilan bertanggung jawab untuk memilih satu set awal ratusan kandidat dari semua kandidat yang mungkin. Tujuan utama model ini adalah untuk secara efisien menyingkirkan semua kandidat yang tidak diminati pengguna. Karena model pengambilan mungkin berurusan dengan jutaan kandidat, model ini harus efisien secara komputasi.
  2. Tahap pemeringkatan mengambil output dari model pengambilan dan menyempurnakannya untuk memilih beberapa rekomendasi terbaik. Tugasnya adalah mempersempit set item yang mungkin diminati pengguna menjadi daftar kandidat yang mungkin.

Kami akan fokus pada tahap kedua, peringkat. Jika Anda tertarik dalam tahap pengambilan, kita lihat kami pengambilan tutorial.

Dalam tutorial ini, kita akan:

  1. Dapatkan data kami dan pisahkan menjadi set pelatihan dan pengujian.
  2. Menerapkan model peringkat.
  3. Sesuaikan dan evaluasi.

Impor

Pertama-tama mari kita singkirkan impor kita.

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

Mempersiapkan kumpulan data

Kita akan menggunakan data yang sama dengan pengambilan tutorial. Kali ini, kami juga akan mempertahankan peringkat: ini adalah tujuan yang kami coba prediksi.

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

Seperti sebelumnya, kami akan membagi data dengan menempatkan 80% dari peringkat di set kereta, dan 20% di set tes.

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)

Mari kita juga mencari tahu id pengguna unik dan judul film yang ada dalam data.

Hal ini penting karena kita harus mampu memetakan nilai mentah dari fitur kategoris kita untuk menyematkan vektor dalam model kita. Untuk melakukan itu, kita memerlukan kosakata yang memetakan nilai fitur mentah ke bilangan bulat dalam rentang yang berdekatan: ini memungkinkan kita untuk mencari embedding yang sesuai di tabel embedding kita.

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

Menerapkan model

Arsitektur

Model peringkat tidak menghadapi kendala efisiensi yang sama seperti model pengambilan, jadi kami memiliki sedikit lebih banyak kebebasan dalam pilihan arsitektur kami.

Model yang terdiri dari beberapa lapisan padat bertumpuk adalah arsitektur yang relatif umum untuk tugas peringkat. Kita dapat menerapkannya sebagai berikut:

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

Model ini mengambil ID pengguna dan judul film, dan menampilkan peringkat yang diprediksi:

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

Kerugian dan metrik

Komponen selanjutnya adalah loss yang digunakan untuk melatih model kita. TFRS memiliki beberapa lapisan kerugian dan tugas untuk membuatnya mudah.

Dalam hal ini, kami akan memanfaatkan Ranking objek tugas: pembungkus kenyamanan yang bundel bersama-sama fungsi kerugian dan metrik perhitungan.

Kami akan menggunakannya bersama-sama dengan MeanSquaredError kerugian Keras untuk memprediksi peringkat.

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

Tugas itu sendiri adalah lapisan Keras yang menganggap benar dan diprediksi sebagai argumen, dan mengembalikan kerugian yang dihitung. Kami akan menggunakannya untuk mengimplementasikan loop pelatihan model.

Model lengkapnya

Kita sekarang dapat menggabungkan semuanya menjadi sebuah model. TFRS mengekspos kelas model dasar ( tfrs.models.Model ) yang merampingkan model bulding: semua yang perlu kita lakukan adalah untuk mengatur komponen dalam __init__ metode, dan melaksanakan compute_loss metode, mengambil dalam fitur mentah dan mengembalikan nilai kerugian .

Model dasar kemudian akan mengurus pembuatan loop pelatihan yang sesuai agar sesuai dengan model kita.

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)

Pas dan mengevaluasi

Setelah mendefinisikan model, kita dapat menggunakan rutinitas pemasangan dan evaluasi Keras standar untuk menyesuaikan dan mengevaluasi model.

Mari kita instantiate modelnya dulu.

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

Kemudian shuffle, batch, dan cache data pelatihan dan evaluasi.

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

Kemudian latih modelnya:

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>

Saat model berlatih, kerugian berkurang dan metrik RMSE membaik.

Akhirnya, kami dapat mengevaluasi model kami pada set pengujian:

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}

Semakin rendah metrik RMSE, semakin akurat model kami dalam memprediksi peringkat.

Menguji model peringkat

Sekarang kita dapat menguji model peringkat dengan menghitung prediksi untuk satu set film dan kemudian memberi peringkat film ini berdasarkan prediksi:

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

Mengekspor untuk disajikan

Model dapat dengan mudah diekspor untuk disajikan:

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

Kami sekarang dapat memuatnya kembali dan melakukan prediksi:

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

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

Langkah selanjutnya

Model di atas memberi kita awal yang baik untuk membangun sistem peringkat.

Tentu saja, membuat sistem peringkat yang praktis membutuhkan lebih banyak usaha.

Dalam kebanyakan kasus, model peringkat dapat ditingkatkan secara substansial dengan menggunakan lebih banyak fitur daripada hanya pengidentifikasi pengguna dan kandidat. Untuk melihat bagaimana melakukan hal itu, kita lihat di sisi fitur tutorial.

Pemahaman yang cermat tentang tujuan yang layak dioptimalkan juga diperlukan. Untuk memulai membangun recommender yang mengoptimalkan beberapa tujuan, kita lihat kami multitask tutorial.