Un algorithme qui choisit entre plusieurs options (offres, visuels, prix) en tirant au sort selon la probabilité que chacune soit la meilleure. Les options prometteuses sont montrées de plus en plus souvent, les mauvaises disparaissent vite. C'est l'A/B test qui cesse de perdre des ventes pendant le test.
Un restaurateur hésite entre quatre plats du jour. Chaque midi, il propose plus souvent celui qui a le plus de chances d'être le préféré, sans abandonner les autres tant qu'un doute subsiste.
Chaque option est décrite par une loi Beta, qui résume ce que l'on sait de son taux de conversion. Au départ tout est possible, puis la courbe se resserre à chaque visiteur observé.
À chaque visiteur, on tire une valeur plausible dans la croyance de chaque option et on affiche celle qui sort la plus haute. Une option encore incertaine sort parfois en tête : c'est l'exploration.
Achat ou non, on ajoute le résultat au compteur de l'option affichée. Plus une option accumule de preuves, moins sa croyance varie, et plus le choix se concentre sur la meilleure.
Livraison offerte, -10 %, cadeau, -5 %. Pour chaque visiteur, on sait quel code a été affiché et s'il a acheté. Les données sont simulées : les vrais taux sont connus, ce qui permet de vérifier que l'algorithme trouve le bon code.
Dans l'exemple Python, sur 20 000 visiteurs, le meilleur code (-10 %) reçoit près de 90 % des affichages. L'algorithme donne aussi, pour chaque code, la probabilité qu'il soit le meilleur.
On mesure le regret : les achats perdus par rapport à un choix parfait fait dès le premier visiteur. Dans l'exemple, 956 achats contre 1 000 attendus avec le meilleur code dès le départ (regret d'environ 44 achats), et environ 775 avec un partage égal du trafic, comme dans un A/B test classique.
Thompson Sampling n'a presque pas d'hyperparamètre. Ce sont les choix de modélisation qui font la différence.
Beta(1, 1) signifie « aucune idée ». Si l'historique situe la conversion autour de 4 %, un a priori comme Beta(4, 96) évite les emballements sur les premiers visiteurs.
Pour un résultat oui / non, la loi Beta convient. Pour un panier en euros, on change de loi (normale ou gamma) : le principe du tirage reste le même.
Si les goûts changent (saison, concurrence), on donne moins de poids aux anciens résultats, par exemple en multipliant les compteurs par 0,99 chaque jour. Sans cela, l'algorithme reste figé sur son premier gagnant.
# Codes promo sur le site : Thompson Sampling en R
set.seed(42)
codes <- c("LIVRAISON", "MOINS10", "CADEAU", "MOINS5")
taux_reels <- c(0.030, 0.050, 0.040, 0.035) # inconnus dans la réalité
succes <- rep(0, 4)
echecs <- rep(0, 4)
for (visiteur in 1:20000) {
# Un tirage dans la loi Beta de chaque code : on affiche le plus haut
tirages <- rbeta(4, 1 + succes, 1 + echecs)
k <- which.max(tirages)
achat <- runif(1) < taux_reels[k]
succes[k] <- succes[k] + achat
echecs[k] <- echecs[k] + 1 - achat
}
# Probabilité que chaque code soit le meilleur (10 000 tirages a posteriori)
tir <- sapply(1:4, function(j) rbeta(10000, 1 + succes[j], 1 + echecs[j]))
p_meilleur <- tabulate(max.col(tir), nbins = 4) / 10000
print(data.frame(code = codes, affichages = succes + echecs,
taux_observe = round(succes / (succes + echecs), 4), p_meilleur))
cat("Achats obtenus :", sum(succes), "| attendus en partage égal :", 20000 * mean(taux_reels), "\n")
# Codes promo sur le site : Thompson Sampling en Python
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
codes = ["LIVRAISON", "MOINS10", "CADEAU", "MOINS5"]
taux_reels = np.array([0.030, 0.050, 0.040, 0.035]) # inconnus dans la réalité
succes = np.zeros(4)
echecs = np.zeros(4)
for visiteur in range(20000):
# Un tirage dans la loi Beta de chaque code : on affiche le plus haut
tirages = rng.beta(1 + succes, 1 + echecs)
k = np.argmax(tirages)
achat = rng.random() < taux_reels[k]
succes[k] += achat
echecs[k] += 1 - achat
# Probabilité que chaque code soit le meilleur (10 000 tirages a posteriori)
tir = rng.beta(1 + succes, 1 + echecs, size=(10000, 4))
bilan = pd.DataFrame({"affichages": (succes + echecs).astype(int),
"taux_observe": (succes / (succes + echecs)).round(4),
"p_meilleur": np.bincount(tir.argmax(axis=1), minlength=4) / 10000}, index=codes)
print(bilan)
print("Achats obtenus :", int(succes.sum()), "| attendus en partage égal :", int(20000 * taux_reels.mean()))
Un A/B test partage le trafic à parts fixes jusqu'à la fin, puis on choisit. Thompson Sampling déplace le trafic vers la meilleure option pendant le test. On perd moins de ventes, mais les options délaissées sont estimées moins précisément.
Les deux gèrent le dilemme entre exploration et exploitation. UCB choisit de façon déterministe l'option dont l'estimation optimiste est la plus haute. Thompson Sampling tire au sort, ce qui le rend robuste quand les résultats arrivent par paquets, et il fait en pratique au moins aussi bien dans la plupart des études publiées.
Une règle courante : arrêter quand une option dépasse 95 % de probabilité d'être la meilleure, ou quand la perte attendue en choisissant la favorite devient négligeable. On peut aussi ne jamais arrêter et laisser l'algorithme s'adapter en continu.
Le problème général : explorer et exploiter en même temps. Epsilon-greedy et UCB en sont d'autres solutions, plus simples mais souvent moins efficaces.
Voir la fiche → le moteurChaque résultat met à jour la croyance sur le taux de conversion : c'est Bayes appliqué visiteur après visiteur.
Voir la fiche → quand les décisions s'enchaînentPour des choix dont l'effet dépend des choix passés, comme un prix fixé chaque jour sur un stock qui baisse.
Voir la fiche →Dataistudio forme les équipes au machine learning et à l'IA, sur des cas concrets.
Nous utilisons des cookies de mesure d'audience et de suivi publicitaire pour comprendre la fréquentation du site et l'efficacité de nos annonces. Rien n'est déposé sans votre accord. En savoir plus