RSVP для вашего местного мероприятия TensorFlow Everywhere сегодня!
Эта страница переведена с помощью Cloud Translation API.
Switch to English

Рекомендации фильмов: поиск

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Реальные рекомендательные системы часто состоят из двух этапов:

  1. Этап поиска отвечает за выбор первоначального набора из сотен кандидатов из всех возможных кандидатов. Основная цель этой модели - эффективно отсеять всех кандидатов, которые не интересуют пользователя. Поскольку модель поиска может иметь дело с миллионами кандидатов, она должна быть эффективной с вычислительной точки зрения.
  2. Этап ранжирования берет выходные данные модели поиска и настраивает их, чтобы выбрать лучшую из возможных рекомендаций. Его задача - сузить набор элементов, которые могут заинтересовать пользователя, до короткого списка вероятных кандидатов.

В этом уроке мы сосредоточимся на первом этапе - извлечении. Если вас интересует этап ранжирования, ознакомьтесь с нашим руководством по ранжированию .

Модели поиска часто состоят из двух подмоделей:

  1. Модель запроса, вычисляющая представление запроса (обычно вектор внедрения фиксированной размерности) с использованием функций запроса.
  2. Модель-кандидат, вычисляющая представление кандидата (вектор одинакового размера) с использованием функций-кандидатов

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

В этом руководстве мы собираемся построить и обучить такую ​​модель с двумя башнями, используя набор данных Movielens.

Собирались:

  1. Получите наши данные и разделите их на набор для обучения и тестирования.
  2. Реализуйте модель поиска.
  3. Подгоните и оцените.
  4. Экспортируйте его для эффективного обслуживания путем построения приблизительного индекса ближайших соседей (ИНС).

Набор данных

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

Данные можно обрабатывать двумя способами:

  1. Его можно интерпретировать как выражение того, какие фильмы пользователи смотрели (и оценивали), а какие нет. Это форма неявной обратной связи, при которой часы пользователей сообщают нам, что они предпочитают видеть, а что - не видеть.
  2. Это также можно рассматривать как выражение того, насколько пользователям понравились фильмы, которые они смотрели. Это форма явной обратной связи: учитывая, что пользователь смотрел фильм, мы можем примерно сказать, насколько он понравился, посмотрев на оценку, которую он дал.

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

Импорт

Давайте сначала уберем наш импорт.

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

from typing import Dict, Text

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

Подготовка набора данных

Давайте сначала посмотрим на данные.

Мы используем набор данных MovieLens из набора данных Tensorflow . Загрузка movielens/100k_ratings дает объект tf.data.Dataset содержащий данные оценок, а загрузка movielens/100k_movies дает объект tf.data.Dataset содержащий только данные фильмов.

Обратите внимание, что, поскольку набор данных MovieLens не имеет предопределенных разделений, все данные находятся в режиме разделения train .

# Ratings data.
ratings = tfds.load("movielens/100k-ratings", split="train")
# Features of all the available movies.
movies = tfds.load("movielens/100k-movies", split="train")
Downloading and preparing dataset movielens/100k-ratings/0.1.0 (download: 4.70 MiB, generated: 32.41 MiB, total: 37.10 MiB) to /home/kbuilder/tensorflow_datasets/movielens/100k-ratings/0.1.0...
Shuffling and writing examples to /home/kbuilder/tensorflow_datasets/movielens/100k-ratings/0.1.0.incompleteN2R8B5/movielens-train.tfrecord
Dataset movielens downloaded and prepared to /home/kbuilder/tensorflow_datasets/movielens/100k-ratings/0.1.0. Subsequent calls will reuse this data.
Downloading and preparing dataset movielens/100k-movies/0.1.0 (download: 4.70 MiB, generated: 150.35 KiB, total: 4.84 MiB) to /home/kbuilder/tensorflow_datasets/movielens/100k-movies/0.1.0...
Shuffling and writing examples to /home/kbuilder/tensorflow_datasets/movielens/100k-movies/0.1.0.incompleteH88HCK/movielens-train.tfrecord
Dataset movielens downloaded and prepared to /home/kbuilder/tensorflow_datasets/movielens/100k-movies/0.1.0. Subsequent calls will reuse this data.

Набор данных рейтингов возвращает словарь идентификатора фильма, идентификатора пользователя, присвоенного рейтинга, отметки времени, информации о фильме и информации о пользователе:

for x in ratings.take(1).as_numpy_iterator():
  pprint.pprint(x)
{'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'}

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

for x in movies.take(1).as_numpy_iterator():
  pprint.pprint(x)
{'movie_genres': array([4]),
 'movie_id': b'1681',
 'movie_title': b'You So Crazy (1994)'}

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

