Aller au contenu principal

Module 2 — Filtrage collaboratif utilisateur et objet

Le filtrage collaboratif, formulé par Goldberg et Nichols en 1992 puis popularisé par GroupLens en 1994, repose sur un principe très simple : les gens qui se ressemblent aiment des choses qui se ressemblent. Ce module l'implémente sur le catalogue de cours, en montrant pourquoi la variante « objet-objet » a supplanté la variante « utilisateur-utilisateur » dans la quasi-totalité des systèmes de production.

Similarité entre utilisateurs

Deux apprenants sont proches s'ils ont noté les mêmes cours de la même façon. Mathématiquement, cela se mesure sur les cours qu'ils ont en commun. La similarité cosinus, la plus utilisée, s'écrit :

sim(u,v)=iCuvRu,iRv,iiCuvRu,i2  iCuvRv,i2\mathrm{sim}(u, v) = \frac{\sum_{i \in C_{uv}} R_{u,i}\, R_{v,i}}{\sqrt{\sum_{i \in C_{uv}} R_{u,i}^2}\; \sqrt{\sum_{i \in C_{uv}} R_{v,i}^2}}

CuvC_{uv} est l'ensemble des cours notés à la fois par uu et vv. La valeur est comprise entre 1-1 et +1+1 pour des notes signées, entre 00 et 11 pour des interactions positives seulement.

Cette formule brute a deux limites bien connues. D'abord, elle ne corrige pas la tendance individuelle : un apprenant qui note tous ses cours quatre ou cinq étoiles semble d'accord avec tout le monde, y compris ceux qui n'aiment rien. La corrélation de Pearson résout ce point en centrant chaque utilisateur sur sa note moyenne :

simPearson(u,v)=iCuv(Ru,iRˉu)(Rv,iRˉv)(Ru,iRˉu)2  (Rv,iRˉv)2\mathrm{sim}_{\mathrm{Pearson}}(u, v) = \frac{\sum_{i \in C_{uv}} (R_{u,i} - \bar{R}_u)(R_{v,i} - \bar{R}_v)}{\sqrt{\sum (R_{u,i} - \bar{R}_u)^2}\;\sqrt{\sum (R_{v,i} - \bar{R}_v)^2}}

Ensuite, avec seulement deux cours en commun, la similarité est instable et peut valoir exactement 11 par pur hasard. On pénalise donc les paires à faible recouvrement, souvent par une atténuation simmin(Cuv,γ)/γ\mathrm{sim}\cdot \min(|C_{uv}|, \gamma)/\gamma avec γ30\gamma \approx 30.

Prédiction pondérée

Une fois les kk voisins les plus proches identifiés (typiquement k=30k = 30 ou 5050), la note prédite pour un cours non vu est la moyenne pondérée des notes de ces voisins pour ce cours :

R^u,i=Rˉu+vNk(u)sim(u,v)(Rv,iRˉv)vNk(u)sim(u,v)\hat{R}_{u,i} = \bar{R}_u + \frac{\sum_{v \in N_k(u)} \mathrm{sim}(u, v)\, (R_{v,i} - \bar{R}_v)}{\sum_{v \in N_k(u)} |\mathrm{sim}(u, v)|}

On centre à nouveau par la moyenne de chaque voisin pour ramener leur avis à un écart signé, on pondère par leur similarité, puis on rajoute la moyenne de l'utilisateur cible pour retrouver l'échelle d'origine. Les cours candidats sont ensuite ordonnés par R^u,i\hat{R}_{u,i} décroissante ; les cinq premiers sont recommandés.

De l'utilisateur à l'objet

Sarwar et al. ont montré en 2001 (« Item-based collaborative filtering ») que regarder la similarité entre cours plutôt qu'entre apprenants change tout. La matrice de similarité I×II \times I est bien plus stable que la matrice U×UU \times U : les cours changent lentement, alors que les utilisateurs vont et viennent. Elle peut se précalculer hors ligne et se mettre en cache, ce qui rend la recommandation en temps réel presque instantanée.

La formulation devient :

R^u,i=jR(u)sim(i,j)Ru,jjR(u)sim(i,j)\hat{R}_{u,i} = \frac{\sum_{j \in R(u)} \mathrm{sim}(i, j)\, R_{u,j}}{\sum_{j \in R(u)} |\mathrm{sim}(i, j)|}

R(u)R(u) est l'ensemble des cours déjà notés par uu. On agrège l'opinion de uu sur des cours proches de ii ; la similarité entre cours est calculée par cosinus sur les vecteurs colonnes de RR, chaque cours étant représenté par le profil de tous ses évaluateurs.

