Рубрики
Без рубрики

Создание службы рекомендаций фильмов с помощью Apache Spark & Flask – Часть 1

Автор оригинала: Jose A Dianes.

В этом учебнике по Apache Spark вы шаг за шагом узнаете, как использовать набор данных MovieLens для создания рекомендателя фильмов с использованием совместной фильтрации с реализацией Spark, чередующейся с Saqures . Он состоит из двух частей. Первый из них касается получения и анализа данных о фильмах и рейтингах в Spark RDDS. Второй-о создании и использовании рекомендателя и сохранении его для последующего использования в нашей онлайн-системе рекомендаций.

Этот учебник можно использовать независимо для построения модели рекомендателя фильмов на основе набора данных MovieLens. Большая часть кода в первой части, о том, как использовать ALS с общедоступным набором данных MovieLens , взята из моего решения одного из упражнений, предложенных в CS100.1x Введение в большие данные с Apache Spark Энтони Д. Джозефом на edX , который также общедоступен с 2014 года на саммите Spark . Начиная с этого момента, я добавил с незначительными изменениями, чтобы использовать больший набор данных, код о том, как хранить и перезагружать модель для последующего использования, и, наконец, веб-сервис с использованием Flask.

В любом случае, использование этого алгоритма с этим набором данных не ново (вы можете Погуглить об этом ), и это потому, что мы делаем акцент на том, чтобы в конечном итоге получить полезную модель в онлайн-среде и как использовать ее в различных ситуациях. Но я действительно вдохновился решением упражнения, предложенного в этом курсе, и я настоятельно рекомендую вам принять его. Там вы узнаете не только ALS, но и многие другие алгоритмы Spark.

Это вторая часть учебника, в которой объясняется, как использовать Python/Flask для создания веб-сервиса поверх моделей Spark. Сделав это, вы сможете разработать полный онлайн-сервис рекомендаций фильмов .

Весь код для этого урока доступен в репозитории GitHub . Существует также репо, объясняющее многие концепции, связанные с Spark . Иди туда и сделай их своими.

Получение и обработка данных

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

Список задач, которые мы можем предварительно вычислить, включает в себя:

  • Загрузка и анализ набора данных. Сохранение полученного RDD для последующего использования.
  • Построение рекомендательной модели с использованием полного набора данных. Сохраните набор данных для последующего использования.

В этой тетради объясняется первая из этих задач.

Загрузка файла

Исследование GroupLens собрало и предоставило наборы рейтинговых данных с веб-сайта MovieLens . Наборы данных собирались в течение различных периодов времени, в зависимости от размера набора. Их можно найти здесь .

В нашем случае мы будем использовать последние наборы данных:

  • Небольшой: 100 000 оценок и 2488 приложений с тегами, примененных к 8570 фильмам 706 пользователями. Последнее обновление 4/2015.
  • Полный: 21 000 000 рейтингов и 470 000 приложений тегов, примененных к 27 000 фильмам 230 000 пользователей. Последнее обновление 4/2015.
complete_dataset_url = 'http://files.grouplens.org/datasets/movielens/ml-latest.zip'
small_dataset_url = 'http://files.grouplens.org/datasets/movielens/ml-latest-small.zip'

Нам также необходимо определить места загрузки.

import os

datasets_path = os.path.join('..', 'datasets')

complete_dataset_path = os.path.join(datasets_path, 'ml-latest.zip')
small_dataset_path = os.path.join(datasets_path, 'ml-latest-small.zip')

Теперь мы можем продолжить обе загрузки.

import urllib

small_f = urllib.urlretrieve (small_dataset_url, small_dataset_path)
complete_f = urllib.urlretrieve (complete_dataset_url, complete_dataset_path)

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

import zipfile

with zipfile.ZipFile(small_dataset_path, "r") as z:
    z.extractall(datasets_path)

with zipfile.ZipFile(complete_dataset_path, "r") as z:
    z.extractall(datasets_path)

Загрузка и анализ наборов данных

Теперь мы готовы прочитать в каждом из файлов и создать RDD, состоящий из проанализированных строк.

Каждая строка в наборе данных рейтингов ( ratings.csv ) форматируется следующим образом:

userId,MovieID,рейтинг,метка времени

Каждая строка в наборе данных movies ( movies.csv ) форматируется следующим образом:

