Logica per la risoluzione di problemi


Introduzione - Intelligenza artificiale e programmazione logica

"L'Intelligenza Artificiale è lo studio del comportamento intelligente. Il suo scopo finale è una teoria della intelligenza che descriva il comportamento delle entità che entrano in gioco nell'intelligenza naturale e che guidi la creazione di entità artificiali capaci di comportamento intelligente" [Geneseret-Nilsson, 1987].

La capacità di trarre deduzioni logiche è una delle funzioni più caratterizzanti dell'intelligenza umana; la logica è la scienza che ha per oggetto le strutture deduttive dei linguaggi esatti; da ciò deriva il sempre maggiore interesse dell'applicazione della logica alla programmazione dei calcolatori, nel tentativo di ottenere da questi comportamento intelligente.

L'inferenza è il processo di derivare conclusioni da premesse. In alcuni casi si deriva una conclusione da un insieme di premesse in un solo passo; in altri casi si devono derivare conclusioni intermedie. Ciascun passo in questo processo dev'essere giustificato da una regola d'inferenza.

Diciamo che una proposizione (una qualsiasi frase dichiarativa che sia o vera o falsa, ma non entrambe) ottenuta dall'applicazione di regole d'inferenza ad un insieme di proposizioni date si dice "teorema". Le proposizioni date sono le ipotesi; i passi inferenziali la dimostrazione del teorema.

Quando un linguaggio di programmazione è espresso in logica simbolica il programma può essere considerato come un insieme di ipotesi e le nostre richieste come teoremi che vorremmo dimostrare. I passi inferenziali equivalgono alle operazioni meccaniche che il calcolatore deve eseguire per determinare se l'informazione contenuta nel programma implica logicamente l'esistenza di una soluzione da dare al problema richiesto.

La macchina deve essere un "risolutore di problemi". In questo modo dimostrare teoremi, eseguire programmi e risolvere problemi vengono a costituire un medesimo obiettivo.

La dimostrazione automatica di teoremi è uno dei domini di ricerca dell'Intelligenza Artificiale. Le basi in questo campo sono state sviluppate da Herbrand nel 1930, ma il metodo risultò di difficile applicazione fino all'invenzione del calcolatore digitale. Solo nel 1965, quando Robinson sviluppò il principio di risoluzione, si compirono i passi più importanti per ottenere dimostratori reali di teoremi implementati su calcolatore.

La differenza fondamentale fra i comuni linguaggi di programmazione (detti linguaggi imperativi) e un programma espresso in logica simbolica (linguaggi descrittivi) è la seguente.

Nel primo caso il programma è costituito da un insieme di regole che dicono "come" ottenere un certo risultato. In questo caso la conoscenza contenuta nel programma è detta di tipo procedurale, perché è contenuta nelle operazioni eseguite dal programma.

Nel secondo caso il programma è costituito da un insieme di affermazioni che descrivono un determinato "stato del mondo"; la conoscenza contenuta nel programma è detta dichiarativa, perchè è contenuta nelle dichiarazioni riguardanti il mondo; si dicono linguaggi dichiarativi (o descrittivi) perché descrivono la relazione che lega l'uscita con l'ingresso.

La conoscenza espressa in modo dichiarativo offre molteplici vantaggi; il principale si basa sull'idea di Kowalski secondo cui ogni algoritmo consta dell'unione di logica e controllo:

(Algoritmo= Logica + Controllo)

Per "logica" s'intende la descrizione di un problema; per "controllo" la descrizione dei passi da compiere per risolverlo.

Logica e controllo in un linguaggio dichiarativo sono indipendenti l'una dall'altro e di conseguenza un intervento sulla conoscenza dichiarativa della macchina non implica grossi cambiamenti nel programma; inoltre le stesse dichiarazioni che rappresentano la conoscenza nel calcolatore possono essere utilizzate per differenti propositi non esplicitati al momento dell'assemblamento del programma.

