Apprenez de l'expérience avec Monte-Carlo et TD
Dans la partie 1, le robot du lac gelé avait un avantage énorme : il connaissait la carte et les probabilités de glissade. Un vrai robot n’a pas ce luxe. Il doit jouer, observer ce qui arrive, et en tirer des conclusions. Comment estimer la valeur d’une politique quand on ne peut plus calculer ?
La réponse tient en un mot : échantillonner. Au lieu de calculer une espérance exacte, on la remplace par une moyenne d’expériences. Ce chapitre présente les deux grandes façons de le faire, Monte-Carlo et les différences temporelles, sur le problème de la prédiction : estimer pour une politique fixée.
Apprendre sans modèle : le principe
Section intitulée « Apprendre sans modèle : le principe »Rappelez-vous la définition de la valeur : . C’est une espérance. Or une espérance inconnue, on sait l’estimer depuis le premier cours de statistiques : on observe plusieurs réalisations et on fait la moyenne. La loi des grands nombres garantit que cette moyenne converge vers l’espérance.
Pour garder des calculs faisables à la main, nous reprenons le couloir de la partie 1 : trois cases A, B et G, deux actions (gauche, droite), une récompense de en atteignant G, . On évalue la politique aléatoire uniforme, dont on connaît la valeur exacte : et . La différence, c’est que l’agent n’a plus le droit de regarder le modèle. Il a seulement une fonction pas(etat, action) qui lui renvoie l’état suivant et la récompense, comme env.step dans gymnasium.
Monte-Carlo : moyenner des retours complets
Section intitulée « Monte-Carlo : moyenner des retours complets »La méthode de Monte-Carlo (MC) est la plus directe. On joue un épisode complet jusqu’à l’état terminal, on calcule le retour obtenu depuis chaque état visité, et on fait la moyenne de ces retours, état par état.
Plutôt que de stocker tous les retours, on met à jour la moyenne au fil de l’eau. Si est la moyenne des premiers retours et le nouveau retour :
Cette forme « ancienne estimation + pas × (cible − ancienne estimation) » est la forme générale de presque toutes les mises à jour de ce cours. On remplace souvent par un taux d’apprentissage (learning rate) constant , qui donne plus de poids aux retours récents, utile quand la politique évolue :
Monte-Carlo a deux qualités : il ne suppose rien sur le modèle, et il est sans biais, puisque est un échantillon exact de ce qu’on veut estimer. Mais il a deux défauts. Il doit attendre la fin de l’épisode pour apprendre quoi que ce soit, ce qui exclut les tâches sans fin. Et ses estimations ont une forte variance : un retour dépend de toutes les actions et transitions aléatoires de l’épisode, qui s’accumulent.
Les différences temporelles : apprendre à chaque pas
Section intitulée « Les différences temporelles : apprendre à chaque pas »Et si l’on n’attendait pas la fin de l’épisode ? Après un seul pas, de vers avec la récompense , on dispose déjà d’une estimation du retour : la récompense observée, plus la valeur estimée de l’état d’arrivée. C’est l’idée de l’apprentissage par différences temporelles (temporal difference, TD). La méthode la plus simple, TD(0), fait :
- est la cible TD : un pas réel, puis l’estimation actuelle pour la suite ;
- est l’erreur TD : l’écart entre ce qu’on attendait et ce qu’on vient d’observer ;
- est le taux d’apprentissage.
Reconnaissez-vous la cible ? C’est l’équation de Bellman d’espérance, dans laquelle l’espérance sur est remplacée par un seul échantillon. TD(0) est en quelque sorte une programmation dynamique échantillonnée.
On dit que TD fait du bootstrap : il met à jour une estimation à partir d’une autre estimation (). C’est ce qui lui permet d’apprendre à chaque pas, sans attendre la fin de l’épisode.
Monte-Carlo contre TD : biais et variance
Section intitulée « Monte-Carlo contre TD : biais et variance »Les deux méthodes convergent vers (sous des conditions raisonnables sur ), mais elles ne se comportent pas de la même façon en chemin. On retrouve le compromis biais-variance de l’apprentissage supervisé.
| Monte-Carlo | TD(0) | |
|---|---|---|
| Cible | retour complet | |
| Biais | aucun | oui, tant que est mal estimée |
| Variance | forte (toute la trajectoire) | faible (un seul pas aléatoire) |
| Quand apprend-il ? | à la fin de l’épisode | à chaque pas |
| Tâches sans fin | impossible | possible |
| Sensibilité à la propriété de Markov | faible | forte (le bootstrap s’appuie dessus) |
En pratique, TD converge souvent plus vite, grâce à sa faible variance. Monte-Carlo reste utile quand l’état ne résume pas bien la situation (propriété de Markov douteuse), car il ne s’appuie pas sur les estimations des états suivants. Entre les deux, on peut regarder pas en avant avant de faire du bootstrap (méthodes TD à pas, et TD(), qui mélange tous les horizons).
Voici les deux méthodes côte à côte sur le couloir. Le code n’utilise que la bibliothèque standard de Python.
import random
gamma = 0.9
def pas(etat, action): """Couloir A (0), B (1), G : renvoie (état suivant, récompense, terminé).""" if etat == 0: return (0, 0.0, False) if action == 0 else (1, 0.0, False) return (0, 0.0, False) if action == 0 else (None, 1.0, True)
def episode(): """Joue un épisode depuis A avec la politique aléatoire.""" etat, trajectoire = 0, [] while True: action = random.randint(0, 1) # gauche ou droite, 1 chance sur 2 suivant, r, fini = pas(etat, action) trajectoire.append((etat, r, suivant, fini)) if fini: return trajectoire etat = suivant
def monte_carlo(n_episodes): somme, compte = [0.0, 0.0], [0, 0] for _ in range(n_episodes): traj, G = episode(), 0.0 for etat, r, _, _ in reversed(traj): # on remonte l'épisode G = r + gamma * G # retour depuis cet instant somme[etat] += G # (version « toutes les visites ») compte[etat] += 1 return [somme[s] / compte[s] for s in (0, 1)]
def td0(n_episodes, alpha=0.01): V = [0.0, 0.0] for _ in range(n_episodes): for etat, r, suivant, fini in episode(): cible = r + (0.0 if fini else gamma * V[suivant]) V[etat] += alpha * (cible - V[etat]) # correction vers la cible return V
random.seed(0)print("Monte-Carlo :", [round(v, 3) for v in monte_carlo(5000)])print("TD(0) :", [round(v, 3) for v in td0(5000)])Avec cette graine, on obtient pour Monte-Carlo et pour TD(0), à comparer aux valeurs exactes . Sans jamais consulter les probabilités de transition, les deux méthodes retrouvent la valeur calculée par Bellman au chapitre 2. Avec un constant, l’estimation TD continue de fluctuer légèrement autour de la vraie valeur ; faire décroître au fil du temps supprime ces fluctuations.
De la prédiction au contrôle
Section intitulée « De la prédiction au contrôle »Estimer , c’est bien, mais le but reste de trouver une bonne politique. Pourquoi ne pas faire comme en partie 1 : estimer , puis rendre la politique gloutonne ? Parce que l’amélioration gloutonne à partir de demande de calculer pour comparer les actions, donc de connaître le modèle.
La solution consiste à estimer directement au lieu de . Avec , l’amélioration ne demande plus le modèle : il suffit de prendre . Les versions de Monte-Carlo et de TD qui estiment s’appellent respectivement Monte-Carlo control et SARSA (ou Q-learning), et font l’objet du chapitre 7.
Il reste un obstacle, et il est de taille. Si l’agent agit toujours de façon gloutonne, il ne visite que les actions qu’il croit déjà bonnes, et n’apprend jamais la valeur des autres. Il lui faut explorer. C’est le sujet du chapitre suivant.
En résumé
Section intitulée « En résumé »- Sans modèle, on remplace les espérances exactes par des moyennes d’expériences : on apprend en jouant.
- Monte-Carlo moyenne les retours complets observés depuis chaque état ; il est sans biais mais a une forte variance et doit attendre la fin des épisodes.
- La mise à jour générale s’écrit , avec un taux d’apprentissage .
- TD(0) vise la cible : c’est une équation de Bellman échantillonnée, qui fait du bootstrap et apprend à chaque pas.
- MC et TD illustrent le compromis biais-variance ; TD converge souvent plus vite.
- Pour passer au contrôle sans modèle, on estime plutôt que , et on doit explorer.
Exercices
Section intitulée « Exercices »Exercice · Calculer des retours Monte-Carlo
facileDans le couloir (), le robot joue l’épisode B → A → B → G (gauche depuis B, puis droite, puis droite). Donnez les retours depuis chaque instant, puis les estimations Monte-Carlo « première visite » et « toutes les visites » de et après ce seul épisode.
Voir la solution
Récompenses : 0, 0, 1. En partant de la fin :
- instant 2 (B) : ;
- instant 1 (A) : ;
- instant 0 (B) : .
A n’est visité qu’une fois : dans les deux versions. B est visité aux instants 0 et 2 : en première visite, ; en toutes les visites, .
En code
gamma = 0.9etats = ["B", "A", "B"] # épisode B -> A -> B -> Grecompenses = [0, 0, 1]
# Retours calculés en partant de la finG, retours = 0.0, [0.0] * 3for t in reversed(range(3)): G = recompenses[t] + gamma * G retours[t] = Gprint([round(g, 3) for g in retours]) # [0.81, 0.9, 1.0]
for s in ("A", "B"): obs = [g for e, g in zip(etats, retours) if e == s] print(s, "première visite :", round(obs[0], 3), "toutes les visites :", round(sum(obs) / len(obs), 3))# A première visite : 0.9 toutes les visites : 0.9# B première visite : 0.81 toutes les visites : 0.905gamma <- 0.9etats <- c("B", "A", "B") # épisode B -> A -> B -> Grecompenses <- c(0, 0, 1)
# Retours calculés en partant de la finretours <- numeric(3); G <- 0for (t in 3:1) { G <- recompenses[t] + gamma * G; retours[t] <- G }retours # 0.81 0.90 1.00
for (s in c("A", "B")) { obs <- retours[etats == s] cat(s, "première visite :", obs[1], "toutes les visites :", mean(obs), "\n")}# A première visite : 0.9 toutes les visites : 0.9# B première visite : 0.81 toutes les visites : 0.905Exercice · Dérouler TD(0)
moyenOn part de , avec et . Le robot joue l’épisode A → B → G. Appliquez TD(0) à chaque transition. Rejouez ensuite le même épisode une deuxième fois. Que remarquez-vous sur la façon dont l’information se propage ?
Voir la solution
Premier épisode :
- A → B, : cible , donc reste à 0.
- B → G, : cible (G est terminal), donc .
Deuxième épisode :
- A → B : cible , donc .
- B → G : cible 1, donc .
Au premier épisode, seule la valeur de B a bougé : A ne pouvait pas encore profiter de la récompense, car valait 0 au moment de sa mise à jour. L’information remonte d’un état par épisode, exactement comme elle remontait d’une case par balayage en programmation dynamique. Monte-Carlo, lui, aurait mis à jour A dès le premier épisode, avec le retour complet 0,9.
En code
alpha, gamma = 0.5, 0.9V = {"A": 0.0, "B": 0.0, "G": 0.0} # G terminal : valeur 0episode = [("A", 0, "B"), ("B", 1, "G")] # (état, récompense, état suivant)
for numero in (1, 2): for s, r, s_suiv in episode: cible = r + gamma * V[s_suiv] V[s] += alpha * (cible - V[s]) # mise à jour TD(0) print(numero, round(V["A"], 3), round(V["B"], 3))# 1 0.0 0.5# 2 0.225 0.75alpha <- 0.5; gamma <- 0.9V <- c(A = 0, B = 0, G = 0) # G terminal : valeur 0episode <- list(list(s = "A", r = 0, suiv = "B"), list(s = "B", r = 1, suiv = "G"))
for (numero in 1:2) { for (tr in episode) { cible <- tr$r + gamma * V[[tr$suiv]] V[[tr$s]] <- V[[tr$s]] + alpha * (cible - V[[tr$s]]) # mise à jour TD(0) } cat(numero, V[["A"]], V[["B"]], "\n")}# 1 0 0.5# 2 0.225 0.75Exercice · Choisir entre Monte-Carlo et TD
moyenPour chacune des situations suivantes, dites si vous privilégieriez Monte-Carlo ou TD, et pourquoi.
- Un agent de trading qui gère un portefeuille en continu, sans fin d’épisode naturelle.
- Un jeu de cartes où l’état observé par l’agent (sa main) ne contient pas toute l’information utile (les cartes des adversaires).
- Un robot dont chaque essai dure plusieurs heures.
Voir la solution
- TD : Monte-Carlo a besoin d’épisodes qui se terminent pour calculer les retours. TD apprend à chaque pas.
- Monte-Carlo est plus robuste : l’état observé n’est pas markovien, et le bootstrap de TD s’appuie sur des estimations qui supposent que l’état résume la situation. Monte-Carlo n’utilise que les retours réellement obtenus.
- TD : attendre plusieurs heures avant la moindre mise à jour est très lent. TD corrige ses estimations à chaque action, ce qui accélère beaucoup l’apprentissage quand les épisodes sont longs.