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

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

Рекомендация на основе модели с Spark

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

Таблица 1 - Матрица пользовательского фильма

В предыдущей таблице пользовательские рейтинги фильмов представлены в виде матрицы пользовательских элементов, где ячейка представляет собой оценки пользователя для конкретного фильма. Клетка с? представляет фильм, о котором пользователь U4 не знает или не смотрел. Исходя из текущего предпочтения U4, ячейка с? можно заполнить примерный рейтинг пользователей, которые имеют схожие с U4 интересы. Итак, на данный момент ALS не может сделать это в одиночку, но LF затем используются для прогнозирования отсутствующих записей.

Spark API обеспечивает реализацию алгоритма ALS, который используется для изучения этих LF на основе следующих шести параметров:

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

Обратите внимание, что для создания экземпляра ALS с параметрами по умолчанию вы можете установить значение в соответствии с вашими требованиями. Значения по умолчанию следующие: numBlocks: -1, rank: 10, iterations: 10, lambda: 0,01, implicitPrefs: false и alpha: 1.0.

Исследование данных

Фильм и соответствующий набор рейтинговых данных были загружены с сайта MovieLens (https://movielens.org). Согласно описанию данных на сайте MovieLens, все рейтинги описаны в файле rating.csv. Каждая строка этого файла, за которой следует заголовок, представляет одну оценку одного фильма одним пользователем.

В наборе данных CSV есть следующие столбцы: userId, movieId, rating и timestamp. Строки упорядочиваются сначала по userId, а внутри пользователя - по movieId. Рейтинги выставляются по пятибалльной шкале с шагом в половину звезды (от 0,5 до 5,0 звезд). Временные метки представляют собой секунды, прошедшие с полуночи по всемирному координированному времени (UTC) 1 января 1970 года. Вы получили 105 339 оценок от 668 пользователей к 10 325 фильмам:

С другой стороны, информация о фильме содержится в файле movies.csv. Каждая строка, помимо информации заголовка, представляет один фильм, содержащий следующие столбцы: movieId, название и жанры. Названия фильмов либо создаются, либо вставляются вручную, либо импортируются с веб-сайта базы данных фильмов по адресу https://www.themoviedb.org/. Однако год выпуска указан в скобках.

Поскольку заголовки фильмов вставляются вручную, в этих заголовках могут существовать некоторые ошибки или несоответствия. Поэтому читателям рекомендуется проверить базу данных IMDb (https://www.imdb.com/), чтобы убедиться, что нет несоответствий или неправильных заголовков с соответствующим годом выпуска:

Жанры представлены в отдельном списке и выбираются из следующих жанровых категорий:

  • Боевики, Приключения, Анимация, Детские, Комедия и Криминал
  • Документальный, Драма, Фэнтези, Нуар, Ужасы и Мюзикл
  • Мистика, Романтика, Научная фантастика, Триллер, Вестерн и Война

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

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

Шаг 1. Импортируйте пакеты, загрузите, проанализируйте и изучите набор данных фильма и рейтинга

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

package com.packt.ScalaML.MovieRecommendation

import org.apache.spark.sql.SparkSession

import org.apache.spark.mllib.recommendation.ALS

import org.apache.spark.mllib.recommendation.MatrixFactorizationModel

import org.apache.spark.mllib.recommendation.Rating

import scala.Tuple2

import org.apache.spark.rdd.RDD

Этот сегмент кода должен вернуть вам DataFrame оценок:

val ratigsFile = "data/ratings.csv"

val df1 = spark.read.format("com.databricks.spark.csv").option("header", true).load(ratigsFile)

val ratingsDF = df1.select(df1.col("userId"), df1.col("movieId"), df1.col("rating"), df1.col("timestamp"))ratingsDF.show(false)

В следующем фрагменте кода показан DataFrame фильмов:

val moviesFile = "data/movies.csv"

val df2 = spark.read.format("com.databricks.spark.csv").option("header", "true").load(moviesFile)

val moviesDF = df2.select(df2.col("movieId"), df2.col("title"), df2.col("genres"))

Шаг 2. Зарегистрируйте оба DataFrames как временные таблицы, чтобы упростить запросы

Чтобы зарегистрировать оба набора данных, вы можете использовать следующий код:

ratingsDF.createOrReplaceTempView("ratings")

moviesDF.createOrReplaceTempView("movies")

Это поможет ускорить выполнение запросов в памяти за счет создания временного представления в виде таблицы в памяти. Время жизни временной таблицы с использованием метода createOrReplaceTempView () привязано к [[SparkSession]], который использовался для создания этого DataFrame.

Шаг 3. Изучите и запросите соответствующую статистику

Давайте проверим статистику по рейтингам. Просто используйте следующие строки кода:

val numRatings = ratingsDF.count()

val numUsers = ratingsDF.select(ratingsDF.col("userId")).distinct().count()

val numMovies = ratingsDF.select(ratingsDF.col("movieId")).distinct().count() println("Got " + numRatings + " ratings from " + numUsers + " users on " + numMovies + " movies.")

>>>

Получил 105339 оценок от 668 пользователей к 10325 фильмам.

Вы должны найти 105 339 оценок от 668 пользователей к 10 325 фильмам. Теперь давайте получим максимальную и минимальную оценки, а также количество пользователей, которые оценили фильм. Однако вам необходимо выполнить SQL-запрос к таблице рейтингов, которую вы только что создали в памяти на предыдущем шаге. Сделать запрос здесь просто, и он похож на запрос из базы данных MySQL или СУБД.

Однако, если вы не знакомы с запросами на основе SQL, рекомендуется ознакомиться со спецификацией запроса SQL, чтобы узнать, как выполнить выбор с помощью SELECT из определенной таблицы, как выполнить упорядочивание с помощью ORDER и как выполнить операция присоединения с использованием ключевого слова JOIN.

Что ж, если вы знаете SQL-запрос, вы должны получить новый набор данных, используя сложный SQL-запрос, как показано ниже:

// Получить максимальную и минимальную оценки вместе с количеством пользователей, которые оценили фильм.

val results = spark.sql("select movies.title, movierates.maxr, movierates.minr, movierates.cntu "

+ "from(SELECT ratings.movieId,max(ratings.rating) as maxr,"

+ "min(ratings.rating) as minr,count(distinct userId) as cntu "

+ "FROM ratings group by ratings.movieId) movierates "

+ "join movies on movierates.movieId=movies.movieId "

+ "order by movierates.cntu desc") results.show(false)

Выход:

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

val mostActiveUsersSchemaRDD = spark.sql("SELECT ratings.userId, count(*) as ct from ratings "+ "group by ratings.userId order by ct desc limit 10")mostActiveUsersSchemaRDD.show(false)

>>>

Рис.: 10 самых активных пользователей и сколько раз они оценивали фильм

Давайте посмотрим на конкретного пользователя и найдем фильмы, которые, скажем, пользователь 668 оценили выше 4:

val results2 = spark.sql(

"SELECT ratings.userId, ratings.movieId,"

+ "ratings.rating, movies.title FROM ratings JOIN movies"

+ "ON movies.movieId=ratings.movieId"

+ "where ratings.userId=668 and ratings.rating > 4") results2.show(false)

>>>

Шаг 4. Подготовьте данные для обучения и тестирования и проверьте счетчики

Следующий код разделяет рейтинги RDD на RDD обучающих данных (75%) и RDD тестовых данных (25%). Посев здесь необязателен, но необходим для воспроизводимости:

// Разделить рейтинги RDD на RDD обучения (75%) и RDD тестирования (25%)

val splits = ratingsDF.randomSplit(Array(0.75, 0.25), seed = 12345L)

val (trainingData, testData) = (splits(0), splits(1))

val numTraining = trainingData.count()

val numTest = testData.count()

println("Training: " + numTraining + " test: " + numTest)

Вы должны заметить, что в обучении 78 792 оценки, а в тестовом DataFrame - 26 547 оценок.

Шаг 5. Подготовьте данные для построения модели рекомендаций с помощью ALS

Алгоритм ALS использует RDD рейтингов для обучения. Для этого следующий код иллюстрирует:

val ratingsRDD = trainingData.rdd.map(row => {

val userId = row.getString(0)

val movieId = row.getString(1)

val ratings = row.getString(2)

Rating(userId.toInt, movieId.toInt, ratings.toDouble)

})

РейтингиRDD - это RDD рейтингов, который содержит userId, movieId и соответствующие рейтинги из набора обучающих данных, подготовленного на предыдущем шаге. С другой стороны, для оценки модели также требуется тестовый RDD. Следующий тестовый RDD также содержит ту же информацию, что и тестовый DataFrame, который вы подготовили на предыдущем шаге:

val testRDD = testData.rdd.map(row => {

val userId = row.getString(0)

val movieId = row.getString(1)

val ratings = row.getString(2)

Rating(userId.toInt, movieId.toInt, ratings.toDouble)

})

Шаг 6. Создайте матрицу пользовательских продуктов ALS

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

val rank = 20

val numIterations = 15

val lambda = 0.10

val alpha = 1.00 val block = -1

val seed = 12345L

val implicitPrefs = false

val model = new ALS().setIterations(numIterations) .setBlocks(block).setAlpha(alpha)

.setLambda(lambda)

.setRank(rank) .setSeed(seed)

.setImplicitPrefs(implicitPrefs)

.run(ratingsRDD)

Наконец, вы повторили модель для обучения 15 раз. С этой настройкой вы получаете хорошую точность прогнозов. Читателям рекомендуется применить настройку гиперпараметров, чтобы узнать наиболее оптимальные значения этих параметров. Кроме того, установите количество блоков как для пользовательских блоков, так и для блоков продукта, чтобы распараллелить вычисления в проходе -1 для автоматически настраиваемого количества блоков. Значение -1.

Шаг 7. Прогнозы

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

// Делаем прогнозы. Получите 6 лучших прогнозов фильмов для пользователя 668

println("Rating:(UserID, MovieID, Rating)")

println("----------------------------------")

val topRecsForUser = model.recommendProducts(668, 6) for (rating <- topRecsForUser) { println(rating.toString()) } println("----------------------------------") >>>

Шаг 8. Оценка модели

Чтобы проверить качество модели, среднеквадратичная ошибка (RMSE) используется для измерения разницы между значениями, предсказанными моделью, и фактически наблюдаемыми значениями. По умолчанию, чем меньше расчетная ошибка, тем лучше модель. Для проверки качества модели используются тестовые данные (которые были разделены на шаге 4).

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

val rmseTest = computeRmse(model, testRDD, true)

println("Test RMSE: = " + rmseTest) //Less is better

Для этого параметра вы получите следующий результат:

RMSE теста: = 0,9019872589764073

Этот метод вычисляет RMSE для оценки модели, чем меньше RMSE, тем лучше модель и ее возможности прогнозирования. Следует отметить, что computeRmse () - это UDF, которая выглядит следующим образом:

def computeRmse(model: MatrixFactorizationModel, data: RDD[Rating], implicitPrefs: Boolean): Double = { val predictions: RDD[Rating] = model.predict(data.map(x => (x.user, x.product))) valpredictionsAndRatings = predictions.map { x => ((x.user, x.product), x.rating) } .join(data.map(x => ((x.user, x.product), x.rating))).values if (implicitPrefs) { println("(Prediction, Rating)") println(predictionsAndRatings.take(5).mkString("n")) } math.sqrt(predictionsAndRatings.map(x => (x._1 - x._2) * (x._1 - x._2)).mean()) }

>>>

Наконец, давайте дадим рекомендации по фильму для конкретного пользователя. Давайте получим шесть лучших прогнозов фильмов для пользователя 668:

println("Recommendations: (MovieId => Rating)") println("----------------------------------") val recommendationsUser = model.recommendProducts(668, 6) recommendationsUser.map(rating => (rating.product, rating.rating)).foreach(println) println("----------------------------------")

>>>

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

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

Для получения дополнительных обновлений вы можете подписаться на меня в Твиттере на моем твиттере @NavRudraSambyal

Спасибо за чтение, поделитесь, если вы нашли его полезным