CSC 4538 – Introduction à la science des données

Portail informatique

CI9 : Systèmes de recommandation

Implémenter un système de recommandation.

Système de recommendation

Dans cet exercice, nous allons écrire un système de recommandation de film.

Rendez-vous sur le site de MovieLens et téléchargez le fichier ml-latest-small.zip.

Écrire une fonction load_data() qui :
  • Charge les données du fichier ratings.csv à l'aide de Python.
  • Prend la liste des utilisateurs et films uniques.
  • Associe un identifiant unique (et commençant à zéro) à chaque utilisateur et objet.
  • Initialise la matrice d'utilité avec des zéros (on utilisera numpy), puis la remplit avec les notes.
On retournera la matrice d'utilité.

Pour cet exercice, nous allons considérer que la mesure de similarité est la similarité cosinus. Écrire une fonction get_similarities(utility) retournant la matrice de tous les produits vectoriels d'utilisateur, la matrice de tous les produits vectoriels de films, un vecteur avec l'inverse des normes de tous les films, et un vecteur avec l'inverse des normes de tous les objets. On fera bien attention de ne pas faire de boucle, mais plutôt d'utiliser des opérations matricielles.

Écrivez deux fonctions rank_users(id_user, similarities, inv_norm) et rank_items(id_movie, similarities, inv_norm) qui retourne le classement de tous les utilisateurs (ou films) selon leur score de similarité. Le format de la sortie sera une liste de couple (user, similarité) ou (item, similarité).

Écrivez les fonctions recommend_user_based_item(id_user, id_item, utility, similarities, inv_norm, k=10) qui prédit le score pour un utilisateur et un objet donné avec une approche basée sur l'utilisateur. Pour le score, nous utiliserons la moyenne pondérée.

De même, écrivez recommend_item_based_item(id_user, id_item, utility, similarities, inv_norm, k=10).

Modifiez la fonction load_data() pour séparer le dataset en un jeu d'entrainement et de test. Pour cela, on pourra mélanger les lignes (df.sample(frac=1)) puis mettre les 80% premières lignes dans le train, et le 20% restant dans le test. Le train servira à construire la matrice d'utilité. Le test sera transformé en une liste de (userId, movieId, rating). On retournera maintenant un couple : la matrice d'utilité et la liste des tests.

Implémentez une fonction get_rmse_user_based(test_set, utility, similarities, inv_norm, k=10) qui calcule la RMSE pour l'approche basée sur l'utilisateur. De même, implémentez get_rmse_item_based(test_set, utility, similarities, inv_norm, k=10) pour l'approche basée sur l'objet. Comparez les résultats.

Écrivez un méthode get_ndcg_at_k_user_based(test_set, utility, similarities, inv_norm, k=10, k_ndcg=10) qui calcule le NDCG@K en utilisant une approche basée sur les utilisateurs. Pour ce faire, on appliquera l'approche naïve consistant à parcourir tous les utilisateurs du test set, prédire un score pour chaque objet, et classer en fonction de ces scores.
Cette méthode naïve devrait être très longue à calculer. Vous pouvez accélérer les calculs en précalculant pour chaque utilisateur les utilisateurs les plus proches.

De même, écrivez un méthode get_ndcg_at_k_item_based(test_set, utility, similarities, inv_norm, k=10, k_ndcg=10) qui calcule le NDCG@K avec une approche basée sur les objets.

Nous allons implémenter l'approche basée sur un modèle à l'aide de l'algorithme SVD. Pour cela, écrivez une fonction get_rmse_svd(test_set, utility, N=100) qui calcule la RMSE de l'approche SVD. À noter que dans Numpy, pour SVD, il faut restreindre manuellement les matrices au rang k souhaité (en enlevant des colonnes à U, S, et V).

Faites varier le rang k pour trouver le k avec le RMSE minimal.

De même, écrivez la fonction get_ndcg_svd(test_set, utility, k_ndcg=100, N=100) qui calcule le NDCG dans le cadre de l'approche avec SVD. Notez que la vitesse d'exécution est bien plus rapide qu'avant.

Afficher les scores de prédiction. Que remarquez-vous ?
Beaucoup de scores sont à 0.

Pour résoudre ce problème, enlever la moyenne des notes sur le jeu d'entrainement aux valeurs connues uniquement. Modifier la fonction load_data en conséquence. Est-ce que cela améliore les performances ?