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