Построение моделей глубокого извлечения

В featurization обучающих мы включили несколько функций в нашу модель, но модели состоят только вложение слоя. Мы можем добавить более плотные слои к нашим моделям, чтобы увеличить их выразительную силу.

Как правило, более глубокие модели способны изучать более сложные закономерности, чем более мелкие модели. Например, наша модель пользователя включает в себя идентификаторы пользователей и временные метки модели предпочтений пользователя в определенный момент времени. Неглубокая модель (скажем, один слой внедрения) может быть в состоянии изучить только самые простые отношения между этими функциями и фильмами: данный фильм наиболее популярен во время его выпуска, и данный пользователь обычно предпочитает фильмы ужасов комедиям. Чтобы зафиксировать более сложные отношения, такие как пользовательские предпочтения, меняющиеся с течением времени, нам может понадобиться более глубокая модель с несколькими плотными слоями.

Конечно, у сложных моделей есть и свои недостатки. Во-первых, это вычислительные затраты, так как более крупные модели требуют больше памяти и больше вычислений для соответствия и обслуживания. Во-вторых, требуется больше данных: в целом, для использования более глубоких моделей требуется больше обучающих данных. С большим количеством параметров глубокие модели могут переобучить или даже просто запомнить обучающие примеры вместо того, чтобы изучать функцию, которая может обобщать. Наконец, обучение более глубоких моделей может быть сложнее, и необходимо проявлять больше осторожности при выборе таких параметров, как регуляризация и скорость обучения.

Найти хорошую архитектуру для реального мира рекомендательной системы представляет собой сложное искусство, требующее хорошую интуицию и тщательную настройку гиперпараметра . Например, такие факторы, как глубина и ширина модели, функция активации, скорость обучения и оптимизатор, могут радикально изменить производительность модели. Выбор модели еще более усложняется тем фактом, что хорошие метрики автономной оценки могут не соответствовать хорошей производительности онлайн, и что выбор того, для чего оптимизировать, часто более важен, чем выбор самой модели.

Тем не менее усилия, затраченные на создание и доводку более крупных моделей, часто окупаются. В этом руководстве мы покажем, как создавать модели глубокого поиска с использованием рекомендаций TensorFlow. Мы сделаем это, создавая все более сложные модели, чтобы увидеть, как это влияет на производительность модели.

Предварительные

Сначала импортируем необходимые пакеты.

pip install -q tensorflow-recommenders
pip install -q --upgrade tensorflow-datasets
import os
import tempfile

%matplotlib inline
import matplotlib.pyplot as plt

import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds

import tensorflow_recommenders as tfrs

plt
.style.use('seaborn-whitegrid')

В этом уроке мы будем использовать модели из в featurization учебника для создания вложений. Следовательно, мы будем использовать только идентификатор пользователя, отметку времени и название фильма.

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"])
2021-10-02 11:11:47.672650: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

Мы также занимаемся домашним хозяйством, чтобы подготовить словари функций.

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

Определение модели

Модель запроса

Начну с пользовательской моделью , определенной в в учебнике featurization в качестве первого слоя нашей модели, поставлено задача превращения сырья примеров ввода в художественные вложения.

class UserModel(tf.keras.Model):

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

   
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),
   
])
   
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):
   
# 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)

Определение более глубоких моделей потребует от нас наложения слоев режима поверх этих первых входных данных. Постепенно сужающийся набор слоев, разделенных функцией активации, является распространенным шаблоном:

                            +----------------------+
                           
|      128 x 64        |
                           
+----------------------+
                                       
| relu
                         
+--------------------------+
                         
|        256 x 128         |
                         
+--------------------------+
                                       
| relu
                       
+------------------------------+
                       
|          ... x 256           |
                       
+------------------------------+

Поскольку выразительная сила глубоких линейных моделей не больше, чем у неглубоких линейных моделей, мы используем активацию ReLU для всех, кроме последнего скрытого слоя. Последний скрытый слой не использует никаких функций активации: использование функции активации ограничит выходное пространство окончательных вложений и может негативно повлиять на производительность модели. Например, если ReLU используются в проекционном слое, все компоненты в выходном встраивании будут неотрицательными.

Мы собираемся попробовать что-то подобное здесь. Чтобы упростить эксперименты с различной глубиной, давайте определим модель, глубина (и ширина) которой определяется набором параметров конструктора.

class QueryModel(tf.keras.Model):
 
"""Model for encoding user queries."""

 
def __init__(self, layer_sizes):
   
"""Model for encoding user queries.

    Args:
      layer_sizes:
        A list of integers where the i-th entry represents the number of units
        the i-th layer contains.
    """

   
super().__init__()

   
# We first use the user model for generating embeddings.
   
self.embedding_model = UserModel()

   
# Then construct the layers.
   
self.dense_layers = tf.keras.Sequential()

   
# Use the ReLU activation for all but the last layer.
   
for layer_size in layer_sizes[:-1]:
     
self.dense_layers.add(tf.keras.layers.Dense(layer_size, activation="relu"))

   
# No activation for the last layer.
   
for layer_size in layer_sizes[-1:]:
     
self.dense_layers.add(tf.keras.layers.Dense(layer_size))

 
def call(self, inputs):
    feature_embedding
= self.embedding_model(inputs)
   
return self.dense_layers(feature_embedding)

layer_sizes параметр дает нам глубину и ширину модели. Мы можем варьировать его, чтобы экспериментировать с более мелкими или более глубокими моделями.

