부가 기능 사용: 기능 전처리

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

딥 러닝 프레임워크를 사용하여 추천 모델을 구축할 때의 가장 큰 장점 중 하나는 풍부하고 유연한 기능 표현을 자유롭게 구축할 수 있다는 것입니다.

원시 기능은 일반적으로 모델에서 즉시 사용할 수 없기 때문에 그렇게 하는 첫 번째 단계는 기능을 준비하는 것입니다.

예를 들어:

  • 사용자 및 항목 ID는 문자열(제목, 사용자 이름) 또는 큰 비연속 정수(데이터베이스 ID)일 수 있습니다.
  • 항목 설명은 원시 텍스트일 수 있습니다.
  • 상호작용 타임스탬프는 원시 Unix 타임스탬프일 수 있습니다.

모델을 구축하는 데 유용하려면 다음과 같이 적절하게 변환해야 합니다.

  • 사용자 및 항목 ID는 임베딩 벡터로 변환되어야 합니다. 모델이 목표를 더 잘 예측할 수 있도록 교육 중에 조정되는 고차원 수치 표현입니다.
  • 원시 텍스트는 토큰화(개별 단어와 같은 더 작은 부분으로 분할)하고 임베딩으로 변환해야 합니다.
  • 수치적 특징은 그 값이 0 부근의 작은 간격에 놓이도록 정규화해야 합니다.

다행스럽게도 TensorFlow를 사용하면 별도의 전처리 단계가 아닌 이러한 전처리를 모델의 일부로 만들 수 있습니다. 이것은 편리할 뿐만 아니라 사전 처리가 훈련 중과 서빙 중에 정확히 동일하다는 것을 보장합니다. 이를 통해 매우 정교한 사전 처리가 포함된 모델을 안전하고 쉽게 배포할 수 있습니다.

이 튜토리얼에서, 우리는 추천인에 초점을 맞출 것하고는 전처리 우리는에 할 필요가 MovieLens 데이터 세트 . 당신이 추천 시스템의 초점없이 큰 튜토리얼에 관심이 있다면, 전체를 살펴 가지고 Keras 전처리 가이드 .

MovieLens 데이터 세트

먼저 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-08-14 11:49:31.702943: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected
2021-08-14 11:49:31.703005: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (kokoro-gcp-ubuntu-prod-1716621842): /proc/driver/nvidia/version does not exist
2021-08-14 11:49:31.703782: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-14 11:49:31.790170: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
{'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-08-14 11:49:32.048008: 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.

여기에는 몇 가지 주요 기능이 있습니다.

  • 영화 제목은 영화 식별자로 유용합니다.
  • 사용자 ID는 사용자 식별자로 유용합니다.
  • 타임스탬프를 사용하면 시간의 영향을 모델링할 수 있습니다.

처음 두 가지는 범주형 기능입니다. 타임스탬프는 연속적인 기능입니다.

범주형 기능을 임베딩으로 전환

범주 기능은 연속 수량을 표현하지 않는 기능이 아니라 고정 된 값의 세트 중 하나를 취한다.

대부분의 딥 러닝 모델은 이러한 기능을 고차원 벡터로 변환하여 표현합니다. 모델 훈련 중에 해당 벡터의 값은 모델이 목표를 더 잘 예측할 수 있도록 조정됩니다.

예를 들어, 우리의 목표가 어떤 사용자가 어떤 영화를 볼 것인지 예측하는 것이라고 가정합니다. 이를 위해 임베딩 벡터로 각 사용자와 각 영화를 나타냅니다. 처음에는 이러한 임베딩이 임의의 값을 취하지만 훈련 중에 사용자와 사용자가 보는 영화의 임베딩이 더 가까워지도록 조정합니다.

원시 범주형 기능을 가져와 임베딩으로 전환하는 것은 일반적으로 2단계 프로세스입니다.

  1. 먼저 원시 값("스타워즈")을 정수(예: 15)로 매핑하는 매핑("어휘"라고 함)을 구축하여 원시 값을 인접한 정수 범위로 변환해야 합니다.
  2. 둘째, 이 정수를 가져와 임베딩으로 변환해야 합니다.

어휘 정의

첫 번째 단계는 어휘를 정의하는 것입니다. Keras 전처리 레이어를 사용하면 이 작업을 쉽게 수행할 수 있습니다.

import numpy as np
import tensorflow as tf

movie_title_lookup = tf.keras.layers.experimental.preprocessing.StringLookup()

레이어 자체에는 아직 어휘가 없지만 데이터를 사용하여 구축할 수 있습니다.

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

일단 이것이 있으면 레이어를 사용하여 원시 토큰을 임베딩 ID로 변환할 수 있습니다.

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

레이어의 어휘에는 하나(또는 그 이상!) 알 수 없는(또는 "어휘가 없음", OOV) 토큰이 포함되어 있습니다. 이것은 정말 편리합니다. 레이어가 어휘에 없는 범주형 값을 처리할 수 있음을 의미합니다. 실용적인 측면에서 이것은 모델이 어휘 구성 중에 볼 수 없었던 기능을 사용하여도 계속 학습하고 권장할 수 있음을 의미합니다.

기능 해싱 사용

사실, StringLookup 계층은 우리가 여러 OOV 인덱스를 구성 할 수 있습니다. 그렇게 하면 어휘에 없는 모든 원시 값은 OOV 인덱스 중 하나로 결정적으로 해시됩니다. 이러한 인덱스가 많을수록 두 개의 서로 다른 원시 기능 값이 동일한 OOV 인덱스로 해시될 가능성이 낮아집니다. 결과적으로, 그러한 인덱스가 충분하다면 모델은 토큰 목록을 유지해야 하는 단점 없이 명시적 어휘를 사용하여 모델뿐만 아니라 학습할 수 있어야 합니다.

우리는 이것을 논리적 극단으로 가져갈 수 있으며 어휘가 전혀 없는 기능 해싱에 전적으로 의존할 수 있습니다. 이것은 구현된다 tf.keras.layers.experimental.preprocessing.Hashing 층.

# 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.experimental.preprocessing.Hashing(
    num_bins=num_hashing_bins
)

