Construction et comparaison de trois architectures de filtrage collaboratif sur le jeu de donnees MovieLens 25M : similarite cosinus item-based, filtrage user-based et factorisation matricielle par SVD tronquee, avec visualisation de l’espace latent par UMAP.
Le jeu de donnees MovieLens 25M, publie par le GroupLens Research Lab de l’Universite du Minnesota, est une reference standard dans la litterature sur les systemes de recommandation. Il regroupe 25 millions d’interactions collectees aupres de 162 541 utilisateurs sur un catalogue de plus de 62 000 films. Deux fichiers CSV constituent le socle de ce projet.
La donnee structurelle la plus importante est le taux de sparsité de 99.93 % : sur les 34 milliards de paires (utilisateur, film) concevables, moins d’une sur mille a effectivement donne lieu a une note. Tout l’enjeu de la modelisation est de combler intelligemment ces vides a partir des rares interactions observees.
L’echelle de notation s’etend de 0.5 a 5.0 par pas de 0.5. La distribution est asymetrique a gauche avec un pic marque sur les notes entières, en particulier 4.0, traduisant une propension des utilisateurs a evaluer positivement les films qu’ils ont choisi de voir.
Les deux distributions suivent une loi de puissance prononcee. En echelle lineaire, la masse des observations est concentree sur un petit nombre de valeurs elevees, rendant la queue droite invisible. L’echelle logarithmique revele la structure complete : la majorite des utilisateurs ont note entre 20 et 200 films, tandis que la majorite des films n’ont recu que quelques notes.
La matrice brute de dimension 162 541 x 209 171 represente theoriquement 34 milliards de cellules. Son stockage en dense serait physiquement impossible (environ 270 Go en float64). Le format CSR (Compressed Sparse Row) de SciPy ne memorise que les 25 millions d’entrees non nulles, ramenant l’empreinte memoire a quelques centaines de megaoctets.
def build_user_item_matrix(df_ratings): row_indices = df_ratings['userId'].values - 1 col_indices = df_ratings['movieId'].values - 1 ratings = df_ratings['rating'].values n_users = df_ratings['userId'].max() n_movies = df_ratings['movieId'].max() # CSR : seules les valeurs non nulles sont stockees user_item_matrix = csr_matrix( (ratings, (row_indices, col_indices)), shape=(n_users, n_movies) ) return user_item_matrix, n_users, n_movies
Le projet repose exclusivement sur le filtrage collaboratif, qui exploite uniquement les interactions entre utilisateurs et films sans recourir aux attributs intrinseques du contenu. Ce choix se justifie par deux raisons complementaires : MovieLens ne fournit pas de metadonnees riches au format textuel utilisable directement, et le filtrage collaboratif offre un spectre de complexite algorithmique suffisamment etendu pour une comparaison instructive.
Similarite cosinus entre les vecteurs d’evaluations des films dans l’espace utilisateur. Deux films sont proches s’ils sont notes de facon coherente par les memes individus.
Identification des utilisateurs au profil de gout similaire a un utilisateur cible. Les films bien notes par ces voisins et non encore vus constituent le jeu de recommandations.
Decomposition matricielle projetant utilisateurs et films dans un espace latent commun de dimension k=50. La prediction de notes passe par le produit scalaire dans cet espace compresse.
Trois films sont choisis comme points d’ancrage pour evaluer qualitativement les recommandations. Leur selection est deliberement contrastee afin de couvrir des profils tres differents en termes de popularite, de genre et d’audience.
| Film | movieId | Genre dominant | Profil |
|---|---|---|---|
| The Greatest Showman (2017) | 180 985 | Musical / Drama | Grand public, forte audience |
| The Lion King (2019) | 203 222 | Animation / Adventure | Blockbuster familial recent |
| I Want to Eat Your Pancreas | 198 611 | Animation japonaise | Film de niche, peu note |
L’approche item-based evalue la proximite entre deux films en comparant leurs vecteurs de notes dans l’espace des utilisateurs. Formellement, si vi designe le vecteur de ratings recus par le film i, la similarite entre deux films i et j est :
La metrique cosinus est invariante a l’echelle : elle mesure l’orientation relative des vecteurs, non leur magnitude. Cela la rend robuste aux biais systematiques de notation (utilisateurs qui notent toujours haut ou toujours bas).
def get_top_similar_movies_item_based(item_user_matrix, anchor_movie_id, movies_df, top_k=10): anchor_idx = anchor_movie_id - 1 anchor_vector = item_user_matrix[anchor_idx] # vecteur (1 x n_users) # Similarite cosinus avec l'ensemble du catalogue similarities = cosine_similarity(anchor_vector, item_user_matrix).flatten() similarities[anchor_idx] = -1 # exclusion du film lui-meme top_indices = similarities.argsort()[-top_k:][::-1] top_movie_ids = top_indices + 1 # ... recuperation des titres et scores
Les dix films les plus proches sont quasi exclusivement des sorties de 2017-2018, toutes categories confondues (comedie musicale, super-heros, animation). Ce phenomene de co-occurrence temporelle est un artefact structurel de l’item-based CF : les utilisateurs qui ont note un film recemment ont tendance a avoir note d’autres films contemporains disponibles sur les memes plateformes.
| Titre | Genres | Similarite |
|---|---|---|
| Jumanji: Welcome to the Jungle (2017) | Action|Adventure|Children | 0.3138 |
| Beauty and the Beast (2017) | Fantasy|Romance | 0.2855 |
| Black Panther (2017) | Action|Adventure|Sci-Fi | 0.2771 |
| Wonder (2017) | Drama | 0.2722 |
| Ready Player One | Action|Sci-Fi|Thriller | 0.2712 |
| Murder on the Orient Express (2017) | Crime|Drama|Mystery | 0.2632 |
| Ant-Man and the Wasp (2018) | Action|Adventure|Comedy|Fantasy|Sci-Fi | 0.2598 |
| Star Wars: The Last Jedi (2017) | Action|Adventure|Fantasy|Sci-Fi | 0.2530 |
| Moana (2016) | Adventure|Animation|Children|Comedy|Fantasy | 0.2525 |
| Wonder Woman (2017) | Action|Adventure|Fantasy | 0.2523 |
La similarite maximale avec Aladdin (2019) atteint 0.4075, sensiblement plus elevee qu’en moyenne. Ce resultat s’explique par le contexte de sortie : les deux films appartiennent a la meme serie de remakes Disney en prises de vue reelles, sortis la meme annee, avec une audience cible presque identique. Le modele capte parfaitement cette coherence comportementale.
| Titre | Genres | Similarite |
|---|---|---|
| Aladdin (2019) | Adventure|Fantasy|Romance | 0.4075 |
| Toy Story 4 (2019) | Adventure|Animation|Children|Comedy | 0.3064 |
| Dumbo (2019) | Adventure|Children|Fantasy | 0.2580 |
| Dark Phoenix (2019) | Action|Sci-Fi | 0.2347 |
| Captain Marvel (2018) | Action|Adventure|Sci-Fi | 0.2343 |
| Spider-Man: Far from Home (2019) | Action|Adventure|Sci-Fi | 0.2331 |
| Pokemon: Detective Pikachu (2019) | Action|Children|Crime|Fantasy|Mystery | 0.2319 |
| Mary Poppins Returns (2018) | Children|Fantasy | 0.2313 |
| The Secret Life of Pets 2 (2019) | Adventure|Animation|Children|Comedy | 0.2294 |
| Shazam! (2019) | Action|Adventure|Fantasy|Sci-Fi | 0.2242 |
Ce cas illustre la limite structurelle du CF en regime sparse. Les scores de similarite sont paradoxalement eleves (0.52 pour le premier voisin), mais les films recommandes n’ont aucun rapport thematique ou stylistique avec l’anime japonais d’origine. Ce phenomene resulte directement de la faible densite d’interactions : quelques utilisateurs eclectiques ayant note ce film peu populaire ont genere des co-occurrences artificielles avec des titres tout aussi marginaux.
| Titre | Genres | Similarite |
|---|---|---|
| The Last Trick (1964) | Animation | 0.5237 |
| Transfert per camera verso Virulentia (1967) | Documentary | 0.4534 |
| Invisible Ink (1921) | Animation|Comedy | 0.4527 |
| Nostalgia (2018) | Drama | 0.4519 |
| Octocat Adventures (2008) | (no genres listed) | 0.4211 |
| The External World (2010) | Animation|Comedy | 0.4188 |
| The Step (1985) | (no genres listed) | 0.4004 |
| The Easiest Way (1931) | Drama|Romance | 0.4003 |
| The Company’s in Love (1932) | Comedy | 0.4003 |
| An Episode in the Life of an Iron Picker (2013) | Drama | 0.4002 |
Le paradigme user-based inverse la logique de comparaison : plutot que de mesurer la proximite entre films, on identifie les utilisateurs partageant le meme profil de gout que l’utilisateur cible, puis on recommande ce que ces voisins ont aime mais que la cible n’a pas encore vu.
En l’absence d’un vrai utilisateur cible avec un historique verifie, un profil synthetique est construit manuellement a partir des films d’ancrage et de quelques titres complementaires. Ce vecteur est ensuite projete dans l’espace de la matrice et compare a l’ensemble des 162 000 utilisateurs reels par similarite cosinus.
fake_user_ratings = {
180985: 5.0, # The Greatest Showman
203222: 4.5, # The Lion King (2019)
198611: 5.0, # I Want to Eat Your Pancreas
5690: 4.0, # Grave of the Fireflies
120258: 4.5, # Shaka Zulu
3404: 4.5, # Titanic
}
# Projection comme vecteur sparse (1 x n_movies)
fake_user_vector = csr_matrix(
(list(fake_user_ratings.values()),
([0] * len(fake_user_ratings),
[mid - 1 for mid in fake_user_ratings.keys()])),
shape=(1, n_movies)
)
user_similarities = cosine_similarity(fake_user_vector, user_item_matrix).flatten()
top_user_indices = user_similarities.argsort()[-20:][::-1]
Les dix films recommandes obtiennent tous un score predit de 5.0, ce qui constitue une anomalie methodologique. Ce plafonnement resulte du fait que les voisins identifies ont une forte tendance a noter au maximum les films de leur catalogue. En l’absence de ponderation par la similarite et de regularisation, la moyenne brute des notes des voisins converge mecaniquement vers 5.0 pour les films qui n’ont ete evalues que par les utilisateurs les plus enthousiastes.
| Titre | Genres | Score predit |
|---|---|---|
| Sudden Death (1995) | Action | 5.0 |
| Before the Rain / Pred dozhdot (1994) | Drama|War | 5.0 |
| Kiss the Girls (1997) | Crime|Drama|Mystery|Thriller | 5.0 |
| Life Is Beautiful (1997) | Comedy|Drama|Romance|War | 5.0 |
| Blair Witch Project (1999) | Drama|Horror|Thriller | 5.0 |
| Patriot Games (1992) | Action|Crime|Drama|Thriller | 5.0 |
| What Lies Beneath (2000) | Drama|Horror|Mystery | 5.0 |
| Anatomy of a Murder (1959) | Drama|Mystery | 5.0 |
| The Notebook (2004) | Drama|Romance | 5.0 |
| The Exorcism of Emily Rose (2005) | Crime|Drama|Horror|Thriller | 5.0 |
L’intersection entre les listes item-based et user-based est nulle : les deux methodes ne partagent aucun film commun dans leurs top-10 respectifs. Ce resultat, instructif en soi, illustre que les deux paradigmes operent sur des logiques fondamentalement differentes.
La decomposition en valeurs singulieres tronquee (TruncatedSVD) approche la matrice utilisateur-item R par un produit de rang reduit :
U encode les utilisateurs dans l’espace latent, les colonnes de V les films. Le parametre k=50 controle la dimensionnalite : les 50 composantes retenues correspondent aux 50 directions de variance maximale dans les comportements collectifs de notation.
Avant la decomposition, les notes sont centrees par utilisateur. Cette etape corrige les biais systematiques de notation (certains utilisateurs notent structurellement plus haut que d’autres) et permet a la SVD de capturer les preferences relatives plutot que les niveaux absolus.
for user_idx in range(n_users): start = user_item_matrix_centered.indptr[user_idx] end = user_item_matrix_centered.indptr[user_idx + 1] if start < end: user_item_matrix_centered.data[start:end] -= user_mean_rating[user_idx] # Verification : la moyenne centree doit etre nulle # Moyenne centree utilisateur 0 : -0.000000
L’analyse du spectre de valeurs singulieres et de la variance expliquee cumulee permet de comprendre comment l’information est repartie dans l’espace latent.
La projection du fake user dans l’espace latent se fait par combinaison lineaire des facteurs items, ponderee par les notes centrees. Les scores predits sont ensuite calcules par produit scalaire avec l’ensemble des vecteurs films.
| Titre | Genres | Score predit |
|---|---|---|
| Blair Witch Project (1999) | Drama|Horror|Thriller | 4.501 |
| Babe (1995) | Children|Drama | 4.501 |
| Independence Day (1996) | Action|Adventure|Sci-Fi|Thriller | 4.501 |
| Raiders of the Lost Ark | Action|Adventure | 4.501 |
| Twelve Monkeys (1995) | Mystery|Sci-Fi|Thriller | 4.501 |
| Apollo 13 (1995) | Adventure|Drama|IMAX | 4.500 |
| The Hangover (2009) | Comedy|Crime | 4.500 |
| American Beauty (1999) | Drama|Romance | 4.500 |
| The Wizard of Oz (1939) | Adventure|Children|Fantasy|Musical | 4.500 |
| Willy Wonka & the Chocolate Factory (1971) | Children|Comedy|Fantasy|Musical | 4.500 |
La decomposition SVD place chaque film dans un espace de 50 dimensions. Pour rendre cet espace interpretable, une reduction supplementaire a 2 dimensions est effectuee par UMAP (Uniform Manifold Approximation and Projection). UMAP est prefere a t-SNE pour sa capacite a preserver simultanement la structure locale des voisinages et la structure globale de l’espace.
umap_model = umap.UMAP( n_neighbors=15, min_dist=0.1, n_components=2, random_state=42 ) item_factors_2d = umap_model.fit_transform(item_factors) # item_factors : (n_movies x 50)
La variance moyenne des coordonnees UMAP par genre quantifie la coherence comportementale de chaque categorie. Un genre compact est vu par un public homogene et specifique ; un genre disperse est consomme par des profils tres heterogenes.
| Genre | Variance moyenne (2D) | Interpretation |
|---|---|---|
| Documentary | 10.21 | Audience tres specifique, cluster compact |
| Horror | 14.39 | Public relativement homogene |
| Romance | 16.72 | Audience heterogene, forte dispersion |
Les trois methodes produisent des recommandations qualitativement distinctes, et cette divergence est elle-meme informationnelle. L’item-based CF capture des co-occurrences de visionnage temporellement et thematiquement coherentes pour les films populaires, mais s’effondre sur les films de niche. Le user-based CF reflete une logique de profil general plutot que de contexte specifique. La SVD, quant a elle, converge vers un ensemble de classiques populaires qui constituent le consensus collectif encode dans les premieres dimensions de l’espace latent.
| Axe | Action proposee | Complexite |
|---|---|---|
| Evaluation | Split temporel + Precision@K, nDCG@K | Moyenne |
| User-based scoring | Ponderation par similarite (k-NN pondere) | Faible |
| SVD k-tuning | Recherche du k optimal par cross-validation | Moyenne |
| Algorithme | ALS ou BPR pour donnees implicites | Elevee |
| Cold start | Hybridation avec attributs de contenu (LightFM) | Elevee |
| Temporalite | Ponderation des evaluations recentes | Faible |
numpy>=1.24 pandas>=1.5 scipy>=1.10 scikit-learn>=1.2 matplotlib>=3.6 seaborn>=0.12 umap-learn>=0.5