Sia la conoscenza procedurale che quella dichiarativa sono necessarie per la realizzazione di macchine intelligenti. Tuttavia la flessibilità dell'intelligenza naturale sembra dipendere fortemente dalla conoscenza dichiarativa e l'Intelligenza Artificiale si occupa soprattutto dello studio di questa.


Sintassi


Definizione di clausola

Vedi in Forma clausale e principio di risoluzione.


Clausole di Horn

Vedi in Forma clausale e principio di risoluzione.


Interpretazione procedurale delle clausole di Horn

Definizione di asserzione d'obiettivo

I letterali in una negazione <== A1,....,An sono interpretati come problemi da risolvere.

Un'espressione del tipo <== A1,...,An viene detta asserzione d'obiettivo.

Se un problema contiene variabili, si devono trovare le sostituzioni opportune tali che consentano soluzione del problema.

Definizione di procedura

Una clausola del tipo A <== A1,...,An viene interpretata come una procedura o metodo di risoluzione di problemi; cioè, per dimostrare A dimostrare i sottoproblemi A1,...,An.

Dato un problema B che si unifichi con A, la procedura riduce la soluzione di B alla soluzione dei sottoproblemi A1Th,...,AnTh dove Th è l'unificatore fra A e B.

Componente di entrata e di uscita di una sostituzione
In generale la sostituzione q che unifica un problema B con una procedura A <== A1, ..., An può essere scomposta in due parti

Th = Thi U Thu

dove:

Thi
riguarda le variabili nella procedura e viene chiamata "componente di ingresso" della sostituzione;
Thu
riguarda le variabili nel problema che dev'essere risolto e viene chiamata "componente di uscita" della sostituzione.
La procedura riduce il problema B ai sottoproblemi A1,...,An e la componente è il contributo della procedura per trovare il valore delle variabili in B.

Definizione di chiamata procedurale

Una clausola di Horn B <== A1,...,An con n>=0 viene detta una procedura la cui coda {A1,...,An} è un insieme di chiamate procedurali Ai.

Definizione di invocazione procedurale

La generazione di un nuovo obiettivo da uno dato unificando la chiamata procedurale con la testa B di una procedura B <== A1,..,An è una invocazione procedurale.


Semantica


Concetti semantici

Vedi in Forma clausale e principio di risoluzione


Semantica della clausola vuota

Vedi in Forma clausale e principio di risoluzione


Semantica delle conclusioni alternative

Vedi in Forma clausale e principio di risoluzione


Apparato deduttivo - Principio di risoluzione

Definizione procedurale di risoluzione

Data una condizione in una clausola e una conclusione nell'altra, il risolvente esiste se la condizione e la conclusione sono uguali. Le due clausole sono dette genitori della clausola risolvente. Esempio:
C1: X <== Y
C2: Y <== Z
 C: X <== Z

Una condizione del risolvente è ottenuta applicando la "sostituzione di unificazione" (o) ad una condizione (differente dalla condizione unificata) di uno dei genitori.

Una conclusione del risolvente è ottenuta applicando la sostituzione di unificazione ad una conclusione (differente dalla conclusione unificata) di uno dei genitori.

Risoluzione diretta

Nella risoluzione diretta si lavora con clausole direzionali scritte in forma infissa, cioè con la presenza del simbolo d'implicazione. Richiamando la terminologia usata nel definire le clausole per il problem-solving, distinguiamo tra fatti e regole.

Essendo la risoluzione diretta completa solo per le clausole di Horn, i fatti saranno della forma: A <== dove A è un letterale positivo, le regole saranno della forma: A <== B1,...,Bn dove {B1,...,Bn} è una congiunzione di letterali negati.

Risoluzione diretta in avanti

In un sistema di deduzione "in avanti" le regole vengono applicate ad una base dati globale di fatti finché non si ottiene una condizione terminale (un fatto) che contraddica l'obiettivo (con la produzione, perciò, della clausola vuota). Le regole in un processo "in avanti" sono della forma:
W1,...,Wn <== L
dove L è un letterale singolo negato e W1,...,Wn è una disgiunzione di letterali positivi.