어휘를 만들 필요 없이 이전과 같이 조회를 수행할 수 있습니다.

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

임베딩 정의

이제 우리는 정수 ID를 가지고, 우리가 사용할 수있는 Embedding 묻어로 사람들을 설정하는 레이어를.

임베딩 레이어에는 두 가지 차원이 있습니다. 첫 번째 차원은 포함할 수 있는 고유한 범주의 수를 알려줍니다. 두 번째는 각각을 나타내는 벡터가 얼마나 클 수 있는지 알려줍니다.

영화 제목에 대한 임베딩 레이어를 만들 때 첫 번째 값을 제목 어휘의 크기(또는 해싱 빈 수)로 설정할 것입니다. 두 번째는 우리에게 달려 있습니다. 크기가 클수록 모델의 용량이 커지지만 적합하고 제공하는 속도가 느려집니다.

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.

원시 텍스트를 가져와 임베딩을 생성하는 단일 레이어에 둘을 함께 넣을 수 있습니다.

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

마찬가지로 영화 제목에 대한 임베딩을 직접 가져올 수 있습니다.

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.00515169, -0.03476317,  0.02916283,  0.02955664, -0.00886112,
        -0.02819985,  0.00271539, -0.00118292, -0.0374759 ,  0.01924748,
        -0.04190575, -0.04996298,  0.00865493, -0.03877429, -0.03998976,
         0.04998415,  0.02250958, -0.00753691,  0.02424571, -0.00512842,
         0.03043294,  0.01054993,  0.01587356,  0.03082317, -0.01394846,
        -0.02633669,  0.02709163, -0.00352198, -0.04228533, -0.01436262,
        -0.00097059, -0.02994019]], dtype=float32)>

사용자 임베딩에서도 동일한 작업을 수행할 수 있습니다.

user_id_lookup = tf.keras.layers.experimental.preprocessing.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.

연속 기능 정규화

연속 기능에도 정규화가 필요합니다. 예를 들어, timestamp 기능은 깊은 모델에서 직접 사용할 수 너무 큰 :

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

사용하기 전에 처리해야 합니다. 이를 수행할 수 있는 방법은 여러 가지가 있지만 이산화와 표준화는 두 가지 일반적인 방법입니다.

표준화

규격화 스케일과이 기능의 평균을 감산하고 그 표준 편차로 나눔으로써 그들의 범위 정상화를 특징으로한다. 일반적인 전처리 변환입니다.

