<jupyter_start><jupyter_text>Building and Testing Recommender Systems With Surprise<jupyter_code>import pandas as pd
import matplotlib as plt
import seaborn as sns
user = pd.read_csv(r'C:\Users\delchain_default\Documents\GitHub\Python-Notes\Machine Learning\Recommender System (Advanced)\BX-Users.csv', sep=';', error_bad_lines=False, encoding="latin-1")
user.columns = ['userID', 'Location', 'Age']

rating = pd.read_csv(r'C:\Users\delchain_default\Documents\GitHub\Python-Notes\Machine Learning\Recommender System (Advanced)\BX-Book-Ratings.csv', sep=';', error_bad_lines=False, encoding="latin-1")
rating.columns = ['userID', 'ISBN', 'bookRating']

df = pd.merge(user, rating, on='userID', how='inner')
df.drop(['Location', 'Age'], axis=1, inplace=True)
df.head()

df.groupby('userID')['bookRating'].count().reset_index().sort_values('bookRating', ascending=False)[:10]<jupyter_output><empty_output><jupyter_text>Most of the users gave less than 5 ratings, and very few users gave many ratings, although the most productive user have given 13,602 ratings.I'm sure you have noticed that the above two charts share the same distribution. The number of ratings per movie and the bnumber of ratings per user decay exponentially.To reduce the dimensionality of the dataset, we will filter out rarely rated movies and rarely rating users.<jupyter_code>min_book_ratings = 50
filter_books = df['ISBN'].value_counts() > min_book_ratings
filter_books = filter_books[filter_books].index.tolist()

min_user_ratings = 50
filter_users = df['userID'].value_counts() > min_user_ratings
filter_users = filter_users[filter_users].index.tolist()

