Sử dụng các tính năng phụ: tiền xử lý tính năng

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Một trong những lợi thế lớn của việc sử dụng khuôn khổ học tập sâu để xây dựng các mô hình khuyến nghị là sự tự do trong việc xây dựng các biểu diễn tính năng linh hoạt và phong phú.

Bước đầu tiên để làm như vậy là chuẩn bị các tính năng, vì các tính năng thô thường sẽ không thể sử dụng được ngay trong một mô hình.

Ví dụ:

  • Id người dùng và mục có thể là chuỗi (tiêu đề, tên người dùng) hoặc số nguyên lớn, không liền nhau (ID cơ sở dữ liệu).
  • Mô tả mặt hàng có thể là văn bản thô.
  • Dấu thời gian tương tác có thể là dấu thời gian Unix thô.

Những điều này cần được chuyển đổi một cách thích hợp để hữu ích trong việc xây dựng các mô hình:

  • Id người dùng và mặt hàng phải được dịch thành vectơ nhúng: biểu diễn số chiều cao được điều chỉnh trong quá trình đào tạo để giúp mô hình dự đoán mục tiêu của nó tốt hơn.
  • Văn bản thô cần được mã hóa (chia thành các phần nhỏ hơn, chẳng hạn như các từ riêng lẻ) và được dịch thành các bản nhúng.
  • Các đối tượng số cần được chuẩn hóa để giá trị của chúng nằm trong một khoảng nhỏ xung quanh 0.

May mắn thay, bằng cách sử dụng TensorFlow, chúng tôi có thể tạo ra một phần tiền xử lý như vậy trong mô hình của mình thay vì một bước tiền xử lý riêng biệt. Điều này không chỉ thuận tiện mà còn đảm bảo rằng quá trình sơ chế của chúng tôi hoàn toàn giống nhau trong quá trình đào tạo và trong quá trình phục vụ. Điều này làm cho việc triển khai các mô hình bao gồm cả quá trình xử lý trước rất phức tạp trở nên an toàn và dễ dàng.

Trong hướng dẫn này, chúng ta sẽ tập trung vào người giới thiệu và tiền xử lý chúng ta cần phải làm trên tập dữ liệu MovieLens . Nếu bạn đang quan tâm đến một hướng dẫn lớn hơn mà không cần tập trung hệ thống recommender, có một cái nhìn tại đầy đủ hướng dẫn tiền xử lý Keras .

Tập dữ liệu MovieLens

Trước tiên, hãy xem những tính năng nào chúng ta có thể sử dụng từ tập dữ liệu MovieLens:

pip install -q --upgrade tensorflow-datasets
import pprint

import tensorflow_datasets as tfds

ratings = tfds.load("movielens/100k-ratings", split="train")

for x in ratings.take(1).as_numpy_iterator():
  pprint.pprint(x)
2021-10-02 11:59:46.956587: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected
{'bucketized_user_age': 45.0,
 'movie_genres': array([7]),
 'movie_id': b'357',
 'movie_title': b"One Flew Over the Cuckoo's Nest (1975)",
 'raw_user_age': 46.0,
 'timestamp': 879024327,
 'user_gender': True,
 'user_id': b'138',
 'user_occupation_label': 4,
 'user_occupation_text': b'doctor',
 'user_rating': 4.0,
 'user_zip_code': b'53211'}
2021-10-02 11:59:47.327679: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

Có một số tính năng chính ở đây:

  • Tiêu đề phim hữu ích như một mã nhận dạng phim.
  • Id người dùng hữu ích như một định danh người dùng.
  • Dấu thời gian sẽ cho phép chúng tôi lập mô hình ảnh hưởng của thời gian.

Hai tính năng đầu tiên là các tính năng phân loại; dấu thời gian là một tính năng liên tục.

Chuyển các đối tượng địa lý phân loại thành tính năng nhúng

Một tính năng phân loại là một tính năng mà không bày tỏ một số lượng liên tục, nhưng thay vì phải mất trên một trong một tập hợp các giá trị cố định.