Le variabili che compaiono nell'implicazione sono universalmente quantificate e rinominate in modo che nessuna variabile compaia in più di una regola e che le variabili nelle regole siano diverse dalle variabili nei fatti. Questo è molto importante ai fini della implementazione, per un programma ben strutturato.

Un sistema di deduzione in avanti equivale al "modus ponens" della logica classica:

 A <==
 B <== A
-------
 B <==

Risoluzione diretta all'indietro

In un sistema di risoluzione "indietro" le regole vengono applicate ad una base dati globale di obiettivi finché non si ottiene una condizione terminale (un obiettivo) che contraddica un fatto (con la produzione perciò della clausola vuota).

Le regole in un processo "indietro" sono della forma:

L ¨ W1,...,Wn
dove W1,...,Wn è una congiunzione di letterali ed L è un letterale singolo positivo. Per quanto riguarda le variabili, se presenti, vale lo stesso discorso fatto per il sistema "in avanti".

Un sistema di deduzione "indietro" equivale al "modus tollens" della logica classica:

 ¬A
 A <== B
-------
 ¬B

Combinazione fra sistema "in avanti" e "all'indietro"

Un sistema di deduzione "in avanti" è la sintesi di nuove informazioni da altre date; un sistema di deduzione "all'indietro" è l'analisi di un obiettivo in sottobiettivi.

Il sistema di deduzione usato dai programmi logici è essenzialmente quello "all'indietro". A volte è possibile usare un sistema "in avanti" il quale, basandosi su un processo di sintesi, sembra avvicinarsi di più al tipo di ragionamento umano.

Entrambi i sistemi presentano limitazioni; infatti un sistema "all'indietro" opera su una base dati globale costituita da soli obiettivi (cioè su congiunzioni di letterali), e un sistema "in avanti" su una base dati globale costituita da fatti (cioè su disgiunzioni di letterali).

È possibile combinare i due sistemi creandone un terzo con base globale dati costituita dall'unione di fatti e di obiettivi.

I dimostratori di teoremi basati su clausole di Horn utilizzano come sistema di deduzione la risoluzione diretta "all'indietro".

Deduzione di risoluzione

Vedi in Forma clausale e principio di risoluzione

Alberi di deduzione

Vedi in Forma clausale e principio di risoluzione

Forma AND/OR

Passaggio da forma clausale a forma and/or

Per convertire una formula del calcolo proposizionale in forma AND/OR:
  1. si eliminano i simboli d'implicazione;
  2. si portano all'interno le negazioni finché lo scopo di ognuna includa al più un singolo predicato;
  3. si skolemizzano le variabili esistenzialmente quantificate;
  4. si mette la formula in forma prenessa;
  5. si rinominano le variabili in modo che la stessa variabile non compaia nelle differenti principali congiunzioni della formula;
  6. i quantificatori universali vengono eliminati; si assume che ogni variabile sia universalmente quantificata.
Inoltre, dal momento che le clausole che si considerano sono direzionali, vengono scritte in forma infissa, reintroducendo dunque l'implicazione secondo l'equivalenza (¬AvB) <==> (A==>B).

La clausola (a):

{¬L1,...,¬Ln,C}
diventa:
L1,...,Ln ==> C

La clausola (b):

{C,¬L1,...,¬Ln}
diventa:
C <== L1,...,Ln

Alberi e grafi AND/OR

Introduzione
Quando la logica è usata per esprimere problemi e metodi di risoluzione di problemi, le procedure di risoluzione si comportano come risolutori di problemi. È lecito e può essere utile confrontare le procedure di dimostrazione per clausole di Horn con uno dei modelli sviluppati in intelligenza artificiale per la risoluzione di problemi: gli alberi e i grafi AND/OR.

Nelle implementazioni su calcolatore di sistemi di risoluzione di problemi basati su clausole di Horn si usa una rappresentazione che combina la ricerca su alberi di deduzione e quella su alberi AND/OR. Lo scopo è quello di minimizzare lo spazio di ricerca scegliendo solo i sottoproblemi che richiedono l'applicazione del minor numero possibile di procedure.

