Formuler un problème, explorer un espace d’états
Département de Mathématiques et Informatique, Faculté des Sciences et Techniques, Université Cheikh Anta Diop de Dakar
Ce que la séance construit
Résoudre un problème en cherchant : formuler le problème comme un espace d’états, l’explorer avec une recette commune, puis comparer cinq algorithmes sur le trajet Dakar \(\to\) Touba ; concevoir une heuristique ; et, quand seul l’état final compte, la recherche locale.
D’où l’on vient, où l’on va
Amont. L’agent fondé sur des buts (Séance 1) : il choisit ses actions selon ce qu’elles lui permettent d’atteindre. La logique (Séance 2) lui permet de déduire où sont les dangers ; il lui reste à trouver une suite d’actions qui mène au but.
Aval. On suppose ici un monde connu avec certitude ; la Séance 5 lève cette hypothèse (probabilités, Bayes), la Séance 6 combine recherche et incertitude. La recherche locale annonce la descente de gradient (Séance 6, puis cours de Machine Learning).
Le trajet de la séance
Fatou et sa famille partent de Dakar pour le Magal de Touba. Aucune route ne relie directement les deux villes : il faut choisir une suite de routes. Un GPS ne « voit » pas le meilleur itinéraire : il dispose d’une carte et l’explore méthodiquement, avant de partir.
L’idée
Quand le monde est observable, déterministe et connu, un agent fondé sur des buts peut chercher une solution en simulant les actions sur un modèle du monde, puis exécuter la suite d’actions trouvée. Le même schéma sert aux itinéraires, aux jeux, à la planification et à la démonstration de théorèmes.
Lecture. Toute la séance compare des stratégies d’exploration sur ce même réseau ; elles diffèrent par le chemin trouvé, et par ce qu’elles coûtent en temps et en mémoire.
Réseau simplifié : coordonnées GPS, distances routières arrondies.
| route | km |
|---|---|
| Dakar – Rufisque | 26 |
| Rufisque – Thiès | 45 |
| Dakar – Mbour | 83 |
| Mbour – Thiès | 64 |
| Thiès – Tivaouane | 24 |
| Tivaouane – Kébémer | 70 |
| Kébémer – Louga | 40 |
| Louga – Saint-Louis | 73 |
| Thiès – Diourbel | 80 |
| Diourbel – Touba | 48 |
| Touba – Louga | 100 |
| Mbour – Diourbel | 120 |
10 villes, 12 routes à double sens.
Les composants d’un problème de recherche
\[ \mathcal P=\big(\mathcal S,\ s_0,\ \mathrm{Actions},\ \mathrm{Résultat},\ c,\ \mathrm{EstBut}\big) \]
\(\mathcal S\) : les états ; \(s_0\) : l’état initial ; \(\mathrm{Actions}(s)\) : les actions applicables en \(s\) ; \(\mathrm{Résultat}(s,a)\) : le modèle de transition ; \(c(s,a,s')>0\) : le coût d’action ; \(\mathrm{EstBut}(s)\) : vrai si \(s\) est un état but.
Sur le réseau
\(\mathcal S\) : les 10 villes ; \(s_0=\) Dakar ; \(\mathrm{Actions}(\text{Thiès})\) : les 4 routes qui partent de Thiès ; \(\mathrm{Résultat}(\text{Thiès},\text{vers Diourbel})=\) Diourbel ; \(c\) : la longueur de la route en km ; \(\mathrm{EstBut}(s)\) : \(s=\) Touba.
Lecture. États, actions et transitions forment l’espace d’états : un graphe dont les sommets sont les villes. Formuler, c’est abstraire : l’état « à Thiès » oublie le carburant, la météo, le péage.
Définitions
Une solution est une suite d’actions de \(s_0\) à un but ; son coût, le coût de chemin, est la somme des coûts d’action. Une solution optimale a le plus petit coût, noté \(C^*\).
Coûts positifs
Tous les algorithmes de la séance supposent \(c>0\) : avec un coût négatif, un cycle pourrait faire baisser le coût indéfiniment.
| chemin sans cycle | routes | km |
|---|---|---|
| Dakar, Rufisque, Thiès, Diourbel, Touba | 4 | 199 |
| Dakar, Mbour, Diourbel, Touba | 3 | 251 |
| Dakar, Mbour, Thiès, Diourbel, Touba | 4 | 275 |
| Dakar, Rufisque, Thiès, Mbour, Diourbel, Touba | 5 | 303 |
| Dakar, …, Tivaouane, Kébémer, Louga, Touba | 6 | 305 |
| Dakar, Mbour, Thiès, …, Louga, Touba | 6 | 381 |
| Dakar, Mbour, Diourbel, Thiès, …, Touba | 7 | 517 |
Lecture. \(C^*=199\) km, en 4 routes ; le chemin par Mbour n’a que 3 routes mais fait 251 km. Le moins de routes n’est pas le moins de km : cette tension sépare les algorithmes qui suivent.
Les deux premiers niveaux de l’arbre de recherche issu de Dakar ; en orange, les états déjà rencontrés.
Lecture. L’arbre déroule des chemins : Dakar y revient par un aller-retour (un cycle), Thiès par deux chemins (un chemin redondant). Avec les cycles, l’arbre est infini, alors que l’espace d’états a 10 sommets.
ROUTES = [("Dakar", "Rufisque", 26), ("Rufisque", "Thies", 45),
("Dakar", "Mbour", 83), ("Mbour", "Thies", 64),
("Thies", "Tivaouane", 24), ("Tivaouane", "Kebemer", 70),
("Kebemer", "Louga", 40), ("Louga", "Saint-Louis", 73),
("Thies", "Diourbel", 80), ("Diourbel", "Touba", 48),
("Touba", "Louga", 100), ("Mbour", "Diourbel", 120)]
G = {} # G[ville][voisine] = km
for a, b, km in ROUTES: # une route se parcourt dans les deux sens
G.setdefault(a, {})[b] = km
G.setdefault(b, {})[a] = km
def voisins(e): # successeurs dans l'ordre alphabetique
return sorted(G[e].items())
print(len(G), sum(len(v) for v in G.values()) // 2)
print(voisins("Thies"))Résultat \(\rightarrow\)
10 12
[('Diourbel', 80), ('Mbour', 64), ('Rufisque', 45), ('Tivaouane', 24)]
Lecture. Un dictionnaire de dictionnaires : 10 villes, 12 routes (chacune rangée une fois par sens). Les successeurs sortent dans l’ordre alphabétique : c’est la convention de tous les algorithmes de la séance.
Énoncé
Cheikh, coursier à vélo, va de la Poste du Plateau (P) à Ouakam (O) ; temps en minutes.
Correction
La frontière
La frontière est l’ensemble des nœuds générés mais pas encore développés. Développer un nœud, c’est générer ses successeurs ; on n’ajoute à la frontière que ceux dont l’état n’est pas déjà atteint. Comme un éclaireur dans le brouillard : il voit la lisière de ce qu’il a découvert, et choisit où avancer.
Recette générique
Lecture. Tous les algorithmes de la séance suivent cette recette ; seule la règle de retrait change : file, pile, ou file de priorité.
class Noeud:
def __init__(self, etat, parent=None, g=0):
self.etat, self.parent, self.g = etat, parent, g
def __lt__(self, autre): # a priorite egale : ordre alphabetique
return self.etat < autre.etat
def chemin(n): # remonter les parents jusqu'a la racine
etats = []
while n is not None:
etats.append(n.etat); n = n.parent
return etats[::-1]
a = Noeud("Dakar"); b = Noeud("Rufisque", a, 26); c = Noeud("Thies", b, b.g + 45)
print(chemin(c), c.g)Résultat \(\rightarrow\)
['Dakar', 'Rufisque', 'Thies'] 71
Lecture. Un nœud porte un état, son parent et le coût \(g\) depuis la racine : Thiès, parent Rufisque, \(g=26+45=71\). Conventions : à priorité égale, ordre alphabétique ; but testé à la sortie de la frontière ; les « nœuds développés » comptés ensuite sont les nœuds sortis de la frontière, le but compris.
Paramètres
\(b\) : le facteur de branchement, nombre maximal de successeurs ; \(d\) : nombre d’actions de la solution la moins profonde ; \(m\) : longueur maximale d’un chemin.
Sur le réseau
\(b=4\) (Thiès) ; \(d=3\) (par Mbour) ; \(m=8\) routes sans cycle depuis Dakar, \(\infty\) avec les cycles.
Nœuds au niveau \(d\) d’un arbre de facteur \(b\) (échelle log).
Lecture. Chaque niveau multiplie par \(b\) : avec \(b=10\), un million de nœuds au niveau 6, mille milliards au niveau 12. C’est le défi central de la recherche.
Complétude, optimalité
Un algorithme est complet s’il trouve une solution quand il en existe une ; il est optimal s’il trouve une solution de coût \(C^*\).
Temps, espace
Le temps compte les nœuds générés ou développés ; l’espace compte les nœuds gardés en mémoire en même temps (frontière et atteints). Les deux s’écrivent avec \(b\), \(d\), \(m\) et, pour les coûts, \(C^*\) et le plus petit coût d’action \(\varepsilon\).
Temps et espace ne vont pas ensemble
Un algorithme peut visiter beaucoup de nœuds en n’en gardant que peu : c’est toute la différence entre BFS et DFS.
L’idée
La recherche en largeur {(breadth-first search, BFS)} range la frontière dans une file FIFO : le premier nœud entré est le premier sorti, comme au guichet de la gare routière. Elle sort donc tous les nœuds à 1 route de la racine, puis tous ceux à 2 routes, et ainsi de suite.
Ce qu’elle garantit
Le premier but sorti est à la plus petite profondeur : BFS minimise le nombre d’actions. Elle n’est optimale en coût que si toutes les actions coûtent autant. Elle est complète si \(b\) est fini.
Lecture. Ici, BFS cherchera le chemin le moins long en routes, sans regarder les km. Le pas à pas qui suit montre le code, la file et la carte à chaque tour de boucle.
frontière : Dakar
frontière : Mbour Rufisque
frontière : Rufisque Diourbel Thiès
frontière : Diourbel Thiès
frontière : Thiès Touba
frontière : Touba Tivaouane
frontière : Tivaouane
frontière : Tivaouane
Sortie \(\rightarrow\)
['Dakar', 'Mbour', 'Diourbel', 'Touba'] 251
Rappel : la somme géométrique
\[ 1+b+\dots+b^d=\frac{b^{d+1}-1}{b-1}\leq2\,b^d (b\geq2) \]
Le dernier niveau domine : \(b=10\), \(d=5\) : \(111\,111\) nœuds, dont \(100\,000\) au niveau 5.
Temps et espace \(O(b^d)\) si le but est testé à la génération, \(O(b^{d+1})\) s’il l’est à la sortie (convention de la séance) : tout nœud généré reste en mémoire.
| \(d\) | nœuds | temps | mémoire |
|---|---|---|---|
| 4 | \(11\,111\) | \(11{,}1\) ms | \(11{,}1\) Mo |
| 6 | \(1{,}11\times10^6\) | \(1{,}11\) s | \(1{,}11\) Go |
| 8 | \(1{,}11\times10^8\) | \(1{,}85\) min | \(111\) Go |
| 10 | \(1{,}11\times10^{10}\) | \(3{,}09\) h | \(11{,}1\) To |
| 12 | \(1{,}11\times10^{12}\) | \(12{,}9\) jours | \(1{,}11\) Po |
| 14 | \(1{,}11\times10^{14}\) | \(3{,}52\) ans | \(111\) Po |
\(b=10\), un million de nœuds par seconde, 1 ko par nœud.
Lecture. À \(d=8\), deux minutes de calcul mais 111 Go de mémoire : la mémoire sature bien avant la patience.
Énoncé
BFS de P à O ; successeurs alphabétiques, but testé à la sortie.
Correction
L’idée
La recherche en profondeur {(depth-first search, DFS)} range la frontière dans une pile LIFO : le dernier nœud empilé sort le premier, comme l’assiette du dessus. Elle descend le plus loin possible, et ne revient en arrière qu’au bout d’une impasse.
Convention
On écarte seulement les états déjà présents sur le chemin courant (recherche arborescente). Les successeurs sont empilés en ordre inverse : le premier dans l’ordre alphabétique se retrouve au sommet et sort d’abord.
Lecture. DFS ne regarde ni les coûts ni la profondeur : son résultat dépend entièrement de l’ordre des successeurs.
pile (sommet à droite) : Dakar
pile (sommet à droite) : Rufisque Mbour
pile (sommet à droite) : Rufisque Thiès Diourbel
pile (sommet à droite) : Rufisque Thiès Touba Thiès
pile (sommet à droite) : Rufisque Thiès Touba Tivaouane Rufisque
pile (sommet à droite) : Rufisque Thiès Touba Tivaouane
pile (sommet à droite) : Rufisque Thiès Touba Kébémer
pile (sommet à droite) : Rufisque Thiès Touba Louga
pile (sommet à droite) : Rufisque Thiès Touba Touba Saint-Louis
pile (sommet à droite) : Rufisque Thiès Touba Touba
pile (sommet à droite) : Rufisque Thiès Touba
pile (sommet à droite) : Rufisque Thiès Touba
Sortie \(\rightarrow\)
['Dakar', 'Mbour', 'Diourbel', 'Thies', 'Tivaouane', 'Kebemer', 'Louga', 'Touba'] 517
Lecture. Initialisation (ligne 8) : la pile contient la racine Dakar.
Lecture. Tour 1 : Dakar sort (le plus récent) ; Rufisque et Mbour sont empilés, le premier dans l’ordre alphabétique au sommet.
Lecture. Tour 2 : Mbour sort (le plus récent) ; Thiès et Diourbel sont empilés, le premier dans l’ordre alphabétique au sommet.
Lecture. Tour 3 : Diourbel sort (le plus récent) ; Touba et Thiès sont empilés, le premier dans l’ordre alphabétique au sommet.
Lecture. Tour 4 : Thiès sort (le plus récent) ; Tivaouane et Rufisque sont empilés, le premier dans l’ordre alphabétique au sommet.
Lecture. Tour 5 : Rufisque sort ; toutes ses voisines sont sur le chemin courant : impasse, rien n’est empilé.
Lecture. Tour 6 : Tivaouane sort (le plus récent) ; Kébémer est empilé.
Lecture. Tour 7 : Kébémer sort (le plus récent) ; Louga est empilé.
Lecture. Tour 8 : Louga sort (le plus récent) ; Touba et Saint-Louis sont empilés, le premier dans l’ordre alphabétique au sommet.
Lecture. Tour 9 : Saint-Louis sort ; toutes ses voisines sont sur le chemin courant : impasse, rien n’est empilé.
Lecture. Tour 10 : Touba sort du sommet de la pile ; c’est le but (ligne 12).
Lecture. Ligne 18 : 517 km, en 7 routes : DFS rend le premier chemin trouvé, pas le meilleur.
Mémoire \(O(bm)\), en recherche arborescente seulement
La pile contient le chemin courant (au plus \(m\) nœuds) et, pour chacun, ses frères en attente (au plus \(b-1\)) : \(b=10\), \(m=16\) donnent 161 nœuds, contre \(10^{16}\) pour BFS. Avec un ensemble global des états visités, la mémoire redevient celle de l’espace d’états. DFS n’est pas optimale (517 km ici, 305 km avec l’ordre inverse), ni complète sur un espace infini.
{Approfondissement itératif {(iterative deepening, IDS)}}
Un DFS limité à la profondeur \(\ell\), relancé pour \(\ell=0,1,2,\dots\) : complet, optimal en nombre d’actions, temps \(O(b^d)\), mémoire \(O(bd)\). Sur Dakar \(\to\) Touba : 1, 3, 6 puis 5 nœuds, soit 15 en tout, et le chemin de BFS (251 km).
Lecture. Refaire les niveaux coûte peu : avec \(b=10\), \(d=5\), IDS développe \(123\,456\) nœuds contre \(111\,111\) pour BFS, 11 % de plus.
L’idée
La recherche à coût uniforme {(uniform-cost search, UCS)} range la frontière dans une file de priorité ordonnée par \(g(n)\), le coût depuis la racine : elle développe toujours le nœud le moins cher. Une nouvelle qui partirait de Dakar par la route atteindrait les villes dans cet ordre.
Deux différences avec BFS
Un état déjà atteint est réinséré si un chemin strictement moins cher y mène (mise à jour) ; l’ancienne entrée du tas est ignorée à sa sortie (la vérification n.g > meilleur). Le but est testé à la sortie, jamais à la génération.
Lecture. La fonction meilleur_dabord du pas à pas servira aussi à la recherche gloutonne et à A* : seule la priorité \(f\) change.
tas : Dakar \(g=0\)
tas : Rufisque \(g=26\) Mbour \(g=83\)
tas : Thiès \(g=71\) Mbour \(g=83\)
tas : Mbour \(g=83\) Tivaouane \(g=95\) Diourbel \(g=151\)
tas : Tivaouane \(g=95\) Diourbel \(g=151\)
tas : Diourbel \(g=151\) Kébémer \(g=165\)
tas : Kébémer \(g=165\) Touba \(g=199\)
tas : Touba \(g=199\) Louga \(g=205\)
tas : Louga \(g=205\)
tas : Louga \(g=205\)
Sortie \(\rightarrow\)
['Dakar', 'Rufisque', 'Thies', 'Diourbel', 'Touba'] 199
Lecture. Initialisation (lignes 2–3) : le tas contient Dakar, de priorité 0.
Lecture. Tour 1 : Dakar sort (plus petit \(g=0\)) ; Rufisque (\(g=26\)), Mbour (\(g=83\)) entrent dans le tas.
Lecture. Tour 2 : Rufisque sort (plus petit \(g=26\)) ; Thiès (\(g=71\)) entre dans le tas.
Lecture. Tour 3 : Thiès sort (plus petit \(g=71\)) ; Tivaouane (\(g=95\)), Diourbel (\(g=151\)) entrent dans le tas.
Lecture. Tour 4 : Mbour sort (plus petit \(g=83\)) ; aucun voisin n’améliore son coût, le tas ne reçoit rien.
Lecture. Tour 5 : Tivaouane sort (plus petit \(g=95\)) ; Kébémer (\(g=165\)) entre dans le tas.
Lecture. Tour 6 : Diourbel sort (plus petit \(g=151\)) ; Touba (\(g=199\)) entre dans le tas.
Lecture. Tour 7 : Kébémer sort (plus petit \(g=165\)) ; Louga (\(g=205\)) entre dans le tas.
Lecture. Tour 8 : Touba sort (plus petit \(g=199\)) ; c’est le but, la fonction rend ce nœud (ligne 9).
Lecture. Dernière ligne : 199 km, 8 nœuds développés.
Théorème (coûts d’action \(\geq0\))
Quand un nœud \(n\) sort de la frontière, \(g(n)\) est le coût minimal pour atteindre son état. Le premier but qui sort coûte donc \(C^*\).
Preuve
On suppose un chemin \(P\) vers le même état, de coût \(<g(n)\). Soit \(n'\) le premier nœud de \(P\) non encore développé : son prédécesseur l’a été, donc \(n'\) est dans la frontière, avec un coût \(\leq\) coût\((P)<g(n)\) (coûts \(\geq0\)). Alors \(n'\) aurait dû sortir avant \(n\) : contradiction.
Complexité et lien avec Dijkstra
Complète si les coûts sont \(\geq\varepsilon>0\) ; temps et espace \(O(b^{1+\lfloor C^*/\varepsilon\rfloor})\) : ici \(C^*=199\), \(\varepsilon=24\), \(b=4\), borne \(4^9=262\,144\), pour 8 nœuds développés. UCS est l’algorithme de Dijkstra (1959), arrêté au but, sur un graphe généré à la demande.
Résultat \(\rightarrow\)
['Touba', 'Diourbel', 'Thies', 'Rufisque', 'Dakar'] 199
Tester le but à la génération rend 251 km
Au retour, Dakar entre dans le tas par Mbour (\(g=168+83=251\)) avant que Rufisque (\(g=173\)) ne sorte et propose \(173+26=199<251\) : mise à jour. S’arrêter dès que Dakar est généré rendrait le chemin par Mbour, 251 km. Le premier chemin généré vers le but n’est pas le moins cher.
Lecture. Le test à la sortie rend 199 km : Touba, Diourbel, Thiès, Rufisque, Dakar. Pour BFS, tester à la génération est sans risque (même profondeur) ; pour UCS et A*, non.
Énoncé
UCS de P à O, priorité \(g\) ; but testé à la sortie.
Correction
Quoi
La recette générique avec une file de priorité sur \(g\), la mise à jour des meilleurs chemins et le test de but à la sortie.
Pourquoi elle est optimale
Les nœuds sortent par \(g\) croissant et, les coûts étant positifs, aucun chemin moins cher ne peut se cacher derrière la frontière.
Quand ça échoue
Elle ignore où est le but : 8 nœuds pour Dakar \(\to\) Touba, dont Mbour, Tivaouane et Kébémer, hors du chemin. Les coûts négatifs cassent la preuve ; les coûts nuls peuvent la faire tourner sans fin.
Heuristique
Une heuristique \(h(n)\) estime le coût du meilleur chemin de l’état de \(n\) à un but, à partir de cet état seul, sans chercher ; \(h=0\) au but. On note \(h^*(n)\) le vrai coût restant. UCS ignore où est Touba ; une estimation de la distance au but peut guider la recherche.
Formule de haversine (\(R=6,371\) km, angles en radians)
\[ d=2R\,\arcsin\sqrt{\sin^2\!\Big(\frac{\varphi_2-\varphi_1}{2}\Big)+\cos\varphi_1\cos\varphi_2\,\sin^2\!\Big(\frac{\lambda_2-\lambda_1}{2}\Big)} \]
\(\varphi\) : latitude, \(\lambda\) : longitude ; la longueur de l’arc de grand cercle, plus court chemin sur la sphère. Dakar \(\to\) Touba : \(x=1{,}88\times10^{-6}+0{,}93499\times1{,}861\times10^{-4}=1{,}759\times10^{-4}\), \(d=2\times6\,371\times0{,}013263=169{,}0\) km.
GPS = {"Dakar": (14.6928, -17.4467), "Rufisque": (14.7167, -17.2667),
"Thies": (14.7910, -16.9256), "Mbour": (14.4198, -16.9640),
"Tivaouane": (14.9500, -16.8167), "Diourbel": (14.6550, -16.2314),
"Touba": (14.8500, -15.8833), "Louga": (15.6181, -16.2244),
"Kebemer": (15.3667, -16.4500), "Saint-Louis": (16.0326, -16.4818)}
def haversine(a, b, R=6371): # distance a vol d'oiseau (km)
(la1, lo1), (la2, lo2) = GPS[a], GPS[b]
p1, p2 = math.radians(la1), math.radians(la2)
dp, dl = p2 - p1, math.radians(lo2 - lo1)
x = math.sin(dp / 2)**2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2)**2
return 2 * R * math.asin(math.sqrt(x))
H = {v: round(haversine(v, "Touba")) for v in GPS}
print(round(haversine("Dakar", "Touba"), 2), H["Mbour"], H["Rufisque"])Résultat \(\rightarrow\)
169.0 126 149
| ville | Dakar | Rufisque | Thiès | Mbour | Tivaouane | Diourbel | Touba | Louga | Kébémer | Saint-Louis |
|---|---|---|---|---|---|---|---|---|---|---|
| \(h\) (km) | 169 | 149 | 112 | 126 | 101 | 43 | 0 | 93 | 84 | 146 |
| \(h^*\) (km) | 199 | 173 | 128 | 168 | 152 | 48 | 0 | 100 | 140 | 173 |
Lecture. 169,0 km à vol d’oiseau contre 199 km par la route ; Mbour (126) paraît plus proche de Touba que Rufisque (149) : ce détail décide de la recherche gloutonne.
Quoi : \(h\leq h^*\) partout
\(h\) est admissible si elle ne surestime jamais le coût restant. Le vol d’oiseau l’est : toute route est une courbe sur la sphère, et le grand cercle est la plus courte.
\(h\) en orange, \(h^*\) en bleu ; neuf villes (Touba exclue).
Lecture. Marge minimale : 5 km, à Diourbel ; l’arrondi au km (au plus \(0{,}5\) km) ne rend aucune valeur inadmissible.
tas : Dakar \(h=169\)
tas : Mbour \(h=126\) Rufisque \(h=149\)
tas : Diourbel \(h=43\) Thiès \(h=112\) Rufisque \(h=149\)
tas : Touba \(h=0\) Thiès \(h=112\) Rufisque \(h=149\)
tas : Thiès \(h=112\) Rufisque \(h=149\)
tas : Thiès \(h=112\) Rufisque \(h=149\)
Sortie \(\rightarrow\)
['Dakar', 'Mbour', 'Diourbel', 'Touba'] 251
Lecture. Initialisation (lignes 2–3) : le tas contient Dakar, de priorité 169.
Lecture. Tour 1 : Dakar sort (plus petit \(h=169\)) ; Mbour (\(h=126\)), Rufisque (\(h=149\)) entrent dans le tas.
Lecture. Tour 2 : Mbour sort (plus petit \(h=126\)) ; Diourbel (\(h=43\)), Thiès (\(h=112\)) entrent dans le tas.
Lecture. Tour 3 : Diourbel sort (plus petit \(h=43\)) ; Touba (\(h=0\)) entre dans le tas.
Lecture. Tour 4 : Touba sort (plus petit \(h=0\)) ; c’est le but, la fonction rend ce nœud (ligne 9).
Lecture. Dernière ligne : 251 km, 4 nœuds développés.
{Recherche gloutonne {(greedy best-first search)} : \(f=h\)}
On développe toujours le nœud qui paraît le plus proche du but. Rapide quand \(h\) guide bien (4 nœuds ici, contre 8 pour UCS). Non optimale ; complète en recherche en graphe sur un espace fini, mais pas en recherche arborescente, où elle peut osciller.
Ce qui la trompe
\(h\) dit ce qui reste, pas ce qui a été dépensé : Mbour est à 126 km de Touba à vol d’oiseau, mais y aller coûte 83 km, puis 120 km jusqu’à Diourbel. La route par Rufisque paraît moins prometteuse, alors qu’elle est 52 km plus courte.
Lecture. La recherche gloutonne oublie \(g\) ; UCS oubliait \(h\). A* additionne les deux.
L’idée
A* est la recette avec la priorité \(f(n)=g(n)+h(n)\) : le coût déjà payé jusqu’à \(n\), plus l’estimation de ce qui reste ; \(f(n)\) estime le coût de la meilleure solution qui passe par \(n\). Un chauffeur de taxi-brousse additionne ce que marque le compteur et la distance qui reste à vol d’oiseau.
Cas particuliers
\(h=0\) : A* est UCS. Sans \(g\) : c’est la recherche gloutonne. Publié par Hart, Nilsson et Raphael en 1968.
Lecture. Dans le code, seul le dernier argument de meilleur_dabord change : lambda g, v: g + H[v].
tas : Dakar \(f=169\)
tas : Rufisque \(f=175\) Mbour \(f=209\)
tas : Thiès \(f=183\) Mbour \(f=209\)
tas : Diourbel \(f=194\) Tivaouane \(f=196\) Mbour \(f=209\)
tas : Tivaouane \(f=196\) Touba \(f=199\) Mbour \(f=209\)
tas : Touba \(f=199\) Mbour \(f=209\) Kébémer \(f=249\)
tas : Mbour \(f=209\) Kébémer \(f=249\)
tas : Mbour \(f=209\) Kébémer \(f=249\)
Sortie \(\rightarrow\)
['Dakar', 'Rufisque', 'Thies', 'Diourbel', 'Touba'] 199
Lecture. Initialisation (lignes 2–3) : le tas contient Dakar, de priorité 169.
Lecture. Tour 1 : Dakar sort (plus petit \(f=0+169=169\)) ; Rufisque (\(f=175\)), Mbour (\(f=209\)) entrent dans le tas.
Lecture. Tour 2 : Rufisque sort (plus petit \(f=26+149=175\)) ; Thiès (\(f=183\)) entre dans le tas.
Lecture. Tour 3 : Thiès sort (plus petit \(f=71+112=183\)) ; Diourbel (\(f=194\)), Tivaouane (\(f=196\)) entrent dans le tas.
Lecture. Tour 4 : Diourbel sort (plus petit \(f=151+43=194\)) ; Touba (\(f=199\)) entre dans le tas.
Lecture. Tour 5 : Tivaouane sort (plus petit \(f=95+101=196\)) ; Kébémer (\(f=249\)) entre dans le tas.
Lecture. Tour 6 : Touba sort (plus petit \(f=199+0=199\)) ; c’est le but, la fonction rend ce nœud (ligne 9).
Lecture. Dernière ligne : 199 km, 6 nœuds développés.
Recherche arborescente : \(h\) admissible \(\Rightarrow\) A* optimal
On suppose qu’un but \(G_2\) sorte avec \(g(G_2)>C^*\). Un nœud \(n\) du chemin optimal est alors dans la frontière, et \(f(n)=g(n)+h(n)\leq g(n)+h^*(n)=C^*<g(G_2)=f(G_2)\), car \(h(G_2)=0\) : \(n\) aurait dû sortir avant \(G_2\). Contradiction.
Recherche en graphe sans réouverture : \(h\) consistante \(\Rightarrow\) A* optimal
\(h\) est consistante (ou cohérente, monotone) si \(h(n)\leq c(n,a,n')+h(n')\) pour toute action. Alors \(f\) ne décroît pas le long d’un chemin : \(f(n')=g(n)+c+h(n')\geq g(n)+h(n)=f(n)\) ; un état sort la première fois avec son coût minimal, et l’écarter ensuite est sans risque.
Lecture. Sur le chemin optimal, \(f\) vaut 169, 175, 183, 194, 199 : il croît jusqu’à \(f=C^*\). Consistante entraîne admissible.
| route | \(c\) | \(|\Delta h|\) | marge | route | \(c\) | \(|\Delta h|\) | marge |
|---|---|---|---|---|---|---|---|
| Dakar – Rufisque | 26 | 20 | 6 | Tivaouane – Kébémer | 70 | 17 | 53 |
| Rufisque – Thiès | 45 | 37 | 8 | Kébémer – Louga | 40 | 9 | 31 |
| Dakar – Mbour | 83 | 43 | 40 | Louga – Saint-Louis | 73 | 53 | 20 |
| Mbour – Thiès | 64 | 14 | 50 | Thiès – Diourbel | 80 | 69 | 11 |
| Thiès – Tivaouane | 24 | 11 | 13 | Diourbel – Touba | 48 | 43 | 5 |
| Mbour – Diourbel | 120 | 83 | 37 | Touba – Louga | 100 | 93 | 7 |
Lecture. Pour une route à double sens, les deux sens donnent \(|h(a)-h(b)|\leq c(a,b)\). Les 12 marges sont positives ; la plus faible, 5 km, est celle de Diourbel – Touba. Le vol d’oiseau vérifie l’inégalité triangulaire ; l’arrondi n’a cassé aucune marge.
| ville | \(g^*\) | \(h\) | \(f\) | A* |
|---|---|---|---|---|
| Dakar | 0 | 169 | 169 | oui |
| Rufisque | 26 | 149 | 175 | oui |
| Thiès | 71 | 112 | 183 | oui |
| Diourbel | 151 | 43 | 194 | oui |
| Tivaouane | 95 | 101 | 196 | oui |
| Touba | 199 | 0 | 199 | but |
| Mbour | 83 | 126 | 209 | non |
| Kébémer | 165 | 84 | 249 | non |
Efficacité optimale (\(h\) consistante)
A* développe tout nœud de \(f<C^*\) et aucun de \(f>C^*\). Parmi les algorithmes qui garantissent une solution optimale, développent les chemins depuis la racine et n’utilisent que la même heuristique consistante, aucun ne développe moins de nœuds qu’A*, aux égalités près.
Lecture. \(g^*\) : vrai coût depuis Dakar. Mbour et Kébémer, développés par UCS (\(g<199\)), sont évités par A* (\(f>199\)) : 6 nœuds au lieu de 8.
Énoncé : A* de P à O, \(h\) en orange
Correction
Quoi
La recette générique avec la priorité \(f=g+h\), le test de but à la sortie et la mise à jour des meilleurs chemins.
Pourquoi
Quand \(h\) est admissible, \(f(n)\) minore le coût de toute solution qui passe par \(n\) : un nœud de la solution optimale sort avant tout but trop cher. \(h\) écarte les nœuds de \(f>C^*\) que UCS développe.
Quand ça échoue
La mémoire : A* garde tous les nœuds générés, exponentiel dans le pire cas. Une heuristique qui surestime perd la garantie (section suivante). Avec \(h=0\), A* n’est que UCS.
Définition
Une relaxation d’un problème s’obtient en retirant des contraintes sur les actions. Le coût optimal du problème relâché est une heuristique admissible du problème d’origine : toute solution d’origine reste permise, donc le relâché coûte au plus autant.
Deux relaxations déjà rencontrées ou à venir
Réseau routier : on retire « suivre les routes » ; on vole en ligne droite : la distance à vol d’oiseau.
Taquin {(8-puzzle)} : une tuile passe de \(A\) à \(B\) si \(A\) et \(B\) sont voisines et si \(B\) est vide. Retirer « \(B\) vide » donne \(h_2\), la somme des distances de Manhattan ; retirer les deux conditions donne \(h_1\), le nombre de tuiles mal placées.
Lecture. Le problème relâché doit se résoudre sans recherche : sinon, calculer \(h\) coûterait autant que chercher.
Un état à 12 coups du but : tuiles mal placées en orange, distance de Manhattan de chaque tuile en bleu.
Lecture. \(h_1=7\) (seule la tuile 5 est en place), \(h_2=10\), vrai coût 12 : les deux sont admissibles. A* sort 61 nœuds avec \(h_1\), 17 avec \(h_2\) (égalités de \(f\) départagées par ordre lexicographique de l’état) ; IDS en sort \(4\,860\).
Dominance : \(h_2(n)\geq h_1(n)\) pour tout \(n\), toutes deux admissibles
Une tuile mal placée est à au moins une case de sa place : \(h_2\geq h_1\), et \(h_2\) domine \(h_1\). A* avec \(h_2\) ne développe aucun nœud de plus qu’avec \(h_1\) (\(h\) consistantes, aux nœuds de \(f=C^*\) près) : elle guide plus précisément sans compromettre l’optimalité.
Combiner des heuristiques admissibles
\[ h(n)=\max\big(h_1(n),\ h_2(n),\ \dots,\ h_k(n)\big) \]
Chaque \(h_i\) minore \(h^*\), donc leur maximum aussi : il est admissible et domine chacune. Sur l’état de la figure, \(\max(7,10)=10\).
Lecture. Une bonne heuristique est admissible, informative (proche de \(h^*\)) et rapide à calculer. \(h^*\) elle-même serait parfaite, mais la calculer revient à résoudre le problème.
| \(d\) | IDS | A*-\(h_1\) | A*-\(h_2\) |
|---|---|---|---|
| 4 | 46,6 | 5,1 | 5,0 |
| 6 | 169,8 | 8,3 | 7,1 |
| 8 | 555,1 | 14,1 | 9,6 |
| 10 | 1 704,1 | 31,2 | 15,4 |
| 12 | – | 70,7 | 24,3 |
| 16 | – | 425,6 | 90,5 |
| 20 | – | 2 403,3 | 258,9 |
Nœuds développés en moyenne ; 50 états tirés au hasard (graine 221) à distance \(d\) du but (tous pour \(d\leq6\)) ; IDS jusqu’à \(d=10\).
Échelle logarithmique.
Lecture. À \(d=20\), \(h_2\) développe 9 fois moins que \(h_1\). Facteur de branchement effectif \(b^*\) (\(N=1+b^*+\dots+(b^*)^d\), \(N\) nœuds sortis de la frontière) à \(d=20\) : \(1{,}38\) pour \(h_1\), \(1{,}21\) pour \(h_2\).
On pose \(h'(\text{Rufisque})=230\) au lieu de 149
\(230>h^*(\text{Rufisque})=173\) : \(h'\) n’est plus admissible. Après Dakar : \(f(\text{Rufisque})=26+230=256\), \(f(\text{Mbour})=83+126=209\). Mbour sort, puis Diourbel (\(203+43=246\)), puis Touba (\(f=g=251<256\)) : A* rend 251 km, en 4 nœuds, sans avoir développé Rufisque.
L’admissibilité est le verrou
Surestimer \(h(\text{Rufisque})\) fait croire que passer par Rufisque est trop cher, et A* l’écarte : le chemin optimal (199 km) n’est jamais examiné. Plus \(h\) surestime, plus A* ressemble à la recherche gloutonne.
L’état \(5,1,3,/,4,\square,2,/,7,8,6\), à 12 coups du but \(1,2,3,/,4,5,6,/,7,8,\square\)
Correction
Lecture. Les deux sont loin de 12 : les tuiles se gênent, et c’est la contrainte « case vide » que les relaxations ont retirée.
L’idée
Pour placer 8 reines sans conflit, ou bâtir un emploi du temps à l’UCAD, seul l’état final compte. La recherche locale {(local search)} garde un seul état complet et le remplace par l’un de ses voisins, selon un coût \(h\) à minimiser : pas d’arbre, une mémoire constante.
Emploi du temps
20 cours à placer dans 100 créneaux-salles : \(100^{20}=10^{40}\) affectations, hors de portée de BFS ou de A*. Déplacer un cours donne \(20\times99=1\,980\) voisins : l’état courant s’améliore pas à pas.
Le paysage du coût, en une dimension pour le dessin.
Lecture. Une descente qui ne voit que ses voisins s’arrête au premier creux (minimum local) ou sur un plateau, même si un meilleur état existe plus loin.
Hill climbing, coût \(h\) à minimiser
Les 8 reines
État : la ligne de la reine de chaque colonne, \(8^8=16\,777\,216\) états ; voisins : une reine déplacée dans sa colonne, \(8\times7=56\) ; \(h\) : nombre de paires de reines qui s’attaquent ; \(h=0\) : solution.
Lecture. Le nom vient de la version qui maximise un score ; minimiser \(h\) revient à maximiser \(-h\).
Chaque case porte le \(h\) obtenu en y déplaçant la reine de sa colonne ; en orange, le meilleur voisin.
Lecture. \(h\) : 6, puis 4, puis 2 ; le meilleur voisin vaut encore 2 : arrêt sur un plateau, sans solution.
def conflits(e):
return sum(1 for i in range(8) for j in range(i + 1, 8)
if e[i] == e[j] or abs(e[i] - e[j]) == j - i)
def voisins_r(e):
return [e[:c] + (l,) + e[c + 1:] for c in range(8)
for l in range(8) if l != e[c]]
def hill_climbing(e):
h, pas = conflits(e), 0
while h > 0:
v = min(voisins_r(e), key=conflits)
if conflits(v) >= h:
break
e, h, pas = v, conflits(v), pas + 1
return e, h, pas
print(hill_climbing((3, 6, 1, 7, 4, 3, 5, 7)))Résultat \(\rightarrow\)
((2, 6, 1, 7, 0, 3, 5, 7), 2, 2)
Lecture. Le départ de la figure : arrêt à \(h=2\) après 2 pas, sur l’état (2, 6, 1, 7, 0, 3, 5, 7).
Résultat \(\rightarrow\)
(3, 6, 1, 7, 4, 3, 5, 7) 166 4.2
| variante (mêmes 1 000 départs) | succès | pas moyens |
|---|---|---|
| hill climbing | \(16{,}6\) % | \(4{,}20\) |
| hill climbing, au plus 100 pas latéraux consécutifs | \(95{,}3\) % | \(18{,}47\) |
| hill climbing avec redémarrages aléatoires | 100 % | \(6{,}58\) lancers |
Lecture. 166 succès sur 1 000. Un pas latéral accepte un voisin de même coût pour traverser un plateau. Avec redémarrages, le nombre de lancers suit une loi géométrique d’espérance \(1/0{,}166=6{,}02\) ; mesuré : \(6{,}58\).
Un voisin \(v\) tiré au hasard, \(\Delta E=h(v)-h(e)\)
\[ P(\text{accepter }v)=\begin{cases}1 & \text{si }\Delta E\leq0\\ e^{-\Delta E/T} & \text{si }\Delta E>0\end{cases}\qquad T_k=T_0\,\alpha^k \]
\(T\) : la température, qui décroît ; \(T_0=2\), \(\alpha=0{,}995\) : \(T\) passe sous \(0{,}01\) au pas \(1\,058\). \(\Delta E=1\) : \(0{,}607\) à \(T=2\), \(0{,}368\) à \(T=1\), \(0{,}018\) à \(T=0{,}25\).
{Recuit simulé {(simulated annealing)}}
Chaud, on accepte souvent une hausse et l’on explore ; froid, on n’accepte plus que des baisses. Le nom vient de la métallurgie : un métal refroidi lentement atteint un état de basse énergie (Kirkpatrick, Gelatt et Vecchi, 1983).
À gauche, la probabilité d’accepter une hausse ; à droite, \(h\) (noir) et \(T\) (orange) au fil des pas.
Lecture. Même départ que le hill climbing (\(h=6\)) : 42 hausses acceptées, dont une jusqu’à \(h=12\), et \(h=0\) au pas 442, là où le hill climbing restait à \(h=2\).
def recuit(e, rng, T0=2.0, alpha=0.995, pas_max=3000):
h, T = conflits(e), T0
for k in range(pas_max):
if h == 0:
return e, 0, k
c = rng.randrange(8)
l = rng.randrange(7)
l = l + (l >= e[c]) # une autre ligne que l'actuelle
v = e[:c] + (l,) + e[c + 1:]
dE = conflits(v) - h
if dE <= 0 or rng.random() < math.exp(-dE / T):
e, h = v, h + dE # accepte, meme une hausse
T = T * alpha # refroidissement geometrique
return e, h, pas_max
rng = random.Random(221)
res = [recuit(e, rng) for e in departs]
ok = [k for _, h, k in res if h == 0]
print(len(ok), round(sum(ok) / len(ok), 1))Résultat \(\rightarrow\)
942 791.0
Lecture. 942 succès sur 1 000 (\(94{,}2\) %), en \(791{,}0\) pas en moyenne.
Quoi
Un état courant complet, un voisinage, un coût \(h\) ; hill climbing : toujours le meilleur voisin ; recuit : un voisin au hasard, accepté selon \(e^{-\Delta E/T}\).
Pourquoi elle passe à l’échelle, et le lien avec le ML
Sur \(16\,777\,216\) états, le hill climbing n’en évalue que 56 par pas. La descente de gradient, \(\symbfit{\theta}\leftarrow\symbfit{\theta}-\eta\nabla J(\symbfit{\theta})\), est une recherche locale dans l’espace continu des paramètres d’un modèle : elle suit la pente de la perte \(J\) (Séance 6, puis cours de Machine Learning).
Quand ça échoue
Minima locaux et plateaux : \(83{,}4\) % d’échecs pour le hill climbing. Le recuit dépend de \(T_0\), \(\alpha\) et du nombre de pas ; la recherche locale ne dit jamais qu’aucune solution n’existe.
Énoncé
Correction
| algorithme | frontière, priorité | complet | optimal (coût) | temps ; espace | km ; nœuds |
|---|---|---|---|---|---|
| BFS | file FIFO | oui (\(b\) fini) | si coûts égaux | \(O(b^{d+1})\) ; \(O(b^{d+1})\) | 251 ; 6 |
| DFS (arbre) | pile LIFO | si espace fini | non | \(O(b^m)\) ; \(O(bm)\) | 517 ; 10 |
| IDS | pile, limite \(0,1,2,\dots\) | oui (\(b\) fini) | si coûts égaux | \(O(b^d)\) ; \(O(bd)\) | 251 ; 15 |
| UCS | file de priorité, \(g\) | oui (coûts \(\geq\varepsilon>0\)) | oui | \(O(b^{1+\lfloor C^*/\varepsilon\rfloor})\) ; idem | 199 ; 8 |
| recherche gloutonne | file de priorité, \(h\) | en graphe fini | non | \(O(b^m)\) ; \(O(b^m)\) | 251 ; 4 |
| A* | file de priorité, \(g+h\) | oui (coûts \(\geq\varepsilon>0\)) | \(h\) admissible\(^{(*)}\) | exponentiel au pire ; tous les nœuds | 199 ; 6 |
| hill climbing | un état | non | non | selon le paysage ; \(O(1)\) | \(16{,}6\) % (reines) |
| recuit simulé | un état | non | non | fixé par \(T_0\), \(\alpha\) ; \(O(1)\) | \(94{,}2\) % (reines) |
Lecture. Dernière colonne : Dakar \(\to\) Touba (km ; nœuds développés), ou succès sur les 8 reines. BFS : test à la sortie (\(O(b^d)\) à la génération). \(^{(*)}\) consistante en recherche en graphe sans réouverture. Seuls UCS et A* rendent les 199 km.
Limites de A*
Une mémoire exponentielle ; une heuristique à concevoir ; des espaces trop grands (le Go : \(b\approx250\), \(d\approx150\)), où l’on combine recherche, échantillonnage et heuristiques apprises (Monte-Carlo Tree Search, AlphaGo). Lien avec le ML : l’heuristique peut s’apprendre à partir de données (AlphaGo estime la valeur d’une position par un réseau de neurones) ; quand transitions et coûts sont inconnus, l’agent apprend en interagissant : c’est l’apprentissage par renforcement (Séance 6).
À retenir
Séance 5 — L’IA qui doute
Toute cette séance supposait un monde connu avec certitude : des états observés, des actions à l’effet sûr, des coûts connus d’avance. Un médecin qui reçoit Fatou, fiévreuse en pleine saison des pluies, ne voit pas la maladie, seulement des symptômes. La Séance 5 donne à l’agent un langage pour l’incertitude : les probabilités, la probabilité conditionnelle, le théorème de Bayes, puis les réseaux bayésiens.
La Séance 4 est l’évaluation de mi-parcours, sur les Séances 1 à 3. À préparer : refaire à la main BFS, UCS et A* sur la carte de Cheikh, puis vers Saint-Louis sur le réseau de la séance.
Introduction à l’IA — Séance 3 Dr. El Hadji Bassirou TOURÉ • DMI/FST/UCAD
Lecture. Initialisation (lignes 2–3) : la file contient la racine Dakar ; Dakar est marqué atteint.
Lecture. Tour 1 : Dakar sort (le plus ancien) ; Mbour et Rufisque entrent en bout de file, parent Dakar.
Lecture. Tour 2 : Mbour sort (le plus ancien) ; Diourbel et Thiès entrent en bout de file, parent Mbour.
Lecture. Tour 3 : Rufisque sort ; ses voisines sont déjà atteintes, la file ne reçoit rien.
Lecture. Tour 4 : Diourbel sort (le plus ancien) ; Touba entre en bout de file, parent Diourbel.
Lecture. Tour 5 : Thiès sort (le plus ancien) ; Tivaouane entre en bout de file, parent Thiès.
Lecture. Tour 6 : Touba sort de la file (ligne 5) ; c’est le but, la fonction rend ce nœud (ligne 7).
Lecture. Ligne 14 : le chemin se lit par les parents ; \(g=251\) km, en 3 routes.