ผู้แนะนำแบบมัลติทาสก์

ใน การกวดวิชาพื้นฐานการดึง เราสร้างระบบการเรียกใช้นาฬิกาหนังเป็นสัญญาณการมีปฏิสัมพันธ์ในเชิงบวก

อย่างไรก็ตาม ในหลาย ๆ แอปพลิเคชัน มีแหล่งความคิดเห็นมากมายให้เลือกใช้ ตัวอย่างเช่น ไซต์อีคอมเมิร์ซอาจบันทึกการเข้าชมของผู้ใช้ไปยังหน้าผลิตภัณฑ์ (มีสัญญาณค่อนข้างมาก แต่ค่อนข้างต่ำ) การคลิกรูปภาพ การเพิ่มลงในรถเข็น และสุดท้ายคือการซื้อ อาจบันทึกสัญญาณหลังการซื้อ เช่น บทวิจารณ์และการคืนสินค้า

การผสานรวมความคิดเห็นในรูปแบบต่างๆ เหล่านี้มีความสำคัญต่อการสร้างระบบที่ผู้ใช้ชอบใช้ และไม่ได้ปรับให้เหมาะสมสำหรับเมตริกใด ๆ ทั้งสิ้นโดยบั่นทอนประสิทธิภาพโดยรวม

นอกจากนี้ การสร้างแบบจำลองร่วมสำหรับงานหลายงานอาจให้ผลลัพธ์ที่ดีกว่าการสร้างแบบจำลองเฉพาะงานจำนวนมาก โดยเฉพาะอย่างยิ่งเมื่อมีข้อมูลจำนวนมาก (เช่น การคลิก) และข้อมูลบางส่วนมีน้อย (การซื้อ การคืนสินค้า การตรวจสอบด้วยตนเอง) ในสถานการณ์เหล่านั้นรูปแบบการร่วมทุนอาจจะไม่สามารถที่จะใช้เป็นตัวแทนได้เรียนรู้จากงานมากมายในการปรับปรุงการคาดการณ์ของตนในงานเบาบางผ่านปรากฏการณ์ที่เรียกว่า การเรียนรู้การถ่ายโอน ยกตัวอย่างเช่น การวิจัยนี้ แสดงให้เห็นว่ารูปแบบการทำนายคะแนนของผู้ใช้อย่างชัดเจนจากการสำรวจผู้ใช้เบาบางได้ดีขึ้นอย่างมีนัยสำคัญโดยการเพิ่มงานเสริมที่ใช้ข้อมูลเข้าสู่ระบบคลิกที่อุดมสมบูรณ์

ในบทช่วยสอนนี้ เราจะสร้างผู้แนะนำแบบหลายวัตถุประสงค์สำหรับ 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)
   
)
)

ก่อนหน้านี้ เป้าหมายของงานนี้คือการทำนายว่าผู้ใช้จะดูหนังเรื่องไหนหรือไม่ดู

ประกอบเข้าด้วยกัน

เรารวบรวมทุกอย่างไว้ในคลาสโมเดล

องค์ประกอบใหม่ที่นี่คือ - เนื่องจากเรามีงานสองงานและการสูญเสียสองครั้ง - เราจำเป็นต้องตัดสินใจว่าการสูญเสียแต่ละครั้งมีความสำคัญเพียงใด เราสามารถทำได้โดยให้น้ำหนักที่สูญเสียไปแต่ละครั้ง และถือว่าตุ้มน้ำหนักเหล่านี้เป็นพารามิเตอร์ไฮเปอร์ หากเรากำหนดน้ำหนักที่สูญเสียจำนวนมากให้กับงานการให้คะแนน โมเดลของเราจะเน้นไปที่การคาดการณ์การให้คะแนน (แต่ยังคงใช้ข้อมูลบางส่วนจากงานการดึงข้อมูล) หากเรากำหนดน้ำหนักที่สูญเสียไปมากให้กับงานดึงข้อมูล ก็จะเน้นที่การดึงข้อมูลแทน

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)

แม้ว่าผลลัพธ์ในที่นี้จะไม่ได้แสดงให้เห็นถึงประโยชน์ที่แม่นยำอย่างชัดเจนจากแบบจำลองร่วมในกรณีนี้ แต่การเรียนรู้แบบมัลติทาสก์โดยทั่วไปเป็นเครื่องมือที่มีประโยชน์อย่างยิ่ง เราสามารถคาดหวังผลลัพธ์ที่ดีขึ้นได้เมื่อเราสามารถถ่ายโอนความรู้จากงานที่มีข้อมูลมากมาย (เช่น การคลิก) ไปยังงานแยกข้อมูลที่เกี่ยวข้องอย่างใกล้ชิด (เช่น การซื้อ)