Aller au contenu

Trouvez la politique optimale avec value iteration

Policy iteration trouve la politique optimale du lac gelé en quatre itérations seulement. Mais chacune de ces itérations contient une évaluation complète, avec ses dizaines de balayages. Faut-il vraiment attendre que l’évaluation ait convergé avant d’améliorer la politique ? Pas du tout, et c’est l’idée de value iteration : améliorer à chaque balayage.

Dans ce chapitre, vous allez construire cet algorithme, le comparer sans tricher avec policy iteration, puis faire le bilan complet du TP sur le lac gelé, y compris un résultat surprenant avec γ=1\gamma = 1. Nous terminerons par les limites de la programmation dynamique, qui motivent toute la partie 2.

Une seule mise à jour pour évaluer et améliorer

Section intitulée « Une seule mise à jour pour évaluer et améliorer »

Rappelez-vous l’équation de Bellman d’optimalité du chapitre 2. Value iteration la transforme directement en règle de mise à jour, exactement comme l’évaluation de politique le faisait avec l’équation d’espérance :

Vk+1(s)=max⁡a∑s′P(s′∣s,a)[R(s,a,s′)+γ Vk(s′)]V_{k+1}(s) = \max_{a} \sum_{s'} P(s' \mid s, a) \Big[ R(s, a, s') + \gamma \, V_k(s') \Big]

La seule différence avec l’évaluation, c’est le max⁡a\max_a à la place de la moyenne ∑aπ(a∣s)\sum_a \pi(a \mid s). On peut la lire de deux façons :

  • c’est un balayage d’évaluation où, dans chaque état, on suppose que l’agent prend la meilleure action au vu des valeurs actuelles ;
  • c’est policy iteration où l’évaluation est tronquée à un seul balayage avant chaque amélioration.

On arrête quand le plus grand changement Δ\Delta passe sous un seuil (θ=10−6\theta = 10^{-6} en TP). La politique n’apparaît nulle part pendant les itérations : on l’extrait une seule fois à la fin, en prenant l’action gloutonne par rapport à la valeur obtenue.

π∗(s)=arg⁡max⁡a∑s′P(s′∣s,a)[R(s,a,s′)+γ V(s′)]\pi^*(s) = \arg\max_{a} \sum_{s'} P(s' \mid s, a) \Big[ R(s, a, s') + \gamma \, V(s') \Big]

Pourquoi est-ce que ça converge vers V∗V^* ? Pour la même raison que l’évaluation : l’opérateur de Bellman d’optimalité est une contraction de rapport γ\gamma (en norme infinie, c’est-à-dire sur l’écart maximal entre deux états). Pour γ<1\gamma < 1, il a un unique point fixe, V∗V^*, et les itérés s’en approchent géométriquement, quel que soit le point de départ.

import numpy as np
def value_iteration(env, gamma=0.9, theta=1e-6, max_balayages=1000):
n_etats, n_actions = env.observation_space.n, env.action_space.n
V = np.zeros(n_etats)
def valeurs_actions(s):
q = np.zeros(n_actions)
for a in range(n_actions):
for p, s_suiv, r, termine in env.unwrapped.P[s][a]:
q[a] += p * (r + gamma * V[s_suiv])
return q
for k in range(max_balayages):
delta = 0.0
for s in range(n_etats):
ancien = V[s]
V[s] = valeurs_actions(s).max() # Bellman d'optimalité, en place
delta = max(delta, abs(ancien - V[s]))
if delta < theta:
print(f"Convergence après {k + 1} balayages")
break
# Extraction de la politique, une seule fois à la fin
politique = np.zeros((n_etats, n_actions))
for s in range(n_etats):
politique[s, np.argmax(valeurs_actions(s))] = 1.0
return politique, V

En TP, sur le lac 8x8 non glissant avec γ=0,9\gamma = 0{,}9, value iteration a convergé en 15 balayages. Policy iteration, elle, avait trouvé la politique optimale en 4 itérations. La réponse rédigée en TP concluait donc : « policy iteration converge plus vite, 4 itérations contre 15 ».