В наборе данных мы сохраняем только user_id и movie_title .

ratings = ratings.map(lambda x: {
    "movie_title": x["movie_title"],
    "user_id": x["user_id"],
})
movies = movies.map(lambda x: x["movie_title"])

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

Однако в этом простом примере давайте воспользуемся случайным разделением, поместив 80% оценок в набор поездов и 20% в набор тестов.

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)

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

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

movie_titles = movies.batch(1_000)
user_ids = ratings.batch(1_000_000).map(lambda x: x["user_id"])

unique_movie_titles = np.unique(np.concatenate(list(movie_titles)))
unique_user_ids = np.unique(np.concatenate(list(user_ids)))

unique_movie_titles[:10]
array([b"'Til There Was You (1997)", b'1-900 (1994)',
       b'101 Dalmatians (1996)', b'12 Angry Men (1957)', b'187 (1997)',
       b'2 Days in the Valley (1996)',
       b'20,000 Leagues Under the Sea (1954)',
       b'2001: A Space Odyssey (1968)',
       b'3 Ninjas: High Noon At Mega Mountain (1998)',
       b'39 Steps, The (1935)'], dtype=object)

Реализация модели

Выбор архитектуры нашей модели является ключевой частью моделирования.

Поскольку мы строим модель извлечения с двумя башнями, мы можем построить каждую башню отдельно, а затем объединить их в окончательную модель.

Башня запросов

Начнем с башни запросов.

Первый шаг - определиться с размерностью представлений запроса и кандидатов:

embedding_dimension = 32

Более высокие значения будут соответствовать моделям, которые могут быть более точными, но также будут медленнее соответствовать и более склонны к переобучению.

Второй - определить саму модель. Здесь мы собираемся использовать слои предварительной обработки Keras, чтобы сначала преобразовать идентификаторы пользователей в целые числа, а затем преобразовать их в пользовательские вложения через слой Embedding . Обратите внимание, что мы используем список уникальных идентификаторов пользователей, которые мы вычислили ранее, в качестве словаря:

user_model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.StringLookup(
      vocabulary=unique_user_ids, mask_token=None),
  # We add an additional embedding to account for unknown tokens.
  tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
])

Такая простая модель точно соответствует классическому подходу матричной факторизации . Хотя определение подкласса tf.keras.Model для этой простой модели может быть излишним, мы можем легко расширить его до произвольно сложной модели, используя стандартные компоненты Keras, если в конце мы вернем вывод embedding_dimension -wide.

Башня кандидатов

Мы можем сделать то же самое с башней кандидатов.

movie_model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.StringLookup(
      vocabulary=unique_movie_titles, mask_token=None),
  tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
])

Метрики

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

Для этого мы можем использовать метрику tfrs.metrics.FactorizedTopK . У метрики есть один обязательный аргумент: набор данных кандидатов, которые используются в качестве неявных отрицательных значений для оценки.

В нашем случае это набор данных movies , преобразованный во вложения с помощью нашей модели фильма:

metrics = tfrs.metrics.FactorizedTopK(
  candidates=movies.batch(128).map(movie_model)
)

Потеря

Следующий компонент - это потери, используемые для обучения нашей модели. TFRS имеет несколько уровней потерь и задач, которые упрощают эту задачу.

В этом случае мы будем использовать объект задачи Retrieval : удобную оболочку, которая объединяет функцию потерь и вычисление метрики:

task = tfrs.tasks.Retrieval(
  metrics=metrics
)

Сама задача представляет собой слой Keras, который принимает вложения запроса и кандидатов в качестве аргументов и возвращает вычисленные потери: мы будем использовать это для реализации цикла обучения модели.

Полная модель

Теперь мы можем собрать все это вместе в модель. TFRS предоставляет класс базовой модели ( tfrs.models.Model ), который упрощает модели строительства: все, что нам нужно сделать, это настроить компоненты в методе __init__ и реализовать метод compute_loss , принимая необработанные функции и возвращая значение потерь .

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

class MovielensModel(tfrs.Model):

  def __init__(self, user_model, movie_model):
    super().__init__()
    self.movie_model: tf.keras.Model = movie_model
    self.user_model: tf.keras.Model = user_model
    self.task: tf.keras.layers.Layer = task

  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:
    # We pick out the user features and pass them into the user model.
    user_embeddings = self.user_model(features["user_id"])
    # And pick out the movie features and pass them into the movie model,
    # getting embeddings back.
    positive_movie_embeddings = self.movie_model(features["movie_title"])

    # The task computes the loss and the metrics.
    return self.task(user_embeddings, positive_movie_embeddings)

Базовый класс tfrs.Model - это просто tfrs.Model класс: он позволяет нам вычислять потери при обучении и тестировании одним и тем же методом.

