Цели и Задачи

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

План проекта:

  1. Текстовый анализ описания книг и рецензий на них

  2. Построение рекомендательной системы на основе коллаборативной фильтрации

  3. Оценка качества построения первой модели

  4. Построение рекомендательной системы на основе содержательной оценки книг

  5. Оценка качества построения второй модели

  6. Проверка моделей на основе внешних сценариев

  7. Общие выводы

  8. Ответы на вопросы

Предобработка

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

Текстовый анализ

LDA анализ

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

Следующие группы тем были выявлены:

# Предобработка текста для LDA анализа
load("~/shared/minor2_2020/data/good_read/books_g_1.RData")
# Убираем числа, которые могут встретиться в тексте
goodread_comics_text = goodread_comics %>% mutate(description = removeNumbers(description)) %>% dplyr::filter(language_code == "eng")
# Загружаем словари стоп-слов + добавляем слова, часто встречающиеся во всех темах
engstopwords = data.frame(words=c(stopwords::stopwords("en"), "new", "one","collecting","world","first", stringsAsFactors=FALSE))
# Токинезация + объединение датасета и словарей
comics_tokens = goodread_comics_text %>% unnest_tokens(words, description) %>% anti_join(engstopwords)
comics_count = comics_tokens %>% count(book_id, words, sort = TRUE) %>% ungroup()
# Построение модели LDA. 
comics_dtm <- comics_count %>% cast_dtm(book_id, words, n)
comics_lda <- LDA(comics_dtm, k = 10, control = list(seed = 12345))
# Визуализация LDA модели
comics_topics <- tidy(comics_lda, matrix = "beta")
comics_top_terms <- comics_topics %>% group_by(topic) %>% top_n(10, beta) %>% ungroup() %>% arrange(topic, -beta)

comics_top_terms %>% mutate(term = reorder(term, beta)) %>% ggplot(aes(term, beta, fill = factor(topic))) + geom_col(show.legend = FALSE) + labs(title = "Распределение слов в описании комиксах на темы", x = "Слова" , y = "Вероятность попадания в тему") + facet_wrap(~ topic, scales = "free") + coord_flip() + scale_fill_brewer(palette = "Set3") +  theme_test() 

# Строим распределение тем по по id книги
comics_documents <- tidy(comics_lda, matrix = "gamma")
# Теперь есть данные, которые позволяют судить о возможном содержании книги по ее принадлежности к той или иной теме. Таким образом, переменная, распределение по книгам, может быть использована при построении рекомендательной системы на основе содержания.
Анализ тональности текста

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

load("~/shared/minor2_2020/data/good_read/reviews_g_1.RData")
# Загружаем словарь для оценки настроения текста
afin = get_sentiments(lexicon = "afinn")
# Убираем возможные числа и стоп слова, которые могут встретиться в текстах
goodread_reviews = goodread_reviews %>% mutate(review_text = removeNumbers(review_text))
engstopwords = data.frame(words=c(stopwords::stopwords("en"), stringsAsFactors=FALSE))
# Через функцию объединяем датасет со словарями стоп-слов
reviews.tidy = goodread_reviews %>% select(book_id, review_text) %>% unnest_tokens(words, review_text)
reviews.tidy = reviews.tidy %>% anti_join(engstopwords)
# Добавляем словарь лексики к основному датасету
reviews.tidy= reviews.tidy %>% inner_join(afin, by=c("words"="word"))
reviews.sent_count = reviews.tidy %>% group_by(book_id) %>% summarise(mean = mean(value))
# Теперь есть переменная, отражающая настроение отзыва (степень положит/отриц), которая может быть использована в рекомендательной системе
knitr::kable(reviews.sent_count %>% head(5))
book_id mean
7389 1.0373134
13194 1.3317073
13570 1.5421245
13590 1.3629893
13619 0.4631902

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

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

Построение рекомендательной системы

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

# Загрузка данных
load("~/shared/minor2_2020/data/good_read/reviews_g_1.RData")
load("~/shared/minor2_2020/data/good_read/books_g_1.RData")
# Оставляем только нужные колонки
ratings=goodread_reviews %>% select(book_id, user_id, rating)
# Приводим к широкому формату
rates = pivot_wider(ratings, names_from = book_id, values_from = rating)
# Убираем id пользователей из таблички
userNames = rates$user_id
rates = select(rates, -user_id)
set.seed(100)
eval_sets <- evaluationScheme(data = ratings_comics, method = "split",train = 0.8, given = 4, goodRating = 4)
# Строим рекомендательную систему
recc_model <- Recommender(data = getData(eval_sets, "train"), method = "IBCF")
recc_predicted <-predict(object = recc_model, newdata = getData(eval_sets, "known"), n = 5, type = "ratings")

Функция №1

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

# Cмотрим, какой комикс можно использовать в качестве комикса по умолчанию. Ищем книгу с наилучшей оценкой среди популярных
top = goodread_comics %>% filter(as.numeric(ratings_count)>9900)
top_1 = top %>% arrange(average_rating)
top_2 = top_1 %>% tail(3)
top_list=top_2$title
# Создаем функцию
getComics = function(user_id, num = 5){
  recc_predicted <-
  predict(
    object = recc_model,
    newdata = ratings_comics,
    n = num
  )
recc_user <- recc_predicted@items[[user_id]]
comics_user <- recc_predicted@itemLabels[recc_user]
rec_def = top_1 %>% tail(num)
rec_list_def = rec_def$title
names_comics_user = goodread_comics$title[match(comics_user,goodread_comics$book_id)]
if (rlang::is_empty(names_comics_user)==T){
     rec_list_def
} else {
     names_comics_user
}
}

Пример:

getComics("0088ac052921fbdad6145c29322f9144")
## [1] "Fairy Tail, Vol. 1 (Fairy Tail, #1)"                          
## [2] "Death Note, Vol. 2: Confluence (Death Note, #2)"              
## [3] "The Walking Dead, Book Three (The Walking Dead #25-36)"       
## [4] "Transmetropolitan, Vol. 6: Gouge Away (Transmetropolitan, #6)"
## [5] "Locke & Key, Vol. 4: Keys to the Kingdom"