C’est un piège classique. Ces deux nombres ne mesurent pas la même chose ! Une itération de policy iteration contient une évaluation complète, et nous avons vu au chapitre 3 que ces évaluations avaient demandé 26, 15, 15 et 15 balayages. Le bon décompte est donc :

Policy iterationValue iteration
Itérations « externes »415
Balayages de tous les états26 + 15 + 15 + 15 = 7115
Améliorations de politique41 (l’extraction finale)

Le coût réel d’un balayage est le même dans les deux cas : pour chaque état, parcourir chaque action et chaque transition. En balayages, value iteration est ici près de cinq fois moins coûteuse que policy iteration. La conclusion honnête n’est donc pas celle du TP.

020406080balayages de tous les étatsγ = 0,8γ = 0,9γ = 0,9517151547 balayages152615151571 balayages153715151582 balayages15policy iteration (un bloc par évaluation)value iteration
Comptées en balayages, les « 4 itérations » de policy iteration pèsent 71 balayages contre 15 pour value iteration ; l'écart grandit avec γ à cause de la première évaluation.

Cela ne veut pas dire que value iteration gagne toujours. Quand γ\gamma est très proche de 1 et que l’espace d’états est grand, value iteration peut demander énormément de balayages, alors que policy iteration trouve la bonne politique en très peu d’améliorations. Le tableau suivant résume les deux approches.

Policy iterationValue iteration
Équation utiliséeBellman d’espérance, puis argmaxBellman d’optimalité (max dans la mise à jour)
Nombre d’itérations externesfaibleplus élevé
Coût d’une itérationélevé (évaluation complète)un seul balayage
Critère d’arrêtla politique ne change plusΔ<θ\Delta < \theta
Politiquedisponible à chaque itérationextraite à la fin

Entre les deux extrêmes, on peut évaluer une politique avec quelques balayages seulement avant de l’améliorer (on parle de policy iteration modifiée). Toutes ces variantes sont des cas de l’itération généralisée sur les politiques (generalized policy iteration) : évaluation et amélioration alternent, plus ou moins poussées, et se tirent mutuellement vers l’optimum. Ce schéma reviendra dans toute la partie 2.

V = valeur de π (évaluation)π = glouton(V) (amélioration)V*, π*départ policy iteration : chaque flècheverticale va jusqu’à la droite violettevalue iteration : un seul balayage,la flèche s’arrête en chemin
L'itération généralisée : chaque évaluation rapproche V de la valeur de π, chaque amélioration rend π gloutonne par rapport à V, et le processus converge vers le seul point qui satisfait les deux.

Voici les résultats du TP, pour les quatre valeurs de γ\gamma testées (lac 8x8 non glissant).

γ\gammaÉvaluation de la politique aléatoirePolicy iterationValue iteration
0,817 balayages3 itérations (17 + 15 + 15 balayages)15 balayages
0,926 balayages4 itérations (26 + 15 + 15 + 15)15 balayages
0,9537 balayages4 itérations (37 + 15 + 15 + 15)15 balayages
1,071 balayagesplafond de 200 itérations atteint15 balayages

Les deux méthodes donnent-elles la même politique ?

Section intitulée « Les deux méthodes donnent-elles la même politique ? »

Oui : pour γ=0,9\gamma = 0{,}9, policy iteration et value iteration renvoient la même politique. C’est attendu, puisque les deux convergent vers V∗V^* et en extraient une politique par le même argmax (qui prend la première action en cas d’égalité). En général, s’il y a plusieurs actions optimales dans un état, deux méthodes peuvent renvoyer des politiques différentes mais également optimales, avec la même fonction de valeur.

Voici la fonction de valeur optimale pour γ=0,9\gamma = 0{,}9 (valeurs recalculées et arrondies ; les trous et l’objectif valent 0) :