Под капотом по-прежнему простенькая модель Keras. Вы можете достичь той же функциональности, унаследовав от tf.keras.Model и переопределив функции train_step и test_step (подробности см. В руководстве ):

class NoBaseClassMovielensModel(tf.keras.Model):

  def __init__(self, user_model, movie_model):
    super().__init__()
    self.movie_model: tf.keras.Model = movie_model
    self.user_model: tf.keras.Model = user_model
    self.task: tf.keras.layers.Layer = task

  def train_step(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:

    # Set up a gradient tape to record gradients.
    with tf.GradientTape() as tape:

      # Loss computation.
      user_embeddings = self.user_model(features["user_id"])
      positive_movie_embeddings = self.movie_model(features["movie_title"])
      loss = self.task(user_embeddings, positive_movie_embeddings)

      # Handle regularization losses as well.
      regularization_loss = sum(self.losses)

      total_loss = loss + regularization_loss

    gradients = tape.gradient(total_loss, self.trainable_variables)
    self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))

    metrics = {metric.name: metric.result() for metric in self.metrics}
    metrics["loss"] = loss
    metrics["regularization_loss"] = regularization_loss
    metrics["total_loss"] = total_loss

    return metrics

  def test_step(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:

    # Loss computation.
    user_embeddings = self.user_model(features["user_id"])
    positive_movie_embeddings = self.movie_model(features["movie_title"])
    loss = self.task(user_embeddings, positive_movie_embeddings)

    # Handle regularization losses as well.
    regularization_loss = sum(self.losses)

    total_loss = loss + regularization_loss

    metrics = {metric.name: metric.result() for metric in self.metrics}
    metrics["loss"] = loss
    metrics["regularization_loss"] = regularization_loss
    metrics["total_loss"] = total_loss

    return metrics

Однако в этих руководствах мы будем использовать базовый класс tfrs.Model чтобы сосредоточиться на моделировании и абстрагироваться от некоторых шаблонов.

Установка и оценка

После определения модели мы можем использовать стандартные процедуры подгонки и оценки Keras для подбора и оценки модели.

Давайте сначала создадим экземпляр модели.

model = MovielensModel(user_model, movie_model)
model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))

Затем перемешайте, пакетируйте и кэшируйте данные обучения и оценки.

cached_train = train.shuffle(100_000).batch(8192).cache()
cached_test = test.batch(4096).cache()

Затем обучите модель:

model.fit(cached_train, epochs=3)
Epoch 1/3
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32

WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32

WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.

WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.

WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32

WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32

WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.

WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.

10/10 [==============================] - 2s 238ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0011 - factorized_top_k/top_5_categorical_accuracy: 0.0090 - factorized_top_k/top_10_categorical_accuracy: 0.0190 - factorized_top_k/top_50_categorical_accuracy: 0.0961 - factorized_top_k/top_100_categorical_accuracy: 0.1735 - loss: 69885.1072 - regularization_loss: 0.0000e+00 - total_loss: 69885.1072
Epoch 2/3
10/10 [==============================] - 2s 224ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0028 - factorized_top_k/top_5_categorical_accuracy: 0.0187 - factorized_top_k/top_10_categorical_accuracy: 0.0374 - factorized_top_k/top_50_categorical_accuracy: 0.1686 - factorized_top_k/top_100_categorical_accuracy: 0.2919 - loss: 67523.3707 - regularization_loss: 0.0000e+00 - total_loss: 67523.3707
Epoch 3/3
10/10 [==============================] - 2s 219ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0035 - factorized_top_k/top_5_categorical_accuracy: 0.0222 - factorized_top_k/top_10_categorical_accuracy: 0.0457 - factorized_top_k/top_50_categorical_accuracy: 0.1877 - factorized_top_k/top_100_categorical_accuracy: 0.3158 - loss: 66302.9609 - regularization_loss: 0.0000e+00 - total_loss: 66302.9609

<tensorflow.python.keras.callbacks.History at 0x7f04c679ca20>

По мере обучения модели потери падают, и обновляется набор топ-k показателей поиска. Они говорят нам, есть ли истинный положительный результат в k самых популярных элементах из всего набора кандидатов. Например, показатель категориальной точности 0,2 в топ-5 говорит о том, что в среднем истинный положительный результат находится в 5 лучших извлекаемых элементах в 20% случаев.

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

Наконец, мы можем оценить нашу модель на тестовом наборе:

model.evaluate(cached_test, return_dict=True)
5/5 [==============================] - 1s 118ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0010 - factorized_top_k/top_5_categorical_accuracy: 0.0097 - factorized_top_k/top_10_categorical_accuracy: 0.0226 - factorized_top_k/top_50_categorical_accuracy: 0.1244 - factorized_top_k/top_100_categorical_accuracy: 0.2327 - loss: 31079.0628 - regularization_loss: 0.0000e+00 - total_loss: 31079.0628

