Aller au contenu principal

Module 9 — Rétroaction implicite et biais de position

Presque tous les moteurs modernes s'entraînent sur la rétroaction implicite : clics, inscriptions, minutes visionnées, achats. Ce module explique pourquoi ce signal, si abondant, est fondamentalement contaminé par la façon dont il est collecté, et comment corriger cette contamination sans laquelle un modèle apprend surtout ses propres décisions passées.

Le clic n'est pas la préférence

Un apprenant clique sur un cours parce que trois conditions sont réunies : il l'a vu, il l'a examiné, il l'a jugé digne d'un clic. Un « non-clic » peut refléter le rejet, mais aussi le fait de ne pas avoir vu la carte du tout, ou de l'avoir vue en un dixième de seconde en scrollant. Interpréter chaque non-clic comme un jugement négatif est donc faux.

La formulation propre (Hu, Koren, Volinsky, 2008) transforme cette ambiguïté en pondération par confiance. On définit un signal binaire de préférence pu,i=1p_{u,i} = 1 si ru,i>0r_{u,i} > 0 (interaction observée) et pu,i=0p_{u,i} = 0 sinon. On assortit chaque cellule d'une confiance :

cu,i=1+αru,ic_{u,i} = 1 + \alpha \cdot r_{u,i}

ru,ir_{u,i} est le compteur d'interactions (clics, minutes, inscriptions). La perte à minimiser devient :

L=u,icu,i(pu,ipuqi)2+λ(PF2+QF2)L = \sum_{u,i} c_{u,i} \left( p_{u,i} - p_u^{\top} q_i \right)^2 + \lambda \left( \|P\|_F^2 + \|Q\|_F^2 \right)

Les cellules non observées comptent toujours (leur confiance vaut 1) mais avec un poids faible ; les cellules très observées comptent beaucoup (cu,ic_{u,i} grand). Le modèle est donc encouragé à prédire zéro sur les cellules non observées, mais ne le fait pas au prix d'une mauvaise prédiction sur les positifs. C'est la formulation implémentée par la bibliothèque implicit.

import implicit
from scipy.sparse import csr_matrix

# R : matrice creuse apprenants x cours, valeur = minutes visionnées.
modele = implicit.als.AlternatingLeastSquares(
factors=64,
regularization=0.05,
alpha=40, # amplifie la confiance sur les positifs
iterations=30,
)
# implicit attend une matrice item x user, transposée de R.
modele.fit(R.T.tocsr())
ids, scores = modele.recommend(userid=0, user_items=R[0], N=10)

Le paramètre alpha gouverne la force de la confiance : trop faible, tout se ressemble ; trop élevé, on surajuste les clics à l'excès. La valeur 40 des auteurs originaux marche étonnamment bien en pratique et sert de point de départ.

Le biais de position

L'apprenant clique beaucoup plus sur la première position que sur la dixième, indépendamment de la qualité relative des deux cours. Sur la plupart des plateformes, la première position reçoit environ dix fois plus de clics que la dixième pour un même cours. C'est le biais de position : le clic mesure l'exposition autant que la préférence.

Ce biais devient un piège fermé quand on entraîne un modèle sur les clics de sa propre production. Le modèle A place le cours ii en position 1, l'apprenant clique, le modèle enregistre ce clic comme signal positif, le prochain modèle A' apprend à placer ii encore plus haut. On appelle cette boucle un biais de rétroaction : le système confirme ses propres décisions au lieu d'apprendre les préférences.