0.254 0.282 0.314 0.349 0.387 0.430 0.478 0.531
0.282 0.314 0.349 0.387 0.430 0.478 0.531 0.590
0.314 0.349 0.387 H 0.478 0.531 0.590 0.656
0.349 0.387 0.430 0.478 0.531 H 0.656 0.729
0.314 0.349 0.387 H 0.590 0.656 0.729 0.810
0.282 H H 0.590 0.656 0.729 H 0.900
0.314 H 0.478 0.531 H 0.810 H 1.000
0.349 0.387 0.430 H 0.810 0.900 1.000 G

Chaque valeur est une puissance de 0,9 : une case à dd pas de l’objectif vaut 0,9d−10{,}9^{d-1}. La case de départ, à 14 pas, vaut 0,913≈0,2540{,}9^{13} \approx 0{,}254. La carte se lit comme une carte de distances à l’objectif, déformée par les trous.

HHHHHHHHHHG0,2540,2820,3140,3490,3870,4300,4780,5310,2820,3140,3490,3870,4300,4780,5310,5900,3140,3490,3870,4780,5310,5900,6560,3490,3870,4300,4780,5310,6560,7290,3140,3490,3870,5900,6560,7290,8100,2820,5900,6560,7290,9000,3140,4780,5310,8101,0000,3490,3870,4300,8100,9001,000V* pour γ = 0,9 (nuances) et action optimale extraite par argmax (flèches) ;le chemin surligné part de S et atteint G en 14 pas
Les mêmes valeurs que le tableau, en nuances : elles croissent vers G par puissances de 0,9. Les flèches (argmax) descendent le gradient de distance et forment le chemin de 14 pas depuis S.

Pourquoi value iteration converge-t-elle toujours en 15 balayages ?

Section intitulée « Pourquoi value iteration converge-t-elle toujours en 15 balayages ? »

Le nombre de balayages de value iteration ne dépend pas de γ\gamma ici. Le lac est déterministe : la valeur d’une case est fixée dès que celle de la case suivante sur le meilleur chemin l’est. Comme au chapitre 3, l’information remonte depuis l’objectif d’environ une case par balayage, le chemin le plus long vers G fait 14 pas, et le 15e balayage constate que plus rien ne bouge. La valeur de γ\gamma change les nombres (0,9 puissance d−1d-1 ou 0,8 puissance d−1d-1), pas le nombre d’étapes pour les obtenir.

En TP, relancer policy iteration à partir de la politique optimale a donné une seule itération (une évaluation de 15 balayages) : l’amélioration redonne la même politique, et l’algorithme s’arrête aussitôt. C’est rassurant, et c’est une propriété utile en pratique : si l’environnement change un peu, repartir de l’ancienne politique (démarrage à chaud, warm start) coûte beaucoup moins cher que repartir de zéro.

Avec γ=1\gamma = 1, policy iteration n’a jamais déclaré la politique stable : le plafond de 200 itérations a été atteint, avec des évaluations de 3, 4 et 5 balayages qui se répétaient en boucle. Que s’est-il passé ?

En rejouant le calcul pas à pas, on comprend le mécanisme. Sans actualisation, atteindre l’objectif rapporte 1 quelle que soit la durée du trajet. Après la deuxième évaluation, toutes les cases depuis lesquelles G est accessible valent donc exactement 1. Mais alors, dans une telle case, pousser contre le mur ou reculer vaut aussi 1 : on reste sur une case qui vaut 1 ! Beaucoup d’actions sont à égalité, et np.argmax prend la première, « gauche ». La nouvelle politique va à gauche presque partout, se coince contre les bords ou tourne en rond, et n’atteint plus jamais l’objectif : sa valeur retombe à 0. L’amélioration suivante ne répare que quelques cases proches de G (les seules qui valent encore 1), et le même mécanisme se reproduit : les évaluations courtes de 3, 4 et 5 balayages s’expliquent ainsi, puisque presque toutes les valeurs sont nulles.