{'factorized_top_k/top_1_categorical_accuracy': 0.0010000000474974513,
 'factorized_top_k/top_5_categorical_accuracy': 0.009650000371038914,
 'factorized_top_k/top_10_categorical_accuracy': 0.022600000724196434,
 'factorized_top_k/top_50_categorical_accuracy': 0.12439999729394913,
 'factorized_top_k/top_100_categorical_accuracy': 0.23270000517368317,
 'loss': 28244.76953125,
 'regularization_loss': 0,
 'total_loss': 28244.76953125}

Производительность набора тестов намного хуже, чем производительность обучения. Это связано с двумя факторами:

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

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

Делаем прогнозы

Теперь, когда у нас есть модель, мы хотели бы иметь возможность делать прогнозы. Для этого мы можем использовать слой tfrs.layers.factorized_top_k.BruteForce .

# Create a model that takes in raw query features, and
index = tfrs.layers.factorized_top_k.BruteForce(model.user_model)
# recommends movies out of the entire movies dataset.
index.index(movies.batch(100).map(model.movie_model), movies)

# Get recommendations.
_, titles = index(tf.constant(["42"]))
print(f"Recommendations for user 42: {titles[0, :3]}")
Recommendations for user 42: [b'Bridges of Madison County, The (1995)'
 b'Father of the Bride Part II (1995)' b'Rudy (1993)']

Конечно, уровень BruteForce будет слишком медленным, чтобы обслуживать модель с множеством возможных кандидатов. В следующих разделах показано, как это ускорить с помощью приблизительного индекса поиска.

Модель сервировки

После обучения модели нам нужен способ ее развертывания.

В модели извлечения с двумя башнями обслуживание состоит из двух компонентов:

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

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

Чтобы развернуть подобную модель, мы просто экспортируем слой BruteForce мы создали выше:

# Export the query model.
with tempfile.TemporaryDirectory() as tmp:
  path = os.path.join(tmp, "model")

  # Save the index.
  index.save(path)

  # Load it back; can also be done in TensorFlow Serving.
  loaded = tf.keras.models.load_model(path)

  # Pass a user id in, get top predicted movie titles back.
  scores, titles = loaded(["42"])

  print(f"Recommendations: {titles[0][:3]}")
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.

WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.

WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.

WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.

INFO:tensorflow:Assets written to: /tmp/tmp6r6itle7/model/assets

INFO:tensorflow:Assets written to: /tmp/tmp6r6itle7/model/assets

WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.

WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.

Recommendations: [b'Bridges of Madison County, The (1995)'
 b'Father of the Bride Part II (1995)' b'Rudy (1993)']

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

Для этого мы можем использовать пакет scann . Это необязательная зависимость TFRS, и мы установили ее отдельно в начале этого руководства, вызвав !pip install -q scann .

После установки мы можем использовать слой TFRS ScaNN :

scann_index = tfrs.layers.factorized_top_k.ScaNN(model.user_model)
scann_index.index(movies.batch(100).map(model.movie_model), movies)
<tensorflow_recommenders.layers.factorized_top_k.ScaNN at 0x7f04c6617dd8>

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

# Get recommendations.
_, titles = scann_index(tf.constant(["42"]))
print(f"Recommendations for user 42: {titles[0, :3]}")
Recommendations for user 42: [b'Bridges of Madison County, The (1995)'
 b'Father of the Bride Part II (1995)' b'Rudy (1993)']

Экспортировать его для обслуживания так же просто, как экспортировать слой BruteForce :

# Export the query model.
with tempfile.TemporaryDirectory() as tmp:
  path = os.path.join(tmp, "model")

  # Save the index.
  scann_index.save(
      path,
      options=tf.saved_model.SaveOptions(namespace_whitelist=["Scann"])
  )

  # Load it back; can also be done in TensorFlow Serving.
  loaded = tf.keras.models.load_model(path)

  # Pass a user id in, get top predicted movie titles back.
  scores, titles = loaded(["42"])

  print(f"Recommendations: {titles[0][:3]}")
INFO:tensorflow:Assets written to: /tmp/tmpduzk4yez/model/assets

INFO:tensorflow:Assets written to: /tmp/tmpduzk4yez/model/assets

WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.

WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.

Recommendations: [b'Bridges of Madison County, The (1995)'
 b'Father of the Bride Part II (1995)' b'Rudy (1993)']

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

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

На этом руководство по извлечению завершено.

Чтобы расширить то, что здесь представлено, взгляните на:

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