Dipartimento di Ingegneria dell’Informazione Università degli Studi di Parma Intelligenza Artificiale Risoluzione dei Problemi Agostino Poggi Stefano Cagnoni Risoluzione di Problemi Un problema è definito da un goal e dall’insieme di strumenti a disposizione per raggiungerlo. La prima fase della risoluzione di un problema è la formulazione del problema. In questa fase, oltre a dare una buona definizione del goal, bisogna decidere quali sono le azioni, quale è lo spazio degli stati e quale è lo stato da cui si parte (stato iniziale). E’ essenziale scegliere delle azioni, e quindi degli stati, che permettano di formulare soluzioni al giusto livello di dettaglio. Risoluzione dei Problemi 2 Risoluzione di Problemi A partire dallo stato iniziale bisogna trovare una sequenza di azioni, detta soluzione, che soddisfi il goal del problema. L’esecuzione di ogni azione ha un costo, che può variare da azione ad azione, che costituisce il criterio utilizzato nella ricerca della soluzione migliore. Quindi, ad ogni sequenza di azioni è possibile assegnare un costo complessivo (somma dei costi associati alle varie azioni), per valutare quale sia la soluzione migliore. La scelta della sequenza corrisponde alla seconda fase della risoluzione di un problema e viene individuata con il nome di ricerca. Infine, trovata la soluzione, nella terza fase, detta di esecuzione, le azioni sono eseguite per raggiungere il goal. Risoluzione dei Problemi 3 Classi di Problemi I problemi possono essere divisi in quattro classi in dipendenza della conoscenza che si ha sullo stato del mondo e sulle azioni che si possono eseguire su di esso: Problemi a stato singolo (single-state problems) : si ha conoscenza dello stato del mondo e degli effetti delle azioni. Problemi a stato multiplo (multiple-state problems) : si ha conoscenza completa degli effetti delle azioni, ma non dello stato del mondo. Problemi con imprevisti (contingency problems): alcune azioni possono avere effetti non del tutto determinati (incertezza, malfunzionamenti ecc.). Spesso per la soluzione è necessario agire (e verificare l’effetto della propria azione) prima di avere trovato una soluzione. Problemi di esplorazione (exploration problems) : non si ha conoscenza né sullo stato del mondo né sugli effetti delle azioni. E’ necessario apprendere i possibili stati del mondo e gli effetti delle proprie azioni attraverso l’esperienza. Risoluzione dei Problemi 4 Esempi di Problemi Problemi giocattolo (modelli semplificati di classi di problemi reali): Il problema dell’8-puzzle (versione ridotta del gioco del 15) Il problema delle 8 regine (disporre 8 regine su una scacchiera in modo che nessuna dia scacco alle altre) Il problema della cripto-aritmetica (GATTO - CANE = TOPO) Il problema dei tre missionari e dei tre cannibali (trasportare 3 m. e 3 c. da un lato a un altro di un fiume con una barca a due posti in modo tale che sulle 2 rive non ci siano mai c. in sovrannumero rispetto ai m.) Problemi reali Il calcolo di un percorso su un grafo Il problema del commesso viaggiatore (date n città, trovare il percorso più breve che passa una e una sola volta per ciascuna di esse) Il disegno di un layout VLSI La navigazione di robot Il calcolo di una sequenza di assemblaggio Risoluzione dei Problemi 5 Algoritmo Generale Cerchiamo di definire un algoritmo generale di ricerca per il caso di problemi a stato singolo. Per semplificare la sua definizione, introduciamo alcune funzioni ausiliarie. Occorre definire il tipo di dato Problem Datatype Problem Components: Initial-State, Operators, Goal-Test, Path-Cost-Function Occorre una funzione per ricavare lo stato iniziale dalla descrizione del problema: Init-State(Problem) Occorre una funzione per trasformare una descrizione di stato nel nodo di un albero: Make-Node(State) Risoluzione dei Problemi 6 Algoritmo generale Occorre definire il tipo di dato Node Datatype Node Components: State, Parent-Node, Operator, Depth, Path-Cost Occorre una funzione per espandere un nodo: Expand(Node, Problem, [Depth ]), genera i successori del nodo se non è stata raggiunta la profondità Depth. Servono 4 funzioni per gestire delle code di nodi: Make-Queue(Elements), genera una coda contenente Elements; Empty(Queue), controlla se la coda è vuota; Remove-Front(Queue), estrae il primo elemento; Queueing-Fn(Queue, Elements), inserisce Elements nella coda. Infine serve una funzione per controllare se il nodo coincide con un goal del problema: Goal(Node, Problem) Risoluzione dei Problemi 7 Algoritmo Generale Possiamo definire un algoritmo generale per la ricerca su alberi come segue: function General-Search(problem, Queueing-Fn, [ Depth ]) { nodes = Make-Queue(Make-Node(Initial-State(problem))); while true { if Empty(nodes) return failure; node =Remove-Front(nodes); if Goal(State(node), problem) return node; nodes = Queueing-Fn(nodes, Expand(node, problem, Depth)); } L’insieme di nodi contenuti in nodes è detta frontiera dell’albero di ricerca. Risoluzione dei Problemi 8 Valutazione delle strategie di ricerca Completezza : l’algoritmo garantisce di trovare una soluzione, se questa esiste ? Complessità temporale : quanto tempo è necessario per raggiungerla ? Complessità spaziale : quanta memoria è richiesta per raggiungerla ? Ottimalità : la soluzione trovata è la migliore ? Risoluzione dei Problemi 9 Ricerca in Ampiezza (Breadth-First Search) Un algoritmo di ricerca in ampiezza garantisce che ogni nodo di profondità d sia espanso prima di qualsiasi altro nodo di profondità maggiore. Possiamo definire un algoritmo di ricerca in ampiezza come segue: function Breadth-First-Search(problem) { return General-Search(problem, Enqueue-At-End); } Risoluzione dei Problemi 10 Ricerca in Ampiezza Ottimalità: è ottimo quando il costo è una funzione non decrescente della profondità del nodo; il costo è lo stesso per ciascun operatore. Completezza: è completo visto che utilizza una strategia sistematica che considera prima le soluzioni di lunghezza 1, poi quelle di lunghezza 2 e così via. Complessità: Se b è il fattore di ramificazione e d la profondità della soluzione, allora il numero massimo di nodi espansi è: 1 + b + b2 + b3 + ... + bd Complessità temporale: bd Complessità spaziale: bd Risoluzione dei Problemi 11 Ricerca in Ampiezza Se b =10 e per ogni nodo sono richiesti un tempo di elaborazione di 1 millisecondo e un’occupazione di memoria di 100 byte avremo i seguenti valori: Profondità Nodi Tempo Memoria 0 2 4 6 8 10 12 14 1 111 11111 106 108 1010 1012 1014 1 millisec 100 millisec 11 secondi 18 minuti 31 ore 128 giorni 35 anni 3500 anni 100 byte 11 kilobyte 1 Megabyte 111 Megabyte 11 Gigabyte 1 Terabyte 111 Terabyte 11111 Terabyte (la cosiddetta “Curse of Dimensionality”!) Risoluzione dei Problemi 12