Mesurez la valeur d'une politique avec Bellman
Au chapitre précédent, le robot du lac gelé disposait d’une politique, mais rien ne permettait encore de dire si elle était bonne. Une politique se juge sur ce qu’elle rapporte en moyenne, depuis chaque case du lac. Cette moyenne porte un nom, la fonction de valeur, et elle obéit à une équation d’une grande élégance : l’équation de Bellman.
Ce chapitre est le cœur théorique du cours. Tous les algorithmes qui suivent, de la programmation dynamique au Q-learning, ne sont que des façons différentes de résoudre les équations que vous allez écrire ici.
La valeur d’un état
Section intitulée « La valeur d’un état »Placez le robot sur une case du lac et laissez-le suivre sa politique . Le retour qu’il obtient est aléatoire : la politique peut tirer ses actions au hasard, et le lac peut être glissant. On définit donc la valeur de l’état sous la politique comme l’espérance de ce retour :
- est la fonction de valeur d’état (state-value function) ;
- est l’espérance quand l’agent suit (et que l’environnement suit ) ;
- la condition signifie qu’on part de l’état .
Au lac, où la seule récompense est le de l’objectif, se lit comme une probabilité d’atteindre G, pondérée par la durée du trajet. Une case proche de l’objectif, pour une bonne politique, a une valeur proche de 1 ; une case d’où l’on tombe presque toujours dans un trou a une valeur proche de 0.
Les états terminaux ont une valeur nulle : une fois dans un trou ou sur l’objectif, il n’y a plus de récompense à venir. La récompense de l’objectif est comptée au moment où l’on y entre, donc dans la valeur de la case d’avant.
La valeur d’une action
Section intitulée « La valeur d’une action »Pour décider, il est souvent plus utile de connaître la valeur d’une action précise. On définit la fonction de valeur d’action (action-value function), ou fonction Q :
C’est le retour espéré si l’on fait d’abord l’action dans l’état , puis qu’on suit ensuite. Les deux fonctions sont liées très simplement : la valeur d’un état est la moyenne des valeurs de ses actions, pondérée par la politique.
Pourquoi s’embêter avec deux fonctions ? Parce que dit où il fait bon être, alors que dit quoi faire. Nous verrons que, pour améliorer une politique, c’est qu’il faut regarder.
L’équation de Bellman d’espérance
Section intitulée « L’équation de Bellman d’espérance »Voici l’idée qui change tout. Le retour se décompose en « ce que je gagne maintenant » plus « ce que je gagnerai ensuite, actualisé » :
En prenant l’espérance, et grâce à la propriété de Markov (la suite ne dépend que de l’état atteint), on obtient une relation entre la valeur d’un état et celle de ses successeurs. C’est l’équation de Bellman d’espérance :
Lisez-la de l’intérieur vers l’extérieur :
- est ce que rapporte une transition précise : la récompense immédiate, plus la valeur actualisée de l’état d’arrivée ;
- la somme sur , pondérée par , fait la moyenne sur les issues possibles de l’action (c’est ) ;
- la somme sur , pondérée par , fait la moyenne sur les actions que la politique peut choisir.
Si vous avez suivi le cours de modèles stochastiques, ce raisonnement vous est familier : c’est exactement le conditionnement sur le premier pas qui sert à calculer les probabilités et les temps d’absorption d’une chaîne de Markov. Une politique fixée transforme le MDP en chaîne de Markov, et se calcule comme une quantité d’absorption.
Un exemple à la main : le couloir
Section intitulée « Un exemple à la main : le couloir »Le lac a 64 états, c’est trop pour un calcul à la main. Prenons un couloir de trois cases, A, B et G (l’objectif, terminal). Deux actions : gauche et droite, sans glissade. Depuis A, gauche fait buter contre le mur (on reste en A) et droite mène en B. Depuis B, gauche ramène en A et droite atteint G, avec une récompense de . On prend et la politique aléatoire uniforme ( pour chaque action).
Une équation linéaire, un système à résoudre
Section intitulée « Une équation linéaire, un système à résoudre »L’exemple le montre : pour une politique fixée, l’équation de Bellman est linéaire en les valeurs. En notation matricielle, avec la matrice de transition de la chaîne obtenue en suivant et le vecteur des récompenses immédiates espérées, elle s’écrit :
- est le vecteur des valeurs, une composante par état ;
- est la probabilité de passer de à en suivant ;
- est la récompense espérée au prochain pas depuis ;
- est la matrice identité. Pour , la matrice est toujours inversible.
import numpy as np
gamma = 0.9# États non terminaux : 0 = A, 1 = B (G est terminal, de valeur 0)# Transitions de la politique aléatoire, restreintes aux états non terminauxP_pi = np.array([[0.5, 0.5], # depuis A : reste en A ou va en B [0.5, 0.0]]) # depuis B : va en A (l'autre moitié sort vers G)R_pi = np.array([0.0, 0.5]) # depuis B, une chance sur deux de gagner 1
V = np.linalg.solve(np.eye(2) - gamma * P_pi, R_pi)print(V.round(3)) # [0.647 0.791]Pour un petit problème, cette résolution directe est parfaite. Mais inverser une matrice coûte de l’ordre de opérations : pour un million d’états, c’est hors de portée. On préfère alors itérer l’équation, et c’est tout l’objet du chapitre suivant.
Les valeurs optimales et l’équation de Bellman d’optimalité
Section intitulée « Les valeurs optimales et l’équation de Bellman d’optimalité »Parmi toutes les politiques, certaines sont meilleures que d’autres. On définit la valeur optimale d’un état comme la meilleure valeur atteignable :
Ces fonctions vérifient elles aussi une équation de Bellman, où la moyenne sur les actions est remplacée par un maximum : un agent optimal choisit toujours la meilleure action. C’est l’équation de Bellman d’optimalité :
Le symbole prend la plus grande valeur sur toutes les actions possibles ; désigne l’action choisie dans l’état suivant .
Deux différences importantes avec l’équation d’espérance :
- À cause du , l’équation est non linéaire : impossible de la résoudre par une simple inversion de matrice. On la résoudra par itérations (value iteration, chapitre 4).
- Une fois connue, la politique optimale s’en déduit immédiatement : dans chaque état, on prend l’action de plus grande valeur.
Le symbole désigne l’action qui réalise le maximum (et non la valeur de ce maximum). Un résultat fondamental garantit que, dans un MDP fini, il existe toujours une politique optimale déterministe, meilleure ou égale à toutes les autres dans tous les états à la fois.
Au lac gelé, le même raisonnement donne pour : la valeur optimale du départ est le retour du plus court chemin. À titre de comparaison, si vous évaluez la politique aléatoire uniforme sur le même lac, vous obtenez une valeur de départ d’environ : en marchant au hasard, le robot tombe presque toujours dans un trou avant d’arriver.
En résumé
Section intitulée « En résumé »- est le retour espéré en partant de et en suivant ; est le retour espéré en faisant d’abord , puis en suivant .
- : la valeur d’un état est la moyenne des valeurs de ses actions.
- L’équation de Bellman d’espérance relie la valeur d’un état à celle de ses successeurs ; pour une politique fixée, c’est un système linéaire, .
- L’équation de Bellman d’optimalité remplace la moyenne sur les actions par un maximum ; elle est non linéaire et caractérise et .
- La politique optimale se lit sur : , et il en existe toujours une déterministe dans un MDP fini.
- Au lac gelé avec , , contre environ pour la politique aléatoire.
Exercices
Section intitulée « Exercices »Exercice · Passer de Q à V
facileDans un état , une politique choisit gauche avec probabilité 0,5, bas avec probabilité 0,3 et droite avec probabilité 0,2 (jamais haut). On connaît , , et .
- Calculez .
- Quelle action une politique « gloutonne » par rapport à choisirait-elle ici ?
Voir la solution
- .
- L’action de plus grande valeur est « haut » (), que la politique actuelle ne choisit jamais. Une politique gloutonne choisirait « haut » dans cet état. Nous verrons au chapitre 3 que ce changement ne peut qu’améliorer la politique.
En code
import numpy as np
actions = ["gauche", "bas", "droite", "haut"]pi = np.array([0.5, 0.3, 0.2, 0.0]) # pi(a | s)Q = np.array([0.2, 0.6, 0.5, 0.9]) # Q^pi(s, a)
print(round(pi @ Q, 3)) # V^pi(s) = somme pi * Q : 0.38print(actions[np.argmax(Q)]) # action gloutonne : hautactions <- c("gauche", "bas", "droite", "haut")pi <- c(0.5, 0.3, 0.2, 0) # pi(a | s)Q <- c(0.2, 0.6, 0.5, 0.9) # Q^pi(s, a)
sum(pi * Q) # V^pi(s) : 0.38actions[which.max(Q)] # action gloutonne : "haut"Exercice · Le couloir avec une autre politique
moyenDans le couloir A, B, G du chapitre (), on considère la politique déterministe suivante : en A aller à droite, en B aller à gauche.
- Écrivez les équations de Bellman de cette politique et résolvez-les. Qu’observez-vous ?
- Calculez pour cette politique. Qu’en concluez-vous ?
Voir la solution
- Les équations sont et . En substituant, , donc et . Le robot fait des allers-retours entre A et B sans jamais atteindre G : il ne reçoit aucune récompense.
- , puisque G est terminal. Faire une seule fois « droite » en B rapporte 1, contre en suivant la politique. Il suffit donc de changer l’action en B pour améliorer nettement la politique. C’est exactement le principe de l’amélioration de politique.
En code
import numpy as np
gamma = 0.9# Politique : A -> droite (va en B), B -> gauche (va en A). G n'est jamais atteint.P_pi = np.array([[0.0, 1.0], # depuis A : B [1.0, 0.0]]) # depuis B : AR_pi = np.array([0.0, 0.0]) # aucune récompense sur ces transitions
V = np.linalg.solve(np.eye(2) - gamma * P_pi, R_pi)print(V) # [0. 0.]
# Q(B, droite) : on atteint G (terminal, valeur 0) avec la récompense 1print(1 + gamma * 0) # 1.0 > V(B) = 0 : changer l'action en B amélioregamma <- 0.9# Politique : A -> droite (va en B), B -> gauche (va en A). G n'est jamais atteint.P_pi <- matrix(c(0, 1, 1, 0), nrow = 2, byrow = TRUE)R_pi <- c(0, 0) # aucune récompense sur ces transitions
solve(diag(2) - gamma * P_pi, R_pi) # 0 0
# Q(B, droite) : on atteint G (terminal, valeur 0) avec la récompense 11 + gamma * 0 # 1 > V(B) = 0 : changer l'action en B amélioreExercice · Vérifier l'optimalité sur le lac
difficileAu lac gelé non glissant avec , on admet que vaut 1 sur la case 62 (juste à gauche de G) et 0,9 sur la case 61 (deux cases à gauche de G). La case 54, au-dessus de 62, est un trou.
- À l’aide de l’équation de Bellman d’optimalité, calculez pour les quatre actions (la case 62 est sur la dernière ligne ; son voisin de gauche est 61, celui de droite est 63).
- Supposons maintenant le lac glissant : chaque action réussit avec probabilité et dérape vers chacune des deux directions perpendiculaires avec probabilité . On garde, pour simplifier, les valeurs et . Calculez et . Laquelle de ces deux actions paraît préférable ?
Voir la solution
-
En déterministe, :
- gauche : arrivée en 61, ;
- bas : on bute contre le bord et on reste en 62, ;
- droite : arrivée en G, ;
- haut : chute dans le trou 54, .
Le maximum vaut 1, pour « droite », ce qui est cohérent avec .
-
En glissant :
- droite : réussite vers G (1), dérapage vers le haut dans le trou (0), dérapage vers le bas qui laisse en 62 (). .
- bas : réussite qui laisse en 62 (), dérapage à gauche vers 61 (), dérapage à droite vers G (1). .
« Bas » devient préférable : il ne peut jamais faire tomber dans le trou, et il atteint G une fois sur trois par dérapage. Ces valeurs ne sont qu’un calcul d’une étape (les vraies valeurs du lac glissant sont différentes), mais elles montrent que la meilleure action dépend des probabilités de transition, et pas seulement de la case visée.
En code
gamma = 0.9V = {61: 0.9, 62: 1.0, 63: 0.0, 54: 0.0} # 63 = G et 54 = trou : terminauxissue = {"gauche": (61, 0), "bas": (62, 0), # (état d'arrivée, récompense) depuis 62 "droite": (63, 1), "haut": (54, 0)}
def q(action): s_suiv, r = issue[action] return r + gamma * V[s_suiv]
# 1. Lac déterministeprint({a: round(q(a), 3) for a in issue})# {'gauche': 0.81, 'bas': 0.9, 'droite': 1.0, 'haut': 0.0}
# 2. Lac glissant : l'action visée et ses deux perpendiculaires, 1/3 chacuneglisse = {"droite": ["droite", "haut", "bas"], "bas": ["bas", "gauche", "droite"]}for a, issues in glisse.items(): print(a, round(sum(q(b) for b in issues) / 3, 3))# droite 0.633# bas 0.903gamma <- 0.9V <- c("61" = 0.9, "62" = 1, "63" = 0, "54" = 0) # 63 = G et 54 = trou : terminaux# depuis 62 : état d'arrivée et récompense de chaque directionarrivee <- c(gauche = "61", bas = "62", droite = "63", haut = "54")recomp <- c(gauche = 0, bas = 0, droite = 1, haut = 0)q <- function(a) recomp[[a]] + gamma * V[[arrivee[[a]]]]
# 1. Lac déterministesapply(names(arrivee), q)# gauche 0.81, bas 0.9, droite 1, haut 0
# 2. Lac glissant : l'action visée et ses deux perpendiculaires, 1/3 chacuneround(mean(sapply(c("droite", "haut", "bas"), q)), 3) # droite : 0.633round(mean(sapply(c("bas", "gauche", "droite"), q)), 3) # bas : 0.903