Xem trên TensorFlow.org | Chạy trong Google Colab | Xem nguồn trên GitHub | Tải xuống sổ ghi chép |
Trong hướng dẫn xếp hạng cơ bản , chúng tôi được đào tạo một mô hình có thể dự đoán xếp hạng cho cặp user / phim. Mô hình đã được đào tạo để giảm thiểu sai số bình phương trung bình của các xếp hạng dự đoán.
Tuy nhiên, việc tối ưu hóa các dự đoán của mô hình trên các phim riêng lẻ không nhất thiết là phương pháp tốt nhất để đào tạo các mô hình xếp hạng. Chúng tôi không cần các mô hình xếp hạng để dự đoán điểm số với độ chính xác cao. Thay vào đó, chúng tôi quan tâm nhiều hơn đến khả năng của mô hình trong việc tạo danh sách có thứ tự các mặt hàng phù hợp với thứ tự tùy chọn của người dùng.
Thay vì tối ưu hóa các dự đoán của mô hình trên các cặp truy vấn / mục riêng lẻ, chúng tôi có thể tối ưu hóa xếp hạng của mô hình của một danh sách nói chung. Phương pháp này được gọi là listwise xếp hạng.
Trong hướng dẫn này, chúng tôi sẽ sử dụng TensorFlow Recommenders để xây dựng các mô hình xếp hạng theo danh sách. Để làm như vậy, chúng tôi sẽ tận dụng xếp hạng thiệt hại và số liệu được cung cấp bởi TensorFlow Ranking , một gói TensorFlow tập trung vào học tập để cấp bậc .
Sơ bộ
Nếu TensorFlow Ranking không có sẵn trong môi trường thời gian chạy, bạn có thể cài đặt nó sử dụng pip
:
pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets
pip install -q tensorflow-ranking
Sau đó, chúng tôi có thể nhập tất cả các gói cần thiết:
import pprint
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py:119: PkgResourcesDeprecationWarning: 0.18ubuntu0.18.04.1 is an invalid version and will not be supported in a future release PkgResourcesDeprecationWarning,
import tensorflow_ranking as tfr
import tensorflow_recommenders as tfrs
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_addons/utils/ensure_tf_install.py:67: UserWarning: Tensorflow Addons supports using Python ops for all Tensorflow versions above or equal to 2.4.0 and strictly below 2.7.0 (nightly versions are not supported). The versions of TensorFlow you are currently using is 2.7.0 and is not supported. Some things might work, some things might not. If you were to encounter a bug, do not file an issue. If you want to make sure you're using a tested and supported configuration, either change the TensorFlow version or the TensorFlow Addons's version. You can find the compatibility matrix in TensorFlow Addon's readme: https://github.com/tensorflow/addons UserWarning,
Chúng tôi sẽ tiếp tục sử dụng bộ dữ liệu MovieLens 100K. Như trước đây, chúng tôi tải các tập dữ liệu và chỉ giữ lại id người dùng, tiêu đề phim và các tính năng xếp hạng của người dùng cho hướng dẫn này. Chúng tôi cũng làm một số công việc bảo quản nhà cửa để chuẩn bị từ vựng của chúng tôi.
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"],
"user_rating": x["user_rating"],
})
movies = movies.map(lambda x: x["movie_title"])
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"]))))
Xử lý trước dữ liệu
Tuy nhiên, chúng tôi không thể sử dụng trực tiếp bộ dữ liệu MovieLens để tối ưu hóa danh sách. Để thực hiện tối ưu hóa theo danh sách, chúng ta cần có quyền truy cập vào danh sách các phim mà mỗi người dùng đã xếp hạng, nhưng mỗi ví dụ trong tập dữ liệu MovieLens 100K chỉ chứa xếp hạng của một phim duy nhất.
Để giải quyết vấn đề này, chúng tôi biến đổi tập dữ liệu để mỗi ví dụ chứa một id người dùng và danh sách các phim được người dùng đó xếp hạng. Một số phim trong danh sách sẽ được xếp hạng cao hơn những phim khác; mục tiêu của mô hình của chúng tôi sẽ là đưa ra các dự đoán phù hợp với thứ tự này.
Để làm điều này, chúng tôi sử dụng tfrs.examples.movielens.movielens_to_listwise
chức năng helper. Nó lấy tập dữ liệu MovieLens 100K và tạo tập dữ liệu chứa các ví dụ danh sách như đã thảo luận ở trên. Các chi tiết thực hiện có thể được tìm thấy trong mã nguồn .
tf.random.set_seed(42)
# Split between train and tests sets, as before.
shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)
train = shuffled.take(80_000)
test = shuffled.skip(80_000).take(20_000)
# We sample 50 lists for each user for the training data. For each list we
# sample 5 movies from the movies the user rated.
train = tfrs.examples.movielens.sample_listwise(
train,
num_list_per_user=50,
num_examples_per_list=5,
seed=42
)
test = tfrs.examples.movielens.sample_listwise(
test,
num_list_per_user=1,
num_examples_per_list=5,
seed=42
)
Chúng tôi có thể kiểm tra một ví dụ từ dữ liệu đào tạo. Ví dụ bao gồm id người dùng, danh sách 10 id phim và xếp hạng của họ bởi người dùng.
for example in train.take(1):
pprint.pprint(example)
{'movie_title': <tf.Tensor: shape=(5,), dtype=string, numpy= array([b'Postman, The (1997)', b'Liar Liar (1997)', b'Contact (1997)', b'Welcome To Sarajevo (1997)', b'I Know What You Did Last Summer (1997)'], dtype=object)>, 'user_id': <tf.Tensor: shape=(), dtype=string, numpy=b'681'>, 'user_rating': <tf.Tensor: shape=(5,), dtype=float32, numpy=array([4., 5., 1., 4., 1.], dtype=float32)>}
Định nghĩa mô hình
Chúng tôi sẽ đào tạo cùng một mô hình với ba mức lỗ khác nhau:
- có nghĩa là lỗi bình phương,
- mất bản lề cặp, và
- mất danh sách ListMLE.
Ba tổn thất này tương ứng với tối ưu hóa theo chiều kim, theo chiều cặp và theo chiều dọc.
Để đánh giá mô hình chúng tôi sử dụng bình thường được chiết khấu tăng tích lũy (NDCG) . NDCG đo lường xếp hạng dự đoán bằng cách lấy tổng trọng số của xếp hạng thực tế của từng ứng viên. Xếp hạng của những bộ phim được xếp hạng thấp hơn theo mô hình sẽ được giảm giá nhiều hơn. Kết quả là, một mô hình tốt xếp các bộ phim được đánh giá cao trên đầu sẽ có kết quả NDCG cao. Vì số liệu này có tính đến vị trí được xếp hạng của từng ứng viên nên nó là một số liệu theo danh sách.
class RankingModel(tfrs.Model):
def __init__(self, loss):
super().__init__()
embedding_dimension = 32
# Compute embeddings for users.
self.user_embeddings = tf.keras.Sequential([
tf.keras.layers.StringLookup(
vocabulary=unique_user_ids),
tf.keras.layers.Embedding(len(unique_user_ids) + 2, embedding_dimension)
])
# Compute embeddings for movies.
self.movie_embeddings = tf.keras.Sequential([
tf.keras.layers.StringLookup(
vocabulary=unique_movie_titles),
tf.keras.layers.Embedding(len(unique_movie_titles) + 2, embedding_dimension)
])
# Compute predictions.
self.score_model = tf.keras.Sequential([
# Learn multiple dense layers.
tf.keras.layers.Dense(256, activation="relu"),
tf.keras.layers.Dense(64, activation="relu"),
# Make rating predictions in the final layer.
tf.keras.layers.Dense(1)
])
self.task = tfrs.tasks.Ranking(
loss=loss,
metrics=[
tfr.keras.metrics.NDCGMetric(name="ndcg_metric"),
tf.keras.metrics.RootMeanSquaredError()
]
)
def call(self, features):
# We first convert the id features into embeddings.
# User embeddings are a [batch_size, embedding_dim] tensor.
user_embeddings = self.user_embeddings(features["user_id"])
# Movie embeddings are a [batch_size, num_movies_in_list, embedding_dim]
# tensor.
movie_embeddings = self.movie_embeddings(features["movie_title"])
# We want to concatenate user embeddings with movie emebeddings to pass
# them into the ranking model. To do so, we need to reshape the user
# embeddings to match the shape of movie embeddings.
list_length = features["movie_title"].shape[1]
user_embedding_repeated = tf.repeat(
tf.expand_dims(user_embeddings, 1), [list_length], axis=1)
# Once reshaped, we concatenate and pass into the dense layers to generate
# predictions.
concatenated_embeddings = tf.concat(
[user_embedding_repeated, movie_embeddings], 2)
return self.score_model(concatenated_embeddings)
def compute_loss(self, features, training=False):
labels = features.pop("user_rating")
scores = self(features)
return self.task(
labels=labels,
predictions=tf.squeeze(scores, axis=-1),
)
Đào tạo người mẫu
Bây giờ chúng tôi có thể đào tạo từng mô hình trong số ba mô hình.
epochs = 30
cached_train = train.shuffle(100_000).batch(8192).cache()
cached_test = test.batch(4096).cache()
Mô hình lỗi bình phương trung bình
Mô hình này rất giống với mô hình trong hướng dẫn xếp hạng cơ bản . Chúng tôi đào tạo mô hình để giảm thiểu sai số bình phương trung bình giữa xếp hạng thực tế và xếp hạng dự đoán. Do đó, sự mất mát này được tính riêng cho từng bộ phim và việc đào tạo là quan trọng.
mse_model = RankingModel(tf.keras.losses.MeanSquaredError())
mse_model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
mse_model.fit(cached_train, epochs=epochs, verbose=False)
<keras.callbacks.History at 0x7f64791a5d10>
Mô hình mất bản lề theo cặp
Bằng cách giảm thiểu tổn thất bản lề theo cặp, mô hình cố gắng tối đa hóa sự khác biệt giữa các dự đoán của mô hình cho một mục được đánh giá cao và một mục được đánh giá thấp: sự khác biệt đó càng lớn thì tổn thất của mô hình càng thấp. Tuy nhiên, một khi sự khác biệt đủ lớn, khoản lỗ sẽ trở thành 0, ngăn mô hình tối ưu hóa hơn nữa cặp cụ thể này và để nó tập trung vào các cặp khác được xếp hạng không chính xác
Sự mất mát này không được tính cho các phim riêng lẻ, mà là cho các phim cặp. Do đó, việc đào tạo sử dụng sự mất mát này là đi đôi với nhau.
hinge_model = RankingModel(tfr.keras.losses.PairwiseHingeLoss())
hinge_model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
hinge_model.fit(cached_train, epochs=epochs, verbose=False)
<keras.callbacks.History at 0x7f647914f190>
Mô hình theo danh sách
Các ListMLE
mất từ TensorFlow bày tỏ Ranking liệt kê ước tính khả năng tối đa. Để tính toán tổn thất ListMLE, trước tiên chúng tôi sử dụng xếp hạng của người dùng để tạo xếp hạng tối ưu. Sau đó, chúng tôi tính toán khả năng mỗi ứng viên bị xếp hạng cao hơn bất kỳ mục nào bên dưới mục đó trong bảng xếp hạng tối ưu bằng cách sử dụng điểm số dự đoán. Mô hình cố gắng giảm thiểu khả năng xảy ra như vậy để đảm bảo các ứng viên được đánh giá cao không bị các ứng viên được đánh giá thấp xếp hạng. Bạn có thể tìm hiểu thêm về các chi tiết của ListMLE trong phần 2.2 của giấy ListMLE Chức-aware: Một tuần tự Learning Process .
Lưu ý rằng vì khả năng xảy ra được tính đối với một ứng cử viên và tất cả các ứng viên thấp hơn nó trong xếp hạng tối ưu, sự mất mát không theo cặp mà là theo danh sách. Do đó, đào tạo sử dụng tối ưu hóa danh sách.
listwise_model = RankingModel(tfr.keras.losses.ListMLELoss())
listwise_model.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))
listwise_model.fit(cached_train, epochs=epochs, verbose=False)
<keras.callbacks.History at 0x7f647b35f350>
So sánh các mô hình
mse_model_result = mse_model.evaluate(cached_test, return_dict=True)
print("NDCG of the MSE Model: {:.4f}".format(mse_model_result["ndcg_metric"]))
1/1 [==============================] - 0s 405ms/step - ndcg_metric: 0.9053 - root_mean_squared_error: 0.9671 - loss: 0.9354 - regularization_loss: 0.0000e+00 - total_loss: 0.9354 NDCG of the MSE Model: 0.9053
hinge_model_result = hinge_model.evaluate(cached_test, return_dict=True)
print("NDCG of the pairwise hinge loss model: {:.4f}".format(hinge_model_result["ndcg_metric"]))
1/1 [==============================] - 0s 457ms/step - ndcg_metric: 0.9058 - root_mean_squared_error: 3.8330 - loss: 1.0180 - regularization_loss: 0.0000e+00 - total_loss: 1.0180 NDCG of the pairwise hinge loss model: 0.9058
listwise_model_result = listwise_model.evaluate(cached_test, return_dict=True)
print("NDCG of the ListMLE model: {:.4f}".format(listwise_model_result["ndcg_metric"]))
1/1 [==============================] - 0s 432ms/step - ndcg_metric: 0.9071 - root_mean_squared_error: 2.7224 - loss: 4.5401 - regularization_loss: 0.0000e+00 - total_loss: 4.5401 NDCG of the ListMLE model: 0.9071
Trong số ba mô hình, mô hình được đào tạo bằng cách sử dụng ListMLE có chỉ số NDCG cao nhất. Kết quả này cho thấy cách tối ưu hóa theo chiều danh sách có thể được sử dụng để đào tạo các mô hình xếp hạng và có khả năng tạo ra các mô hình hoạt động tốt hơn so với các mô hình được tối ưu hóa theo kiểu điểm hoặc theo cặp.