استفاده از ویژگی های جانبی: پیش پردازش ویژگی

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در GitHub دانلود دفترچه یادداشت

یکی از مزایای بزرگ استفاده از چارچوب یادگیری عمیق برای ساخت مدل‌های توصیه‌گر، آزادی در ساختن نمایش‌های غنی و انعطاف‌پذیر ویژگی‌ها است.

اولین گام در انجام این کار، آماده سازی ویژگی ها است، زیرا ویژگی های خام معمولاً بلافاصله در یک مدل قابل استفاده نیستند.

مثلا:

  • شناسه های کاربر و آیتم ممکن است رشته ها (عناوین، نام های کاربری) یا اعداد صحیح بزرگ و غیر پیوسته (شناسه های پایگاه داده) باشند.
  • توضیحات مورد می تواند متن خام باشد.
  • مُهرهای زمانی تعامل می‌توانند مُهرهای زمانی یونیکس خام باشند.

برای اینکه در مدل‌های ساختمانی مفید باشند، اینها باید به‌طور مناسب تغییر شکل دهند:

  • شناسه‌های کاربر و آیتم باید به بردارهای تعبیه‌شده ترجمه شوند: نمایش‌های عددی با ابعاد بالا که در طول آموزش تنظیم می‌شوند تا به مدل کمک کند هدف خود را بهتر پیش‌بینی کند.
  • متن خام باید نشانه گذاری شود (به قسمت های کوچکتر مانند کلمات جداگانه تقسیم شود) و به جاسازی ترجمه شود.
  • ویژگی های عددی باید به گونه ای نرمال شوند که مقادیر آنها در یک بازه کوچک در حدود 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-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.

چند ویژگی کلیدی در اینجا وجود دارد:

  • عنوان فیلم به عنوان شناسه فیلم مفید است.
  • شناسه کاربر به عنوان شناسه کاربری مفید است.
  • مُهرهای زمانی به ما این امکان را می‌دهند که اثر زمان را مدل‌سازی کنیم.

دو مورد اول ویژگی‌های طبقه‌بندی هستند. مهر زمانی یک ویژگی پیوسته است.

تبدیل ویژگی‌های طبقه‌بندی به تعبیه‌ها

ویژگی طبقه یکی از ویژگی های است که یک مقدار مستمر Express است، بلکه در یکی از مجموعه ای از ارزش های ثابت طول می کشد.

اکثر مدل های یادگیری عمیق این ویژگی را با تبدیل آنها به بردارهایی با ابعاد بالا بیان می کنند. در طول آموزش مدل، مقدار آن بردار تنظیم می شود تا به مدل کمک کند تا هدف خود را بهتر پیش بینی کند.

به عنوان مثال، فرض کنید هدف ما این است که پیش بینی کنیم کدام کاربر قرار است کدام فیلم را تماشا کند. برای انجام این کار، هر کاربر و هر فیلم را با یک بردار جاسازی نشان می‌دهیم. در ابتدا، این جاسازی‌ها مقادیر تصادفی می‌گیرند - اما در طول آموزش، آنها را طوری تنظیم می‌کنیم که جاسازی‌های کاربران و فیلم‌هایی که تماشا می‌کنند به هم نزدیک‌تر شوند.

در نظر گرفتن ویژگی‌های طبقه‌بندی خام و تبدیل آنها به جاسازی معمولاً یک فرآیند دو مرحله‌ای است:

  1. ابتدا، ما باید مقادیر خام را به طیفی از اعداد صحیح پیوسته ترجمه کنیم، معمولاً با ساختن یک نقشه (به نام "واژگان") که مقادیر خام ("جنگ ستارگان") را به اعداد صحیح نگاشت می کند (مثلاً 15).
  2. ثانیاً باید این اعداد صحیح را بگیریم و آنها را به جاسازی تبدیل کنیم.

تعریف واژگان

اولین قدم تعریف واژگان است. ما می توانیم این کار را به راحتی با استفاده از لایه های پیش پردازش Keras انجام دهیم.

import numpy as np
import tensorflow as tf

movie_title_lookup = tf.keras.layers.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)']

وقتی این را داشتیم، می‌توانیم از لایه برای ترجمه توکن‌های خام به شناسه‌های جاسازی شده استفاده کنیم:

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

تعریف تعبیه ها

حالا که ما شناسه عدد صحیح، ما می توانیم با استفاده از Embedding لایه را به تبدیل آن به درونه گیریها.