MovieID,название,жанры

Были жанры имеет формат:

Жанр 1|Жанр 2|Жанр 3…

Файл тегов ( tags.csv ) имеет формат:

Идентификатор пользователя,MovieID,тег,метка времени

И, наконец, файл links.csv имеет формат:

MovieID,imdbId,imdbid

Формат этих файлов уникален и прост, поэтому мы можем использовать Python split() для анализа их строк после загрузки в RDDS. Анализ файлов фильмов и рейтингов дает два RDD:

  • Для каждой строки в наборе данных рейтингов мы создаем кортеж (userId, MovieID, Рейтинг) . Мы отбрасываем метку времени , потому что она нам не нужна для этого.
  • Для каждой строки в наборе данных фильмов мы создаем кортеж (MovieID, Title) . Мы отбрасываем жанры , потому что мы не используем их для этой цели.

Итак, давайте загрузим необработанные данные о рейтингах. Нам нужно отфильтровать заголовок, включенный в каждый файл.

small_ratings_file = os.path.join(datasets_path, 'ml-latest-small', 'ratings.csv')

small_ratings_raw_data = sc.textFile(small_ratings_file)
small_ratings_raw_data_header = small_ratings_raw_data.take(1)[0]

Теперь мы можем проанализировать необработанные данные в новый RDD.

small_ratings_data = small_ratings_raw_data.filter(lambda line: line!=small_ratings_raw_data_header)\
    .map(lambda line: line.split(",")).map(lambda tokens: (tokens[0],tokens[1],tokens[2])).cache()

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

small_ratings_data.take(3)
[(u'1', u'6', u'2.0'), (u'1', u'22', u'3.0'), (u'1', u'32', u'2.0')]

Аналогично мы поступаем с файлом movies.csv .

small_movies_file = os.path.join(datasets_path, 'ml-latest-small', 'movies.csv')

small_movies_raw_data = sc.textFile(small_movies_file)
small_movies_raw_data_header = small_movies_raw_data.take(1)[0]

small_movies_data = small_movies_raw_data.filter(lambda line: line!=small_movies_raw_data_header)\
    .map(lambda line: line.split(",")).map(lambda tokens: (tokens[0],tokens[1])).cache()
    
small_movies_data.take(3)
[(u'1', u'Toy Story (1995)'),
 (u'2', u'Jumanji (1995)'),
 (u'3', u'Grumpier Old Men (1995)')]

В следующих разделах представлены Коллаборативная фильтрация и объясняется, как использовать Spark MLlib для построения рекомендательной модели. Мы закроем учебник, объяснив, как такая модель используется для выработки рекомендаций и как сохранить ее для последующего использования (например, в нашем веб-сервисе Python/flask).

Коллаборативная фильтрация

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

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

коллаборативная фильтрация

Библиотека Spark MLlib для машинного обучения обеспечивает реализацию Совместной фильтрации с использованием Чередующихся наименьших квадратов . Реализация в MLlib имеет следующие параметры:

  • numBlocks-это количество блоков, используемых для распараллеливания вычислений (значение -1 для автоматической настройки).
  • ранг – это количество скрытых факторов в модели.
  • итерации-это количество выполняемых итераций.
  • лямбда задает параметр регуляризации в ALS.
  • implicitPrefs указывает, следует ли использовать вариант ALS с явной обратной связью или вариант, адаптированный для неявных данных обратной связи.
  • альфа-это параметр, применимый к варианту неявной обратной связи ALS, который определяет базовую уверенность в наблюдениях предпочтений.

Выбор параметров ALS с помощью небольшого набора данных

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

training_RDD, validation_RDD, test_RDD = small_ratings_data.randomSplit([6, 2, 2], seed=0L)
validation_for_predict_RDD = validation_RDD.map(lambda x: (x[0], x[1]))
test_for_predict_RDD = test_RDD.map(lambda x: (x[0], x[1]))

Теперь мы можем приступить к этапу обучения.

from pyspark.mllib.recommendation import ALS
import math

seed = 5L
iterations = 10
regularization_parameter = 0.1
ranks = [4, 8, 12]
errors = [0, 0, 0]
err = 0
tolerance = 0.02

