Следуйте инструкциям и создайте мощный рекомендатель продукта с помощью NMF.
Введение
Неотрицательная матричная факторизация (NMF) — очень мощный алгоритм, который исторически использовался во многих различных областях. Он применялся в астрономии, анализе текста, биоинформатике и ядерной визуализации (см. Здесь для других приложений). В этой статье я объясню и продемонстрирую, как можно эффективно применять NMF в рекомендательной системе. Используя реализацию sklearn на Python, я применю алгоритм к набору данных книг, чтобы предоставить читателям список книг, которые они еще не читали.
Прежде чем перейти к примеру с Python, я кратко рассмотрю шаги алгоритма. Если вы хотите получить более подробную информацию и объяснение математики, лежащей в основе этого метода, посетите мою предыдущую статью: Рекомендуемые продукты с NMF.
Алгоритм
Предположим, у нас есть матрица V, в которой строки обозначают идентификаторы книг, столбцы — идентификаторы пользователей, а значения матрицы — числа от 1 до 10, указывающие, как пользователи оценили книги, где 10 — наилучшая возможная оценка. Алгоритм NMF разлагает матрицу V на две неотрицательные матрицы W и H в соответствии с приведенными ниже функциями:
Эти новые матрицы W и H могут предоставить нам новую информацию о скрытых факторах, которые не обязательно видны в исходных данных. Затем мы можем восстановить V, взяв произведение W и H, чтобы получить WH. Эта новая матрица WH даст нам представление о вероятных предпочтениях пользователей, о которых нам не могли рассказать исходные данные. В нашем примере мы получим представление о том, насколько высоко пользователь может оценить книгу, которую он раньше не читал. В этом сила NMF!
Пример Python
В этом примере мы будем использовать набор данных Book-Crossing, найденный на Kaggle.
Начнем с импорта необходимых пакетов. В этом примере мы будем использовать sklearn реализацию NMF.
import numpy as np import pandas as pd from sklearn.metrics import mean_squared_error from sklearn.decomposition import NMF
Затем мы загрузим данные, которые мы скачали с сайта Kaggle.
users_master = pd.read_csv('Users.csv', sep=';') books_master = pd.read_csv('Books.csv', sep=';') ratings_master = pd.read_csv('Ratings.csv', sep=';')
Чтобы сократить размер набора данных и ускорить наш пример, мы будем хранить только книги, которые оценили более 20 пользователей, и только пользователей, которые оценили более 3 книг. Есть много других способов, которыми мы могли бы очистить этот набор данных, но для этого примера мы продолжим и начнем подготовку к вводу данных в алгоритм NMF.
ratings = ratings_master.copy() # Keep books with more than 20 ratings book_rating_group = ratings.groupby(['ISBN']).count() book_rating_group = book_rating_group[book_rating_group['Rating']>20] ratings = ratings[ratings['ISBN'].isin(book_rating_group.index)] # Keep users that have rated more than 3 books user_rating_group = ratings.groupby(['User-ID']).count() user_rating_group = user_rating_group[user_rating_group['Rating']>3] ratings = ratings[ratings['User-ID'].isin(user_rating_group.index)] # Apply to the books and users datasets books = books_master.copy() books = books[books['ISBN'].isin(ratings['ISBN'])] users = users_master.copy() users = users[users['User-ID'].isin(ratings['User-ID'])]
Затем мы объединим наборы данных в одну большую матрицу V с идентификаторами книг в строках и идентификаторами пользователей в столбцах. Значения матрицы будут соответствующими рейтингами. В нашем примере мы будем использовать 0, чтобы указать, что книга не была оценена пользователем. Фактические рейтинги книг будут ненулевыми. Обратите внимание, что для запуска этого блока кода на моем компьютере потребовалось несколько минут.
cols = np.concatenate((['ISBN'],user_ids)) df = pd.DataFrame(columns=cols) book_ids = books['ISBN'] df['ISBN'] = book_ids df['ISBN'] = df['ISBN'].astype(str) # Fill the df with the ratings from the Ratings.csv file for i in range(ratings.shape[0]): user_id = ratings['User-ID'].iloc[i] book_id = ratings['ISBN'].iloc[i] rating = ratings['Rating'].iloc[i] row = df[df['ISBN']==book_id].index if len(row)>0: row = row[0] df.loc[row, user_id] = rating df.columns = df.columns.astype(str) # We will use this original_df later to verify whether or not a user has read a certain book original_df = df.copy() original_df = original_df.set_index('ISBN') original_df.fillna('No Ranking', inplace=True) # Replace NaN's with 0 to indicate books with no ratings df.fillna(0,inplace=True) df = df.set_index('ISBN') df
Теперь, когда у нас есть матрица V, мы почти готовы запустить модель NMF. Но прежде чем мы это сделаем, нам нужно выяснить оптимальный ранг матрицы. Для этого мы будем итеративно запускать несколько моделей NMF, пока среднеквадратическая ошибка (RMSE) нашей исходной матрицы V и новой матрицы WH не окажется в пределах определенного порога. Код для вычисления этого оптимального ранга приведен ниже, но, поскольку его выполнение занимает некоторое время, я жестко запрограммирую значение, которое функция должна возвращать, в нижней части блока кода.
def rank_calculation(data=df): """ Calculate the optimal rank of the specified dataframe. """ # Read the data df = data # Calculate benchmark value benchmark = np.linalg.norm(df, ord='fro') * 0.0001 # Iterate through various values of rank to find optimal rank = 3 while True: # initialize the model model = NMF(n_components=rank, init='random', random_state=0, max_iter=500) W = model.fit_transform(df) H = model.components_ V = W @ H # Calculate RMSE of original df and new V RMSE = np.sqrt(mean_squared_error(df, V)) if RMSE < benchmark: return rank, V # Increment rank if RMSE isn't smaller than the benchmark rank += 1 return rank # Hardcode the value so that we don't have to run the above function #optimal_rank = rank_calculation() optimal_rank = 15
Теперь, когда мы рассчитали оптимальный рейтинг, мы готовы запустить модель NMF и построить нашу матрицу рекомендаций WH.
# Decompose the dataset using sklearn NMF model = NMF(n_components=optimal_rank) model.fit(df) H = pd.DataFrame(model.components_) W = pd.DataFrame(model.transform(df)) V = pd.DataFrame(np.dot(W,H), columns=df.columns) V.index = df.index V
С помощью этой новой матрицы теперь мы можем видеть оценки книг, которые пользователь еще даже не читал. Используя исходные наборы данных, содержащие информацию о пользователях и книгах, мы можем порекомендовать 10 лучших книг, которые конкретный пользователь не читал. NMF говорит нам, что это книги, которые пользователь, скорее всего, оценит выше всех других книг, которые он еще не читал. В качестве примера возьмем пользователя 276521.
user_id = '276521' # Grab top 10 books ID's that the user hasn't reviewed user_col = V[user_id] user_col = user_col.sort_values(ascending=False) top_10_ISBN = [] for book in user_col.index: if original_df[user_id].loc[book] == 'No Ranking': # haven't read the book top_10_ISBN.append(book) if len(top_10_ISBN) == 10: break top_10_ISBN
# Return the titles and authors of the recommended books books_df = books.set_index('ISBN') books_df.index = books_df.index.astype(str) top_10_books = [] for book in top_10_ISBN: top_10_books.append([books_df['Title'].loc[book], books_df['Author'].loc[book]]) top_10_books = pd.DataFrame(top_10_books, columns=['Title','Author']) print(f'Top 10 Book Recommendations for user {user_id}:') top_10_books
Основываясь на прошлых оценках нашего целевого пользователя и других пользователей в наборе данных, наша модель рекомендовала 10 новых книг для целевого пользователя. Очевидно, наша модель узнала, что этот пользователь вписывается в нишу саспенса/ужаса, и теперь у него есть длинный список новых книг для чтения!
Заключение
Благодарим вас за то, что следили за этим пошаговым примером рекомендателя NMF в Python. NMF — очень мощный алгоритм, и я надеюсь, что вы рассмотрите возможность его использования в своих будущих проектах! Пожалуйста, подпишитесь, чтобы узнать больше о математике и науке о данных!