Il metodo di ricerca più usato sugli alberi AND/OR è quello di ricerca in profondità; è efficiente da implementare su calcolatore perché considera solo un ramo alla volta. Quando nessuna delle procedure ancora da considerare può essere applicata al sottobiettivo selezionato nella asserzione d'obiettivo, la strategia di ricerca torna indietro al nodo successivo all'ultimo considerato e prova a risolvere il sottobiettivo selezionato in un modo alternativo. Per questo la ricerca in profondità è anche detta con backtracking.

Alberi AND/OR
Gli alberi AND/OR costituiscono una rappresentazione grafica del "problema di riduzione", cioè del problema di trovare una soluzione ad un problema dato usando un insieme di fatti iniziale o di obiettivi e regole capaci di ridurre i problemi in sottoproblemi.

Ogni clausola in forma AND/OR può essere rappresentata da un albero o un grafo AND/OR: la clausola viene scomposta nei letterali che la compongono.

Alberi AND/OR in un sistema in avanti
In un sistema "in avanti" i nodi dell'albero sono contrassegnati da fatti.
Alberi AND/OR in un sistema all'indietro
In un sistema "all'indietro" i nodi dell'albero sono contrassegnati dagli obiettivi.
Grafi AND/OR
Una rappresentazione con grafi AND/OR si ottiene dall'albero AND/OR dove i nodi contrassegnati dallo stesso fatto od obiettivo sono identificati.
Sottoproblemi indipendenti
Quando i sottoproblemi in cui viene suddiviso un dato obiettivo (e perciò facenti parte di rami differenti in un albero AND/OR) non condividono variabili, questi sono detti indipendenti e possono essere risolti da dimostratori di teoremi che lavorano contemporaneamente e indipendentemente.

Nel caso di sottoproblemi indipendenti la rappresentazione con alberi AND/OR offre un importante vantaggio. Infatti l'albero di deduzione che rappresenta una deduzione basata su regole presenta delle ridondanze. L'albero AND/OR invece no.

In generale, dato:

un albero di deduzione contiene n×m rami, mentre l'albero AND/OR ne contiene solo n+m.
Sottoproblemi dipendenti
Quando i sottoproblemi in cui viene suddiviso un obiettivo dato (e perciò facenti parte di rami differenti in un albero AND/OR) condividono variabili, questi sono detti dipendenti. In questo caso è dunque necessario trovare un unificatore più generale che unifichi le variabili presenti rendendo consistente l'insieme delle sostituzioni.

Metodi di ricerca su alberi

Vedi in Forma clausale e principio di risoluzione.


Metateoria


Correttezza e completezza della risoluzione diretta

La risoluzione diretta è una procedura dimostrativa corretta e completa solo per le clausole di Horn, cioè per quelle clausole che hanno al più un letterale negato.


Sistemi a regole

Chiamiamo "sistemi a regole" i sistemi usati in intelligenza artificiale basati sulla logica dei predicati del primo ordine; tali sistemi sono un formalismo usato per rappresentare linguaggi di tipo dichiarativo; loro caratteristica è quella di presentare una netta differenza fra dati, operazioni e controllo. I linguaggi di programmazione basati sulla logica sono linguaggi dichiarativi. Si può richiamare qui l'equazione di Kowakski:

Algoritmo = logica + controllo

con logica e controllo indipendenti nel caso di programmi logici. Diremo in seguito dei vantaggi di questa indipendenza.

