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

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

  1. Взвешенный рейтинг
  2. Совместная фильтрация

Мы увидим реализацию каждого метода шаг за шагом.

Начальная настройка:

Импорт наборов данных

Наборы данных, используемые в этом файле, были загружены с kaggle.

Ссылка: Набор данных Kaggle Books

Нам доступны три набора данных:

  1. Книги
  2. Пользователи
  3. Рейтинги

Форма данных:

Заполнение пропущенных значений:

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

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)

Результирующий кадр данных:

Подход на основе совместной фильтрации

Для системы рекомендаций на основе пользователей мы создаем новый тип данных, например:

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

  1. Цените оценки опытных пользователей:

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

  1. Цените книги с высокими рейтингами:

Мы бы рекомендовали только книги, которые имеют более 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:

Книга-Рекомендация-Система