Hầu hết các mô hình học sâu thể hiện các tính năng này bằng cách biến chúng thành các vectơ chiều cao. Trong quá trình đào tạo mô hình, giá trị của vectơ đó được điều chỉnh để giúp mô hình dự đoán mục tiêu của nó tốt hơn.

Ví dụ: giả sử rằng mục tiêu của chúng ta là dự đoán người dùng sẽ xem bộ phim nào. Để làm được điều đó, chúng tôi đại diện cho từng người dùng và từng bộ phim bằng một vectơ nhúng. Ban đầu, những lần nhúng này sẽ nhận các giá trị ngẫu nhiên - nhưng trong quá trình đào tạo, chúng tôi sẽ điều chỉnh chúng để các lần nhúng của người dùng và phim họ xem kết thúc gần nhau hơn.

Lấy các đối tượng địa lý phân loại thô và biến chúng thành bản nhúng thường là một quá trình gồm hai bước:

  1. Trước tiên, chúng ta cần dịch các giá trị thô thành một dải các số nguyên liền nhau, thông thường bằng cách xây dựng một ánh xạ (được gọi là "từ vựng") ánh xạ các giá trị thô ("Chiến tranh giữa các vì sao") sang các số nguyên (giả sử, 15).
  2. Thứ hai, chúng ta cần lấy các số nguyên này và biến chúng thành các tệp nhúng.

Định nghĩa từ vựng

Bước đầu tiên là xác định một từ vựng. Chúng ta có thể làm điều này một cách dễ dàng bằng cách sử dụng các lớp tiền xử lý của Keras.

import numpy as np
import tensorflow as tf

movie_title_lookup = tf.keras.layers.StringLookup()

Bản thân lớp này chưa có từ vựng, nhưng chúng ta có thể xây dựng nó bằng cách sử dụng dữ liệu của mình.

movie_title_lookup.adapt(ratings.map(lambda x: x["movie_title"]))

print(f"Vocabulary: {movie_title_lookup.get_vocabulary()[:3]}")
Vocabulary: ['[UNK]', 'Star Wars (1977)', 'Contact (1997)']

Khi chúng tôi có điều này, chúng tôi có thể sử dụng lớp để dịch các mã thông báo thô sang các id nhúng:

movie_title_lookup(["Star Wars (1977)", "One Flew Over the Cuckoo's Nest (1975)"])
<tf.Tensor: shape=(2,), dtype=int64, numpy=array([ 1, 58])>

Lưu ý rằng từ vựng của lớp bao gồm một (hoặc nhiều!) Không xác định (hoặc "hết từ vựng", OOV). Điều này thực sự tiện dụng: nó có nghĩa là lớp có thể xử lý các giá trị phân loại không có trong từ vựng. Về mặt thực tế, điều này có nghĩa là mô hình có thể tiếp tục tìm hiểu và đưa ra các đề xuất ngay cả khi sử dụng các tính năng chưa được nhìn thấy trong quá trình xây dựng từ vựng.

Sử dụng tính năng băm

Trong thực tế, StringLookup lớp cho phép chúng ta cấu hình nhiều chỉ số OOV. Nếu chúng tôi làm điều đó, bất kỳ giá trị thô nào không có trong từ vựng sẽ được băm xác định cho một trong các chỉ số OOV. Chúng ta càng có nhiều chỉ số như vậy, thì càng có ít mối lo ngại rằng hai giá trị đặc trưng thô khác nhau sẽ băm thành cùng một chỉ mục OOV. Do đó, nếu chúng ta có đủ các chỉ số như vậy, mô hình sẽ có thể đào tạo về mô hình cũng như một mô hình có từ vựng rõ ràng mà không liên quan đến việc phải duy trì danh sách mã thông báo.

Chúng ta có thể coi điều này đến mức cực đoan hợp lý của nó và hoàn toàn dựa vào băm đặc trưng, ​​không có từ vựng nào cả. Này được thực hiện trong tf.keras.layers.Hashing lớp.

# We set up a large number of bins to reduce the chance of hash collisions.
num_hashing_bins = 200_000

movie_title_hashing = tf.keras.layers.Hashing(
    num_bins=num_hashing_bins
)

Chúng ta có thể thực hiện tra cứu như trước đây mà không cần xây dựng các từ vựng:

movie_title_hashing(["Star Wars (1977)", "One Flew Over the Cuckoo's Nest (1975)"])
<tf.Tensor: shape=(2,), dtype=int64, numpy=array([101016,  96565])>

Xác định các nhúng

Bây giờ chúng ta có id số nguyên, chúng ta có thể sử dụng Embedding lớp để biến những thành embeddings.

Lớp nhúng có hai thứ nguyên: thứ nguyên thứ nhất cho chúng ta biết chúng ta có thể nhúng bao nhiêu danh mục riêng biệt; thứ hai cho chúng ta biết vectơ đại diện cho mỗi chúng có thể lớn như thế nào.

Khi tạo lớp nhúng cho tiêu đề phim, chúng tôi sẽ đặt giá trị đầu tiên cho kích thước của từ vựng tiêu đề của chúng tôi (hoặc số thùng băm). Thứ hai là tùy thuộc vào chúng ta: nó càng lớn thì công suất của dòng máy càng cao, nhưng nó càng chậm để phù hợp và phục vụ.

movie_title_embedding = tf.keras.layers.Embedding(
    # Let's use the explicit vocabulary lookup.
    input_dim=movie_title_lookup.vocab_size(),
    output_dim=32
)
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.

Chúng ta có thể đặt cả hai lại với nhau thành một lớp duy nhất đưa văn bản thô vào và tạo ra các thao tác nhúng.

movie_title_model = tf.keras.Sequential([movie_title_lookup, movie_title_embedding])

Như vậy, chúng tôi có thể trực tiếp lấy các bản nhúng cho tiêu đề phim của mình:

movie_title_model(["Star Wars (1977)"])
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['Star Wars (1977)']
Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['Star Wars (1977)']
Consider rewriting this model with the Functional API.
<tf.Tensor: shape=(1, 32), dtype=float32, numpy=
array([[-0.00255408,  0.00941082,  0.02599109, -0.02758816, -0.03652344,
        -0.03852248, -0.03309812, -0.04343383,  0.03444691, -0.02454401,
         0.00619583, -0.01912323, -0.03988413,  0.03595274,  0.00727529,
         0.04844356,  0.04739804,  0.02836904,  0.01647964, -0.02924066,
        -0.00425701,  0.01747661,  0.0114414 ,  0.04916174,  0.02185034,
        -0.00399858,  0.03934855,  0.03666003,  0.01980535, -0.03694187,
        -0.02149243, -0.03765338]], dtype=float32)>

Chúng tôi có thể làm điều tương tự với người dùng nhúng:

user_id_lookup = tf.keras.layers.StringLookup()
user_id_lookup.adapt(ratings.map(lambda x: x["user_id"]))

user_id_embedding = tf.keras.layers.Embedding(user_id_lookup.vocab_size(), 32)

user_id_model = tf.keras.Sequential([user_id_lookup, user_id_embedding])
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.

Chuẩn hóa các tính năng liên tục

Các tính năng liên tục cũng cần chuẩn hóa. Ví dụ, timestamp tính năng là quá lớn để có thể sử dụng trực tiếp trong một mô hình sâu:

for x in ratings.take(3).as_numpy_iterator():
  print(f"Timestamp: {x['timestamp']}.")
Timestamp: 879024327.
Timestamp: 875654590.
Timestamp: 882075110.

Chúng ta cần phải xử lý nó trước khi có thể sử dụng nó. Mặc dù có nhiều cách mà chúng ta có thể thực hiện việc này, nhưng tùy chỉnh và tiêu chuẩn hóa là hai cách phổ biến.

Tiêu chuẩn hóa

Tiêu chuẩn tính lại tỷ lệ tính năng bình thường hóa phạm vi của họ bằng cách trừ đối tượng địa lý có ý nghĩa và chia cho độ lệch chuẩn của nó. Nó là một biến đổi tiền xử lý phổ biến.

Điều này có thể dễ dàng thực hiện bằng cách sử dụng tf.keras.layers.Normalization lớp:

