Цель проекта - создать рекомендательную систему для комиксов, которая бы предсказывала, какие книги будут интересны пользователю, имея информацию о книгах, которые он предпочитает. Оценка качества модели будет основана на внутренней пользовательской оценке и проверке на адекватность модели.
План проекта:
Текстовый анализ описания книг и рецензий на них
Построение рекомендательной системы на основе коллаборативной фильтрации
Оценка качества построения первой модели
Построение рекомендательной системы на основе содержательной оценки книг
Оценка качества построения второй модели
Проверка моделей на основе внешних сценариев
Общие выводы
Ответы на вопросы
Предобработка - один из важных этапов построения рекомендательной системы. Анализ данных был проведен с помощью инструментов текстового анализа. По окончании анализа, мы получили две переменные, которые дальше будут участвовать в анализе.
Латентное размещение Дирихле - метод текстовой обработки, который позволяет определить скрытые тематики, предполагая, что эти темы сформированы на основании вероятности вхождения слова из заданного текста. Так как для построения второй рекомендации вероятность принадлежности той или иной книги к теме может существенно улучшить модель, 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")
Первая функция будет предлагать пользователю подборку из пяти книг по умолчанию (в функции можно будет написать желательное число книг). Входными данными будут служить 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"
Вторая функция будет выводить рекомендации похожих книг для тех пользователей, кто ввел свой любимый комикс. Если для комикса не нашлось похожих, то будет рекомендоваться книга с наилучшей средней оценкой среди популярных комиксов.
# Строим функцию
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:
Чтобы сравнить две модели рекомендации 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.
Чтобы проверить рекомендацию на адекватность, будем сравнить книги, понравившиеся пользователю, с теми, которые ему рекомендуют.
Входные данные - случайно выбранный пользователь:
# Выбираем рандомного пользователя из датасета
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 издательство пропущено, что говорит о несовершенстве самого датасета, а не о рекомендательной системе. В целом, наши ожидания и результат практически совпали.
Входные данные - название любимой книги:
Предположим, что наш пользователь, которому мы хотим порекомендовать комикс для чтения, фанат супергероев Марвел, а в особенности Человека - Паука. В таком случае, выбираем книгу - “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 рекомендации заключается в том, что по истории действий пользователя можно создать для него вектор его предпочтений в пространстве и рекомендовать книги, близкие к этому вектору. То есть у комиксов должно быть какое-то признаковое описание. В датесете присутствуют такие переменные как издатель, темы на полках, год выпуска и тд.
Первые (неизмененные) входные данные:
Средний рейтинг
Количество страниц
Год издания
Количество оценок
Вторые (измененные) переменные:
Издатель
Популярные темы
Сентимент
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)
Первая функция для рекомендательной системы, построенной на основе содержательной оценки характеристик книг, выдает подборку из 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"))
|
Вторая функция предполагает, что пользователя нет еще в системе (новый посетитель). Он в таком случае может ввести название своей любимой книги, при условии, что она есть в системе.
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 |
Оценивание рекомендации: Чтобы проверить рекомендацию на качество предлагаемых книг, будем сравнить книги, понравившиеся пользователю, с теми, которые ему рекомендуют.
# Находим пользователя для проверки и смотрим его оценки
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"))
|
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.
Поскольку у нас много параметров, по которым мы ищем похожесть тех или иных комиксов, множество характеристик не могут совпадать, но, в целом, рекомендации по нашей функции имеют свою логику.
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 рекомендация работает и может подобрать что-то действительно похожее на то, что нравится пользователям. Да и вообще любым любителям комиксов.
В этой секции будет рассмотрена внешняя пользовательскя оценка рекомендательных систем. С помощью анализа разных сценариев можно определить, насколько рекомендательная система отвчает параметрам гибкости и может подстроиться под разные иллюстрации.
Обобщая все заданные вопросы и предложения к секции проверки, можно выделить три группы вопросов. Начнём по порядку:
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 рекомендации и мы выбрали несколько самых интересных.
Два вопроса по Мстителям крайне похожи друг на друга:
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"))
|
На самом деле, здесь и правда не присутсвуют рекомендации от Марвел. Мы видим комикс про Бэтмена, который так же производства 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 |
Также были вопросы про рекомендации к Стражам Галактики и Ходячим мертвецам. Поскольку мы затронули эти примеры при проверке адекватности наших функций, мы решили ими пренебречь сейчас. Рекомендации к данным комиксам можно посмотреть в соответствующей части нашего отчёта.
В этом блоке представлены ответы на вопросы, возникшие у внешних пользователей во время презентации проекта:
Вопрос: почему именно число из 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.