یک لایه جاسازی دو بعد دارد: بعد اول به ما می گوید که چند دسته مجزا را می توانیم جاسازی کنیم. دومی به ما می گوید که بردار نشان دهنده هر یک از آنها چقدر می تواند بزرگ باشد.

هنگام ایجاد لایه جاسازی برای عناوین فیلم، اولین مقدار را به اندازه واژگان عنوان خود (یا تعداد bin های هش) قرار می دهیم. دوم به ما بستگی دارد: هرچه بزرگتر باشد ظرفیت مدل بالاتر است، اما کندتر در جا گرفتن و سرویس دهی می شود.

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

ما می توانیم همین کار را با جاسازی های کاربر انجام دهیم:

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.

عادی سازی ویژگی های پیوسته

ویژگی های پیوسته نیز نیاز به عادی سازی دارند. به عنوان مثال، timestamp از ویژگی های بسیار بزرگ به طور مستقیم در یک مدل عمیق استفاده می شود:

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

قبل از اینکه بتوانیم از آن استفاده کنیم باید آن را پردازش کنیم. در حالی که راه های زیادی برای انجام این کار وجود دارد، گسسته سازی و استانداردسازی دو روش رایج هستند.

استاندارد سازی

استاندارد rescales ویژگی های برای عادی دامنه خود را با کم کردن ویژگی متوسط و تقسیم توسط انحراف استاندارد آن است. این یک تبدیل پیش پردازش رایج است.

این می تواند به راحتی با استفاده از انجام tf.keras.layers.Normalization لایه:

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

گسسته سازی

یکی دیگر از تغییرات رایج تبدیل یک ویژگی پیوسته به تعدادی ویژگی طبقه بندی است. اگر دلایلی برای مشکوک بودن اثر یک ویژگی داشته باشیم، منطقی است.

برای انجام این کار، ابتدا باید مرزهای سطل هایی را که برای گسسته سازی استفاده خواهیم کرد مشخص کنیم. ساده ترین راه این است که مقدار حداقل و حداکثر ویژگی را شناسایی کنید و فاصله حاصل را به طور مساوی تقسیم کنید:

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

ویژگی های پردازش متن

همچنین ممکن است بخواهیم ویژگی های متنی را به مدل خود اضافه کنیم. معمولاً مواردی مانند توضیحات محصول به صورت متن آزاد هستند و می‌توان امیدوار بود که مدل ما بتواند یاد بگیرد که از اطلاعات آنها برای ارائه توصیه‌های بهتر استفاده کند، مخصوصاً در سناریوی شروع سرد یا دم طولانی.

در حالی که مجموعه داده MovieLens ویژگی های متنی غنی را به ما نمی دهد، ما همچنان می توانیم از عناوین فیلم استفاده کنیم. این ممکن است به ما در درک این واقعیت کمک کند که فیلم‌هایی با عناوین بسیار مشابه احتمالاً متعلق به یک سری هستند.

اولین تغییری که باید در متن اعمال کنیم، نشانه گذاری است (تقسیم به کلمات تشکیل دهنده یا تکه های کلمه)، به دنبال آن یادگیری واژگان و به دنبال آن جاسازی است.

Keras tf.keras.layers.TextVectorization لایه می تواند دو مرحله اول برای ما انجام دهید:

title_text = tf.keras.layers.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 یا ترانسفورماتور در اینجا مفید هستند، اما میانگین‌گیری همه جاسازی‌های کلمات با هم نقطه شروع خوبی است.

همه اش را بگذار کنار هم

با قرار دادن این اجزا می توانیم مدلی بسازیم که تمام پیش پردازش ها را با هم انجام دهد.

مدل کاربر

مدل کامل کاربر ممکن است به شکل زیر باشد:

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)

بیایید آن را امتحان کنیم:

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]

مدل فیلم

ما می توانیم همین کار را برای مدل فیلم انجام دهیم:

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)

بیایید آن را امتحان کنیم:

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]

مراحل بعدی

با دو مدل بالا، اولین گام‌ها را برای نمایش ویژگی‌های غنی در یک مدل توصیه‌گر برداشته‌ایم: برای پیشبرد این موضوع و کشف اینکه چگونه می‌توان از آنها برای ساختن یک مدل توصیه‌کننده عمیق موثر استفاده کرد، به آموزش Deep Recommenders ما نگاهی بیندازید.