Книги — отличный источник развлечений и знаний. Способность и одержимость людей записью истории и мыслей в виде текста всегда развивались с течением времени. В настоящее время, когда у книг есть сотни жанров и тысячи писателей пишут книги миллионами, стало важно удовлетворять потребности людей в соответствии со вкусом и личными предпочтениями.
Следовательно, система рекомендаций книг полезна, и в этом блоге мы обсудим, как мы можем создать систему рекомендаций книг, используя два подхода:
- Взвешенный рейтинг
- Совместная фильтрация
Мы увидим реализацию каждого метода шаг за шагом.
Начальная настройка:
Импорт наборов данных
Наборы данных, используемые в этом файле, были загружены с kaggle.
Ссылка: Набор данных Kaggle Books
Нам доступны три набора данных:
- Книги
- Пользователи
- Рейтинги
Форма данных:
Заполнение пропущенных значений:
Обнаружив, что в наборах данных книг есть несколько отсутствующих значений, таких как автор, соответствующий автор был заполнен.
books.iloc[187689]['Book-Author'] = 'Downes, Larissa Anne'
Удаление столбца возраста
Поскольку в столбце возраста было много отсутствующих значений, вменение могло бы привести к смещению в модели, поэтому в идеале мы отбрасывали их для более согласованной модели и данных.
users.drop(columns=['Age'], inplace=True)
Помимо этого некоторые расхождения в данных также были устранены с помощью следующих строк кода:
#'MY TEACHER FRIED MY BRAINS (RACK SIZE) (MY TEACHER BOOKS)' books.loc[37487, 'Year-Of-Publication'] = 1991 # 'MY TEACHER FLUNKED THE PLANET (RACK SIZE) (MY TEACHER BOOKS)' books.loc[55676, 'Year-Of-Publication'] = 2005 # 'MY TEACHER FLUNKED THE PLANET (RACK SIZE) (MY TEACHER BOOKS)' books.loc[37487, 'Book-Author'] = 'Bruce Coville' # "Alice's Adventures in Wonderland and Through the Looking Glass (Puffin Books)" books.loc[80264, 'Year-Of-Publication'] = 2003 # 'Field Guide to the Birds of North America, 3rd Ed.' books.loc[192993, 'Year-Of-Publication'] = 2003 # Crossing America books.loc[78168, 'Year-Of-Publication'] = 2001 # Outline of European Architecture (Pelican S.) books.loc[97826, 'Year-Of-Publication'] = 1981 # Three Plays of Eugene Oneill books.loc[116053, 'Year-Of-Publication'] = 1995 # Setting to current date of project since no information could be found # Das gro��e B�¶se- M�¤dchen- Lesebuch. books.loc[118294, 'Year-Of-Publication'] = 2023 # FOREST PEOPLE (Touchstone Books (Hardcover)) books.loc[228173, 'Year-Of-Publication'] = 1987 # In Our Time: Stories (Scribner Classic) books.loc[240169, 'Year-Of-Publication'] = 1996 # CLOUT books.loc[246842, 'Year-Of-Publication'] = 1925 # To Have and Have Not books.loc[255409, 'Year-Of-Publication'] = 1937 # FOOTBALL SUPER TEAMS : FOOTBALL SUPER TEAMS books.loc[260974, 'Year-Of-Publication'] = 1991
Исследовательский анализ данных:
Некоторые важные EDA, такие как самые популярные книги, самые популярные авторы, авторы с наибольшим количеством книг и издатели с наибольшим количеством опубликованных книг, были показаны на следующих графиках:
Кажется, Агата Кристи очень любила писать книги, насчитывающие более 600 книг.
Арлекин возглавляет чарты с более чем вдвое более чем 7500 опубликованными книгами. ( Очень много )
Расчет среднего и взвешенного рейтинга книг
Набор данных рейтингов и книг был объединен, и средний рейтинг для каждой книги был рассчитан вместе со взвешенным рейтингом следующим образом:
bookRating = pd.merge(ratings, books, on="ISBN") bookRating.drop(columns=['Image-URL-S','Image-URL-M','Image-URL-L'], inplace=True) averageRating = pd.DataFrame(bookRating.groupby('ISBN')['Book-Rating']. mean().round(1)) averageRating.reset_index(inplace=True) averageRatingdf = pd.merge(bookRating, averageRating, on='ISBN') averageRatingUnique = averageRatingOnly[['ISBN','Average-Rating']]. drop_duplicates(subset=['ISBN']) ratingBooks = pd.merge(books, averageRatingUnique, on='ISBN', how='inner') books_with_rating = pd.merge(books, averageRatingUnique, on='ISBN')
Результирующий кадр данных:
Модель на основе популярности:
Мы выбираем 50 лучших книг с самыми высокими средними оценками, а затем показываем их пользователю.
Но проблема с этой системой в том, что если у какой-то книги мало голосов, но высокий рейтинг, то она может стать предвзятой и пропорционально неверной.
Итак, чтобы избежать этого, мы установим критерии, по которым мы выбирайте только средние оценки книг, за которые проголосовало не менее 100 пользователей.
ratings_books_merged = ratings.merge(books, on='ISBN') ratings_books_nonzero = ratings_books_merged[ratings_books_merged['Book-Rating' ]!=0] num_rating_df = ratings_books_nonzero.groupby('Book-Title').count()[ 'Book-Rating'].sort_values(ascending=False).reset_index() num_rating_df.rename(columns={'Book-Rating':'Number-of-Ratings'}, inplace=True)
Результирующий кадр данных:
Нахождение среднего рейтинга книг
avg_rating_df = ratings_books_nonzero.groupby('Book-Title').mean(numeric_only= True)['Book-Rating'].reset_index() avg_rating_df.rename(columns={'Book-Rating':'Average-Rating'}, inplace=True)
Объединение этих фреймов данных:
popularity_df = pd.merge(num_rating_df, avg_rating_df, on='Book-Title') popularity_df
Берем книги, набравшие более 100 голосов
popularity_df_above_100 = popularity_df[popularity_df['Number-of-Ratings']>= 100]
Новая метрика для расчета рейтинга
У нас все еще есть проблема, если есть небольшое количество высоких оценок и большое количество более низких оценок, мы получаем предвзятость и несправедливость в рейтинговом сценарии.
Чтобы решить эту проблему, давайте воспользуемся новой метрикой, известной как взвешенная метрика, которая может быть рассчитана следующим образом:
взвешенный_рейтинг = (средний_рейтинг * количество_рейтингов + минимальный_порог * рейтинг по умолчанию)/(количество_рейтингов + минимальный_порог)
где,
средний_рейтинг доступен из фрейма данных
количество_рейтингов доступен из фрейма данных
минимальный_порог – это минимальное количество голосов, отданных за Проверка. Здесь 100
default_rating — нейтральное состояние. Здесь 5.0
# Defining a new function that can calculate the metric def calcWeightedRating(row, avgRating, numOfRatings, minThres, defRating): weightedRating = ((row[avgRating] * row[numOfRatings]) + (minThres * defRating))/(row[numOfRatings] + minThres) return weightedRating # For number of ratings above 100 popularity_df_above_100 = popularity_df_above_100.copy() popularity_df_above_100['Weighted-Rating'] = popularity_df_above_100.apply( lambda x: calcWeightedRating( x, 'Average-Rating', 'Number-of-Ratings', 100, 5),axis=1) popularity_df_above_100.sort_values( 'Weighted-Rating', ascending=False).head(20)
Результирующий кадр данных:
Подход на основе совместной фильтрации
Для системы рекомендаций на основе пользователей мы создаем новый тип данных, например:
Где каждый пользователь будет иметь рейтинг для каждой книги, и мы также будем следовать таким критериям, как:
- Цените оценки опытных пользователей:
Мы возьмем оценки пользователей, которые оценили более 200 книг, то есть изучили и просмотрели большое количество книг с наивысшим приоритетом.
- Цените книги с высокими рейтингами:
Мы бы рекомендовали только книги, которые имеют более 50 рейтингов, то есть популярные и известные книги.
Мы фильтруем пользователей, которые оценили более 200 книг с помощью count(), а также книги с более чем 50 оценками с помощью count().
Затем мы создадим сводную таблицу пользователей и книг, например:
pt = filtered_books.pivot_table(index='Book-Title',columns='User-ID', values='Book-Rating') pt.fillna(0, inplace=True)
Работа модели:
Каждую книгу мы делаем точкой, созданной вектором с помощью пользователей. Это означает, что каждая книга представлена в виде рейтинга пользователя, набравшего более 200 голосов. Здесь 816 пользователей.
После представления их в виде косинусных векторов мы переходим к использованию косинусного сходства в качестве меры сходства.
from sklearn.metrics.pairwise import cosine_similarity similarities = cosine_similarity(pt) similarities
Выход:
Создание функции
Давайте создадим функцию, которая будет рекомендовать книги на основе заданного сходства.
Шаг 1: Получение индекса
# Retrieving the index of movie np.where(pt.index=='1984')
Он находится на 3-м месте в опорной колонке.
np.where(pt.index=='stardust')[0][0]
Шаг 2: Используйте индекс, чтобы найти массив внутри подобия
# For the 1st movie in the pivot table display(similarities[0]) # Chaining from the above similarities[np.where(pt.index=='stardust')[0][0]]
Шаг 3: Отсортируйте полученный массив
list(enumerate(similarities[0])) # Sorting without index sorted(list(enumerate(similarities[0])), key=lambda x: x[1], reverse=True)
Шаг 4: Удалите первый элемент и получите первые 5
Мы удаляем первый элемент из этого массива, потому что нам не нужно рекомендовать книгу, которая уже была прочитана или показана
sorted(list(enumerate(similarities[0])), key=lambda x: x[1], reverse=True)[1:6]
Шаг 5: Отображение названия книг из топ-5
for book in sorted(list(enumerate(similarities[0])), key=lambda x: x[1], reverse=True)[1:6]: print(pt.index[book[0]])
Шаг 6. Сделайте запасной вариант, если книга не существует
if 'hamkmfa' in pt.index: np.where(pt.index=='hamkda')[0][0] else: print('Book not found')
Создание функции
def recommend(book_name): if book_name in pt.index: index = np.where(pt.index == book_name)[0][0] similar_books_list = sorted( list(enumerate(similarities[index])), key=lambda x: x[1], reverse=True)[1:11] print(f'Recommendations for the book {book_name}:') print('-'*5) for book in similar_books_list: print(pt.index[book[0]]) print('\n') else: print('Book Not Found') print('\n') recommend('Harry Potter and the Chamber of Secrets (Book 2)')
Отлично, мы только что создали работающий рекомендатель книги по вкусу пользователей, а также универсальный рейтинг.
Для получения дополнительной справки и подробного рабочего процесса здесь мой репозиторий github: