Tận dụng các tính năng ngữ cảnh

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 tạo lông, chúng tôi đã kết hợp nhiều tính năng ngoài chỉ định danh người dùng và phim vào các mô hình của mình, nhưng chúng tôi chưa khám phá liệu các tính năng đó có cải thiện độ chính xác của mô hình hay không.

Nhiều yếu tố ảnh hưởng đến việc liệu các tính năng ngoài id có hữu ích trong mô hình đề xuất hay không:

  1. Tầm quan trọng của ngữ cảnh : nếu sở thích của người dùng tương đối ổn định giữa các ngữ cảnh và thời gian, thì các tính năng ngữ cảnh có thể không mang lại nhiều lợi ích. Tuy nhiên, nếu sở thích của người dùng có tính ngữ cảnh cao, thì việc thêm ngữ cảnh sẽ cải thiện mô hình đáng kể. Ví dụ: ngày trong tuần có thể là một tính năng quan trọng khi quyết định giới thiệu một clip ngắn hay phim: người dùng có thể chỉ có thời gian để xem nội dung ngắn trong tuần, nhưng có thể thư giãn và thưởng thức một bộ phim đủ thời lượng vào cuối tuần. . Tương tự, dấu thời gian truy vấn có thể đóng một vai trò quan trọng trong việc lập mô hình động lực về mức độ phổ biến: một bộ phim có thể rất nổi tiếng vào khoảng thời gian phát hành, nhưng sẽ nhanh chóng bị phân rã sau đó. Ngược lại, những bộ phim khác có thể là những bộ phim được xem vui vẻ hết lần này đến lần khác.
  2. Dữ liệu thưa thớt : việc sử dụng các tính năng không phải id có thể rất quan trọng nếu dữ liệu thưa thớt. Với một vài quan sát có sẵn cho một người dùng hoặc mặt hàng nhất định, mô hình có thể gặp khó khăn trong việc ước tính mức độ đại diện tốt cho mỗi người dùng hoặc mỗi mặt hàng. Để xây dựng một mô hình chính xác, các tính năng khác như danh mục mục, mô tả và hình ảnh phải được sử dụng để giúp mô hình tổng quát hóa ngoài dữ liệu đào tạo. Điều này đặc biệt có liên quan trong các tình huống khởi động lạnh , nơi có sẵn tương đối ít dữ liệu về một số mặt hàng hoặc người dùng.

Trong hướng dẫn này, chúng tôi sẽ thử nghiệm việc sử dụng các tính năng ngoài tiêu đề phim và id người dùng cho mô hình MovieLens của chúng tôi.

Sơ bộ

Đầu tiên chúng tôi nhập các gói cần thiết.

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

Chúng tôi làm theo hướng dẫn tạo lông và giữ nguyên id người dùng, dấu thời gian và tiêu đề phim.

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

Chúng tôi cũng thực hiện một số công việc dọn phòng để chuẩn bị các từ vựng nổi bật.

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

Định nghĩa mô hình

Mô hình truy vấn

Chúng tôi bắt đầu với mô hình người dùng được xác định trong hướng dẫn tạo lông là lớp đầu tiên của mô hình của chúng tôi, có nhiệm vụ chuyển đổi các ví dụ đầu vào thô thành các bản nhúng tính năng. Tuy nhiên, chúng tôi thay đổi nó một chút để cho phép chúng tôi bật hoặc tắt các tính năng dấu thời gian. Điều này sẽ cho phép chúng tôi chứng minh dễ dàng hơn tác dụng của các tính năng dấu thời gian đối với mô hình. Trong đoạn mã dưới đây, tham số use_timestamps cho phép chúng tôi kiểm soát việc chúng tôi có sử dụng các tính năng dấu thời gian hay không.

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)

Lưu ý rằng việc chúng tôi sử dụng các tính năng dấu thời gian trong hướng dẫn này tương tác với lựa chọn phân chia đào tạo-kiểm tra theo cách không mong muốn. Bởi vì chúng tôi đã phân chia dữ liệu của mình một cách ngẫu nhiên thay vì theo thứ tự thời gian (để đảm bảo rằng các sự kiện thuộc tập dữ liệu thử nghiệm xảy ra muộn hơn các sự kiện trong tập huấn luyện), nên mô hình của chúng tôi có thể rút kinh nghiệm một cách hiệu quả trong tương lai. Điều này là không thực tế: sau tất cả, chúng ta không thể đào tạo một mô hình ngày hôm nay dựa trên dữ liệu từ ngày mai.

Điều này có nghĩa là việc thêm các tính năng thời gian vào mô hình sẽ cho phép mô hình học các mẫu tương tác trong tương lai . Chúng tôi thực hiện việc này chỉ với mục đích minh họa: bản thân bộ dữ liệu MovieLens rất dày đặc và không giống như nhiều bộ dữ liệu trong thế giới thực không được hưởng lợi nhiều từ các tính năng ngoài id người dùng và tiêu đề phim.

Lưu ý này sang một bên, các mô hình trong thế giới thực cũng có thể được hưởng lợi từ các tính năng dựa trên thời gian khác như thời gian trong ngày hoặc ngày trong tuần, đặc biệt nếu dữ liệu có các mô hình theo mùa mạnh mẽ.

Người mẫu ứng cử viên

Để đơn giản, chúng tôi sẽ giữ cho mô hình ứng viên cố định. Một lần nữa, chúng tôi sao chép nó từ hướng dẫn tạo lông :

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)

Mô hình kết hợp

Với cả UserModelMovieModel được định nghĩa, chúng ta có thể đặt một mô hình kết hợp lại với nhau và triển khai logic tổn thất và số liệu của chúng ta.

Ở đây chúng tôi đang xây dựng một mô hình truy xuất. Để biết thêm về cách hoạt động của tính năng này, hãy xem Hướng dẫn truy xuất cơ bản .

Lưu ý rằng chúng tôi cũng cần đảm bảo rằng mô hình truy vấn và mô hình ứng cử viên xuất ra các bản nhúng có kích thước tương thích. Bởi vì chúng tôi sẽ thay đổi kích thước của chúng bằng cách thêm nhiều tính năng hơn, cách dễ nhất để thực hiện điều này là sử dụng lớp chiếu dày đặc sau mỗi mô hình:

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)

Thí nghiệm

Chuẩn bị dữ liệu

Đầu tiên, chúng tôi chia dữ liệu thành tập huấn luyện và tập thử nghiệm.

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

Đường cơ sở: không có tính năng dấu thời gian

Chúng tôi đã sẵn sàng dùng thử mô hình đầu tiên của mình: hãy bắt đầu với việc không sử dụng các tính năng dấu thời gian để thiết lập đường cơ sở của chúng tôi.

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.

Điều này cung cấp cho chúng tôi độ chính xác hàng đầu của 100 cơ sở vào khoảng 0,2.

Nắm bắt động lực thời gian với các tính năng thời gian

Kết quả có thay đổi nếu chúng ta thêm các tính năng thời gian không?

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.

Điều này khá tốt hơn một chút: không chỉ độ chính xác đào tạo cao hơn nhiều, mà độ chính xác của bài kiểm tra cũng được cải thiện đáng kể.

Bước tiếp theo

Hướng dẫn này cho thấy rằng ngay cả các mô hình đơn giản cũng có thể trở nên chính xác hơn khi kết hợp nhiều tính năng hơn. Tuy nhiên, để tận dụng tối đa các tính năng của bạn, bạn thường phải xây dựng các mô hình lớn hơn, sâu hơn. Hãy xem hướng dẫn truy xuất sâu để khám phá điều này chi tiết hơn.