이것은 쉽게 사용하여 수행 할 수 tf.keras.layers.experimental.preprocessing.Normalization 레이어를 :

timestamp_normalization = tf.keras.layers.experimental.preprocessing.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].

이산화

또 다른 일반적인 변환은 연속 특성을 여러 범주 특성으로 바꾸는 것입니다. 기능의 효과가 비연속적이라고 의심할 만한 이유가 있는 경우 이는 의미가 있습니다.

이렇게 하려면 먼저 이산화에 사용할 버킷의 경계를 설정해야 합니다. 가장 쉬운 방법은 기능의 최소값과 최대값을 식별하고 결과 간격을 균등하게 나누는 것입니다.

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]

버킷 경계가 주어지면 타임스탬프를 임베딩으로 변환할 수 있습니다.

timestamp_embedding_model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.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.00294433  0.03721369  0.01041937 -0.04951981 -0.0183605  -0.04930016
   0.04897598 -0.00274762  0.02720143  0.00673866 -0.01207218 -0.03943858
  -0.03538172  0.0255433   0.01165134  0.04162772  0.04566142  0.03643009
  -0.00124774  0.04558236 -0.00279278 -0.03486296  0.00070759 -0.02682102
   0.01930856 -0.0009293   0.01357811  0.0326503  -0.00133516 -0.0190359
   0.03088352  0.00073742]].

텍스트 기능 처리

모델에 텍스트 기능을 추가할 수도 있습니다. 일반적으로 제품 설명과 같은 것은 자유 형식의 텍스트이며 특히 콜드 스타트 ​​또는 롱테일 시나리오에서 더 나은 권장 사항을 만들기 위해 포함된 정보를 사용하는 방법을 모델이 학습할 수 있기를 바랍니다.

MovieLens 데이터 세트는 풍부한 텍스트 기능을 제공하지 않지만 여전히 영화 제목을 사용할 수 있습니다. 이것은 제목이 매우 유사한 영화가 같은 시리즈에 속할 가능성이 있다는 사실을 파악하는 데 도움이 될 수 있습니다.

텍스트에 적용해야 하는 첫 번째 변환은 토큰화(구성 단어 또는 단어 조각으로 분할)이고, 그 다음에는 어휘 학습, 임베딩이 뒤따릅니다.

Keras tf.keras.layers.experimental.preprocessing.TextVectorization 층이 우리를 위해 처음 두 단계를 수행 할 수 있습니다

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

시도해 봅시다:

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)

각 제목은 토큰화한 각 조각에 대해 하나씩 일련의 토큰으로 변환됩니다.

레이어가 올바른 토큰화를 사용하고 있는지 확인하기 위해 학습된 어휘를 확인할 수 있습니다.

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

레이어가 제목을 개별 단어로 토큰화하고 있습니다.

처리를 마치려면 이제 텍스트를 포함해야 합니다. 각 제목에는 여러 단어가 포함되어 있으므로 각 제목에 대해 여러 임베딩을 가져옵니다. donwstream 모델에서 사용하기 위해 일반적으로 단일 임베딩으로 압축됩니다. RNN 또는 Transformers와 같은 모델이 여기에서 유용하지만 모든 단어의 임베딩을 함께 평균화하는 것이 좋은 출발점입니다.

함께 모아서

이러한 구성 요소가 있으면 모든 사전 처리를 함께 수행하는 모델을 구축할 수 있습니다.

사용자 모델

전체 사용자 모델은 다음과 같습니다.

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.experimental.preprocessing.Discretization(timestamp_buckets.tolist()),
      tf.keras.layers.Embedding(len(timestamp_buckets) + 2, 32)
    ])
    self.normalized_timestamp = tf.keras.layers.experimental.preprocessing.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)

시도해 봅시다:

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.00717808 -0.01894927  0.00963577]

영화 모델

영화 모델에 대해서도 동일한 작업을 수행할 수 있습니다.

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.experimental.preprocessing.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)

시도해 봅시다:

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.03706546 -0.00222445 -0.00739993]

다음 단계

위의 두 모델을 사용하여 추천기 모델에서 풍부한 기능을 나타내는 첫 번째 단계를 수행했습니다. 더 나아가 이러한 기능을 사용하여 효과적인 심층 추천기 모델을 구축하는 방법을 알아보려면 Deep Recommenders 자습서를 살펴보세요.