Value iteration n’est pas épargnée. Elle converge bien, en 15 balayages, vers la bonne valeur (V∗(S)=1V^*(S) = 1). Mais si l’on déroule la politique qu’elle extrait avec γ=1\gamma = 1, le robot parti de S reste bloqué et n’atteint jamais G : la valeur est juste, la politique extraite par argmax ne l’est pas.

HHHHHHHHHHGγ = 0,9 : G atteint en 14 pasHHHHHHHHHHG← S pousse contre le bordγ = 1 : bloqué contre le murdans les deux cas, value iteration converge en 15 balayages ;avec γ = 1, V*(S) = 1 mais l’argmax prend « gauche » à égalité
Même algorithme, même nombre de balayages. Avec γ = 1, toutes les actions qui ne tombent pas dans un trou valent 1 : l'argmax prend « gauche » à égalité, et le robot reste en S.

Dernière question du TP : pourquoi toutes ces méthodes exigent-elles de connaître PP et RR, et qu’est-ce que cela implique ?

Chaque mise à jour calcule une espérance exacte : ∑s′P(s′∣s,a)[… ]\sum_{s'} P(s' \mid s, a) [\dots]. Sans les probabilités de transition, impossible de faire cette somme. Et l’amélioration de politique a besoin de comparer les actions via QQ, donc de savoir où chaque action peut mener. D’où plusieurs limites :

  • Le modèle est rarement connu. Un robot réel, un marché, un joueur humain : personne ne fournit les probabilités de transition. FrozenLake est un cas d’école où env.unwrapped.P donne tout.
  • Chaque balayage parcourt tous les états. Pour chaque état et chaque action, il faut sommer sur les états suivants, soit jusqu’à ∣S∣2∣A∣\lvert S \rvert^2 \lvert A \rvert opérations par balayage. C’est trivial pour 64 cases, impossible pour les positions d’un jeu de go (plus de 1017010^{170}).
  • La malédiction de la dimension. Si l’état est décrit par plusieurs variables, leur nombre de combinaisons explose. Un état continu (une position, une vitesse) doit être discrétisé, ce qui aggrave encore le problème.

Les réponses à ces limites structurent la suite du cours : apprendre à partir d’échantillons au lieu de calculer des espérances exactes (Monte-Carlo, différences temporelles, Q-learning), et approcher VV ou QQ par une fonction paramétrée, typiquement un réseau de neurones (deep RL). Une troisième voie, l’apprentissage avec modèle (model-based RL), consiste à apprendre PP et RR à partir des interactions, puis à planifier avec le modèle appris.

  • Value iteration applique l’équation de Bellman d’optimalité balayage après balayage, puis extrait la politique optimale par un argmax final.
  • Elle converge vers V∗V^* car l’opérateur d’optimalité est une contraction de rapport γ\gamma.
  • En TP : 15 balayages pour value iteration, contre 4 itérations mais 71 balayages pour policy iteration ; il faut comparer en balayages, pas en itérations.
  • Les deux méthodes ont donné la même politique ; partir de la politique optimale ne demande qu’une itération de policy iteration.
  • Avec γ=1\gamma = 1, les égalités entre actions ont fait échouer le critère d’arrêt de policy iteration (plafond de 200 itérations) : rien ne pousse l’agent à progresser.
  • La programmation dynamique exige le modèle et un balayage de tous les états : elle ne passe pas à l’échelle, d’où les méthodes sans modèle et l’approximation de fonctions.

Exercice · Compter le coût réel

facile

À partir des résultats du TP, calculez le nombre total de balayages de policy iteration pour γ=0,8\gamma = 0{,}8 et pour γ=0,95\gamma = 0{,}95, et comparez à value iteration. Qu’en concluez-vous ?

Voir la solution
  • γ=0,8\gamma = 0{,}8 : 17+15+15=4717 + 15 + 15 = 47 balayages pour policy iteration, contre 15 pour value iteration (environ 3 fois moins).
  • γ=0,95\gamma = 0{,}95 : 37+15+15+15=8237 + 15 + 15 + 15 = 82 balayages, contre 15 (plus de 5 fois moins).

