다중 작업 추천자

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

이전과 마찬가지로 이 작업의 목표는 사용자가 시청하거나 시청하지 않을 영화를 예측하는 것입니다.

함께 넣어

우리는 모든 것을 모델 클래스에 모았습니다.

여기서 새로운 구성요소는 - 우리는 2개의 작업과 2개의 손실을 가지고 있기 때문에 - 각각의 손실이 얼마나 중요한지 결정해야 한다는 것입니다. 각 손실에 가중치를 부여하고 이러한 가중치를 하이퍼파라미터로 처리하여 이를 수행할 수 있습니다. 평가 작업에 큰 손실 가중치를 할당하면 모델은 평가 예측에 중점을 둘 것입니다(그러나 여전히 검색 작업의 일부 정보를 사용함). 검색 작업에 큰 손실 가중치를 할당하면 대신 검색에 중점을 둡니다.

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)

여기의 결과는 이 경우 조인트 모델의 명확한 정확도 이점을 보여주지 않지만 다중 작업 학습은 일반적으로 매우 유용한 도구입니다. 데이터가 풍부한 작업(예: 클릭)에서 밀접하게 관련된 데이터가 부족한 작업(예: 구매)으로 지식을 이전할 수 있을 때 더 나은 결과를 기대할 수 있습니다.