df_new = df[(df['ISBN'].isin(filter_books)) & (df['userID'].isin(filter_users))]
print('The original data frame shape:\t{}'.format(df.shape))
print('The new data frame shape:\t{}'.format(df_new.shape))<jupyter_output>The original data frame shape:	(1149780, 3)
The new data frame shape:	(140516, 3)<jupyter_text>EDA  Surprise To load a data set from the above pandas data frame, we will use the load_from_df() method, we will also need a Reader object, and the rating_scale parameter must be specified. The data frame must have three columns, corresponding to the user ids, the item ids, and the ratings in this order. Each row thus corresponds to a given rating. With the Surprise library, we will benchmark the following algorithms**Basic algorithms**NormalPredictor: * NormalPredictor algorithm predicts a random rating based on the distribution of the training set, which is assumed to be normal. This is one of the most basic algorithms that do not do much work. BaselineOnly * BasiclineOnly algorithm predicts the baseline estimate for given user and item. k-NN algorithms * KNNBasic : is a basic collaborative filtering algorithm. * KNNWithMeans : is basic collaborative filtering algorithm, taking into account the mean ratings of each user.*  KNNWithZScore : is a basic collaborative filtering algorithm, taking into account the z-score normalization of each user. * KNNBaseline :  is a basic collaborative filtering algorithm taking into account a baseline rating.  **Matrix Factorization-based algorithms*** SVD:  SVD algorithm is equivalent to Probabilistic Matrix Factorization (http://papers.nips.cc/paper/3208-probabilistic-matrix-factorization.pdf) * SVDpp :  The SVDpp algorithm is an extension of SVD that takes into account implicit ratings. * NMF : NMF is a collaborative filtering algorithm based on Non-negative Matrix Factorization. It is very similar with SVD. * Slope One:  Slope One is a straightforward implementation of the SlopeOne algorithm. (https://arxiv.org/abs/cs/0702144) * Co-clustering :  Co-clustering is a collaborative filtering algorithm based on co-clustering  (http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.113.6458&rep=rep1&type=pdf) **We use rmse as our accuracy metric for the predictions.**<jupyter_code>from surprise import Reader
from surprise import Dataset

from surprise.model_selection import cross_validate
from surprise import NormalPredictor
from surprise import KNNBasic
from surprise import KNNWithMeans
from surprise import KNNWithZScore
from surprise import KNNBaseline
from surprise import SVD
from surprise import BaselineOnly
from surprise import SVDpp
from surprise import NMF
from surprise import SlopeOne
from surprise import CoClustering

from surprise.accuracy import rmse
from surprise import accuracy
from surprise.model_selection import train_test_split
reader = Reader(rating_scale=(1, 10))
data = Dataset.load_from_df(df_new[['userID', 'ISBN', 'bookRating']], reader)
benchmark = []

# Iterate over all algorithms
for algorithm in [SVD(), SVDpp(),SlopeOne(), NormalPredictor(), KNNBaseline(), KNNBasic(), KNNWithMeans(), KNNWithZScore(), BaselineOnly(), CoClustering()]:   # NMF()
    # Perform cross validation
    results = cross_validate(algorithm, data, measures=['RMSE'], cv=3, verbose=False)
    
    # Get results & append algorithm name
    tmp = pd.DataFrame.from_dict(results).mean(axis=0)
    tmp = tmp.append(pd.Series([str(algorithm).split(' ')[0].split('.')[-1]], index=['Algorithm']))
    benchmark.append(tmp)
       

surprise_results = pd.DataFrame(benchmark).set_index('Algorithm').sort_values('test_rmse')
surprise_results
    

print('Using ALS')
bsl_options = {'method': 'als',
               'n_epochs': 5,
               'reg_u': 12,
               'reg_i': 5
               }
algo = BaselineOnly(bsl_options=bsl_options)
cross_validate(algo, data, measures=['RMSE'], cv=3, verbose=False)<jupyter_output>Using ALS
Estimating biases using als...
Estimating biases using als...
Estimating biases using als...<jupyter_text>Train and Predict BaselineOnly algorithm gave us the best rmse, therefore, we will train and predict with BaselineOnly and use Alternating Least Squares (ALS).<jupyter_code>print('Using ALS')
bsl_options = {'method': 'als',
               'n_epochs': 5,
               'reg_u': 12,
               'reg_i': 5
               }
algo = BaselineOnly(bsl_options=bsl_options)
cross_validate(algo, data, measures=['RMSE'], cv=3, verbose=False)<jupyter_output>Using ALS
Estimating biases using als...
Estimating biases using als...
Estimating biases using als...<jupyter_text>We use the train_test_split() to sample a trainset and a testset with given sizes, and use the accuracy metric of rmse. We’ll then use the fit() method which will train the algorithm on the trainset, and the test() method which will return the predictions made from the testset.<jupyter_code>trainset, testset = train_test_split(data, test_size=0.25)
algo = BaselineOnly(bsl_options=bsl_options)
predictions = algo.fit(trainset).test(testset)
accuracy.rmse(predictions)<jupyter_output>Estimating biases using als...
RMSE: 3.3785<jupyter_text>To inspect our predictions in details, we are going to build a pandas data frame with all the predictions. The following code were largely taken from this notebook.<jupyter_code>trainset = algo.trainset
print(algo.__class__.__name__)<jupyter_output>BaselineOnly<jupyter_text>To inspect our predictions in details, we are going to build a pandas data frame with all the predictions. def get_Iu(uid):    """ return the number of items rated by given user    args:       uid: the id of the user    returns:       the number of items rated by the user    """    try:        return len(trainset.ur[trainset.to_inner_uid(uid)])    except ValueError:  user was not part of the trainset        return 0    def get_Ui(iid):    """ return number of users that have rated given item    args:      iid: the raw id of the item    returns:      the number of users that have rated the item.    """    try:         return len(trainset.ir[trainset.to_inner_iid(iid)])    except ValueError:        return 0    df = pd.DataFrame(predictions, columns=['uid', 'iid', 'rui', 'est', 'details'])df['Iu'] = df.uid.apply(get_Iu)df['Ui'] = df.iid.apply(get_Ui)df['err'] = abs(df.est - df.rui)<jupyter_code>df.head()

best_predictions = df.sort_values(by='err')[:10]
worst_predictions = df.sort_values(by='err')[-10:]
best_predictions
worst_predictions<jupyter_output><empty_output><jupyter_text>The above are the best predictions, and they are not lucky guesses. Because Ui is anywhere between 26 to 146, they are not really small, meaning that significant number of users have rated the target book.The worst predictions look pretty surprise. Let's look in more details of the last one ISBN "055358264X", the book was rated by 47 users, user "26544" rated 10, our BaselineOnly algorithm predicts 0.<jupyter_code>df_new.loc[df_new['ISBN'] == '055358264X']['bookRating'].describe()

import matplotlib.pyplot as plt
%matplotlib notebook

df_new.loc[df_new['ISBN'] == '055358264X']['bookRating'].hist()
plt.xlabel('rating')
plt.ylabel('Number of ratings')
plt.title('Number of ratings book ISBN 055358264X has received')
plt.show();<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10020220175241519, 'fraction_numerical': 0.01516513143113907, 'mean_word_length': 5.199860724233983, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 30, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '199317', 'license': 'BSD-2-Clause', 'n_tokens_mistral': 2775, 'n_tokens_neox': 2579, 'n_words': 904, 'path': 'Machine Learning/Recommender System (Advanced)/Recommender with Surprise.ipynb', 'repo_name': 'k21k/Python-Notes', 'size': '8902'}