min_error = float('inf')
best_rank = -1
best_iteration = -1
for rank in ranks:
    model = ALS.train(training_RDD, rank, seed=seed, iterations=iterations,
                      lambda_=regularization_parameter)
    predictions = model.predictAll(validation_for_predict_RDD).map(lambda r: ((r[0], r[1]), r[2]))
    rates_and_preds = validation_RDD.map(lambda r: ((int(r[0]), int(r[1])), float(r[2]))).join(predictions)
    error = math.sqrt(rates_and_preds.map(lambda r: (r[1][0] - r[1][1])**2).mean())
    errors[err] = error
    err += 1
    print 'For rank %s the RMSE is %s' % (rank, error)
    if error < min_error:
        min_error = error
        best_rank = rank

print 'The best model was trained with rank %s' % best_rank
For rank 4 the RMSE is 0.963681878574
For rank 8 the RMSE is 0.96250475933
For rank 12 the RMSE is 0.971647563632
The best model was trained with rank 8

Но давайте немного объясним это. Во-первых, давайте посмотрим, как выглядят наши прогнозы.

predictions.take(3)
[((32, 4018), 3.280114696166238),
 ((375, 4018), 2.7365714977314086),
 ((674, 4018), 2.510684514310653)]

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

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

rates_and_preds.take(3)
[((558, 788), (3.0, 3.0419325487471403)),
 ((176, 3550), (4.5, 3.3214065001580986)),
 ((302, 3908), (1.0, 2.4728711204440765))]

Для этого мы применяем квадратную разницу и используем действие mean () , чтобы получить MSE и применить sqrt .

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

model = ALS.train(training_RDD, best_rank, seed=seed, iterations=iterations,
                      lambda_=regularization_parameter)
predictions = model.predictAll(test_for_predict_RDD).map(lambda r: ((r[0], r[1]), r[2]))
rates_and_preds = test_RDD.map(lambda r: ((int(r[0]), int(r[1])), float(r[2]))).join(predictions)
error = math.sqrt(rates_and_preds.map(lambda r: (r[1][0] - r[1][1])**2).mean())
    
print 'For testing data the RMSE is %s' % (error)
For testing data the RMSE is 0.972342381898

Использование полного набора данных для построения окончательной модели

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

# Load the complete dataset file
complete_ratings_file = os.path.join(datasets_path, 'ml-latest', 'ratings.csv')
complete_ratings_raw_data = sc.textFile(complete_ratings_file)
complete_ratings_raw_data_header = complete_ratings_raw_data.take(1)[0]

# Parse
complete_ratings_data = complete_ratings_raw_data.filter(lambda line: line!=complete_ratings_raw_data_header)\
    .map(lambda line: line.split(",")).map(lambda tokens: (int(tokens[0]),int(tokens[1]),float(tokens[2]))).cache()
    
print "There are %s recommendations in the complete dataset" % (complete_ratings_data.count())
There are 21063128 recommendations in the complete dataset

Теперь мы готовы обучить рекомендательную модель.

training_RDD, test_RDD = complete_ratings_data.randomSplit([7, 3], seed=0L)

complete_model = ALS.train(training_RDD, best_rank, seed=seed, 
                           iterations=iterations, lambda_=regularization_parameter)

Теперь мы тестируем на нашем тестовом наборе.

test_for_predict_RDD = test_RDD.map(lambda x: (x[0], x[1]))

predictions = complete_model.predictAll(test_for_predict_RDD).map(lambda r: ((r[0], r[1]), r[2]))
rates_and_preds = test_RDD.map(lambda r: ((int(r[0]), int(r[1])), float(r[2]))).join(predictions)
error = math.sqrt(rates_and_preds.map(lambda r: (r[1][0] - r[1][1])**2).mean())
    
print 'For testing data the RMSE is %s' % (error)
For testing data the RMSE is 0.82183583368

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

Как давать рекомендации

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

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

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

complete_movies_file = os.path.join(datasets_path, 'ml-latest', 'movies.csv')
complete_movies_raw_data = sc.textFile(complete_movies_file)
complete_movies_raw_data_header = complete_movies_raw_data.take(1)[0]

# Parse
complete_movies_data = complete_movies_raw_data.filter(lambda line: line!=complete_movies_raw_data_header)\
    .map(lambda line: line.split(",")).map(lambda tokens: (int(tokens[0]),tokens[1],tokens[2])).cache()

