日付を保存! Google I / Oが5月18日から20日に戻ってきます今すぐ登録
このページは Cloud Translation API によって翻訳されました。
Switch to English

マルチタスク推奨者

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロードする

基本的な検索チュートリアルでは、映画の時計を肯定的な相互作用信号として使用する検索システムを構築しました。

ただし、多くのアプリケーションでは、利用できるフィードバックの豊富なソースが複数あります。たとえば、eコマースサイトでは、製品ページへのユーザーのアクセス(豊富ですが、信号が比較的少ない)、画像のクリック、カートへの追加、そして最後に購入を記録できます。レビューや返品などの購入後のシグナルを記録することもあります。

これらのさまざまな形式のフィードバックをすべて統合することは、ユーザーが使いたがり、全体的なパフォーマンスを犠牲にして1つのメトリックを最適化しないシステムを構築するために重要です。

さらに、複数のタスクの共同モデルを構築すると、多数のタスク固有のモデルを構築するよりも良い結果が得られる可能性があります。これは、一部のデータが豊富で(クリックなど)、一部のデータがまばらである(購入、返品、手動レビュー)場合に特に当てはまります。これらのシナリオでは、ジョイントモデルは、豊富なタスクから学習した表現を使用して、転移学習として知られる現象を介してスパースタスクの予測を改善できる場合があります。たとえば、このペーパーでは、スパースユーザー調査から明示的なユーザー評価を予測するモデルは、豊富なクリックログデータを使用する補助タスクを追加することで大幅に改善できることを示しています。

このチュートリアルでは、暗黙的(映画鑑賞)と明示的信号(評価)の両方を使用して、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

データセットの準備

Movielens100Kデータセットを使用します。

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

マルチタスクモデル

マルチタスク推奨には2つの重要な部分があります。

  1. それらは2つ以上の目的に最適化されるため、2つ以上の損失があります。
  2. それらはタスク間で変数を共有し、転移学習を可能にします。

このチュートリアルでは、以前と同じようにモデルを定義しますが、1つのタスクではなく、2つのタスクがあります。1つは評価を予測し、もう1つは映画の視聴を予測します。

ユーザーモデルと映画モデルは以前と同じです。

user_model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.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.experimental.preprocessing.StringLookup(
      vocabulary=unique_movie_titles, mask_token=None),
  tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
])

ただし、ここで2つのタスクがあります。 1つ目は、評価タスクです。

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

その目標は、評価を可能な限り正確に予測することです。