La conséquence est mesurable. Si vous entraînez une fois par nuit sur les clics de la journée, la qualité hors ligne progresse (le modèle prédit de mieux en mieux les clics futurs) tandis que la qualité en ligne stagne ou régresse (la couverture chute, la diversité s'effondre, les apprenants voient toujours les mêmes cinq cours).

Correction par propension inverse

La technique de référence, popularisée par Joachims (2017) puis étendue par Google et Facebook, est l'IPS (Inverse Propensity Scoring). L'idée : si un cours à la position jj a une probabilité observationnelle πj\pi_j d'être vu — souvent estimée par un modèle d'attention πj1/log(j+1)\pi_j \propto 1/\log(j+1) ou par des A/B tests contrôlés — alors le clic observé est pondéré par 1/πj1/\pi_j dans l'entraînement.

Concrètement, la perte devient :

LIPS=(u,i,j,c)cπj(u,i)L_{\mathrm{IPS}} = \sum_{(u, i, j, c)} \frac{c}{\pi_j} \cdot \ell(u, i)

c{0,1}c \in \{0, 1\} est le clic observé, jj la position d'affichage, et \ell la perte de base. Le résultat est mathématiquement séduisant — l'IPS est un estimateur non biaisé de la perte contrefactuelle — mais souffre en pratique de la variance : quand πj\pi_j est petit (positions basses), 1/πj1/\pi_j devient énorme et un clic bruité domine tout le gradient. On atténue par clipping :

L^=min(cπj,M)(u,i)\hat{L} = \sum \min\left(\frac{c}{\pi_j}, M\right) \cdot \ell(u, i)

avec MM typiquement entre 10 et 100. Le compromis biais-variance se règle par validation.

Le biais de popularité

Un cousin proche du biais de position est le biais de popularité. Sur la plupart des interfaces, la popularité influence deux étapes : elle biaise la disposition (les cours populaires sont en tête), et elle biaise la sélection éditoriale (les cours populaires sont montrés plus souvent). Le résultat est une distribution de clics qui suit une loi de puissance encore plus prononcée que la « vraie » préférence.

Un modèle entraîné sans correction apprend à placer les cours populaires très haut, ce qui rétroactivement amplifie encore leur popularité. Sur MovieLens, une factorisation matricielle standard donne un top-10 dont les cinq premiers sont dans le top-20 de popularité pour 90 % des apprenants — bien plus que ce que la préférence réelle justifie.

Les contre-mesures pratiques :

  • Retrait du log de popularité dans le score, comme au module 6 : s~(u,i)=s(u,i)γlogpi\tilde{s}(u, i) = s(u, i) - \gamma \log p_i avec γ\gamma entre 0,3 et 1.
  • Sur-échantillonnage des cours rares pendant l'entraînement, pour rééquilibrer le gradient.
  • Injecter des impressions négatives explicites : les cours affichés mais non cliqués sont des négatifs, contrairement aux cours jamais affichés qui restent ambigus. Cette distinction change tout et se retrouve dans les architectures modernes de LinkedIn et Alibaba.

Le principe non négociable de la journalisation

Toute correction du biais de position exige de journaliser la position d'affichage à côté du clic. Une base de données qui n'enregistre que « l'apprenant uu a cliqué sur le cours ii à l'instant tt » ne permet aucune correction — l'information manque à la source. Une base sérieuse journalise le triplet : (u,i,t,position)(u, i, t, \mathrm{position}), et idéalement la liste complète des cours affichés pour cette session.

Cette exigence transforme le pipeline de collecte. Elle coûte du volume (10 à 20 fois plus de logs), mais elle rend possible tout ce que ce module décrit. C'est une décision d'architecture à prendre au tout début du projet, pas au moment où l'on rencontre le problème pour la première fois.

def entrainement_ips(interactions):
for u, i, position, clic in interactions:
# atténuation de position, plafonnée pour maîtriser la variance
propension = 1 / np.log2(position + 1) # modèle simple
poids = min(1 / propension, 50)
gradient_local = poids * (clic - sigmoid(p[u] @ q[i]))
p[u] += eta * gradient_local * q[i] - eta * lam * p[u]
q[i] += eta * gradient_local * p[u] - eta * lam * q[i]
Le bogue le plus fréquent

Traiter un « affiché non cliqué » comme un « négatif certain » est une des erreurs les plus fréquentes des équipes qui découvrent l'implicite. C'est l'exact inverse de la formulation Hu-Koren-Volinsky : ce qu'ils appellent négatif certain, ce sont les cours non affichés. Un cours affiché mais non cliqué est un « négatif faible » — informatif, mais avec un poids beaucoup plus prudent. Confondre les deux dégrade silencieusement la couverture et fait exploser le biais de popularité.

En résumé

  • Le clic est un signal bruité : il mêle exposition, examen et jugement ; on l'exploite par la pondération par confiance de Hu-Koren-Volinsky.
  • Le biais de position crée une boucle de rétroaction où le modèle confirme ses propres décisions ; l'IPS produit un estimateur non biaisé, avec un clipping pour la variance.
  • Le biais de popularité amplifie les cours en tête ; on le corrige par retrait de logpi\log p_i, sur-échantillonnage des rares, et distinction affiché versus non-affiché.
  • La journalisation de la position est une exigence d'architecture non négociable ; sans elle, aucune correction n'est possible.

Module suivant : assembler tout ce qui précède en un moteur complet évalué hors ligne.