complete_movies_titles = complete_movies_data.map(lambda x: (int(x[0]),x[1]))
    
print "There are %s movies in the complete dataset" % (complete_movies_titles.count())
There are 27303 movies in the complete dataset

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

def get_counts_and_averages(ID_and_ratings_tuple):
    nratings = len(ID_and_ratings_tuple[1])
    return ID_and_ratings_tuple[0], (nratings, float(sum(x for x in ID_and_ratings_tuple[1]))/nratings)

movie_ID_with_ratings_RDD = (complete_ratings_data.map(lambda x: (x[1], x[2])).groupByKey())
movie_ID_with_avg_ratings_RDD = movie_ID_with_ratings_RDD.map(get_counts_and_averages)
movie_rating_counts_RDD = movie_ID_with_avg_ratings_RDD.map(lambda x: (x[0], x[1][0]))

Добавление новых рейтингов пользователей

Теперь нам нужно оценить некоторые фильмы для нового пользователя. Мы поместим их в новый RDD и будем использовать идентификатор пользователя 0, который не назначен в наборе данных MovieLens. Проверьте файл dataset movies на наличие идентификатора для присвоения титла (чтобы вы знали, какие фильмы вы на самом деле оцениваете).

new_user_ID = 0

# The format of each line is (userID, movieID, rating)
new_user_ratings = [
     (0,260,4), # Star Wars (1977)
     (0,1,3), # Toy Story (1995)
     (0,16,3), # Casino (1995)
     (0,25,4), # Leaving Las Vegas (1995)
     (0,32,4), # Twelve Monkeys (a.k.a. 12 Monkeys) (1995)
     (0,335,1), # Flintstones, The (1994)
     (0,379,1), # Timecop (1994)
     (0,296,3), # Pulp Fiction (1994)
     (0,858,5) , # Godfather, The (1972)
     (0,50,4) # Usual Suspects, The (1995)
    ]
new_user_ratings_RDD = sc.parallelize(new_user_ratings)
print 'New user ratings: %s' % new_user_ratings_RDD.take(10)
New user ratings: [(0, 260, 9), (0, 1, 8), (0, 16, 7), (0, 25, 8), (0, 32, 9), (0, 335, 4), (0, 379, 3), (0, 296, 7), (0, 858, 10), (0, 50, 8)]

Теперь мы добавим их к данным, которые будем использовать для обучения нашей рекомендательной модели. Для этого мы используем преобразование Spark union () .

complete_data_with_new_ratings_RDD = complete_ratings_data.union(new_user_ratings_RDD)

И, наконец, мы обучаем модель ALS, используя все параметры, которые мы выбрали ранее (при использовании небольшого набора данных).

from time import time

t0 = time()
new_ratings_model = ALS.train(complete_data_with_new_ratings_RDD, best_rank, seed=seed, 
                              iterations=iterations, lambda_=regularization_parameter)
tt = time() - t0

print "New model trained in %s seconds" % round(tt,3)
New model trained in 56.61 seconds

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

Получение лучших рекомендаций

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

new_user_ratings_ids = map(lambda x: x[1], new_user_ratings) # get just movie IDs
# keep just those not on the ID list (thanks Lei Li for spotting the error!)
new_user_unrated_movies_RDD = (complete_movies_data.filter(lambda x: x[0] not in new_user_ratings_ids).map(lambda x: (new_user_ID, x[0])))

# Use the input RDD, new_user_unrated_movies_RDD, with new_ratings_model.predictAll() to predict new ratings for the movies
new_user_recommendations_RDD = new_ratings_model.predictAll(new_user_unrated_movies_RDD)

У нас есть готовые рекомендации. Теперь мы можем распечатать 25 фильмов с самыми высокими прогнозируемыми рейтингами. И соедините их с фильмами RDD, чтобы получить названия и подсчет рейтингов, чтобы получить фильмы с минимальным количеством отсчетов. Сначала мы сделаем соединение и посмотрим, как выглядит результат.

# Transform new_user_recommendations_RDD into pairs of the form (Movie ID, Predicted Rating)
new_user_recommendations_rating_RDD = new_user_recommendations_RDD.map(lambda x: (x.product, x.rating))
new_user_recommendations_rating_title_and_count_RDD = \
    new_user_recommendations_rating_RDD.join(complete_movies_titles).join(movie_rating_counts_RDD)
