Esercizio - Ricerca di costo uniforme, greedy e A∗ sulla mappa della Romania (slide del corso)
In questa pagina 7
Esercizio ricavato dalle slide del corso di Intelligenza Artificiale, Ing. dell'Informazione UniPD, sulla mappa della Romania: tre esecuzioni di algoritmi a mano (costo uniforme da Sibiu a Bucarest, greedy e A∗ da Arad a Bucarest), più un caso aggiuntivo (da Lugoj a Bucarest) per vedere quando il greedy sbaglia e quando no. Teoria in Ricerca nello spazio degli stati - formulazione dei problemi e ricerca non informataUn agente risolutore di problemi è un agente basato su obiettivi con rappresentazione atomica: formula un problema (stato iniziale, azioni, modello di transizione, test dell'obiettivo, costo dei passi) e cerca una sequenza di azioni che porta a uno stato obiettivo costruendo un albero di ricerca. Le strategie non informate usano solo la definizione del problema e si distinguono per l'ordine di espansione della frontiera: ampiezza (coda FIFO), costo uniforme (coda con priorità sul costo g), profondità (pila LIFO), profondità limitata, approfondimento iterativo e ricerca bidirezionale. Si confrontano per completezza, ottimalità, tempo e spazio in funzione di b, d, m; tutte sono casi di best-first search con una diversa funzione di valutazione f.Ricerca nello spazio degli stati - formulazione dei problemi e ricerca non informata → e Ricerca informata - greedy best-first, A* ed euristicheLa ricerca informata usa una funzione euristica h(n) che stima il costo da n all'obiettivo. Greedy best-first espande il nodo con h minima (f = h): veloce ma non ottima né in generale completa. A∗ usa f = g + h e, con h ammissibile (mai sopra il costo vero) e, sul grafo con tabella dei raggiunti, consistente, è completa e ottima; il fattore di diramazione effettivo b∗ misura la qualità di h, e un'euristica che ne domina un'altra espande meno nodi. A∗ pesato (f = g + W h) rinuncia all'ottimalità per espandere meno nodi. Per i problemi in cui conta solo lo stato finale esiste la ricerca locale (hill climbing, simulated annealing, beam search, algoritmi genetici). In appendice, cenni di ricerca avversaria (minimax e potatura alfa-beta).Ricerca informata - greedy best-first, A* ed euristiche →. Ogni esecuzione è verificata con il programma in fondo.
Testo
Sulla mappa della Romania (costi in chilometri) con la distanza in linea retta da Bucarest come euristica:
- Con la ricerca a costo uniforme, trovare il cammino da Sibiu a Bucarest: ordine di espansione, frontiera a ogni passo, costo.
- Con la ricerca greedy best-first (), trovare il cammino da Arad a Bucarest. È ottimo?
- Con A∗ (), trovare il cammino da Arad a Bucarest. Quante espansioni servono? È ottimo?
- Ripetere con A∗, greedy e costo uniforme da Lugoj a Bucarest.
- Verificare che è ammissibile e consistente sulle strade della mappa.
Strade (km): Oradea–Zerind 71, Oradea–Sibiu 151, Arad–Zerind 75, Arad–Sibiu 140, Arad–Timisoara 118, Lugoj–Timisoara 111, Lugoj–Mehadia 70, Mehadia–Drobeta 75, Drobeta–Craiova 120, Craiova–Rimnicu Vilcea 146, Craiova–Pitesti 138, Rimnicu Vilcea–Sibiu 80, Fagaras–Sibiu 99, Fagaras–Bucarest 211, Pitesti–Bucarest 101, Pitesti–Rimnicu Vilcea 97, Bucarest–Giurgiu 90, Bucarest–Urziceni 85, Urziceni–Hirsova 98, Hirsova–Eforie 86, Urziceni–Vaslui 142, Vaslui–Iasi 92, Iasi–Neamt 87.
(km): Arad 366, Bucarest 0, Craiova 160, Drobeta 242, Eforie 161, Fagaras 176, Giurgiu 77, Hirsova 151, Iasi 226, Lugoj 244, Mehadia 241, Neamt 234, Oradea 380, Pitesti 100, Rimnicu Vilcea 193, Sibiu 253, Timisoara 329, Urziceni 80, Vaslui 199, Zerind 374.
Punto 1 - costo uniforme da Sibiu a Bucarest
Regola. Si estrae sempre il nodo di costo minimo; il test dell'obiettivo si fa all'estrazione. Se uno stato si raggiunge con un costo minore, il nuovo nodo prende il posto del vecchio (la voce vecchia, più costosa, non serve più).
| passo | si espande () | nodi nuovi () | frontiera dopo (ordinata per ) |
|---|---|---|---|
| 1 | Sibiu (0) | Rimnicu Vilcea 80, Fagaras 99, Arad 140, Oradea 151 | RV 80, Fagaras 99, Arad 140, Oradea 151 |
| 2 | Rimnicu Vilcea (80) | Pitesti , Craiova | Fagaras 99, Arad 140, Oradea 151, Pitesti 177, Craiova 226 |
| 3 | Fagaras (99) | Bucarest | Arad 140, Oradea 151, Pitesti 177, Craiova 226, Bucarest 310 |
| 4 | Arad (140) | Zerind , Timisoara | Oradea 151, Pitesti 177, Zerind 215, Craiova 226, Timisoara 258, Bucarest 310 |
| 5 | Oradea (151) | Zerind via Oradea : scartato | Pitesti 177, Zerind 215, Craiova 226, Timisoara 258, Bucarest 310 |
| 6 | Pitesti (177) | Bucarest : sostituisce; Craiova : scartato | Zerind 215, Craiova 226, Timisoara 258, Bucarest 278 |
| 7 | Zerind (215) | nessuno (Oradea e Arad già raggiunte a costo minore) | Craiova 226, Timisoara 258, Bucarest 278 |
| 8 | Craiova (226) | Drobeta | Timisoara 258, Bucarest 278, Drobeta 346 |
| 9 | Timisoara (258) | Lugoj | Bucarest 278, Drobeta 346, Lugoj 369 |
| 10 | Bucarest (278) | obiettivo |
Cammino: Sibiu Rimnicu Vilcea Pitesti Bucarest, costo , dopo 10 espansioni. La slide del corso racconta la stessa storia in sei passi: non cita Arad e Oradea (successori di Sibiu "all'indietro"), che però costano 140 e 151, meno di Pitesti (177), e quindi vengono espansi prima di Pitesti, senza cambiare il risultato. Si noti il punto 3: Bucarest compare per la prima volta con 310 ma non viene estratta, perché nella frontiera ci sono nodi più economici; il 278 si scopre solo espandendo Pitesti.
Punto 2 - greedy da Arad a Bucarest
Si estrae il nodo con minima, senza guardare .
| passo | si espande () | frontiera dopo () |
|---|---|---|
| 1 | Arad (366) | Sibiu 253, Timisoara 329, Zerind 374 |
| 2 | Sibiu (253) | Fagaras 176, Rimnicu Vilcea 193, Timisoara 329, Zerind 374, Oradea 380 |
| 3 | Fagaras (176) | Bucarest 0, Rimnicu Vilcea 193, Timisoara 329, Zerind 374, Oradea 380 |
| 4 | Bucarest (0) | obiettivo |
Cammino Arad Sibiu Fagaras Bucarest, costo , con 4 espansioni. Non è ottimo: il cammino ottimo (punto 3) costa 418. A Sibiu il greedy sceglie Fagaras (, che "sembra" più vicina) invece di Rimnicu Vilcea (), ma la strada da Fagaras a Bucarest è lunga 211 contro da Rimnicu Vilcea via Pitesti, e contro da Sibiu.
Punto 3 - A∗ da Arad a Bucarest
| passo | si espande () | nodi nuovi () | frontiera dopo (per ) |
|---|---|---|---|
| 1 | Arad () | Sibiu ; Timisoara ; Zerind | Sibiu 393, Timisoara 447, Zerind 449 |
| 2 | Sibiu (393) | Rimnicu Vilcea ; Fagaras ; Oradea | RV 413, Fagaras 415, Timisoara 447, Zerind 449, Oradea 671 |
| 3 | Rimnicu Vilcea (413) | Pitesti ; Craiova | Fagaras 415, Pitesti 417, Timisoara 447, Zerind 449, Craiova 526, Oradea 671 |
| 4 | Fagaras (415) | Bucarest | Pitesti 417, Timisoara 447, Zerind 449, Bucarest 450, Craiova 526, Oradea 671 |
| 5 | Pitesti (417) | Bucarest : sostituisce | Bucarest 418, Timisoara 447, Zerind 449, Bucarest 450, Craiova 526, Oradea 671 |
| 6 | Bucarest (418) | obiettivo |
I costi : Sibiu ; Rimnicu Vilcea ; Fagaras ; Pitesti ; Bucarest via Pitesti . Cammino Arad Sibiu Rimnicu Vilcea Pitesti Bucarest, costo 418, ottimo, con 6 espansioni (l'ordine non decrescente è ). Al passo 4 A∗ espande Fagaras perché : è una deviazione che si rivela inutile ma costa poco. Il costo uniforme da Arad a Bucarest ne farebbe 13.
Punto 4 - da Lugoj a Bucarest
A∗ ():
| passo | si espande (, ) | nodi nuovi () |
|---|---|---|
| 1 | Lugoj (0, 244) | Mehadia ; Timisoara |
| 2 | Mehadia (70, 311) | Drobeta |
| 3 | Drobeta (145, 387) | Craiova |
| 4 | Craiova (265, 425) | Pitesti ; Rimnicu Vilcea |
| 5 | Timisoara (111, 440) | Arad |
| 6 | Pitesti (403, 503) | Bucarest |
| 7 | Bucarest (504, 504) | obiettivo |
Cammino Lugoj Mehadia Drobeta Craiova Pitesti Bucarest, costo , 7 espansioni (la deviazione per Timisoara al passo 5 serve a escludere un percorso migliore: ). Il greedy trova lo stesso cammino (504) con 6 espansioni: da Lugoj la strada "che sembra giusta" è davvero quella ottima, perché da Lugoj non c'è una scorciatoia ingannevole. Il costo uniforme trova ancora 504 ma con 13 espansioni (Lugoj, Mehadia, Timisoara, Drobeta, Arad, Craiova, Zerind, Sibiu, Oradea, Pitesti, Rimnicu Vilcea, Fagaras, Bucarest).
Morale: il greedy non è sempre sbagliato, è solo senza garanzie; A∗ dà sempre l'ottimo (con ammissibile e consistente) con molte meno espansioni del costo uniforme (6 contro 13 da Arad, 7 contro 13 da Lugoj).
Punto 5 - è ammissibile e consistente
Ammissibile: , perché la distanza in linea retta non supera mai il percorso stradale. Per Arad: ; Sibiu ; Rimnicu Vilcea ; Fagaras ; Pitesti . Consistente: in entrambi i versi di ogni strada, per esempio Arad–Sibiu: e ; Pitesti–Bucarest: e . Il programma sotto controlla tutte le 23 strade.
Programma di verifica
import heapq, itertools
strade = {('Oradea', 'Zerind'): 71, ('Oradea', 'Sibiu'): 151, ('Arad', 'Zerind'): 75, ('Arad', 'Sibiu'): 140,
('Arad', 'Timisoara'): 118, ('Lugoj', 'Timisoara'): 111, ('Lugoj', 'Mehadia'): 70, ('Drobeta', 'Mehadia'): 75,
('Craiova', 'Drobeta'): 120, ('Craiova', 'Rimnicu Vilcea'): 146, ('Craiova', 'Pitesti'): 138,
('Rimnicu Vilcea', 'Sibiu'): 80, ('Fagaras', 'Sibiu'): 99, ('Bucharest', 'Fagaras'): 211,
('Bucharest', 'Pitesti'): 101, ('Bucharest', 'Giurgiu'): 90, ('Bucharest', 'Urziceni'): 85,
('Hirsova', 'Urziceni'): 98, ('Eforie', 'Hirsova'): 86, ('Urziceni', 'Vaslui'): 142,
('Iasi', 'Vaslui'): 92, ('Iasi', 'Neamt'): 87, ('Pitesti', 'Rimnicu Vilcea'): 97}
vicini = {}
for (a, b), c in strade.items():
vicini.setdefault(a, {})[b] = c
vicini.setdefault(b, {})[a] = c
h = {'Arad': 366, 'Bucharest': 0, 'Craiova': 160, 'Drobeta': 242, 'Eforie': 161, 'Fagaras': 176, 'Giurgiu': 77,
'Hirsova': 151, 'Iasi': 226, 'Lugoj': 244, 'Mehadia': 241, 'Neamt': 234, 'Oradea': 380, 'Pitesti': 100,
'Rimnicu Vilcea': 193, 'Sibiu': 253, 'Timisoara': 329, 'Urziceni': 80, 'Vaslui': 199, 'Zerind': 374}
def best_first(start, goal, f):
"f(stato, g) -> priorità. Restituisce cammino, costo e ordine di espansione."
conta = itertools.count() # per rompere i pareggi in ordine di inserimento
frontiera = [(f(start, 0), next(conta), start, 0, [start])]
raggiunti = {start: 0} # stato -> miglior g trovato
espansi = []
while frontiera:
_, _, s, g, cammino = heapq.heappop(frontiera)
if g > raggiunti[s]: # voce vecchia: c'è un cammino migliore
continue
espansi.append(s)
if s == goal:
return cammino, g, espansi
for t, costo in sorted(vicini[s].items()):
g2 = g + costo
if t not in raggiunti or g2 < raggiunti[t]:
raggiunti[t] = g2
heapq.heappush(frontiera, (f(t, g2), next(conta), t, g2, cammino + [t]))
print(best_first('Sibiu', 'Bucharest', lambda s, g: g)) # costo uniforme
print(best_first('Arad', 'Bucharest', lambda s, g: h[s])) # greedy
print(best_first('Arad', 'Bucharest', lambda s, g: g + h[s])) # A*
print(all(h[a] <= c + h[b] and h[b] <= c + h[a] for (a, b), c in strade.items())) # consistenzaOutput (cammino, costo, città espanse in ordine):
(['Sibiu', 'Rimnicu Vilcea', 'Pitesti', 'Bucharest'], 278, ['Sibiu', 'Rimnicu Vilcea', 'Fagaras', 'Arad', 'Oradea', 'Pitesti', 'Zerind', 'Craiova', 'Timisoara', 'Bucharest'])
(['Arad', 'Sibiu', 'Fagaras', 'Bucharest'], 450, ['Arad', 'Sibiu', 'Fagaras', 'Bucharest'])
(['Arad', 'Sibiu', 'Rimnicu Vilcea', 'Pitesti', 'Bucharest'], 418, ['Arad', 'Sibiu', 'Rimnicu Vilcea', 'Fagaras', 'Pitesti', 'Bucharest'])
TrueI pareggi si rompono in ordine di inserimento e i vicini sono scorsi in ordine alfabetico, ma qui nessun pareggio cambia le espansioni mostrate. Il codice scarta le voci vecchie nello heap (if g > raggiunti[s]), cosa che il codice del laboratorio non fa (Esercizio - Algoritmi di ricerca in Python con Problem, Node e best-first search (laboratorio 3)).
Versione ripasso
- Costo uniforme da Sibiu a Bucarest (estrae minimo; test all'estrazione; un costo migliore sostituisce il nodo): espande Sibiu 0, Rimnicu Vilcea 80, Fagaras 99, Arad 140, Oradea 151, Pitesti 177, Zerind 215, Craiova 226, Timisoara 258, Bucarest 278. Bucarest entra con , poi con sostituisce. Cammino Sibiu, Rimnicu Vilcea, Pitesti, Bucarest, 278. La slide omette Arad e Oradea (140 e 151 sono sotto 177).
- Greedy da Arad (): Arad 366, Sibiu 253, Fagaras 176, Bucarest 0; cammino Arad, Sibiu, Fagaras, Bucarest, costo , 4 espansioni, non ottimo (a Sibiu sceglie Fagaras con di Rimnicu Vilcea).
- A∗ da Arad (): Arad 366; Sibiu ; Rimnicu Vilcea ; Fagaras ; Pitesti ; Bucarest via Fagaras 450, via Pitesti sostituisce. Costo 418, 6 espansioni, ottimo (costo uniforme 13).
- Lugoj Bucarest: A∗ 7 espansioni (Lugoj, Mehadia, Drobeta, Craiova, Timisoara, Pitesti, Bucarest), cammino Lugoj, Mehadia, Drobeta, Craiova, Pitesti, Bucarest, costo ; il greedy trova lo stesso cammino con 6 espansioni; costo uniforme 13 espansioni. Il greedy non è sempre sbagliato, è senza garanzie.
- : ammissibile (, , , , ) e consistente in entrambi i versi su tutte le 23 strade (, , ).
- Frontiere del costo uniforme (dopo ogni espansione): RV 80, Fagaras 99, Arad 140, Oradea 151; poi Pitesti 177 e Craiova 226; Bucarest 310; dopo Arad Zerind 215 e Timisoara 258; Oradea non cambia nulla (); dopo Pitesti Bucarest 278 sostituisce 310 e Craiova via Pitesti () è scartata; poi Drobeta 346 (da Craiova) e Lugoj 369 (da Timisoara).
- Frontiere di A∗ da Arad: dopo Arad Sibiu 393, Timisoara 447, Zerind 449; dopo Sibiu RV 413, Fagaras 415, Oradea 671; dopo RV Pitesti 417, Craiova 526; dopo Fagaras Bucarest 450; dopo Pitesti Bucarest 418 sostituisce 450; estratti non decrescenti: .
- Costi da Arad: Sibiu 140; Rimnicu Vilcea ; Fagaras ; Pitesti ; Bucarest via Pitesti , via Fagaras .
- Errori tipici: fermarsi quando Bucarest entra la prima volta nella frontiera (310); ordinare la frontiera per in A∗ invece che per ; non sostituire un nodo che si raggiunge con un costo minore; dimenticare di controllare la consistenza nei due versi.