Gli elementi di un sistema a regole sono:

  1. una base dati (la struttura che contiene i dati riguardanti un certo dominio), cioè l'insieme di clausole da cui si parte per un processo di risoluzione;
  2. un insieme di regole, dette "di produzione" (che opera sulla base dati modificandola; ogni regola ha una "precondizione" che se soddisfatta dalla base dati permette l'applicazione della regola), cioè le clausole contenenti l'implicazione.
  3. un sistema di controllo (che sceglie le regole da applicare e fa terminare la computazione quando è trovata una condizione terminale che permette la produzione della clausola vuota), cioè la strategia di risoluzione.
Scopo di un sistema a regole è dimostrare un "obiettivo" manipolando la base dati con le regole di produzione, sotto la direzione del sistema di controllo. L' obiettivo di un sistema a regole è il teorema da dimostrare, cioè la clausola conseguenza logica delle clausole che costituiscono la base dati.


Programmi logici


Definizione di programma logico

Un programma logico consiste in un insieme di procedure espresse in clausole di Horn attivate da una asserzione d'obiettivo iniziale.


Algoritmo = Logica + Controllo

Nei programmi convenzionali le due componenti di logica e controllo sono mescolate l'una all'altra. In un sistema a regole e nei programmi logici no. I vantaggi di questa separazione sono essenzialmente due:
  1. la possibilità di cambiare una delle due componenti senza modificare l'altra;
  2. la possibilità per il programmatore inesperto e l'inesperto esecutore del programma di occuparsi solo della componente logica, lasciando la determinazione della componente controllo al calcolatore.


Non determinismo

Non determinismo I

Come è stato più volte sottolineato, i programmi logici esprimono solo la componente logica di un algoritmo; perciò, non occupandosi del "come" il programma viene seguito, i programmi espressi in clausole di Horn ed eseguiti con sistemi "all'indietro" presentano due tipi di non-determinismo.
  1. Quando è possibile una unificazione fra una chiamata procedurale e più procedure, non è possibile determinare l'ordine in cui le procedure saranno invocate.
Dal momento che procedure alternative computano componenti d'uscita alternativi, se è necessaria solo una componente d'uscita non è determinato quale componente sarà trovato. Se tutti le componenti d'uscita sono richiesti, non è determinato in che ordine saranno generati.

La strategia usata per ovviare a questo tipo di non-determinismo è quella di incamerare e richiamare i dati sequenzialmente (dall'alto in basso, da sinistra a destra); è la strategia del "backtracking" che infatti ha un comportamento determinato non dal programma, ma dall'esecutore del programma.

Non determinismo II

  1. Quando in una asserzione d'obiettivo sono presenti più chiamate procedurali, non è possibile determinare l'ordine in cui le chiamate saranno eseguite. La maniera in cui verranno eseguite è determinata non dal programma, ma dal meccanismo di esecuzione.
La scelta che determina l'ordine di esecuzione delle chiamate procedurali dipende da una considerazione riguardo i componenti d'ingresso (l'input) e quelli di uscita (l'output). Infatti, in generale, è più efficiente eseguire prima una chiamata procedurale che contiene l'input (cioè il termine dato all'inizio della computazione), piuttosto che una chiamata che contenga l'output (cioè la variabile che sarà sostituita dal risultato).
Esempio
Dato il problema di trovare una versione classificata y di una lista di partenza L è meglio selezionare la chiamata procedurale Perm(L,y) che contiene l'input, piuttosto che Ord(y) che contiene l'output.
<== Sort(L,y)
<== Perm(L,y), Ord(y)

In ogni caso l'ordine in cui si eseguono le chiamate procedurali non compromette il risultato che sarà sempre lo stesso. Anche in questo non-determinismo la strategia per eseguire le chiamate procedurali non è determinata dal programma, ma dall'esecutore del programma.


Programmi ben strutturati

In un programma ben strutturato è desiderabile che i dati (i fatti) siano separati dalle procedure che li interrogano e li manipolano. In questo modo una struttura dati, se trovata inefficiente, può essere sostituita da un'altra senza alterare il livello più alto delle procedure.

Inoltre è necessario che le variabili presenti nelle procedure siano diverse da quelle che compaiono nei dati; questo può essere facilmente evitato non dimenticando di rinominare le variabili nel riscrivere le formule del calcolo proposizionale in form a AND/OR.

Per quanto riguarda la distinzione fra logica e controllo, in un programma ben strutturato la componente.