Amazon a documenté ce basculement en 2003 : chez eux, la recommandation « Les clients qui ont acheté X ont aussi acheté Y » est exactement du filtrage collaboratif objet-objet. Le calcul de la matrice de similarité reste coûteux hors ligne, mais la lecture en ligne est un simple parcours de dictionnaire.

Coût de calcul et creux extrême

Le passage à l'échelle est le nerf de la guerre. Une matrice U×UU \times U complète pour 30 000 apprenants pèse 900 millions de cellules ; sa construction naïve en O(U2I)O(U^2 \cdot I) prend des heures. Deux astuces sont indispensables.

D'abord, ne calculer la similarité que pour les paires ayant au moins un cours en commun. Sur InSkillML, cette contrainte élimine plus de 99,5 % des paires. On l'implémente en itérant sur les cours et non sur les utilisateurs : pour chaque cours, on récupère la liste de ses évaluateurs, et l'on met à jour la matrice de similarité pour toutes les paires de cette liste. C'est le fameux algorithme « objet-centré » de Sarwar et al.

Ensuite, ne conserver que les kk voisins les plus proches par utilisateur ou par cours, ce qui transforme la matrice de similarité dense en une matrice creuse navigable en temps constant. Toutes les bibliothèques modernes (scikit-surprise, implicit, LightFM) exposent ce paramètre.

Malgré ces astuces, le filtrage par voisinage souffre dès que la matrice tombe sous 0,5 % de densité : la plupart des paires de cours n'ont aucun évaluateur commun, la similarité est indéfinie, et le voisinage se réduit à quelques voisins bruités. C'est précisément la faiblesse que la factorisation matricielle du module 3 corrige, en projetant tout dans un espace latent où la similarité reste toujours définie.

Implémentation sur le catalogue

import numpy as np
from scipy.sparse import csr_matrix
from sklearn.metrics.pairwise import cosine_similarity

# R : matrice creuse apprenants x cours (issue du module 1).
# Filtrage collaboratif objet-objet, k=20 voisins par cours.
def voisins_objets(R, k=20):
# cosine_similarity accepte du creux et renvoie du dense (I x I).
S = cosine_similarity(R.T, dense_output=False)
# Mise a zero de la diagonale : un cours n'est pas son propre voisin.
S.setdiag(0)
S.eliminate_zeros()
return S

def recommander(R, S, u, top=10):
profil = R[u].toarray().ravel() # notes de l'apprenant u
scores = S.dot(profil) # somme pondérée
scores[profil > 0] = -np.inf # exclure les cours déjà suivis
return np.argsort(-scores)[:top]

S = voisins_objets(R, k=20)
top10 = recommander(R, S, u=0, top=10)
print("cours recommandés à l'apprenant 0 :", top10)

Utilisateur ou objet ?

Le choix entre les deux variantes se lit sur un ratio simple : U/IU/I. Quand il y a beaucoup plus d'apprenants que de cours (typique de nos plateformes : 30 000 pour 500), la variante objet-objet est meilleure — la matrice de similarité I×II \times I tient en mémoire, se recalcule chaque nuit, et absorbe les nouveaux apprenants sans coût. Quand ce sont les objets qui explosent (des millions de tweets, de photos), la logique s'inverse : c'est la matrice utilisateur-utilisateur qui devient gérable, à condition d'accepter la volatilité qu'apportent les utilisateurs actifs sur quelques jours.

Diagnostic rapide

Si votre système par voisinage donne des recommandations acceptables pour les utilisateurs actifs mais absurdes pour les apprenants ayant moins de trois cours suivis, ce n'est pas un problème de code : c'est le creux extrême du module 1 qui rend la similarité indéfinie. Ne cherchez pas à raffiner la formule ; passez au module 3.

En résumé

  • Le filtrage collaboratif repose sur la similarité entre utilisateurs (cosinus, Pearson) et une prédiction pondérée par les voisins.
  • La variante objet-objet (Sarwar 2001, Amazon 2003) est plus stable, se précalcule hors ligne, et sert la production en temps réel.
  • Le coût quadratique exige de n'itérer que sur les paires ayant des cours en commun et de conserver les kk meilleurs voisins.
  • Le creux extrême est la limite structurelle de la méthode : au-dessous de 0,5 % de densité, la factorisation matricielle du module suivant devient nécessaire.

Module suivant : projeter apprenants et cours dans un espace latent commun par factorisation matricielle.