timestamp_normalization = tf.keras.layers.Normalization(
    axis=None
)
timestamp_normalization.adapt(ratings.map(lambda x: x["timestamp"]).batch(1024))

for x in ratings.take(3).as_numpy_iterator():
  print(f"Normalized timestamp: {timestamp_normalization(x['timestamp'])}.")
Normalized timestamp: [-0.84293723].
Normalized timestamp: [-1.4735204].
Normalized timestamp: [-0.27203268].

Thận trọng

Một phép biến đổi phổ biến khác là biến một đối tượng địa lý liên tục thành một số đối tượng địa lý phân loại. Điều này rất hợp lý nếu chúng tôi có lý do để nghi ngờ rằng hiệu ứng của một tính năng là không liên tục.

Để làm điều này, trước tiên chúng ta cần thiết lập ranh giới của các nhóm mà chúng ta sẽ sử dụng để tùy ý hóa. Cách dễ nhất là xác định giá trị nhỏ nhất và lớn nhất của đối tượng, và chia đều khoảng thời gian thu được:

max_timestamp = ratings.map(lambda x: x["timestamp"]).reduce(
    tf.cast(0, tf.int64), tf.maximum).numpy().max()
min_timestamp = ratings.map(lambda x: x["timestamp"]).reduce(
    np.int64(1e9), tf.minimum).numpy().min()

timestamp_buckets = np.linspace(
    min_timestamp, max_timestamp, num=1000)

print(f"Buckets: {timestamp_buckets[:3]}")
Buckets: [8.74724710e+08 8.74743291e+08 8.74761871e+08]

Với ranh giới nhóm, chúng tôi có thể chuyển đổi dấu thời gian thành nhúng:

timestamp_embedding_model = tf.keras.Sequential([
  tf.keras.layers.Discretization(timestamp_buckets.tolist()),
  tf.keras.layers.Embedding(len(timestamp_buckets) + 1, 32)
])

for timestamp in ratings.take(1).map(lambda x: x["timestamp"]).batch(1).as_numpy_iterator():
  print(f"Timestamp embedding: {timestamp_embedding_model(timestamp)}.")
Timestamp embedding: [[-0.02532113 -0.00415025  0.00458465  0.02080876  0.03103903 -0.03746337
   0.04010465 -0.01709593 -0.00246077 -0.01220842  0.02456966 -0.04816503
   0.04552222  0.03535838  0.00769508  0.04328252  0.00869263  0.01110227
   0.02754457 -0.02659499 -0.01055292 -0.03035731  0.00463334 -0.02848787
  -0.03416766  0.02538678 -0.03446608 -0.0384447  -0.03032914 -0.02391632
   0.02637175 -0.01158618]].

Xử lý các tính năng văn bản

Chúng tôi cũng có thể muốn thêm các tính năng văn bản vào mô hình của mình. Thông thường, những thứ như mô tả sản phẩm là văn bản ở dạng tự do và chúng tôi có thể hy vọng rằng mô hình của mình có thể học cách sử dụng thông tin chứa chúng để đưa ra các đề xuất tốt hơn, đặc biệt là trong trường hợp khởi đầu lạnh hoặc dài hạn.

Mặc dù tập dữ liệu MovieLens không cung cấp cho chúng tôi các tính năng văn bản phong phú, chúng tôi vẫn có thể sử dụng các tiêu đề phim. Điều này có thể giúp chúng tôi nắm bắt được thực tế rằng các bộ phim có tiêu đề rất giống nhau có khả năng thuộc cùng một bộ.

Sự chuyển đổi đầu tiên mà chúng ta cần áp dụng cho văn bản là mã hóa (tách thành các từ cấu thành hoặc các phần từ), tiếp theo là học từ vựng, sau đó là nhúng.

Các Keras tf.keras.layers.TextVectorization lớp có thể thực hiện hai bước đầu tiên đối với chúng tôi:

title_text = tf.keras.layers.TextVectorization()
title_text.adapt(ratings.map(lambda x: x["movie_title"]))

Hãy thử nó ra:

for row in ratings.batch(1).map(lambda x: x["movie_title"]).take(1):
  print(title_text(row))
