ดูบน TensorFlow.org | ทำงานใน Google Colab | ดูแหล่งที่มาบน GitHub | ดาวน์โหลดโน๊ตบุ๊ค |
ใน บทช่วยสอน คุณสมบัติ เราได้รวมคุณสมบัติหลายอย่างนอกเหนือจากตัวระบุผู้ใช้และภาพยนตร์ลงในโมเดลของเรา แต่เราไม่ได้สำรวจว่าคุณสมบัติเหล่านั้นปรับปรุงความแม่นยำของโมเดลหรือไม่
มีหลายปัจจัยที่ส่งผลต่อว่าฟีเจอร์ที่นอกเหนือจาก id นั้นมีประโยชน์ในโมเดลผู้แนะนำหรือไม่:
- ความสำคัญของบริบท : หากค่ากำหนดของผู้ใช้ค่อนข้างคงที่ในบริบทและเวลา คุณลักษณะบริบทอาจไม่ให้ประโยชน์มากนัก อย่างไรก็ตาม หากการตั้งค่าของผู้ใช้มีบริบทสูง การเพิ่มบริบทจะช่วยปรับปรุงโมเดลได้อย่างมาก ตัวอย่างเช่น วันในสัปดาห์อาจเป็นคุณสมบัติที่สำคัญในการตัดสินใจว่าจะแนะนำคลิปสั้นหรือภาพยนตร์: ผู้ใช้อาจมีเวลาดูเนื้อหาสั้น ๆ ระหว่างสัปดาห์เท่านั้น แต่สามารถผ่อนคลายและเพลิดเพลินกับภาพยนตร์เต็มเรื่องได้ในช่วงสุดสัปดาห์ . ในทำนองเดียวกัน การประทับเวลาของข้อความค้นหาอาจมีบทบาทสำคัญในการสร้างแบบจำลองกระแสความนิยม: ภาพยนตร์เรื่องหนึ่งอาจได้รับความนิยมอย่างสูงในช่วงเวลาที่ออกฉาย แต่จะเสื่อมลงอย่างรวดเร็วหลังจากนั้น ในทางกลับกัน หนังเรื่องอื่นๆ อาจเป็นหนังที่ดูแล้วมีความสุขครั้งแล้วครั้งเล่า
- ความเบาบางของ ข้อมูล : การใช้คุณลักษณะที่ไม่ใช่รหัสอาจมีความสำคัญหากข้อมูลมีน้อย ด้วยการสังเกตเพียงเล็กน้อยสำหรับผู้ใช้หรือรายการที่กำหนด โมเดลอาจมีปัญหากับการประมาณการตัวแทนที่ดีต่อผู้ใช้หรือต่อรายการ ในการสร้างแบบจำลองที่ถูกต้อง ต้องใช้คุณสมบัติอื่นๆ เช่น หมวดหมู่รายการ คำอธิบาย และรูปภาพ เพื่อช่วยให้โมเดลมีภาพรวมมากกว่าข้อมูลการฝึก สิ่งนี้มีความเกี่ยวข้องอย่างยิ่งในสถานการณ์ เริ่มต้นแบบเย็น ซึ่งมีข้อมูลค่อนข้างน้อยในบางรายการหรือผู้ใช้
ในบทช่วยสอนนี้ เราจะทดลองกับการใช้คุณสมบัตินอกเหนือจากชื่อภาพยนตร์และรหัสผู้ใช้กับโมเดล MovieLens ของเรา
เบื้องต้น
ก่อนอื่นเรานำเข้าแพ็คเกจที่จำเป็น
pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets
import os
import tempfile
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
import tensorflow_recommenders as tfrs
เราปฏิบัติตาม บทช่วยสอนคุณสมบัติ และเก็บรหัสผู้ใช้ ประทับเวลา และคุณสมบัติชื่อภาพยนตร์
ratings = tfds.load("movielens/100k-ratings", split="train")
movies = tfds.load("movielens/100k-movies", split="train")
ratings = ratings.map(lambda x: {
"movie_title": x["movie_title"],
"user_id": x["user_id"],
"timestamp": x["timestamp"],
})
movies = movies.map(lambda x: x["movie_title"])
เรายังดูแลทำความสะอาดเพื่อเตรียมคำศัพท์เกี่ยวกับคุณลักษณะอีกด้วย
timestamps = np.concatenate(list(ratings.map(lambda x: x["timestamp"]).batch(100)))
max_timestamp = timestamps.max()
min_timestamp = timestamps.min()
timestamp_buckets = np.linspace(
min_timestamp, max_timestamp, num=1000,
)
unique_movie_titles = np.unique(np.concatenate(list(movies.batch(1000))))
unique_user_ids = np.unique(np.concatenate(list(ratings.batch(1_000).map(
lambda x: x["user_id"]))))
คำจำกัดความของโมเดล
แบบสอบถามรุ่น
เราเริ่มต้นด้วยโมเดลผู้ใช้ที่กำหนดไว้ใน บทช่วยสอน คุณสมบัติเป็นเลเยอร์แรกของโมเดลของเรา โดยมอบหมายให้แปลงตัวอย่างอินพุตดิบเป็นการฝังฟีเจอร์ อย่างไรก็ตาม เราเปลี่ยนแปลงเล็กน้อยเพื่อให้เราสามารถเปิดหรือปิดคุณสมบัติการประทับเวลาได้ วิธีนี้จะช่วยให้เราสามารถสาธิตเอฟเฟกต์ที่ฟีเจอร์การประทับเวลามีต่อโมเดลได้ง่ายขึ้น ในโค้ดด้านล่าง พารามิเตอร์ use_timestamps
ช่วยให้เราควบคุมได้ว่าเราใช้คุณลักษณะการประทับเวลาหรือไม่
class UserModel(tf.keras.Model):
def __init__(self, use_timestamps):
super().__init__()
self._use_timestamps = use_timestamps
self.user_embedding = tf.keras.Sequential([
tf.keras.layers.StringLookup(
vocabulary=unique_user_ids, mask_token=None),
tf.keras.layers.Embedding(len(unique_user_ids) + 1, 32),
])
if use_timestamps:
self.timestamp_embedding = tf.keras.Sequential([
tf.keras.layers.Discretization(timestamp_buckets.tolist()),
tf.keras.layers.Embedding(len(timestamp_buckets) + 1, 32),
])
self.normalized_timestamp = tf.keras.layers.Normalization(
axis=None
)
self.normalized_timestamp.adapt(timestamps)
def call(self, inputs):
if not self._use_timestamps:
return self.user_embedding(inputs["user_id"])
return tf.concat([
self.user_embedding(inputs["user_id"]),
self.timestamp_embedding(inputs["timestamp"]),
tf.reshape(self.normalized_timestamp(inputs["timestamp"]), (-1, 1)),
], axis=1)
โปรดทราบว่าการใช้คุณสมบัติการประทับเวลาในบทช่วยสอนนี้โต้ตอบกับตัวเลือกการแบ่งการทดสอบการฝึกของเราในลักษณะที่ไม่พึงประสงค์ เนื่องจากเราได้แบ่งข้อมูลของเราแบบสุ่มแทนที่จะแบ่งตามลำดับเวลา (เพื่อให้แน่ใจว่าเหตุการณ์ที่เป็นของชุดข้อมูลทดสอบจะเกิดขึ้นช้ากว่าที่อยู่ในชุดการฝึก) โมเดลของเราจึงสามารถเรียนรู้จากอนาคตได้อย่างมีประสิทธิภาพ สิ่งนี้ไม่สมจริง เพราะเราไม่สามารถฝึกแบบจำลองในวันนี้กับข้อมูลจากวันพรุ่งนี้
ซึ่งหมายความว่าการเพิ่มคุณสมบัติเวลาให้กับโมเดลช่วยให้เรียนรู้รูปแบบการโต้ตอบ ในอนาคต เราทำเพื่อจุดประสงค์ในการอธิบายเท่านั้น: ชุดข้อมูล MovieLens นั้นมีความหนาแน่นสูงมาก และต่างจากชุดข้อมูลในโลกแห่งความเป็นจริงหลายๆ ตัวที่ไม่ได้รับประโยชน์อย่างมากจากคุณสมบัติอื่นนอกเหนือจาก ID ผู้ใช้และชื่อภาพยนตร์
นอกเหนือจากข้อแม้นี้ โมเดลในโลกแห่งความเป็นจริงอาจได้รับประโยชน์จากคุณลักษณะตามเวลาอื่นๆ เช่น ช่วงเวลาของวันหรือวันในสัปดาห์ โดยเฉพาะอย่างยิ่งหากข้อมูลมีรูปแบบตามฤดูกาลที่ชัดเจน
รูปแบบผู้สมัคร
เพื่อความง่าย เราจะคงรูปแบบผู้สมัครให้คงที่ อีกครั้ง เราคัดลอกจากบท ช่วย สอนคุณสมบัติ:
class MovieModel(tf.keras.Model):
def __init__(self):
super().__init__()
max_tokens = 10_000
self.title_embedding = tf.keras.Sequential([
tf.keras.layers.StringLookup(
vocabulary=unique_movie_titles, mask_token=None),
tf.keras.layers.Embedding(len(unique_movie_titles) + 1, 32)
])
self.title_vectorizer = tf.keras.layers.TextVectorization(
max_tokens=max_tokens)
self.title_text_embedding = tf.keras.Sequential([
self.title_vectorizer,
tf.keras.layers.Embedding(max_tokens, 32, mask_zero=True),
tf.keras.layers.GlobalAveragePooling1D(),
])
self.title_vectorizer.adapt(movies)
def call(self, titles):
return tf.concat([
self.title_embedding(titles),
self.title_text_embedding(titles),
], axis=1)
รุ่นรวม
ด้วยทั้ง UserModel
และ MovieModel
ที่กำหนดไว้ เราสามารถรวบรวมโมเดลที่รวมกัน และใช้ลอจิกการสูญเสียและเมตริกของเรา
ที่นี่เรากำลังสร้างแบบจำลองการดึงข้อมูล สำหรับการทบทวนเกี่ยวกับวิธีการทำงาน ดูบทแนะนำ การดึงข้อมูลพื้นฐาน
โปรดทราบว่าเราจำเป็นต้องตรวจสอบให้แน่ใจด้วยว่ารูปแบบการสืบค้นและการฝังผลลัพธ์ของแบบจำลองตัวเลือกที่มีขนาดที่เข้ากันได้ เนื่องจากเราจะเปลี่ยนขนาดโดยเพิ่มคุณสมบัติให้มากขึ้น วิธีที่ง่ายที่สุดในการดำเนินการนี้คือการใช้เลเยอร์การฉายภาพที่หนาแน่นหลังจากแต่ละรุ่น:
class MovielensModel(tfrs.models.Model):
def __init__(self, use_timestamps):
super().__init__()
self.query_model = tf.keras.Sequential([
UserModel(use_timestamps),
tf.keras.layers.Dense(32)
])
self.candidate_model = tf.keras.Sequential([
MovieModel(),
tf.keras.layers.Dense(32)
])
self.task = tfrs.tasks.Retrieval(
metrics=tfrs.metrics.FactorizedTopK(
candidates=movies.batch(128).map(self.candidate_model),
),
)
def compute_loss(self, features, training=False):
# We only pass the user id and timestamp features into the query model. This
# is to ensure that the training inputs would have the same keys as the
# query inputs. Otherwise the discrepancy in input structure would cause an
# error when loading the query model after saving it.
query_embeddings = self.query_model({
"user_id": features["user_id"],
"timestamp": features["timestamp"],
})
movie_embeddings = self.candidate_model(features["movie_title"])
return self.task(query_embeddings, movie_embeddings)
การทดลอง
เตรียมข้อมูล
ก่อนอื่นเราแบ่งข้อมูลออกเป็นชุดฝึกอบรมและชุดทดสอบ
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)
cached_train = train.shuffle(100_000).batch(2048)
cached_test = test.batch(4096).cache()
พื้นฐาน: ไม่มีคุณสมบัติการประทับเวลา
เราพร้อมที่จะลองใช้โมเดลแรกของเราแล้ว: เริ่มจากไม่ใช้คุณสมบัติการประทับเวลาเพื่อสร้างพื้นฐานของเรา
model = MovielensModel(use_timestamps=False)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
model.fit(cached_train, epochs=3)
train_accuracy = model.evaluate(
cached_train, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]
test_accuracy = model.evaluate(
cached_test, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]
print(f"Top-100 accuracy (train): {train_accuracy:.2f}.")
print(f"Top-100 accuracy (test): {test_accuracy:.2f}.")
Epoch 1/3 WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. 40/40 [==============================] - 10s 169ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0092 - factorized_top_k/top_5_categorical_accuracy: 0.0172 - factorized_top_k/top_10_categorical_accuracy: 0.0256 - factorized_top_k/top_50_categorical_accuracy: 0.0824 - factorized_top_k/top_100_categorical_accuracy: 0.1473 - loss: 14579.4628 - regularization_loss: 0.0000e+00 - total_loss: 14579.4628 Epoch 2/3 40/40 [==============================] - 9s 173ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0020 - factorized_top_k/top_5_categorical_accuracy: 0.0126 - factorized_top_k/top_10_categorical_accuracy: 0.0251 - factorized_top_k/top_50_categorical_accuracy: 0.1129 - factorized_top_k/top_100_categorical_accuracy: 0.2133 - loss: 14136.2137 - regularization_loss: 0.0000e+00 - total_loss: 14136.2137 Epoch 3/3 40/40 [==============================] - 9s 174ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0021 - factorized_top_k/top_5_categorical_accuracy: 0.0155 - factorized_top_k/top_10_categorical_accuracy: 0.0307 - factorized_top_k/top_50_categorical_accuracy: 0.1389 - factorized_top_k/top_100_categorical_accuracy: 0.2535 - loss: 13939.9265 - regularization_loss: 0.0000e+00 - total_loss: 13939.9265 WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. 40/40 [==============================] - 10s 189ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0036 - factorized_top_k/top_5_categorical_accuracy: 0.0226 - factorized_top_k/top_10_categorical_accuracy: 0.0427 - factorized_top_k/top_50_categorical_accuracy: 0.1729 - factorized_top_k/top_100_categorical_accuracy: 0.2944 - loss: 13711.3802 - regularization_loss: 0.0000e+00 - total_loss: 13711.3802 5/5 [==============================] - 3s 267ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0010 - factorized_top_k/top_5_categorical_accuracy: 0.0078 - factorized_top_k/top_10_categorical_accuracy: 0.0184 - factorized_top_k/top_50_categorical_accuracy: 0.1051 - factorized_top_k/top_100_categorical_accuracy: 0.2126 - loss: 30995.8988 - regularization_loss: 0.0000e+00 - total_loss: 30995.8988 Top-100 accuracy (train): 0.29. Top-100 accuracy (test): 0.21.
สิ่งนี้ทำให้เรามีความแม่นยำสูงสุด 100 อันดับแรกที่ประมาณ 0.2
จับภาพไดนามิกของเวลาด้วยคุณสมบัติด้านเวลา
ผลลัพธ์จะเปลี่ยนไปหรือไม่หากเราเพิ่มคุณสมบัติเวลา
model = MovielensModel(use_timestamps=True)
model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
model.fit(cached_train, epochs=3)
train_accuracy = model.evaluate(
cached_train, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]
test_accuracy = model.evaluate(
cached_test, return_dict=True)["factorized_top_k/top_100_categorical_accuracy"]
print(f"Top-100 accuracy (train): {train_accuracy:.2f}.")
print(f"Top-100 accuracy (test): {test_accuracy:.2f}.")
Epoch 1/3 WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. 40/40 [==============================] - 10s 175ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0057 - factorized_top_k/top_5_categorical_accuracy: 0.0148 - factorized_top_k/top_10_categorical_accuracy: 0.0238 - factorized_top_k/top_50_categorical_accuracy: 0.0812 - factorized_top_k/top_100_categorical_accuracy: 0.1487 - loss: 14606.0927 - regularization_loss: 0.0000e+00 - total_loss: 14606.0927 Epoch 2/3 40/40 [==============================] - 9s 176ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0026 - factorized_top_k/top_5_categorical_accuracy: 0.0153 - factorized_top_k/top_10_categorical_accuracy: 0.0304 - factorized_top_k/top_50_categorical_accuracy: 0.1375 - factorized_top_k/top_100_categorical_accuracy: 0.2512 - loss: 13958.5635 - regularization_loss: 0.0000e+00 - total_loss: 13958.5635 Epoch 3/3 40/40 [==============================] - 9s 177ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0026 - factorized_top_k/top_5_categorical_accuracy: 0.0189 - factorized_top_k/top_10_categorical_accuracy: 0.0393 - factorized_top_k/top_50_categorical_accuracy: 0.1713 - factorized_top_k/top_100_categorical_accuracy: 0.3015 - loss: 13696.8511 - regularization_loss: 0.0000e+00 - total_loss: 13696.8511 WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'user_id': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'timestamp': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API. 40/40 [==============================] - 9s 172ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0050 - factorized_top_k/top_5_categorical_accuracy: 0.0323 - factorized_top_k/top_10_categorical_accuracy: 0.0606 - factorized_top_k/top_50_categorical_accuracy: 0.2254 - factorized_top_k/top_100_categorical_accuracy: 0.3637 - loss: 13382.7869 - regularization_loss: 0.0000e+00 - total_loss: 13382.7869 5/5 [==============================] - 1s 237ms/step - 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.0214 - factorized_top_k/top_50_categorical_accuracy: 0.1259 - factorized_top_k/top_100_categorical_accuracy: 0.2468 - loss: 30699.8529 - regularization_loss: 0.0000e+00 - total_loss: 30699.8529 Top-100 accuracy (train): 0.36. Top-100 accuracy (test): 0.25.
สิ่งนี้ค่อนข้างดีขึ้น: ไม่เพียงแต่ความแม่นยำในการฝึกจะสูงขึ้นมากเท่านั้น แต่ความแม่นยำในการทดสอบยังได้รับการปรับปรุงอย่างมากอีกด้วย
ขั้นตอนถัดไป
บทช่วยสอนนี้แสดงให้เห็นว่าแม้แต่โมเดลที่เรียบง่ายก็สามารถมีความแม่นยำมากขึ้นเมื่อรวมคุณสมบัติต่างๆ เข้าด้วยกัน อย่างไรก็ตาม เพื่อให้ได้รับประโยชน์สูงสุดจากคุณลักษณะของคุณ มักจะจำเป็นต้องสร้างแบบจำลองที่ใหญ่และลึกกว่า ดู บทแนะนำการดึงข้อมูลเชิงลึก เพื่อสำรวจรายละเอียดเพิ่มเติม