Sur ce lac déterministe, value iteration est toujours la moins coûteuse en balayages, et l’écart grandit avec γ\gamma, parce que la première évaluation (celle de la politique aléatoire) devient de plus en plus longue.

En code

# Balayages des évaluations successives de policy iteration (résultats du TP)
policy_iteration = {0.8: [17, 15, 15], 0.9: [26, 15, 15, 15], 0.95: [37, 15, 15, 15]}
value_iteration = 15
for gamma, balayages in policy_iteration.items():
total = sum(balayages)
print(gamma, total, round(total / value_iteration, 1))
# 0.8 47 3.1
# 0.9 71 4.7
# 0.95 82 5.5

Exercice · Value iteration sur un lac glissant miniature

moyen

Un couloir A, B, G : depuis A, l’action « droite » mène en B avec probabilité 0,8 et laisse en A avec probabilité 0,2 ; l’action « rester » laisse en A. Depuis B, « droite » atteint G (récompense 1) avec probabilité 0,8 et laisse en B avec probabilité 0,2 ; « rester » laisse en B. On prend γ=0,9\gamma = 0{,}9 et on part de V=(0,0)V = (0, 0).

  1. Faites deux balayages synchrones de value iteration.
  2. Calculez la valeur exacte V∗(B)V^*(B) et V∗(A)V^*(A) en résolvant les équations de Bellman d’optimalité (en supposant que « droite » est optimale partout).
Voir la solution
  1. Balayage 1 : en A, droite vaut 0,8×0,9×0+0,2×0,9×0=00{,}8 \times 0{,}9 \times 0 + 0{,}2 \times 0{,}9 \times 0 = 0 et rester vaut 0, donc V1(A)=0V_1(A) = 0 ; en B, droite vaut 0,8×1+0,2×0,9×0=0,80{,}8 \times 1 + 0{,}2 \times 0{,}9 \times 0 = 0{,}8, donc V1(B)=0,8V_1(B) = 0{,}8. Balayage 2 : en A, droite vaut 0,8×0,9×0,8+0,2×0,9×0=0,5760{,}8 \times 0{,}9 \times 0{,}8 + 0{,}2 \times 0{,}9 \times 0 = 0{,}576 ; en B, droite vaut 0,8+0,2×0,9×0,8=0,9440{,}8 + 0{,}2 \times 0{,}9 \times 0{,}8 = 0{,}944. Donc V2=(0,576,  0,944)V_2 = (0{,}576, \; 0{,}944).
  2. En B : V∗(B)=0,8+0,18 V∗(B)V^*(B) = 0{,}8 + 0{,}18 \, V^*(B), donc V∗(B)=0,8/0,82≈0,976V^*(B) = 0{,}8 / 0{,}82 \approx 0{,}976. En A : V∗(A)=0,72 V∗(B)+0,18 V∗(A)V^*(A) = 0{,}72 \, V^*(B) + 0{,}18 \, V^*(A), donc V∗(A)=0,72×0,976/0,82≈0,857V^*(A) = 0{,}72 \times 0{,}976 / 0{,}82 \approx 0{,}857. On vérifie que « rester » est moins bon : en B, il vaut 0,9×0,976≈0,878<0,9760{,}9 \times 0{,}976 \approx 0{,}878 < 0{,}976. Avec l’aléa, la convergence n’est plus finie en quelques balayages : les valeurs s’approchent de la limite géométriquement.

En code