Модель-кандидат

Мы можем применить тот же подход для модели фильма. Опять же , мы начинаем с MovieModel из featurization учебника:

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)

И расширьте его скрытыми слоями:

class CandidateModel(tf.keras.Model):
 
"""Model for encoding movies."""

 
def __init__(self, layer_sizes):
   
"""Model for encoding movies.

    Args:
      layer_sizes:
        A list of integers where the i-th entry represents the number of units
        the i-th layer contains.
    """

   
super().__init__()

   
self.embedding_model = MovieModel()

   
# Then construct the layers.
   
self.dense_layers = tf.keras.Sequential()

   
# Use the ReLU activation for all but the last layer.
   
for layer_size in layer_sizes[:-1]:
     
self.dense_layers.add(tf.keras.layers.Dense(layer_size, activation="relu"))

   
# No activation for the last layer.
   
for layer_size in layer_sizes[-1:]:
     
self.dense_layers.add(tf.keras.layers.Dense(layer_size))

 
def call(self, inputs):
    feature_embedding
= self.embedding_model(inputs)
   
return self.dense_layers(feature_embedding)

Комбинированная модель

С обоими QueryModel и CandidateModel определены, мы можем собрать комбинированную модель и реализовать наши потери и метрики логики. Для простоты мы добавим, чтобы структура модели была одинаковой для моделей запросов и моделей-кандидатов.

class MovielensModel(tfrs.models.Model):

 
def __init__(self, layer_sizes):
   
super().__init__()
   
self.query_model = QueryModel(layer_sizes)
   
self.candidate_model = CandidateModel(layer_sizes)
   
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, compute_metrics=not training)

Обучение модели

Подготовьте данные

Сначала мы разделяем данные на обучающий набор и тестовый набор.

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

Неглубокая модель

Мы готовы опробовать нашу первую неглубокую модель!

num_epochs = 300

model
= MovielensModel([32])
model
.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))

one_layer_history
= model.fit(
    cached_train
,
    validation_data
=cached_test,
    validation_freq
=5,
    epochs
=num_epochs,
    verbose
=0)

accuracy
= one_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"][-1]
print(f"Top-100 accuracy: {accuracy:.2f}.")
Top-100 accuracy: 0.27.

Это дает нам точность топ-100 около 0,27. Мы можем использовать это как точку отсчета для оценки более глубоких моделей.

Более глубокая модель

Как насчет более глубокой модели с двумя слоями?

model = MovielensModel([64, 32])
model
.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))

two_layer_history
= model.fit(
    cached_train
,
    validation_data
=cached_test,
    validation_freq
=5,
    epochs
=num_epochs,
    verbose
=0)

accuracy
= two_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"][-1]
print(f"Top-100 accuracy: {accuracy:.2f}.")
Top-100 accuracy: 0.29.

Точность здесь составляет 0,29, что немного лучше, чем у неглубокой модели.

Мы можем построить кривые точности проверки, чтобы проиллюстрировать это:

num_validation_runs = len(one_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"])
epochs
= [(x + 1)* 5 for x in range(num_validation_runs)]

plt
.plot(epochs, one_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"], label="1 layer")
plt
.plot(epochs, two_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"], label="2 layers")
plt
.title("Accuracy vs epoch")
plt
.xlabel("epoch")
plt
.ylabel("Top-100 accuracy");
plt
.legend()
<matplotlib.legend.Legend at 0x7f841c7513d0>

PNG

Даже на начальном этапе обучения более крупная модель имеет четкое и стабильное преимущество над мелкой моделью, что позволяет предположить, что добавление глубины помогает модели фиксировать более тонкие взаимосвязи в данных.

Однако даже более глубокие модели не обязательно лучше. Следующая модель расширяет глубину до трех слоев:

model = MovielensModel([128, 64, 32])
model
.compile(optimizer=tf.keras.optimizers.Adagrad(0.1))

three_layer_history
= model.fit(
    cached_train
,
    validation_data
=cached_test,
    validation_freq
=5,
    epochs
=num_epochs,
    verbose
=0)

accuracy
= three_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"][-1]
print(f"Top-100 accuracy: {accuracy:.2f}.")
Top-100 accuracy: 0.26.

На самом деле мы не видим улучшения по сравнению с мелкой моделью:

plt.plot(epochs, one_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"], label="1 layer")
plt
.plot(epochs, two_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"], label="2 layers")
plt
.plot(epochs, three_layer_history.history["val_factorized_top_k/top_100_categorical_accuracy"], label="3 layers")
plt
.title("Accuracy vs epoch")
plt
.xlabel("epoch")
plt
.ylabel("Top-100 accuracy");
plt
.legend()
<matplotlib.legend.Legend at 0x7f841c6d8590>

PNG

Это хорошая иллюстрация того факта, что более глубокие и большие модели, хотя и обладают превосходными характеристиками, часто требуют очень тщательной настройки. Например, в этом руководстве мы использовали единую фиксированную скорость обучения. Альтернативные варианты могут дать очень разные результаты, и их стоит изучить.

При соответствующей настройке и достаточном количестве данных усилия, затраченные на создание более крупных и глубоких моделей, во многих случаях того стоят: более крупные модели могут привести к существенному повышению точности прогнозов.

Следующие шаги

В этом уроке мы расширили нашу модель поиска плотными слоями и функциями активации. Чтобы увидеть , как создать модель , которая может выполнять не только поисковые задачи , но и рейтинг задач, взгляните на в многозадачной учебнике .