new_user_recommendations_rating_title_and_count_RDD.take(3)
[(87040, ((6.834512984654888, u'"Housemaid'), 14)),
 (8194, ((5.966704041954459, u'Baby Doll (1956)'), 79)),
 (130390, ((0.6922328127396398, u'Contract Killers (2009)'), 1))]

Поэтому нам нужно немного сгладить это, чтобы иметь (Название, рейтинг, Количество рейтингов) .

new_user_recommendations_rating_title_and_count_RDD = \
    new_user_recommendations_rating_title_and_count_RDD.map(lambda r: (r[1][0][1], r[1][0][0], r[1][1]))

Наконец, получите рекомендации с самым высоким рейтингом для нового пользователя, отфильтровав фильмы с менее чем 25 рейтингами.

top_movies = new_user_recommendations_rating_title_and_count_RDD.filter(lambda r: r[2]>=25).takeOrdered(25, key=lambda x: -x[1])

print ('TOP recommended movies (with more than 25 reviews):\n%s' %
        '\n'.join(map(str, top_movies)))
TOP recommended movies (with more than 25 reviews):
    (u'"Godfather: Part II', 8.503749129186701, 29198)
    (u'"Civil War', 8.386497469089297, 257)
    (u'Frozen Planet (2011)', 8.372705479107108, 31)
    (u'"Shawshank Redemption', 8.258510064442426, 67741)
    (u'Cosmos (1980)', 8.252254825768972, 948)
    (u'Band of Brothers (2001)', 8.225114960311624, 4450)
    (u'Generation Kill (2008)', 8.206487040524653, 52)
    (u"Schindler's List (1993)", 8.172761674773625, 53609)
    (u'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb (1964)', 8.166229786764168, 23915)
    (u"One Flew Over the Cuckoo's Nest (1975)", 8.15617022970577, 32948)
    (u'Casablanca (1942)', 8.141303207981174, 26114)
    (u'Seven Samurai (Shichinin no samurai) (1954)', 8.139633165142612, 11796)
    (u'Goodfellas (1990)', 8.12931139039048, 27123)
    (u'Star Wars: Episode V - The Empire Strikes Back (1980)', 8.124225700242096, 47710)
    (u'Jazz (2001)', 8.078538221315313, 25)
    (u"Long Night's Journey Into Day (2000)", 8.050176820606127, 34)
    (u'Lawrence of Arabia (1962)', 8.041331489948814, 13452)
    (u'Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981)', 8.0399424815528, 45908)
    (u'12 Angry Men (1957)', 8.011389274280754, 13235)
    (u"It's Such a Beautiful Day (2012)", 8.007734839026181, 35)
    (u'Apocalypse Now (1979)', 8.005094327199552, 23905)
    (u'Paths of Glory (1957)', 7.999379786394267, 3598)
    (u'Rear Window (1954)', 7.9860865203540214, 17996)
    (u'State of Play (2003)', 7.981582126801772, 27)
    (u'Chinatown (1974)', 7.978673289692703, 16195)

Получение индивидуальных оценок

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

my_movie = sc.parallelize([(0, 500)]) # Quiz Show (1994)
individual_movie_rating_RDD = new_ratings_model.predictAll(new_user_unrated_movies_RDD)
individual_movie_rating_RDD.take(1)
[Rating(user=0, product=122880, rating=4.955831875971526)]

Не очень вероятно, что новому пользователю это понравится… Очевидно, мы можем включить в этот список столько фильмов, сколько нам нужно!

Сохранение модели

При необходимости мы можем сохранить базовую модель для последующего использования в наших онлайн-рекомендациях. Хотя новая модель генерируется каждый раз, когда у нас появляются новые рейтинги пользователей, возможно, стоит сохранить текущую модель, чтобы сэкономить время при запуске сервера и т. Д. Мы также могли бы сэкономить время, если бы сохранили некоторые из созданных нами RDDS, особенно те, обработка которых заняла больше времени. Например, следующие строки сохраняют и загружают модель ALS.

from pyspark.mllib.recommendation import MatrixFactorizationModel

model_path = os.path.join('..', 'models', 'movie_lens_als')

# Save and load model
model.save(sc, model_path)
same_model = MatrixFactorizationModel.load(sc, model_path)

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

Жанр и другие области

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

Выводы

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

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

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