Yan özellikleri kullanma: özellik ön işleme

TensorFlow.org'da görüntüleyin Google Colab'da çalıştırın Kaynağı GitHub'da görüntüleyin Not defterini indir

Öneri modelleri oluşturmak için derin öğrenme çerçevesi kullanmanın en büyük avantajlarından biri, zengin, esnek özellik temsilleri oluşturma özgürlüğüdür.

Ham özellikler genellikle bir modelde hemen kullanılabilir olmayacağından, bunu yapmanın ilk adımı özellikleri hazırlamaktır.

Örneğin:

  • Kullanıcı ve öğe kimlikleri, dizeler (başlıklar, kullanıcı adları) veya büyük, bitişik olmayan tam sayılar (veritabanı kimlikleri) olabilir.
  • Öğe açıklamaları ham metin olabilir.
  • Etkileşim zaman damgaları, ham Unix zaman damgaları olabilir.

Model oluşturmada faydalı olabilmeleri için bunların uygun şekilde dönüştürülmesi gerekir:

  • Kullanıcı ve öğe kimlikleri, gömme vektörlerine dönüştürülmelidir: modelin hedefini daha iyi tahmin etmesine yardımcı olmak için eğitim sırasında ayarlanan yüksek boyutlu sayısal temsiller.
  • Ham metnin tokenize edilmesi (bireysel kelimeler gibi daha küçük parçalara bölünmesi) ve gömmelere çevrilmesi gerekir.
  • Sayısal özelliklerin, değerlerinin 0 civarında küçük bir aralıkta olması için normalleştirilmesi gerekir.

Neyse ki, TensorFlow'u kullanarak, bu tür ön işlemeyi ayrı bir ön işleme adımı yerine modelimizin bir parçası haline getirebiliriz. Bu sadece kolaylık sağlamakla kalmaz, aynı zamanda eğitim ve servis sırasında ön işlememizin tamamen aynı olmasını sağlar. Bu, çok karmaşık ön işlemeyi bile içeren modellerin dağıtımını güvenli ve kolay hale getirir.

Bu eğitimde, referans mektubu odaklanmak olacak ve önişleme Bizim yapmamız gereken MovieLens veri kümesi . Bir recommender sistem odak olmadan daha büyük bir öğretici ilgilenen ediyorsanız, tam bir göz Keras önişleme rehber .

MovieLens veri kümesi

Önce MovieLens veri setinden hangi özellikleri kullanabileceğimize bir göz atalım:

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.

Burada birkaç temel özellik var:

  • Film başlığı, film tanımlayıcısı olarak kullanışlıdır.
  • Kullanıcı kimliği, bir kullanıcı tanımlayıcısı olarak kullanışlıdır.
  • Zaman damgaları, zamanın etkisini modellememize izin verecek.

İlk ikisi kategorik özelliklerdir; zaman damgaları sürekli bir özelliktir.

Kategorik özellikleri gömmelere dönüştürme

Bir kategorik özellik sürekli miktarını ifade etmeyen bir özelliktir, ancak daha ziyade sabit değerler grubundan biri üzerinde sürer.

Çoğu derin öğrenme modeli, bu özellikleri yüksek boyutlu vektörlere dönüştürerek ifade eder. Model eğitimi sırasında, bu vektörün değeri, modelin amacını daha iyi tahmin etmesine yardımcı olmak için ayarlanır.

Örneğin, amacımızın hangi kullanıcının hangi filmi izleyeceğini tahmin etmek olduğunu varsayalım. Bunu yapmak için, her kullanıcıyı ve her filmi bir gömme vektörü ile temsil ediyoruz. Başlangıçta, bu yerleştirmeler rastgele değerler alacaktır - ancak eğitim sırasında bunları, kullanıcıların ve izledikleri filmlerin yerleştirmeleri birbirine daha yakın olacak şekilde ayarlayacağız.

Ham kategorik özellikleri almak ve bunları gömmelere dönüştürmek normalde iki adımlı bir işlemdir:

  1. İlk olarak, normalde ham değerleri ("Yıldız Savaşları") tam sayılara (örneğin, 15) eşleyen bir eşleme ("kelime bilgisi" olarak adlandırılır) oluşturarak, ham değerleri bir dizi bitişik tamsayıya çevirmemiz gerekir.
  2. İkinci olarak, bu tamsayıları alıp gömmelere çevirmemiz gerekiyor.

kelime dağarcığının tanımlanması

İlk adım bir kelime tanımlamaktır. Bunu Keras ön işleme katmanlarını kullanarak kolayca yapabiliriz.

import numpy as np
import tensorflow as tf

movie_title_lookup = tf.keras.layers.StringLookup()

Katmanın kendisi henüz bir kelime dağarcığına sahip değil, ancak verilerimizi kullanarak onu oluşturabiliriz.

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

Bunu elde ettikten sonra, ham belirteçleri gömme kimliklerine çevirmek için katmanı kullanabiliriz:

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

Katmanın kelime dağarcığının bir (veya daha fazla!) bilinmeyen (veya "kelime dağarcığı dışında", OOV) belirteçleri içerdiğine dikkat edin. Bu gerçekten kullanışlıdır: katmanın sözlükte olmayan kategorik değerleri işleyebileceği anlamına gelir. Pratik terimlerle, bu, modelin kelime dağarcığı oluşturma sırasında görülmeyen özellikler hakkında bilgi edinmeye ve önerilerde bulunmaya devam edebileceği anlamına gelir.

Özellik karmasını kullanma