import numpy as np
gamma = 0.9
def balayage(V):
"""Un balayage synchrone de value iteration : renvoie V et Q (actions : droite, rester)."""
VA, VB = V
qA = [0.8 * gamma * VB + 0.2 * gamma * VA, gamma * VA]
qB = [0.8 * 1 + 0.2 * gamma * VB, gamma * VB] # G terminal : valeur 0
return np.array([max(qA), max(qB)]), (qA, qB)
V = np.zeros(2)
for k in range(1, 3):
V, _ = balayage(V)
print(k, V.round(3))
# 1 [0. 0.8]
# 2 [0.576 0.944]
for k in range(500): # on itère jusqu'à convergence
V, (qA, qB) = balayage(V)
print(V.round(3), round(qB[1], 3)) # [0.857 0.976], rester en B : 0.878
print(round(0.8 / 0.82, 3), round(0.72 * (0.8 / 0.82) / 0.82, 3)) # 0.976 0.857 (formules exactes)

Exercice · Réparer policy iteration pour γ = 1

difficile

Reprenez la fonction amelioration_politique du chapitre 3. Modifiez-la pour qu’elle ne change l’action d’un état que si une autre action est strictement meilleure (de plus de 10−910^{-9}) que l’action actuelle. Expliquez pourquoi cette modification règle le problème observé avec γ=1\gamma = 1.

Voir la solution
def amelioration_prudente(env, V, politique, gamma=1.0, tol=1e-9):
n_etats, n_actions = env.observation_space.n, env.action_space.n
nouvelle = politique.copy()
for s in range(n_etats):
q = np.zeros(n_actions)
for a in range(n_actions):
for p, s_suiv, r, termine in env.unwrapped.P[s][a]:
q[a] += p * (r + gamma * V[s_suiv])
actuelle = np.argmax(politique[s]) # l'action (ou la plus probable) de la politique actuelle
meilleure = np.argmax(q)
if q[meilleure] > q[actuelle] + tol: # on ne change que si c'est strictement mieux
nouvelle[s] = 0.0
nouvelle[s, meilleure] = 1.0
return nouvelle

(Pour la toute première amélioration, qui part de la politique aléatoire, on peut garder la version d’origine afin d’obtenir une politique déterministe.)

Avec γ=1\gamma = 1, le problème venait des égalités : dès qu’une politique atteint l’objectif depuis toutes les cases possibles, toutes ces cases valent 1, et np.argmax remplaçait l’action actuelle par « gauche », qui vaut aussi 1 sur le papier mais fait tourner en rond. Avec la version prudente, l’action actuelle, qui vaut déjà le maximum, est conservée : l’amélioration renvoie exactement la même politique et l’algorithme s’arrête. Le théorème d’amélioration reste valable, puisqu’on ne change une action que pour une action strictement meilleure.

En code

import numpy as np
# Couloir A (0), B (1), G (2, terminal), gamma = 1 ; actions 0 = gauche, 1 = droite
suivant = np.array([[0, 1], [0, 2]])
recompense = np.array([[0.0, 0.0], [0.0, 1.0]])
def evaluer(pol, balayages=200):
V = np.zeros(3)
for _ in range(balayages): # évaluation en place (gamma = 1)
for s in range(2):
V[s] = recompense[s, pol[s]] + V[suivant[s, pol[s]]]
return V
def policy_iteration(prudente, n_iter=7):
pol = np.array([0, 0]) # résultat de la 1re amélioration depuis l'aléatoire
historique = ["gg"] # (action en A, action en B), g = gauche, d = droite
for _ in range(n_iter):
Q = recompense + evaluer(pol)[suivant]
nouvelle = Q.argmax(axis=1) # égalité : la première action (gauche)
if prudente: # ne change que si c'est strictement mieux
garder = Q.max(axis=1) <= Q[[0, 1], pol] + 1e-9
nouvelle = np.where(garder, pol, nouvelle)
if np.array_equal(nouvelle, pol):
return " -> ".join(historique) + " : stable"
pol = nouvelle
historique.append("".join("gd"[a] for a in pol))
return " -> ".join(historique) + " : jamais stable"
print(policy_iteration(prudente=False))
# gg -> gd -> dd -> gg -> gd -> dd -> gg -> gd : jamais stable
print(policy_iteration(prudente=True))
# gg -> gd -> dd : stable