Функция №2

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

# Строим функцию
sim <- as.matrix(similarity(ratings_comics, method = "cosine", which = "items"))
findComics = function(book){
  book_1 = goodread_comics$book_id[goodread_comics$title == book]
  position = match(as.character(book_1), colnames(ratings_comics))[1]
  sim_book = sim[position, ]
  cond = sim_book<0.99 & !is.na(sim_book) & sim_book>0
  rec_list = as.numeric(names(sim_book[cond]))
  recommend_list = goodread_comics %>% filter(book_id %in% rec_list) %>% select(title)
  recommend_list_1 = recommend_list$title
  if (rlang::is_empty(recommend_list_1)==T){
     top_list
} else {
     recommend_list_1
}
}

Пример:

findComics("Superman: Red Son")
##  [1] "Batman: Arkham Asylum - A Serious House on Serious Earth"     
##  [2] "The Death of Superman (The Death and Return of Superman, #1)" 
##  [3] "Justice League: Cry for Justice"                              
##  [4] "Y: The Last Man, Vol. 3: One Small Step (Y: The Last Man, #3)"
##  [5] "All-Star Batman and Robin, the Boy Wonder, Vol. 1"            
##  [6] "Lazarus, Vol. 1: Family"                                      
##  [7] "She-Hulk, Volume 1: Law and Disorder"                         
##  [8] "Batman: Heart of Hush"                                        
##  [9] "Batman: R.I.P."                                               
## [10] "Batman: The Black Mirror"                                     
## [11] "Superman for All Seasons"                                     
## [12] "The Walking Dead, Vol. 03: Safety Behind Bars"

Оценивание рекомендации: Один из самых важных этапов построения рекомендательной системы является ее оценивание.

Формальная оценка рекомендации

Формальная оценка рекомендации состоит из вычисления таких показателей, как RMSE, MSE, MAE:

  1. RMSE - квадратный корень из среднеквадратичной ошибки
  2. MSE - среднеквадратичная ошибка
  3. MAE - средняя абсолютная ошибка

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

Для IBCF модели оценка:

# Проверяем метрики для IBCF. Средняя ошибка меньше чем в балл.
eval_accuracy2 <- calcPredictionAccuracy(x = recc_predicted, data = getData(eval_sets, "unknown"), byUser = F)
knitr::kable(data.frame(eval_accuracy2))
eval_accuracy2
RMSE 1.1910754
MSE 1.4186606
MAE 0.8671617

Для UBCF модели оценка:

# Проверяем для UBCF
recc_model_1 <-Recommender(data = getData(eval_sets, "train"), method = "UBCF", parameter = list(nn = 8))
recc_predicted_1 <-predict(object = recc_model_1, newdata = getData(eval_sets, "known"), n = 5, type = "ratings")
eval_accuracy2_1 <- calcPredictionAccuracy(x = recc_predicted_1, data = getData(eval_sets, "unknown"),
byUser = F)
knitr::kable(data.frame(eval_accuracy2_1))
eval_accuracy2_1
RMSE 1.2068560
MSE 1.4565013
MAE 0.9179676

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

Проверка на адекватность функции №1

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

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