tf.Tensor([[ 32 266 162   2 267 265  53]], shape=(1, 7), dtype=int64)

Mỗi tiêu đề được dịch thành một chuỗi các mã thông báo, một cho mỗi phần mà chúng tôi đã mã hóa.

Chúng tôi có thể kiểm tra từ vựng đã học để xác minh rằng lớp đang sử dụng mã hóa chính xác:

title_text.get_vocabulary()[40:45]
['first', '1998', '1977', '1971', 'monty']

Điều này có vẻ đúng: lớp đang mã hóa các tiêu đề thành các từ riêng lẻ.

Để hoàn tất quá trình xử lý, bây giờ chúng ta cần nhúng văn bản. Bởi vì mỗi tiêu đề chứa nhiều từ, chúng tôi sẽ nhận được nhiều nhúng cho mỗi tiêu đề. Để sử dụng trong mô hình donwstream, chúng thường được nén thành một lần nhúng duy nhất. Các mô hình như RNN hoặc Transformers rất hữu ích ở đây, nhưng lấy trung bình tất cả các từ nhúng với nhau là một điểm khởi đầu tốt.

Để tất cả chúng cùng nhau

Với các thành phần này, chúng ta có thể xây dựng một mô hình thực hiện tất cả các tiền xử lý cùng nhau.

Mô hình người dùng

Mô hình người dùng đầy đủ có thể trông giống như sau:

class UserModel(tf.keras.Model):

  def __init__(self):
    super().__init__()

    self.user_embedding = tf.keras.Sequential([
        user_id_lookup,
        tf.keras.layers.Embedding(user_id_lookup.vocab_size(), 32),
    ])
    self.timestamp_embedding = tf.keras.Sequential([
      tf.keras.layers.Discretization(timestamp_buckets.tolist()),
      tf.keras.layers.Embedding(len(timestamp_buckets) + 2, 32)
    ])
    self.normalized_timestamp = tf.keras.layers.Normalization(
        axis=None
    )

  def call(self, inputs):

    # Take the input dictionary, pass it through each input layer,
    # and concatenate the result.
    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)

Hãy thử nó ra:

user_model = UserModel()

user_model.normalized_timestamp.adapt(
    ratings.map(lambda x: x["timestamp"]).batch(128))

for row in ratings.batch(1).take(1):
  print(f"Computed representations: {user_model(row)[0, :3]}")
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
Computed representations: [-0.04705765 -0.04739009 -0.04212048]

Mô hình phim

Chúng ta có thể làm tương tự đối với mô hình phim:

class MovieModel(tf.keras.Model):

  def __init__(self):
    super().__init__()

    max_tokens = 10_000

    self.title_embedding = tf.keras.Sequential([
      movie_title_lookup,
      tf.keras.layers.Embedding(movie_title_lookup.vocab_size(), 32)
    ])
    self.title_text_embedding = tf.keras.Sequential([
      tf.keras.layers.TextVectorization(max_tokens=max_tokens),
      tf.keras.layers.Embedding(max_tokens, 32, mask_zero=True),
      # We average the embedding of individual words to get one embedding vector
      # per title.
      tf.keras.layers.GlobalAveragePooling1D(),
    ])

  def call(self, inputs):
    return tf.concat([
        self.title_embedding(inputs["movie_title"]),
        self.title_text_embedding(inputs["movie_title"]),
    ], axis=1)

Hãy thử nó ra:

movie_model = MovieModel()

movie_model.title_text_embedding.layers[0].adapt(
    ratings.map(lambda x: x["movie_title"]))

for row in ratings.batch(1).take(1):
  print(f"Computed representations: {movie_model(row)[0, :3]}")
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
Computed representations: [-0.01670959  0.02128791  0.04631067]

Bước tiếp theo

Với hai mô hình ở trên, chúng tôi đã thực hiện các bước đầu tiên để thể hiện các tính năng phong phú trong mô hình đề xuất: để hiểu thêm về điều này và khám phá cách chúng có thể được sử dụng để xây dựng mô hình đề xuất sâu hiệu quả, hãy xem hướng dẫn Đề xuất sâu của chúng tôi.