Esercizio - Formulazione delle 8 regine, hill climbing e simulated annealing (slide del corso)
In questa pagina 6
Esercizio ricavato dalle slide del corso di Intelligenza Artificiale, Ing. dell'Informazione UniPD, sul problema delle 8 regine: dimensione dello spazio degli stati nelle due formulazioni, calcolo dell'euristica sullo stato della slide, minimi locali, hill climbing e simulated annealing. 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 → (formulazione) 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 → (ricerca locale). Tutti i numeri sono stati verificati con i programmi sotto.
Testo
Mettere 8 regine su una scacchiera in modo che nessuna ne attacchi un'altra (due regine si attaccano se stanno sulla stessa riga, colonna o diagonale).
- Formulazione 1: stati = qualunque disposizione di 0-8 regine; azione = aggiungere una regina in una casella vuota. Quante sequenze di azioni (disposizioni di 8 regine una dopo l'altra) vanno esaminate? Formulazione 2: stati = disposizioni di regine, una per colonna nelle colonne più a sinistra, senza attacchi; azione = aggiungere una regina in una casella non attaccata della colonna vuota più a sinistra. Quanti stati?
- Nella ricerca locale uno stato ha 8 regine, una per colonna, e è il numero di coppie di regine che si attaccano; i vicini si ottengono muovendo una regina dentro la sua colonna. Lo stato della slide ha le regine nelle righe (colonne da sinistra a destra, righe dall'alto a partire da 0). Calcolare e il numero di vicini.
- Dire quali sono le mosse migliori e il loro ; dire se l'hill climbing si può bloccare.
- Lo stato ha : è una soluzione? Che cosa succede all'hill climbing?
- Eseguire hill climbing da stati casuali e simulated annealing, e confrontare le percentuali di successo.
Punto 1 - la formulazione conta
Formulazione 1. Si aggiungono regine una alla volta in una casella vuota qualsiasi: la prima ha 64 posizioni, la seconda 63, ..., l'ottava 57. Le sequenze sono
Formulazione 2. Una regina per colonna, a partire dalla colonna 0, in una casella non attaccata. Le colonne si riempiono in ordine, quindi uno stato è determinato dalle righe delle prime colonne: il numero di stati si conta con una ricorsione sulle colonne:
def ok(q, r): # la regina in riga r nella colonna len(q) è attaccata?
c = len(q)
return all(r != qr and abs(r - qr) != c - i for i, qr in enumerate(q))
stati = soluzioni = 0
def rec(q):
global stati, soluzioni
stati += 1
if len(q) == 8:
soluzioni += 1
return
for r in range(8):
if ok(q, r):
rec(q + [r])
rec([])
print(stati, soluzioni) # 2057 922057 stati, di cui 92 sono soluzioni complete (8 regine). La seconda formulazione ha 2057 stati contro sequenze: la prima enumera anche disposizioni con regine che si attaccano a metà strada, la seconda le vieta subito. Con la seconda anche l'ampiezza o la profondità risolvono il problema in un istante.
Punto 2 - dello stato della slide
: la regina della colonna è nella riga . Due regine nelle colonne si attaccano se (stessa riga) o (stessa diagonale; stessa colonna è impossibile). Controllando tutte le coppie:
- stessa riga (5 coppie): colonne riga 4; e e riga 5; riga 6;
- stessa diagonale (12 coppie): , , , , , , , , , , , .
Totale coppie, come nella slide. Esempio di controllo: colonne : , diagonale. Le regine attaccate sono 8 su 8 (tutte hanno almeno un'attaccante): il numero 17 conta le coppie, non le regine (la slide lo descrive come "somma delle regine sotto attacco", ma le regine sotto attacco sono 8; 17 è il numero di coppie in conflitto). I vicini sono : 8 colonne, 7 righe alternative.
Punto 3 - le mosse migliori
Si calcola per tutti i 56 vicini (la slide mostra la scacchiera con i valori). Esempi: la regina della colonna 0 spostata in riga 0 dà (peggiora); la colonna 1 in riga 1 dà ; la colonna 2 in riga 0 dà ; la colonna 7 in riga 7 dà . Il minimo è , raggiunto da 8 mosse (colonna, riga di arrivo): , , , , , , , . L'hill climbing ne sceglie una (a caso tra le 8) e passa a .
L'hill climbing si può bloccare perché guarda solo i vicini: in uno stato dove tutti i vicini hanno maggiore o uguale, termina anche se (minimo locale per , massimo locale per ).
Punto 4 - un minimo locale
Stato : l'unica coppia in conflitto è quella delle colonne (righe 3 e 0, differenza : stessa diagonale), la coppia cerchiata nella slide. Quindi : non è una soluzione. Dei suoi 56 vicini, nessuno ha (il minimo è , raggiunto da 15 mosse): non si può migliorare con una mossa sola, quindi l'hill climbing si ferma. Per eliminare il conflitto bisogna spostare più di una regina passando da stati con maggiore: il paesaggio ha un minimo locale con e quelli globali () lontani.
Punto 5 - esperimenti
import random, math
def h(q): # q[c] = riga della regina nella colonna c; coppie di regine che si attaccano
return sum(q[i] == q[j] or abs(q[i] - q[j]) == j - i
for i in range(8) for j in range(i + 1, 8))
def successori(q): # muovere una regina dentro la sua colonna: 8 * 7 = 56 vicini
return [q[:c] + [r] + q[c + 1:] for c in range(8) for r in range(8) if r != q[c]]
def hill_climbing(q):
passi = 0
while True:
migliore = min(h(n) for n in successori(q))
if migliore >= h(q): # nessun vicino migliore: minimo locale
return q, passi
q = random.choice([n for n in successori(q) if h(n) == migliore])
passi += 1
def simulated_annealing(q, T=2.0, alpha=0.995, Tmin=1e-3):
while T > Tmin:
if h(q) == 0: return q
c, r = random.randrange(8), random.randrange(8)
n = q[:c] + [r] + q[c + 1:]
dE = h(q) - h(n) # valore = -h: dE > 0 se n è migliore
if dE > 0 or random.random() < math.exp(dE / T):
q = n
T *= alpha
return q
random.seed(0)
casuale = lambda: [random.randrange(8) for _ in range(8)]
q = [4, 5, 6, 3, 4, 5, 6, 5] # stato della slide
print(h(q), min(h(n) for n in successori(q)), sum(h(n) == 12 for n in successori(q)))
prove = [hill_climbing(casuale()) for _ in range(2000)]
riusciti = [p for q, p in prove if h(q) == 0]
print(len(riusciti) / 2000, sum(riusciti) / len(riusciti))
sa = [simulated_annealing(casuale()) for _ in range(500)]
print(sum(h(q) == 0 for q in sa) / 500)Output (con random.seed(0)):
17 12 8
0.138 4.108695652173913
0.816Risultati:
- Hill climbing più ripido: da stati casuali arriva a nel 13,8% dei casi (2000 prove) e in quel caso impiega in media 4,1 passi; nell'86,2% restanti si blocca in un minimo locale. Con i riavvii casuali (si rilancia finché riesce) servono in media lanci; una simulazione di 300 serie di riavvii ne ha dati 6,9 in media. Ogni lancio costa pochi passi, quindi anche con i riavvii il problema è risolto in un istante, mentre ci sono stati.
- Simulated annealing con , fino a (circa 1500 passi): trova una soluzione nell'81,6% dei casi (500 prove). Accetta anche mosse peggiorative con probabilità : a un peggioramento di 1 passa con probabilità , a con (quindi l'algoritmo si comporta come un hill climbing nella fase finale).
Confronto. Senza riavvii il simulated annealing ha successo 6 volte più spesso dell'hill climbing (81,6% contro 13,8%), pagando circa 1500 passi per tentativo contro i ~4 dell'hill climbing: ogni passo di annealing costa il calcolo di un solo vicino, quello dell'hill climbing 56 valutazioni. Con i riavvii l'hill climbing è competitivo su problemi piccoli come questo; il vantaggio dell'annealing si vede quando i minimi locali sono tanti e i riavvii non bastano.
Versione ripasso
- Formulazione 1 (aggiungi una regina in una casella vuota): sequenze. Formulazione 2 (una regina per colonna a sinistra, in casella non attaccata): 2057 stati, di cui 92 soluzioni (ricorsione sulle colonne con
ok(q, r):r != qr and abs(r - qr) != c - i). - Stato della slide (riga per colonna): coppie in conflitto = 5 sulla stessa riga + 12 sulla stessa diagonale; le regine attaccate sono 8 su 8, quindi 17 conta le coppie, non le regine. Due regine nelle colonne si attaccano se o . Vicini: .
- Mosse migliori: , da 8 mosse (colonna, riga): , , , , , , , ; esempi peggiorativi: colonna 0 in riga 0 dà 18, colonna 7 in riga 7 dà 18.
- Minimo locale : (unica coppia: colonne sulla stessa diagonale), tutti i 56 vicini hanno (minimo 2, da 15 mosse): l'hill climbing si ferma senza una soluzione.
- Hill climbing più ripido da stati casuali (
random.seed(0), 2000 prove): successo 13,8%, in media 4,1 passi quando riesce; con i riavvii casuali servono circa lanci (6,9 in una simulazione). Spazio totale stati. - Simulated annealing (, fino a , circa 1500 passi; accetta con probabilità , negativo per una mossa peggiore): successo 81,6% (500 prove,
random.seed(0)); a , a . - Confronto: annealing 81,6% contro 13,8% senza riavvii, ma circa 1500 passi per tentativo contro 4; ogni passo di annealing valuta un solo vicino, quello dell'hill climbing 56. Con i riavvii l'hill climbing basta su problemi piccoli (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 →).
- Errori tipici: contare le regine attaccate invece delle coppie; contare due volte la stessa coppia (si evita con ); dimenticare che i vicini sono 56 e non 64; ritenere quasi risolto: serve un'altra regina da spostare passando per maggiori.