# Выбираем рандомного пользователя из датасета 
Random_user = goodread_reviews %>% dplyr::filter(user_id == "3d96d603e6a953d91e047c76e75245da")
Random_user = inner_join(Random_user, goodread_comics, by = "book_id") %>% select(-review_text, -user_id, -book_id, -review_id, -date_added)
knitr::kable(Random_user %>% select(title, rating, num_pages, publication_year, publisher, ratings_count))
title rating num_pages publication_year publisher ratings_count
Maximum Ride, Vol. 1 (Maximum Ride: The Manga, #1) 5 256 2009 Yen Press 26627
Maximum Ride, Vol. 3 (Maximum Ride: The Manga, #3) 5 240 2010 Yen Press 8214

Как мы видим данный пользователь оценил две книги. Мы готовы сделать предположение о рекомендации: данному id следует рекомендовать книги достаточно высокого рейтинга (около 5), категории манга и издателя Yen Press. Стоит учитывать популярность книги (не менее 8000 отзывов), год издательства должен находится в рамках 11-12 лет назад, а также комиксы не должны быть доступны в электронном ресурсе.

# Запустим функцию, чтобы сравнить наши результаты
# В этот раз мы установили ограничение на количество рекомендаций для книг - 5. Посмотрим на их характеристики
Randuser_test_book_2 <- data.frame(title = matrix(unlist(getComics("3d96d603e6a953d91e047c76e75245da",5)), nrow=5, byrow=TRUE),stringsAsFactors=FALSE)
Randuser_recc =  inner_join(goodread_comics, Randuser_test_book_2, by = "title")
knitr::kable(Randuser_recc %>% select(title, average_rating, num_pages, publication_year, publisher, ratings_count))
title average_rating num_pages publication_year publisher ratings_count
The Walking Dead, Book Three (The Walking Dead #25-36) 4.43 2007 12532
Fairy Tail, Vol. 1 (Fairy Tail, #1) 4.40 198 2008 Del Rey Books 68592
Transmetropolitan, Vol. 6: Gouge Away (Transmetropolitan, #6) 4.44 10254
Death Note, Vol. 2: Confluence (Death Note, #2) 4.42 197 2005 VIZ Media LLC 20867
Locke & Key, Vol. 4: Keys to the Kingdom 4.44 152 2013 IDW Publishing 17896

Исходя из результатов, мы делаем вывод, что средняя оценка рекомендации дейстаительно около 5 и ни одна книга не является электронным ресурсом. Также хотелось бы отметить, что доминирующая часть книг входит в категорию манга, имеет более 8000 отзывов, а также практически все книги были изданы в последние 11-12 лет. Единственным слабым элементов в характеристиках является издательство, но стоит отметить, что в 2 рекомендацях из 5 издательство пропущено, что говорит о несовершенстве самого датасета, а не о рекомендательной системе. В целом, наши ожидания и результат практически совпали.

Проверка на адекватность функции №2

Входные данные - название любимой книги:

Предположим, что наш пользователь, которому мы хотим порекомендовать комикс для чтения, фанат супергероев Марвел, а в особенности Человека - Паука. В таком случае, выбираем книгу - “Miles Morales: Ultimate Spider-Man Ultimate Collection Book 1” как топ книгу по рейтингу, где упомянут человек-паук.

Изучив характеристики прочитанной книги, мы ожидаем в рекомендации комиксы студии Марвел, выпущенных в пределах последних 10 лет, на английском языке, категории комиксов или новелл, а также книги с ретингом около 4, популярные седи пользователей (не менее 300 отзывов) и недоступные в электронном ресурсе.

# Ищем все книги, где в названии упоминается человек-паук
Spider = goodread_comics %>% dplyr::mutate(Spider = str_detect(goodread_comics$title, "Spider-Man")) %>% dplyr::filter(Spider == TRUE) %>% arrange(average_rating)

# Представим, что в первую очередь, читатель, ориентируясь на отзывы в сети, прочитал книгу с наивысшим  средним рейтингом
Test_book_1 = goodread_comics %>% filter(title == "Miles Morales: Ultimate Spider-Man Ultimate Collection Book 1")

#Рекомендация выдала нам 19 комиксов. Посмотрим на их характеристики
Spider_test_book_1 <- data.frame(title = matrix(unlist(findComics("Miles Morales: Ultimate Spider-Man Ultimate Collection Book 1")), nrow=19, byrow=TRUE),stringsAsFactors=FALSE)

Spider_recc =  inner_join(goodread_comics, Spider_test_book_1, by = "title")

knitr::kable(Spider_recc %>% select(title, average_rating, publication_year, publisher, is_ebook, popular_shelves.2.name))
title average_rating publication_year publisher is_ebook popular_shelves.2.name
Rickety Stitch and the Gelatinous Goo: The Road to Epoli 3.81 2017 Alfred A. Knopf Books for Young Readers false fantasy
Bloodshot Reborn, Volume 1: Colorado 3.81 2015 Valiant Entertainment, LLC false graphic-novels
Guardians of the Galaxy, Volume 2: Angela 3.59 2014 Marvel false marvel
Thor: God of Thunder, Volume 2: Godbomb 4.25 2013 Marvel false graphic-novels
Injection, Vol. 1 (Injection, #1) 3.82 2015 Image Comics false graphic-novel
Guardians of the Galaxy, Volume 5: Through the Looking Glass 3.28 2015 Marvel false graphic-novels
Angel Catbird, Volume 1 2.78 2016 Dark Horse false graphic-novel
Tokyo Ghost, Vol. 1: Atomic Garden 4.06 2016 Image Comics false graphic-novels
Moon Knight, Volume 1: From the Dead (Moon Knight Vol. V, #1) 4.14 2014 Marvel false marvel
Deadpool, Volume 2: Soul Hunter 4.15 2013 Marvel false graphic-novels
Uncanny X-Men: Superior, Volume 1: Survival of the Fittest 3.14 2016 Marvel false graphic-novels
FF, Volume 4 4.05 2015 Marvel false graphic-novels
Batman, Volume 8: Superheavy 3.54 2016 DC Comics false graphic-novels
Beowulf 3.77 2013 Astiberri false còmics
Batman: The Black Mirror 4.29 2011 false favorites
Indestructible Hulk, Volume 2: Gods and Monster 3.83 2013 Marvel false graphic-novels
Hinterkind Vol. 1: The Waking World 3.20 2014 Vertigo false comics
Avengers, Volume 1: Avengers World 3.80 2013 Marvel false graphic-novels
Secret Invasion 3.57 2009 Marvel false graphic-novels

Исходя из результатов, мы делаем вывод, что издателем абсолютного большинства рекомендованных комиксов является Марвел, средняя оценка рекомендации дейстаительно около 4 и ни одна книга не является электронным ресурсом. Также хотелось бы отметить, что доминирующая часть книг входит в категории комиксов и новелл, имеет более 300 отзывов, а также все книги были изданы в последние 10 лет (одно исключение - книга 2009 года).

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

Content-based рекомендация

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

Первые (неизмененные) входные данные:

  1. Средний рейтинг

  2. Количество страниц

  3. Год издания

  4. Количество оценок

Вторые (измененные) переменные:

  1. Издатель

  2. Популярные темы

  3. Сентимент

  4. LDA распределение

Построение рекомендательной системы

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

# Загружаем данные про комиксы
load("~/shared/minor2_2020/data/good_read/books_g_1.RData")
load("~/shared/minor2_2020/data/good_read/reviews_g_1.RData")
# Выбираем только нужные переменные для построения рекомендации
books = goodread_comics %>% dplyr::filter(language_code == "eng") %>% dplyr::select(book_id, num_pages, publication_year, ratings_count)
# Усредняем пользовательскую оценку для каждой книги
data = goodread_reviews %>% group_by(book_id) %>% summarize(rating = mean(rating, na.rm = T))
# Соединяем средние оценки с остальными данными
data = inner_join(data, books)
# Приводим к широкому формату издателей для использования в матрице
data_pub = goodread_comics %>% dplyr::filter(language_code == "eng") %>% dplyr::select(book_id, publisher)
# Убираем пропущенные значения
data_pub = data_pub %>% mutate(str = str_detect(data_pub$publisher, "[a-z]"))
data_pub = data_pub %>% filter(str == T) %>% select(-str)
# Преобразовываем к широкому формату
data_pub = data_pub %>% mutate(value = 1)
data_pub_tidy = data_pub %>% pivot_wider(names_from = publisher, 
                values_from = value, 
               values_fill = 0)
# Приводим к широкому формату "полки" для использования в матрице
data_shelves = goodread_comics %>% dplyr::filter(language_code == "eng") %>% dplyr::select(book_id, popular_shelves.0.name, popular_shelves.1.name, popular_shelves.2.name, popular_shelves.3.name)
# Объединяем все переменные в одну
data_shelves = data_shelves %>% mutate(shelves = str_c(popular_shelves.0.name, popular_shelves.1.name, popular_shelves.2.name, popular_shelves.3.name, sep = " " ))
data_shelves = data_shelves %>% select(-popular_shelves.0.name, -popular_shelves.1.name, -popular_shelves.2.name, -popular_shelves.3.name)
# Убираем "-", так как unnest_tokens счетает "-" разными словами
data_shelves$shelves = str_replace_all(data_shelves$shelves, "-", "")
# Токенизируем текст
shelves.tidy = data_shelves %>%
    unnest_tokens(words, shelves, token = "words")
# Преобразовываем к широкому формату
shelves.tidy = shelves.tidy %>% mutate(value = 1)
shelves.tidy = unique(shelves.tidy) %>% pivot_wider(names_from = words, 
                values_from = value, 
               values_fill = 0)
# Соединяем переменные вместе
data = data %>% left_join(data_pub_tidy)
data = data %>% left_join(shelves.tidy)
load("~/shared/minor2_2020/data/good_read/reviews_g_1.RData")
afin = get_sentiments(lexicon = "afinn")

goodread_reviews = goodread_reviews %>% mutate(review_text = removeNumbers(review_text))
engstopwords = data.frame(words=c(stopwords::stopwords("en"), stringsAsFactors=FALSE))
esstopwords = data.frame(words=c(stopwords::stopwords("es")), stringsAsFactors=FALSE)

review_tokens = goodread_reviews %>%
  unnest_tokens(words, review_text) %>% anti_join(engstopwords) %>% anti_join(esstopwords)

word_counts_sent_review = review_tokens %>%
  inner_join(afin, by=c("words"="word"))

reviews.sent_count = word_counts_sent_review %>% 
  group_by(book_id) %>% 
  summarise(sentiment = mean(value))

data = data %>% left_join(reviews.sent_count)
# Убираем числа, которые могут встретиться в тексте
comics_lda = goodread_comics %>% mutate(description = removeNumbers(description))%>% dplyr::filter(language_code == "eng")
# Загружаем словари стоп-слов + добавляем слова, коорые при первой проверке оказались самыми часто встречающимеся
engstopwords = data.frame(words=c(stopwords::stopwords("en"),"comics","collecting","series","world", "one", "new", stringsAsFactors=FALSE))
# Токинезация + объединение датасета и словарей
comics_tokens = comics_lda %>%
  unnest_tokens(words, description) %>% anti_join(engstopwords)

comics_count = comics_tokens %>%
  count(book_id, words, sort = TRUE) %>%
  ungroup()

comics_dtm <- comics_count %>%
  cast_dtm(book_id, words, n)

comics_lda <- LDA(comics_dtm, k = 10, control = list(seed = 12345))
comics_topics <- tidy(comics_lda, matrix = "beta")

comics_top_terms <- comics_topics %>%
  group_by(topic) %>%
  top_n(10, beta) %>%
  ungroup() %>%
  arrange(topic, -beta)

comics_top_terms = comics_top_terms %>% rename(words = term)
comics_count = comics_count %>% left_join(comics_top_terms) %>% select(-n)
comics_lda = comics_count %>% group_by(book_id,topic) %>% summarize(mean_beta = mean(beta))
comics_lda = na.omit(comics_lda)

data_lda = comics_lda %>% pivot_wider(names_from = topic, 
                values_from = mean_beta, 
               values_fill = 0)
data = data %>% left_join(data_lda)

Мы может увидеть вырезку из матрицы схожести книг. Значения варьируются от 0 до 1 (1 - максимальная похожесть).

# Предобработка переменных, меняем все на численные переменные
data$num_pages = as.numeric(data$num_pages)
data$publication_year = as.numeric(data$publication_year)
data$ratings_count = as.numeric(data$ratings_count)
# Номер книги - название строки
rownames = data$book_id
data = data %>% dplyr::select(-book_id)
rownames(data) = rownames
# Считаем матрицу схожести книг
sim = lsa::cosine(t(as.matrix(data)))
# Меняем диагональные значения на 0
diag(sim) = 0
knitr::kable(sim[10:15, 10:15] %>% round(2))
22933 23529 26426 30069 30071 30204
22933 0 NA NA NA NA NA
23529 NA 0.00 0.98 1.00 1.00 NA
26426 NA 0.98 0.00 0.98 0.97 NA
30069 NA 1.00 0.98 0.00 1.00 NA
30071 NA 1.00 0.97 1.00 0.00 NA
30204 NA NA NA NA NA 0
# Строим рекомендацию для отдельного пользователя по одной книги, оцененной им на 5
user = goodread_reviews %>% filter(user_id == "b8e6278ddeda78bdfeb59b083fb99c28" & rating == 5)
# Если у пользователя нет оценок со значением 5, делаем рекомендацию на основе оценок со значением 4
ifelse(test = nrow(user) == 0, (user = goodread_reviews %>% filter(user_id == "b8e6278ddeda78bdfeb59b083fb99c28" & rating == 4)), user)
# Если у пользователя нет отзывов, оцененных на 4 или 5, то предлагаем в рекомендацию топ 5 книг по общему рейтингу
goodread_comics$average_rating = as.numeric(goodread_comics$average_rating)
ifelse(test = nrow(user) == 0, goodread_comics %>% select(title, average_rating) %>% top_n(n=5), user)
# Ищем книги наиболее похожие с той, которую оценил пользователь (только по одной книги!)
mostSimilar = head(sort(sim[,as.character(user$book_id[1])], decreasing = T), n = 5)
result = names(mostSimilar)
# Выводим результат рекомендации
filter(goodread_comics, book_id %in% result) %>% dplyr::select(title)
mostSimilar = data.frame(similar = mostSimilar)
mostSimilar$book_id = as.numeric(rownames(mostSimilar))
# Выводим результат рекомендации + номер книги и степень похожести
mostSimilar %>% left_join(goodread_comics) %>% select(book_id, title, similar) %>% arrange(-similar)
# Пример с учитыванием всех книг, которые пользователь оценил
simCut = sim[,as.character(user$book_id)]
# Ищем книги наиболее похожие с теми, которые оценил пользователь на 5 (по всем книгам!)
mostSimilar = head(sort(simCut, decreasing = T), n = 5)
mostSimilar
a = which(simCut %in% mostSimilar, arr.ind = TRUE, useNames = T)
a ## выдается результат "общим" индексом, а не строки-столбцы, нам нужны строки
index = arrayInd(a, .dim = dim(sim))
index
index[,1]
# номера книг
result = rownames(sim)[index[,1]]
result
# Выводим результат рекомендации + номер книги
filter(goodread_comics,book_id %in% result) %>% dplyr::select(title, book_id)

Функция №1

Первая функция для рекомендательной системы, построенной на основе содержательной оценки характеристик книг, выдает подборку из 5 книг при вводе id пользователя. Если у пользователя есть хотя бы одна книга оцененная на 4 или 5, то рекомендуем книги с помощью матрицы схожести. Если пользователь негативно оценил все книги, которые он прочитал (3 и меньше), тогда выдается подборка из 5 книг с самыми высокими пользовательскими оценками.

# Функция учитывает все книги пользователя
getBooks = function(id){
      user = goodread_reviews %>% filter(user_id == id & rating == 5)
      ifelse(test = nrow(user) == 0, (user = goodread_reviews %>% filter(user_id == id & rating == 4)),       user)
    simCut = sim[,as.character(user$book_id)]
    mostSimilar = head(sort(simCut, decreasing = T), n = 5)
    a = which(simCut %in% mostSimilar, arr.ind = TRUE, useNames = T)
    a ## выдается результат "общим" индексом, а не строки-столбцы, нам нужны строки
    index = arrayInd(a, .dim = dim(sim))
    index
    index[,1]
    # id фильмов
    result = rownames(sim)[index[,1]]
    result
    recommend = ifelse(test = nrow(as.data.frame(result)) == 0, goodread_comics %>% select(title, average_rating) %>% top_n(n=5),filter(goodread_comics,book_id %in% result) %>% dplyr::select(title, book_id))
    
     recommend
}

Пример:

knitr::kable(getBooks("008669319fc58048086da1d512d5bf2a"))
x
The Walking Dead, Compendium 3
Skip Beat!, Vol. 26
Skip Beat!, Vol. 16
僕のヒーローアカデミア 5 [Boku No Hero Academia 5] (My Hero Academia, #5)
Fullmetal Alchemist, Vol. 12 (Fullmetal Alchemist, #12)
Fullmetal Alchemist, Vol. 19 (Fullmetal Alchemist, #19)

Функция №2

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

getBooks2 = function(title2, num = 5){
      user = goodread_comics %>% filter(title == title2)
    simCut = sim[,as.character(user$book_id)]
    mostSimilar = head(sort(simCut, decreasing = T), n = num)
    a = which(simCut %in% mostSimilar, arr.ind = TRUE, useNames = T)
    a ## выдается результат "общим" индексом, а не строки-столбцы, нам нужны строки
    index = arrayInd(a, .dim = dim(sim))
    index
    index[,1]
    # id фильмов
    result = rownames(sim)[index[,1]]
    result
    recommend = filter(goodread_comics,book_id %in% result) %>% dplyr::select(title, book_id)
    
     recommend
}

Пример:

knitr::kable(getBooks2("The Best of the Spirit"))
title book_id
Wandering Son, Vol. 3 12610347
Pretty Guardian Sailor Moon, Vol. 11 (Pretty Soldier Sailor Moon Renewal Edition, #11) 16071859
New Avengers, Volume 3: Other Worlds 18948739
Batgirl, Vol. 3: Mindfields 28109909
Avengers, Volume 6: Infinite Avengers 22259483

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

Проверка на адекватность функции №1

# Находим пользователя для проверки и смотрим его оценки

test_user <- goodread_reviews %>% filter(user_id == "89c2d67c58cced023966b3574dc10ca8")

test_user <- test_user %>% filter(rating >= 4) %>% select(book_id, rating, review_text)

knitr::kable(goodread_comics %>% filter(book_id %in% test_user$book_id) %>% select(title))
title
EI8HT, Vol. 1: Outcast
Runaways, Vol. 1: Pride and Joy (Runaways, #1)
knitr::kable(data %>% filter(rownames %in% c("25426046", "7389")) %>% select(rating, num_pages, publication_year, graphicnovels, comics, graphicnovel, sciencefiction, sentiment))
rating num_pages publication_year graphicnovels comics graphicnovel sciencefiction sentiment
3.960000 144 2006 1 1 1 0 1.0373134
3.583333 128 2015 1 1 0 1 0.8636364

Изучив характеристики понравившихся пользователю комиксов, мы ожидаем увидеть в рекомендации комиксы со схожим рейтингом, комиксы, подходящие в категории graphic novels, comics, science fiction, с похожим показателем сантимента и тематик. Поскольку издателем первого комикса является Marvel, а второго - Dark Horse comics, мы ожидаем увидеть в рекомендации комиксы их производства.

# Применяем функцию
knitr::kable(getBooks("89c2d67c58cced023966b3574dc10ca8"))
x
James Bond, Vol. 2: Eidolon
Death Note, Vol. 2: Confluence (Death Note, #2)
Baltimore, Vol. 5: The Apostle and the Witch of Harju (Baltimore, #5)
All My Friends Are Dead
Locke & Key, Vol. 4: Keys to the Kingdom
test_user_rec <- data.frame(title = matrix(unlist(getBooks("89c2d67c58cced023966b3574dc10ca8")), nrow=5, byrow=TRUE),stringsAsFactors=FALSE)

# Смотрим характеристики и сравниваем с книгами, которые понравились пользователю

knitr::kable(goodread_comics %>% filter(title %in% test_user_rec$title)%>% inner_join(data %>% mutate(book_id = as.numeric(rownames(data))), by = "book_id") %>% select(rating, num_pages.x, publication_year.x, publisher, graphicnovels, comics, graphicnovel, sciencefiction, sentiment))
rating num_pages.x publication_year.x publisher graphicnovels comics graphicnovel sciencefiction sentiment
4.285714 136 2017 Dynamite Entertainment 1 1 0 0 0.8650794
4.220000 197 2005 VIZ Media LLC 1 0 0 0 0.4631902
3.363636 144 2015 Dark Horse Books 1 1 0 0 0.3076923
4.340000 96 2010 Chronicle Books 0 0 0 0 1.4685315
4.360000 152 2013 IDW Publishing 1 1 0 0 1.1834532

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

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

Проверка на адекватность функции №2

knitr::kable(goodread_comics %>% filter(book_id %in% c("16092530", "25066783")) %>% select(title))
title
The Walking Dead: Days Gone Bye (The Walking Dead, #1)
Guardians of the Galaxy, Volume 5: Through the Looking Glass
# Смотрим характеристики
knitr::kable(data %>% filter(rownames %in% c("16092530", "25066783")) %>% select(rating, num_pages, publication_year, `Image Comics`, Marvel, toread, graphicnovels, comics, marvel, favorites, zombies, sentiment))
rating num_pages publication_year Image Comics Marvel toread graphicnovels comics marvel favorites zombies sentiment
4.363636 144 2011 1 0 1 1 0 0 1 1 0.9795918
2.562500 136 2015 0 1 1 1 1 1 0 0 0.7142857

Первый комикс из Вселенной The Walking Dead. Мы ожидаем увидеть либо комиксы про апокалипсис, категории Zombie, или комиксы по другим известным вселленным (потому что the Walking Dead очень известная вселенная), или комиксы издателя Image Comics, или схожие показатели рейтинга, сантимента, или тематик.

Для второй книги про Стражей Галактики мы ожидаем другие комиксы Марвел или других супергеройских вселенных, схожие “полки” и схожие другие характеристики.

# Функция для The Walking Dead
knitr::kable(getBooks2("The Walking Dead: Days Gone Bye (The Walking Dead, #1)"))
title book_id
Batman: Year Two: Fear the Reaper 107062
Avengers, Volume 3: Prelude to Infinity 17860796
Vampire Knight, Vol. 7 6150473
The Amulet of Samarkand (The Graphic Novel) 7831435
Incognito, Vol. 2: Bad Influences 9363241
test_book1_rec <- getBooks2("The Walking Dead: Days Gone Bye (The Walking Dead, #1)")

# Смотрим характеристики рекомендаций, сравниваем
knitr::kable(goodread_comics %>% filter(title %in% test_book1_rec$title)%>% inner_join(data %>% mutate(book_id = as.numeric(rownames(data))), by = "book_id") %>% select(rating, num_pages.x, publication_year.x, `Image Comics`, toread, graphicnovels, favorites, zombies, sentiment))
rating num_pages.x publication_year.x Image Comics toread graphicnovels favorites zombies sentiment
3.363636 172 2002 0 1 1 0 0 -0.2320000
2.636364 152 2013 0 1 1 0 0 0.7480916
4.117647 200 2009 0 1 0 0 0 0.5168539
3.814815 144 2010 0 1 1 0 0 1.6300000
3.480000 144 2011 0 1 1 0 0 0.6792453

У данных рекомендаций есть значительные сходста в количестве страниц(144), по двум полкам так же построена рекомендация, так же в тематиках под номерами 4, 5, 8. Наши прогнозы частично оправдались - в рекомендации есть комиксы про Бетмена и про Мстителей, а они являются очень популярными и известными. Наверное, это логично - рекомендовать более “популярную” литературу такому же популярному комиксу. Конечно, опять же, далеко не по всем параметрам можно отследить логику рекомендаций, но, думаем, что она присутствует, так как совпадения всё-таки есть.

# Рекомедации для второй книги
knitr::kable(getBooks2("Guardians of the Galaxy, Volume 5: Through the Looking Glass"))
title book_id
Wonder Woman, Vol. 7: Contagion 7969754
Daredevil, Volume 4: The Autobiography of Matt Murdock 25066790
My Boyfriend Bites (My Boyfriend Is a Monster, #3) 8939297
Wonder Woman, Vol. 2: Challenge of the Gods 154538
Bucky Barnes: The Winter Soldier, Volume 1: The Man on the Wall 23546815
test_book2_rec <- getBooks2("Guardians of the Galaxy, Volume 5: Through the Looking Glass")

# Смотрим характеристики, сравниваем
knitr::kable(goodread_comics %>% filter(title %in% test_book2_rec$title)%>% inner_join(data %>% mutate(book_id = as.numeric(rownames(data))), by = "book_id") %>% select(rating, num_pages.x, publication_year.x, Marvel, toread, graphicnovels, comics, marvel, sentiment))
rating num_pages.x publication_year.x Marvel toread graphicnovels comics marvel sentiment
3.571429 128 2010 0 0 1 1 0 1.2413793
3.714286 104 2015 0 1 1 1 1 1.1083333
3.047619 126 2011 0 1 1 0 0 0.9226804
4.300000 176 2004 0 1 1 1 0 1.0681818
2.500000 112 2015 0 1 1 1 1 0.5894040

Смотря на характеристики комиксов, рекомендованных к “Стражам Галактики”, мы можем опять же проследить похожесть в основном по полкам и по темам 4 и 5, почти идентичные показатели получаются иногда. Если судить прямо по комиксам и их содержанию, то всё очень логично - получается супергеройская-космическая-марвеловская тематика, если поискать в интернете и почитать описания рекомендаций.

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

Примеры

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

Примеры collaborative filtering

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

1.“Если я введу комиксы Marvel (много вариаций), но захочу почитать что-то другое (НЕ Marvel), что я получу”?

Ожидание: книги другого издателя (проведём проверку на функции, входными данными для которой является пользователь, оценивший комиксы Marvel).

#Выбираем рандомного пользователя, который одновременно является фанатом студии Marvel из датасета 

Marvel_user = goodread_reviews %>% dplyr::filter(user_id == "2bad86cb2cae2f1a71e60b1aa755f44c")
Marvel_user = inner_join(Marvel_user, goodread_comics, by = "book_id") %>% select(-review_text, -user_id, -book_id, -review_id, -date_added)

Наши ожидания: книги другого издателя (т.е. хотим доказать, что наша рекомендательная система не настолько однабокая, чтобы фокусироваться только на какой-то конкретной характеристике),комиксы c рейтингом около 4, категориий comics и graphic-novels. Стоит учитывать популярность книги (не менее 2000 отзывов), период издательства должен включать 2006, а также комиксы не должны быть доступны в электронном ресурсе.

knitr::kable(getComics("2bad86cb2cae2f1a71e60b1aa755f44c",8))
x
The Dragonslayer (Bone, #4)
The Walking Dead, Book One (The Walking Dead #1-12)
Black Butler, Vol. 1 (Black Butler, #1)
Fairy Tail, Vol. 1 (Fairy Tail, #1)
Death Note, Vol. 2: Confluence (Death Note, #2)
The Walking Dead, Book Three (The Walking Dead #25-36)
Transmetropolitan, Vol. 6: Gouge Away (Transmetropolitan, #6)
Locke & Key, Vol. 4: Keys to the Kingdom
#В этот раз мы установили ограничение на количество рекомендаций для книг - 8. Посмотрим на их характеристики:

Marveluser_test_book_peer_review_2 <- data.frame(title = matrix(unlist(getComics("2bad86cb2cae2f1a71e60b1aa755f44c",8)), nrow=8, byrow=TRUE),stringsAsFactors=FALSE)

Marveluser_recc =  inner_join(goodread_comics, Marveluser_test_book_peer_review_2, by = "title")

Результаты: разнообразие издательств действительно поражает: Del Rey Books, Image Comics, VIZ Media LLC, Yen Press, Graphix, IDW Publishing. Также мы видим, что и остальные характеристики оправдывают ожидания: период выпуска книг 2005-2013, средний рейтинг рекмендаций 4+, в среднем более 10000 оценок на сервисе, а также книги не доступны на электронном носителе.

Таким образом, данный пример снова подтвердил высокое качество нашей системы.

2.“Что будет если у меня нет ни одного отзыва и я укажу, что мне нравится”Dark Wolf""?

Ожидание: как вы успели заметить, книга также не входит в наш начальный датасет, поэтому мы ожидаем получить рекомендацию, состоящую из комиксов с наилучшей средней оценкой по “популярности”(проведём проверку на функции, входными данными для которой является название комикса “Dark Wolf”).

knitr::kable(findComics("Dark Wolf"))
x
The Walking Dead, Book Three (The Walking Dead #25-36)
Transmetropolitan, Vol. 6: Gouge Away (Transmetropolitan, #6)
Locke & Key, Vol. 4: Keys to the Kingdom

Таким образом, в нашем проекте рекомендацию можно получить даже для книги, которая не предусмотрена системой сервиса goodread.

Примеры content-based

Было несколько вопросов, касательно результатов использования content-based рекомендации и мы выбрали несколько самых интересных.

Два вопроса по Мстителям крайне похожи друг на друга:

1.“Какой был бы результат, если бы ввела один из комиксов по Мстителям? / Какой комикс похож по содержанию на комикс про Мстителей(кроме самих мстителей)?”

Ожидание: другие книги Marvel, комиксы по супергероям

#Ищем комикс про Avengers в датасете и применяем к нему функцию 
knitr::kable(getBooks2("Avengers, Volume 1: Avengers World"))
title book_id
Justice League, Volume 3: Throne of Atlantis 17671919
Injection, Vol. 1 (Injection, #1) 25787656
Moon Knight, Volume 1: From the Dead (Moon Knight Vol. V, #1) 20898006
Red Hood and the Outlaws, Volume 1: Redemption 13533746
Ultimate Comics: Spider-Man, by Brian Michael Bendis, Volume 1 12471274

По содержанию, 4 из 5 комиксов относятся к супергеройской тематике, издательства Marevl / DC. Насчёт Injection, данный комикс является сиквелом к перечисленному Moon Knight. Ожидания подтвердились, и стоит отметить, что самым похожим к Мстителям комиксом является Лига Справедливости, и нарративы данных книг очень похожи - про некое объединение супергероев в одной команде.

Эта проверка так же предусматривает ответ на другой вопрос:

2.“Какие бы комиксы были рекомендованы пользователю, который является фанатом серии”Супермен"?

Ожидание: “Предполагаю, что будут точно отсутствовать рекомендации Marvel, в особенности их культовых персонажей, но будут присутствовать серии комиксов про Бэтмена, Зеленого Фонаря, Флеша и тд”.

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

Проверим функцию на реальном пользователе, которому понравился комикс про Супермена:

knitr::kable(getBooks("5d6e554bc8961d4464d64c0184c88936"))
x
Batman: Arkham Asylum - A Serious House on Serious Earth
Tsubasa: RESERVoir CHRoNiCLE, Vol. 01
Maximum Ride, Vol. 1 (Maximum Ride: The Manga, #1)
The Walking Dead, Book One (The Walking Dead #1-12)
Paper Girls, Vol. 1 (Paper Girls, #1)

На самом деле, здесь и правда не присутсвуют рекомендации от Марвел. Мы видим комикс про Бэтмена, который так же производства DC, поэтому ожидания нашей коллеги подтверждены.

3.“какая рекомендация будет если пользователю нравятся комиксы про звёздные войны”?

Ожидание: “примерно 2015 год, марвел, про космос, science fiction, рейтинг от 4, примерно 150 страниц”.

star_wars_rec <- getBooks2("Star Wars: Legacy, Volume 5: The Hidden Temple")

# Смотрим характеристики, сравниваем
knitr::kable(goodread_comics %>% filter(title %in% star_wars_rec$title)%>% inner_join(data %>% mutate(book_id = as.numeric(rownames(data))), by = "book_id") %>% select(title, rating, num_pages.x, publication_year.x, Marvel, sciencefiction))
title rating num_pages.x publication_year.x Marvel sciencefiction
Captain Marvel and the Carol Corps 3.230769 120 2015 1 0
Batman: Snow 3.190476 125 2007 0 0
Ultimate Galactus, Volume 2: Secret 3.600000 96 2006 0 0
Black Science, Vol. 4: Godworld 3.857143 128 2016 0 0
Avengers, Volume 6: Infinite Avengers 3.650000 152 2014 1 0

Мы видим заметные сходства в некоторых характеристиках с ожиданиями, хотя есть и большие расхождения. Лишь в некоторых книгах рейтинг, год издания и число страниц приближены к желаемому результату. Так же лишь две книги попадают под издательство Марвел и ни одна не попадает в категорию science fiction. Это не удивительно, потому что изначальные характеристики книги по Звездным войнам очень отличаются от ожидаемого результата:

knitr::kable(goodread_comics %>% filter(title == "Star Wars: Legacy, Volume 5: The Hidden Temple")%>% inner_join(data %>% mutate(book_id = as.numeric(rownames(data))), by = "book_id") %>% select(title, rating, num_pages.x, publication_year.x, Marvel, sciencefiction))
title rating num_pages.x publication_year.x Marvel sciencefiction
Star Wars: Legacy, Volume 5: The Hidden Temple 3.8 104 2009 0 0

Также были вопросы про рекомендации к Стражам Галактики и Ходячим мертвецам. Поскольку мы затронули эти примеры при проверке адекватности наших функций, мы решили ими пренебречь сейчас. Рекомендации к данным комиксам можно посмотреть в соответствующей части нашего отчёта.

Ответы на вопросы peer review

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

Вопрос: почему именно число из 4 отзывов было взято за границу, за которой система рекомендует комиксы одним образом, а до - другим?

Ответ: При построении рекомендательной системы значение параметра given = 4. То есть для предсказания используется 4 оценки. Иными словами, если у пользователя меньше 4 оценок, система просто не сможет создать рекомендацию конкретно для него. Брать меньше 4 оценок кажется бессмысленным, потому что качество рекомендации будет вызывать сомнения.

Вопрос: Показатель средней ошибки в UBCF был не намного больше IBCF и так же был меньше единицы - почему в таком случае так строго был откинут метод UBCF?

Ответ: Выбирая из двух вариантов, просто выбрали лучший вариант. По всем параметрам формальной проверки метод IBCF давал лучшие показатели.

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

Ответ: Рекомендательная система подразумевает, что пользователь выбирает любимый комикс из существующих в системе и вводит его название. Если вы введете название комикса, которого нет в данных, система будет рекомендовать топ-лист. К примеру:

knitr::kable(findComics("new comics"))
x
The Walking Dead, Book Three (The Walking Dead #25-36)
Transmetropolitan, Vol. 6: Gouge Away (Transmetropolitan, #6)
Locke & Key, Vol. 4: Keys to the Kingdom

Вопрос: Почему в рекомендациях content-based были использованы и оценки 4?

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

Вопрос: В самом начале не хватает описания предоставленных данных

Ответ: Учитывая ограничение по времени видео, хотели сконцентрироваться конкретно на нашей модели.

Вопрос: Почему не был показан сетевой анализ? Разумеется, было разрешено выбрать либо текстовый, либо сетевой, либо текстовый и сетевой анализ, но, как мне кажется, следовало бы объяснить, почему?

Ответ: На наш взгляд, текстовый анализ более полезен при построении рекомендательной системы методом content-based. Мы четко представляли, как использовать его результаты для дальнейшей работы.

Вопрос: Почему именно число из 4 отзывов было взято за границу, за которой система рекомендует комиксы одним образом, а до - другим?

Ответ: При построении рекомендательной системы значение параметра given = 4. То есть для предсказания используется 4 оценки. Иными словами, если у пользователя меньше 4 оценок, система просто не сможет создать рекомендацию конкретно для него. Брать меньше 4 оценок кажется бессмысленным, потому что качество рекомендации будет вызывать сомнения. В: Показатель средней ошибки в UBCF был ненамного больше IBCF и так же был меньше единицы - почему в таком случае так строго был откинут метод UBCF? О: Выбирая из двух вариантов, просто выбрали лучший вариант. По всем параметрам формальной проверки метод IBCF давал лучшие показатели.

Вопрос: Что будет, если любимой книги нового пользователя нет в базе данных? О: Рекомендательная система подразумевает, что пользователь выбирает любимый комикс из существующих в системе и вводит его название. Если вы введете название комикса, которого нет в данных, система будет рекомендовать топ-лист. В: Почему в рекомендациях content-based были использованы и оценки 4?

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

Вопрос: Почему не был показан сетевой анализ? Разумеется, было разрешено выбрать либо текстовый, либо сетевой, либо текстовый и сетевой анализ, но, как мне кажется, следовало бы объяснить, почему.

Ответ: На наш взгляд, текстовый анализ более полезен при построении рекомендательной системы методом content-based. Мы четко представляли, как использовать его результаты для дальнейшей работы.

Вопрос: Отсутствие sentiment анализа

Ответ: Sentiment анализ был проведен и был показан в презентации

Вопрос: каким образом входные данные были преобразованы для матрицы схожести по content-based? как рекомендуются комиксы для нового пользователя? есть ли такая возможность?

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

Вопрос: Хотелось бы обосновать использование метода Дирихле при анализе текста, а также узнать, как именно он участвует в создании рекомендательной системы.

Ответ: По своему принципу метод Дирихле разделят текстовые документы на топики со схожей тематикой. Это как раз то, что мы и хотели получить для использования в построении рекомендательной системы. Как и было показано в презентации, результаты анализа использовались в качестве характеристики для построения модели content-based.