Aslında, StringLookup katmanı bize birden oov endekslerini yapılandırmanızı sağlar. Bunu yaparsak, kelime dağarcığında olmayan herhangi bir ham değer, OOV indekslerinden birine deterministik olarak hash edilecektir. Bu tür indekslerimiz ne kadar fazlaysa, iki farklı ham özellik değerinin aynı OOV indeksine hash olması o kadar az olasıdır. Sonuç olarak, bu tür yeterli indekse sahipsek, model, belirteç listesini korumak zorunda kalmanın dezavantajı olmadan açık bir kelime dağarcığına sahip bir model kadar eğitim verebilmelidir.

Bunu mantıksal uç noktasına götürebilir ve hiçbir kelime bilgisi olmadan tamamen özellik karmasına güvenebiliriz. Bu uygulanan tf.keras.layers.Hashing tabakasının.

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

Sözlük oluşturmaya gerek kalmadan aramayı daha önce olduğu gibi yapabiliriz:

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

Gömmeleri tanımlama

Şimdi tamsayı kimlikleri olduğuna göre kullanabilirsiniz Embedding tespitlerinin dönüştürmeniz için katmanı.

Gömme katmanının iki boyutu vardır: ilk boyut bize kaç farklı kategori gömebileceğimizi söyler; ikincisi bize her birini temsil eden vektörün ne kadar büyük olabileceğini söyler.

Film başlıkları için gömme katmanını oluştururken, ilk değeri başlık sözlüğümüzün boyutuna (veya karma kutularının sayısına) ayarlayacağız. İkincisi bize bağlı: ne kadar büyükse, modelin kapasitesi o kadar yüksek, ancak sığması ve hizmet etmesi o kadar yavaş.

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.

İkisini, ham metni alan ve gömmeler sağlayan tek bir katmanda bir araya getirebiliriz.

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

Aynen böyle, film başlıklarımız için doğrudan yerleştirmeleri alabiliriz:

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

Aynı şeyi kullanıcı yerleştirmeleriyle de yapabiliriz:

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.

Sürekli özellikleri normalleştirme

Sürekli özellikler de normalleştirmeye ihtiyaç duyar. Örneğin, timestamp özelliği derin bir modelde doğrudan kullanılmak üzere çok büyük:

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

Kullanmadan önce işlememiz gerekiyor. Bunu yapmanın birçok yolu olsa da, ayrıklaştırma ve standardizasyon iki yaygın olanıdır.

Standardizasyon

Standardizasyon rescales özelliği en ortalama çıkarılarak ve standart sapma bölerek onların aralığını normalleştirmek için mevcuttur. Yaygın bir ön işleme dönüşümüdür.

Bu kolayca kullanılarak gerçekleştirilebilir tf.keras.layers.Normalization katmanını:

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].

Ayrıştırma

Diğer bir yaygın dönüşüm, sürekli bir özelliği bir dizi kategorik özelliğe dönüştürmektir. Bir özelliğin etkisinin sürekli olmadığından şüphelenmek için nedenlerimiz varsa, bu mantıklıdır.

Bunu yapmak için öncelikle ayrıklaştırma için kullanacağımız kovaların sınırlarını belirlememiz gerekiyor. En kolay yol, özelliğin minimum ve maksimum değerini belirlemek ve elde edilen aralığı eşit olarak bölmektir:

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]

Paket sınırları göz önüne alındığında, zaman damgalarını yerleştirmelere dönüştürebiliriz:

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]].

Metin özellikleri işleniyor

Modelimize metin özellikleri de eklemek isteyebiliriz. Genellikle, ürün açıklamaları gibi şeyler serbest biçimli metinlerdir ve modelimizin, özellikle soğuk başlangıç ​​veya uzun kuyruk senaryosunda daha iyi önerilerde bulunmak için içerdikleri bilgileri kullanmayı öğrenebileceğini umabiliriz.

MovieLens veri kümesi bize zengin metinsel özellikler vermese de film başlıklarını kullanabiliriz. Bu, çok benzer başlıklara sahip filmlerin muhtemelen aynı seriye ait olduğu gerçeğini yakalamamıza yardımcı olabilir.

Metne uygulamamız gereken ilk dönüşüm, simgeleştirmedir (bileşen kelimelere veya kelime parçalarına bölme), ardından kelime öğrenimi ve ardından bir yerleştirme.

Keras tf.keras.layers.TextVectorization katman bizim için ilk iki adımı yapabilirsiniz:

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

Deneyelim:

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)

Her başlık, jetonlaştırdığımız her parça için bir tane olmak üzere bir jeton dizisine çevrilir.

Katmanın doğru simgeleştirmeyi kullandığını doğrulamak için öğrenilen kelimeleri kontrol edebiliriz:

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

Bu doğru görünüyor: katman, başlıkları tek tek kelimelere dönüştürüyor.

İşlemi bitirmek için şimdi metni gömmemiz gerekiyor. Her başlık birden fazla kelime içerdiğinden, her başlık için birden fazla yerleştirme alacağız. Geri akış modelinde kullanım için bunlar genellikle tek bir gömme şeklinde sıkıştırılır. RNN'ler veya Transformers gibi modeller burada yararlıdır, ancak tüm kelimelerin bir arada gömülmelerinin ortalamasını almak iyi bir başlangıç ​​noktasıdır.

Hepsini bir araya koy

Bu bileşenler yerindeyken, tüm ön işlemleri birlikte yapan bir model oluşturabiliriz.

kullanıcı modeli

Tam kullanıcı modeli aşağıdaki gibi görünebilir:

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)

Deneyelim:

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]

Film modeli

Aynı şeyi film modeli için de yapabiliriz:

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)

Deneyelim:

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]

Sonraki adımlar

Yukarıdaki iki modelle, bir öneri modelinde zengin özellikleri temsil etmenin ilk adımlarını attık: Bunu daha da ileri götürmek ve bunların etkili bir derin öneri modeli oluşturmak için nasıl kullanılabileceğini keşfetmek için Derin Öneriler eğitimimize göz atın.