2番目は検索タスクです。

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.experimental.preprocessing.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.experimental.preprocessing.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
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
10/10 [==============================] - 2s 235ms/step - root_mean_squared_error: 2.0903 - factorized_top_k/top_1_categorical_accuracy: 4.0000e-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.0296 - factorized_top_k/top_100_categorical_accuracy: 0.0590 - loss: 4.0315 - regularization_loss: 0.0000e+00 - total_loss: 4.0315
Epoch 2/3
10/10 [==============================] - 2s 219ms/step - root_mean_squared_error: 1.1531 - factorized_top_k/top_1_categorical_accuracy: 3.8750e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0026 - factorized_top_k/top_10_categorical_accuracy: 0.0055 - factorized_top_k/top_50_categorical_accuracy: 0.0298 - factorized_top_k/top_100_categorical_accuracy: 0.0593 - loss: 1.3189 - regularization_loss: 0.0000e+00 - total_loss: 1.3189
Epoch 3/3
10/10 [==============================] - 2s 219ms/step - root_mean_squared_error: 1.1198 - factorized_top_k/top_1_categorical_accuracy: 3.8750e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0026 - factorized_top_k/top_10_categorical_accuracy: 0.0055 - factorized_top_k/top_50_categorical_accuracy: 0.0302 - factorized_top_k/top_100_categorical_accuracy: 0.0598 - loss: 1.2479 - regularization_loss: 0.0000e+00 - total_loss: 1.2479
5/5 [==============================] - 1s 107ms/step - root_mean_squared_error: 1.1130 - factorized_top_k/top_1_categorical_accuracy: 5.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0030 - factorized_top_k/top_10_categorical_accuracy: 0.0053 - factorized_top_k/top_50_categorical_accuracy: 0.0298 - factorized_top_k/top_100_categorical_accuracy: 0.0595 - 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
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
10/10 [==============================] - 2s 219ms/step - root_mean_squared_error: 3.7237 - factorized_top_k/top_1_categorical_accuracy: 0.0014 - factorized_top_k/top_5_categorical_accuracy: 0.0101 - factorized_top_k/top_10_categorical_accuracy: 0.0208 - factorized_top_k/top_50_categorical_accuracy: 0.1012 - factorized_top_k/top_100_categorical_accuracy: 0.1795 - loss: 69818.0284 - regularization_loss: 0.0000e+00 - total_loss: 69818.0284
Epoch 2/3
10/10 [==============================] - 2s 217ms/step - root_mean_squared_error: 3.7495 - factorized_top_k/top_1_categorical_accuracy: 0.0028 - factorized_top_k/top_5_categorical_accuracy: 0.0189 - factorized_top_k/top_10_categorical_accuracy: 0.0385 - factorized_top_k/top_50_categorical_accuracy: 0.1691 - factorized_top_k/top_100_categorical_accuracy: 0.2929 - loss: 67473.2876 - regularization_loss: 0.0000e+00 - total_loss: 67473.2876
Epoch 3/3
10/10 [==============================] - 2s 218ms/step - root_mean_squared_error: 3.7648 - factorized_top_k/top_1_categorical_accuracy: 0.0032 - factorized_top_k/top_5_categorical_accuracy: 0.0223 - factorized_top_k/top_10_categorical_accuracy: 0.0456 - factorized_top_k/top_50_categorical_accuracy: 0.1879 - factorized_top_k/top_100_categorical_accuracy: 0.3148 - loss: 66329.2514 - regularization_loss: 0.0000e+00 - total_loss: 66329.2514
5/5 [==============================] - 1s 108ms/step - root_mean_squared_error: 3.7730 - factorized_top_k/top_1_categorical_accuracy: 0.0011 - factorized_top_k/top_5_categorical_accuracy: 0.0098 - 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.2353 - 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
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.
10/10 [==============================] - 2s 216ms/step - root_mean_squared_error: 2.5007 - factorized_top_k/top_1_categorical_accuracy: 0.0015 - factorized_top_k/top_5_categorical_accuracy: 0.0100 - factorized_top_k/top_10_categorical_accuracy: 0.0207 - factorized_top_k/top_50_categorical_accuracy: 0.1009 - factorized_top_k/top_100_categorical_accuracy: 0.1788 - loss: 69811.8239 - regularization_loss: 0.0000e+00 - total_loss: 69811.8239
Epoch 2/3
10/10 [==============================] - 2s 217ms/step - root_mean_squared_error: 1.2097 - factorized_top_k/top_1_categorical_accuracy: 0.0023 - factorized_top_k/top_5_categorical_accuracy: 0.0184 - factorized_top_k/top_10_categorical_accuracy: 0.0367 - factorized_top_k/top_50_categorical_accuracy: 0.1638 - factorized_top_k/top_100_categorical_accuracy: 0.2875 - loss: 67481.2727 - regularization_loss: 0.0000e+00 - total_loss: 67481.2727
Epoch 3/3
10/10 [==============================] - 2s 216ms/step - root_mean_squared_error: 1.1200 - factorized_top_k/top_1_categorical_accuracy: 0.0032 - factorized_top_k/top_5_categorical_accuracy: 0.0223 - factorized_top_k/top_10_categorical_accuracy: 0.0443 - factorized_top_k/top_50_categorical_accuracy: 0.1862 - factorized_top_k/top_100_categorical_accuracy: 0.3150 - loss: 66297.9304 - regularization_loss: 0.0000e+00 - total_loss: 66297.9304
5/5 [==============================] - 1s 111ms/step - root_mean_squared_error: 1.1312 - factorized_top_k/top_1_categorical_accuracy: 8.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0087 - factorized_top_k/top_10_categorical_accuracy: 0.0219 - factorized_top_k/top_50_categorical_accuracy: 0.1248 - factorized_top_k/top_100_categorical_accuracy: 0.2344 - loss: 31062.8213 - regularization_loss: 0.0000e+00 - total_loss: 31062.8213
Retrieval top-100 accuracy: 0.234.
Ranking RMSE: 1.131.

その結果、それぞれの特殊なモデルとほぼ同じように両方のタスクで実行されるモデルが得られます。

ここでの結果は、この場合のジョイントモデルからの明確な精度の利点を示していませんが、マルチタスク学習は一般に非常に便利なツールです。データが豊富なタスク(クリックなど)から密接に関連するデータが少ないタスク(購入など)に知識を転送できると、より良い結果が期待できます。