rulururu

post Tutorial CLIPS - Nozioni base per la costruzione di sistemi esperti

Aprile 29th, 2007

Archiviato in: informatica - Computer Science — Antonio @ 12:23

CLIPS Tutorial 1
Questo tutorial è la traduzione in italiano del tutorial presente in questa pagina: http://iweb.tntech.edu/bhuguenard/ds6530/ClipsTutorial/tableOfContents.htm
Spero possa essere utile a chi vuole iniziare a capire come utilizzare CLIPS per la costruzione di sistemi esperti.

Lavorare con CLIPS

Per installare clips su windows è sufficiente scaricarlo dal sito http://www.ghg.net/clips/CLIPS.html e decomprimere il file scaricato.
Se siete su Linux esiste una serie di pacchetti che permettono l’installazione di CLIPS. Io lo ho installato su Linux Kubuntu semplicemente selezionando i pacchetti dal gestore adept. Maggiori informazioni sono presenti in questa pagina: http://packages.debian.org/stable/interpreters/clips

Per avviare CLIPS su windows, fai doppio click sul file CLIPSWin.exe. Si aprirà una finestra contenente solo la riga di comando CLIPS>. Qui verranno scritti tutti i programmi e il codice. Per uscire da CLIPS, digita (exit) oppure chiudi la finestra come per tutte le applicazioni windows.
Per avviare CLIPS su Linux apri la console e digita clips, l’interprete CLIPS verrà avviato e nella console apparirà la riga di comando CLIPS>.
La differenza principale che ho notato tra CLIPS per Linux e CLIPS per windows è che in Linux è possibile fare copia e incolla direttamente nella console, cosa molto utile in alcuni casi e che su windows non si può fare.

I comandi CLIPS sono sempre racchiusi fra parentesi tonde: (assert (foo)). Ecco una lista dei comandi più importanti:
(exit) Esce da CLIPS
(clear) Rimuove tutti i fatti e le regole dalla memoria di lavoro. Equivale a chiudere e riaprire CLIPS.
(reset) Rimuove i fatti dalla memoria di lavoro (ma non le regole) e resetta l’agenda.
(run) Esegue il programma CLIPS caricato.

Questi comandi possono anche essere eseguiti dal menù della finestra di CLIPS su windows.

Fatti e Regole

Semplificando il più possibile, possiamo dire che CLIPS opera mantenendo una lista ti fatti e un set di regole che operano su di essi. Un fatto è un pezzo di informazione come per esempio (colour green) oppure (parent_of John Susan). I fatti vengono creati asserendoli sulla memoria d lavoro, con il comando assert. Ecco un esempio, completo della risposta di CLIPS:

CLIPS>(assert (colour green))
<Fact-0>

<Fact-0> è la risposta di CLIPS che indica che il primo fatto (fact number 0) è stato creato nella memoria di lavoro. Il comando (facts) visualizza la lista di tutti i fatti esistenti in memoria. Provalo e otterrai:

CLIPS>(facts)
f-0 (colour green)
For a total of 1 fact.

I fatti possono anche essere ritrattati (cancellati) dalla memoria di lavoro usando il comando retract. come esempio, crea due fatti così:

CLIPS>(assert (colour green))
<Fact-0>
CLIPS>(assert (colour red))
<Fact-1>

Dopo rimuovi il primo fatto e mostra la lista dei fatti:

CLIPS>(retract 0)
CLIPS>(facts)
f-1 (colour red)
For a total of 1 fact.

Nota bene: per rimuovere un fatto devi specificare il suo indice, non il fatto stesso.
Gli indici dei fatti non sono riusati. Una volta che il fatto 0 è stato ritrattato, il nuovo fatto asserito avrà indice 2 e non 0.
Un po’ come succede nei database.

I fatti da soli hanno un’utilità limitata. L’applicazione di regole (rules) è necessaria per sviluppare un programma capace di effettuare operazioni utili. In generale, una regola è espressa nella forma ‘IF qualcosa è vera THEN fai qualcosa’. Questo tipo di regola è nota come produzione. Per questa ragione, i sistemi esperti basati su regole sono spesso noti come sistemi a produzioni (CLIPS sta per C Language Integrated Production System). In CLIPS, una regola tipica può essere:

(defrule duck
(animal-is duck)
=>
(assert (sound-is quack)))

La regola consiste di tre parti. La prima parte, (defrule duck, da alla regola un nome univoco. La seconda parte, (animal-is duck), è il pattern (la parte IF) della regola e l’ultima parte, (assert (sound-is quack)), è l’azione (la parte THEN). In linguaggio naturale, questa regola significa ‘se esiste un fatto (animal-is duck) nella memoria di lavoro, allora asserisci un altro fatto, (sound-is quack), nella memoria di lavoro’.
Provalo. Cancella (clear) il sistema e digita la regola così come è scritta sopra. Digitando (rules) otterrai una lista delle regole (una sola, in questo caso) presenti nel sistema. A questo punto non esiste nessun fatto. Ora, digita (assert (animal-is duck)). Controlla la lista dei fatti - c’è un solo fatto. Per attivare la regola, digita (run). Anche se sembra che nnon sia successo niente, se controlli nuovamente la lista dei fatti vedrai che è presente un nuovo fatto, (sound-is quack), che è stato inferito dalla regola. Questa è la potenza dei sistemi basati su regole - l’abilità di fare inferenze a partire dai dati, in particolare, il risultato di una regola può essere usato come pattern per un’altra regola.
Ora aggiungi la regola:

(defrule is-it-a-duck
(animal-has webbed-feet)
(animal-has feathers)
=>
(assert (animal-is duck)))

Dopo digita (reset) per cancellarei fatti (le regole rimangono). nota che questa regola ha 2 pattern. Entrambi devono essere soddisfatti perchè la regola venga attivata.Questo si traduce in ‘IF the animal has webbed feet AND the animal has feathers THEN the animal is a duck’.
Se ora asserisci i fatti (animal-has webbed-feet) e (animal-has feathers) ci saranno due fatti in memoria. Digitando (run) immediatamente ce ne sono quattro. in primo luogo, la regola is-it-a-duck si è attivata, asserendo il fatto (animal-is duck). Questo fatto ha attivato la regola duck, che ha asserito il fatto (sound-is quack). Sistemi molto potenti possono essere costruiti sfruttando la possibilità di concatenare le regole.

In ogni modo asserire solo i fatti non presenta i risultati all’utente. Digita di nuovo la prima regola, questa volta con due azioni, come mostrato sotto:

(defrule duck
(animal-is duck)
=>
(assert (sound-is quack))
(printout t “it’s a duck” crlf))

La prossima volta che mandi in esecuzione il programma, otterrai il messaggio a video “it’s a duck” .

Dofer digitare tutte le regole ogni volta che avvii CLIPS è molto inefficiente. Fortunatamente, le puoi caricare da un file usando il “costrutto Load” oppure il comando apposito nel menù File. CLIPS si aspetta un file con estensione .clp. Questi file .clp sono dei semplici file di testo, dunque puoi editarli con il tuo editor di testo preferito.
CLIPS Tutorial 2 - Pattern e azioni
Fatti Persistenti

Negli esercizi che seguono, sarà necessario usare lo stesso set di fatti molte volte. Piuttosto che digitarli ogni volta, puoi utilizzare il costrutto deffacts. Specificando i fatti in questa maniera essi verranno ricreati ogni volta che viene effettuata una (reset). Per esempio, il codice

(deffacts startup (animal dog) (animal duck) (animal haddock))

asserirà tre fatti nella memoria di lavoro ogni volta che il sistema viene resetato. Una volta asseriti, essi sono fatti come tutti gli altri - possono essere ritrattati e utilizzati nei pattern delle regole - ma anche se vengono ritrattati essi verranno riasseriti otni volta che si effettua una (reset). Sotto è presente la lista dei fatti che ti serviranno - inseriscili in una deffacts, dopo resetta CLIPS e controlla la lista dei fatti per vedere se sono presenti

(animal dog) (animal cat) (animal duck) (animal turtle) (warm-blooded dog) (warm-blooded cat) (warm-blooded duck) (lays-eggs duck) (lays-eggs turtle) (child-of dog puppy) (child-of cat kitten) (child-of turtle hatchling)

Matching

Fino ad ora, i pattern utilizzati nelle regole sono stati molto semplici e restrittivi. Ogni pattern ha “matchato” uno specifico fatto. Utilizzando le wildcards, si possono creare regole che “matchano” svariati fatti, eseguendo le loro azioni ripetutamente. Per esempio, la regola:

(defrule animal
(animal ?)
=>
(printout t “animal found” crlf))

Produce il seguente risultato, quando il programma è mandato in run:

CLIPS>(run)
Animal found
Animal found
Animal found
Animal found
CLIPS>

che mostra che è stata attivata quattro volte, una per ogni fatto corrispondente al pattern (animal ?). In questo pattern, il simbolo ? è una wildcard. Fara “matching” con qualsiasi simbolo. Puoi utilizzare quante wildcard vuoi in un pattern, ma il primo simbolo non deve essere una wildcard. Dunque (child-of ? ?) è legale, (? ? hatchling) è illegale.

Variabili e pattern

Le wildcard da sole sono utili limitatamente. Le variabili sono indispensabili. Se usiamo qualcosa come ?var invece di ? da solo, possiamo usare il valore di ?var ogni volta che la regola è attivata. Prova questo esempio:

(defrule list-animals
(animal ?name)
=>
(printout t ?name ” found” crlf))

Questo produrrà i seguenti risultati:

CLIPS>(run)
turtle found
duck found
cat found
dog found
CLIPS>

La regola ha “matchato” quattro fatti, e ogni volta che la variabile ?name ha preso il valore del simbolo che rappresenta nel pattern, così che nella parte destra della regola esso può essere stampato a video. La reale potenza di questa funzione si nota quando due o più pattern vengono usati nella parte sinistra della regola, come nell’esempio:

(defrule mammal
(animal ?name)
(warm-blooded ?name)
(not (lays-eggs ?name))
=>
(assert (mammal ?name))
(printout t ?name ” is a mammal” crlf))

Noterai l’operatore NOT. Il suo funzionamento è ovvio e banale. La regola da come risultato:

CLIPS>(run)
cat is a mammal
dog is a mammal
CLIPS>

Una volta che hai capito come funziona l’esempio precedente, prova questo nuovo passo:

(defrule mammal2
(mammal ?name)
(child-of ?name ?young)
=>
(assert (mammal ?young))
(printout t ?young ” is a mammal” crlf))

Dopo aver mandato in run, guarda la lista dei fatti

CLIPS>(run)
kitten is a mammal
puppy is a mammal
CLIPS>

Fatti e Regole

Per ritrattare un fatto, è necessario sapere il suo indice (fact-index). Puoi ritrattare fatti dall’interno delle regole assegnandoli a alcune variabili, in questo modo:

(defrule remove-mammals
?fact <- (mammal ?)
=>
(printout t “retracting ” ?fact crlf)
(retract ?fact))

Nella parte sinistra di questa regola, viene assegnato l’indice di ogni fatto che “matcha” il pattern alla variabile ?fact. Ciò significa il simbolo “freccia verso sinistra” (<-). Mandando in run si ottiene:

CLIPS>(run)
retracting <Fact-13>
retracting <Fact-14>
retracting <Fact-15>
retracting <Fact-16>
CLIPS>

Tutti i fatti mammal sono stati ritrattati

Operatori Logigi e Matematici

Abbiamo già visto come due o più pattern in una regola sono automaticamente connessi con un AND logico, ciò significa che entrambi devono essere very perchè la regola venga attivata. Abbiamo anche visto l’operatore NOT, ora vedremo l’uso dell’operatore logico OR. CLIPS ha una funzione OR, che viene usata così:

(defrule take-umbrella
(or (weather raining)
(weather snowing))
=>
(assert (umbrella required)))

Che significa “se sta piovendo o nevicando, allora prendi l’ombrello”. Nota il modo in cui l’OR viene prima degli argomenti, anzichè immezzo ad essi. Questa notazione è conosciuta come “notazione prefissa”, tutti gli operatori CLIPS funzionano in questa maniera. Per esempio, per esprimere la somma di due elementi nella maggior parte dei linguaggi di programmazione, si usa qualcosa come 5 + 7 (notazione infissa). In CLIPS, l’espressione si scriverebbe come (+ 5 7). Esamina l’esempio seguente che mostra gli operatori di addizione, sottrazione, moltiplicazione e divisione:

CLIPS>(+ 5 7)
12
CLIPS>(- 5 7)
-2
CLIPS>(* 5 7)
35
CLIPS>(/ 5 7)

0.7142857142857143
CLIPS>

Riscrivi l’espressione 10+4*19-35/12 nella notazione CLIPS e verifica che ilrisultato ottenuto sia 83.0833.
Getting data from the user

CLIPS ottiene informazioni direttamente dall’utente per mezzo della funzione (read). Ovunque (read) viene incontrata, il programma aspetta che qualcosa venga digitato dall’utente, in seguito sostituisce la risposta. Per dimostrare ciò, digita (assert (user-input (read))) nel prompt CLIPS. Quando premi invio, dovrai digitare qualcos’altro (qualsiasi cosa) prima che il comando sia completato. Se guardi la lista dei fatti, noterai un nuovo fatto contenente il tuo input come secondo elemento. Prova la regola

(defrule what-is-child
(animal ?name)
(not (child-of ?name ?))
=>
(printout t “What do you call the child of a ” ?name “?”)
(assert (child-of ?name (read))))

Quando la esegui, il sistema ti chiederà il nome dei piccoli di ogni animale di cui non ha conoscenza. Utilizzerà i dati da te inseriti per asserire nuovi fatti, che potranno poi essere usati da altre regole.

CLIPS Tutorial 3


Altro sui pattern con wildcard

Nel tutorial precedentel, ti è stato presentato il contetto di “pattern matching” con wildcard, in cui il simbolo ? è usato per prendere il posto di un simbolo presente nella parte sinistra di una regola. Si suppone di avere i fatti:

(member-of beatles john_lennon paul_mccartney george_harrison ringo_starr)
(member-of who roger_daltrey pete_townsend keith_moon)
(member-of ebtg tracey_thorn ben_watt)

Se volessimo scrivere una regola che venga attivata da tutti e tre i fatti, non possiamo utilizzare facilmente il simbolo ?, perchè farà matching con solamente un simbolo. Invece, Utilizzeremo la wildcard multifield, $?, che “matcha” con zero o piu simboli:

(defrule bands
(member-of ?band $?)
=>
(printout t “there is a band called ” ?band crlf))

Produce il risultato:

CLIPS>(run)
there is a band called ebtg
there is a band called beatles
there is a band called who

Portandici un passo avanti, possiamo ottenere la lista di tutti i membri di tutte le band:

(defrule band-members
(member-of ?band $? ?member $?)
=>
(printout t ?member ” is a member of ” ?band crlf))

Nella parte sinistra di questa regola, la wildcard multi-field, $?,farà match con zero o piu simboli. La wildcard ?member farà match con un solo simbolo per volta. Così, la wildcard ?member farà match una sola voltaper ogni membro della band, mentre le wildcard $?faranno matching con tutti gli altri membri precedenti e successivi. Per esempio, la tabella seguente mostra tutti i modi in cui la regola “matcha” i fatti per i Beatles:

Match# first $? matches ?member matches last $? matches
1 nothing john_lennon paul_mccartney george_harrison ringo_starr
2 john_lennon paul_mccartney george_harrison ringo_starr
3 john_lennon paul_mccartney george_harrison ringo_starr
4 john_lennon paul_mccartney george_harrison ringo_starr nothing

Puoi anche usare variabili multi-field:

(defrule band-members
(member-of ?band $?members)
=>
(printout t “The members of ” ?band ” are ” $?members crlf))

Ancora sulle Variabili

Fin’ora, l’unica maniera che abbiamo visto di incorporare una variabile in una regola era crearla come parte di un pattern nella parte sinistra. Usando la funzione bind, è anche possibile creare una variabile temporanea nella parte destra di una regola:

(defrule addup
(number ?x)
(number ?y)
=>
(bind ?total (+ ?x ?y))
(printout t ?x ” + ” ?y ” = ” ?total crlf)
(assert (total ?total)))

Considera che la variabile temporanea esiste solamente all’interno della regola - non è possibile usare la variabile altrove. Se ti servono variabili che possano essere usate in piu di una regola o funzione senza perderne il valore, devi definirle usando il costrutto defglobal in un file, come in questo esempio:

(defglobal
?*var1* = 17
?*oranges* = “seven”
)

Dopo una (reset), ci saranno due avariabili globali, ?*var1* e ?*oranges* (gli asterischi sono necessari) con i valori 17 e “seven” rispettivamente, che sono accessibili da qualsiasi regola. Per cambiarne i valori, si può usare la funzione bind.
Functions

I fatti e le regole, nonostante offrano una grande flessibilità, non sono utilizzabili per tutti i task. CLIPS offre in oltre un completo set di funzioni per la programmazione procedurale.
CLIPS Tutorial 4 - Template e condizioni
Anche se si può fare molto con i semplici fatti che abbiamo già usato, in molti casi è necessario che bit di informazione siano collegati tra loro. Considera, per esempio, un’applicazione che prova a determinare lo stato di forma di molte persone. Verranno usati molti indicatori, così che ogni persona avra dei valori diversi per ognuno di essi. Possiamo esprimere l’informazione per due persone come segue:

(age Andrew 20)
(weight Andrew 80)
(height Andrew 188)
(blood-pressure Andrew 130 80)
(age brenda 23)
(weight brenda 50)
(height brenda 140)
(blood-pressure brenda 120 60)

Ma ciò coinvolge troppi fatti, con niente che li colleghi tra loro tranne che un singolo campo contenente il nome della persona. Una maniera migliore per fare ciò utilizza il costrutto deftemplate, così:

(deftemplate personal-data
(slot name)
(slot age)
(slot weight)
(slot height)
(multislot blood-pressure)
)

deftemplate non crea nessun fatto, piuttosto crea la forma che alcuni fatti possono assumere. Ogni volta che un fatto di questo tipo viene instanziato, esso contiene gli slot presenti nella sua definizione, ognuno dei quali può contenere un valore ed è accessibile tramite il suo nome. I dati di ogni persona possono essere asseriti come:

(assert (personal-data (name Andrew) (age 20) (weight 80)
(height 188) (blood-pressure 130 80)))

oppure in una struttura deffacts come:

(deffacts people
(personal-data (name Andrew) (age 20) (weight 80)
(height 188) (blood-pressure 130 80))
(personal-data (name Cyril) (age 63) (weight 70)
(height 1678) (blood-pressure 180 90)))

Non sei costretto a specificare tutte le informazione, quindi l’asserto

(assert (personal-data (weight 150) (age 23) (name Brenda)))

è perfettamente valido. L’ordine in cui accedi agli slot non conta, perchè fai riferimento ad essi tramite il loro nome. I fatti “template” possono essere modificaty senza che sia necessario ritrattarli e asserirli nuovamente. La funzione modify ti permette di cambiare il valore di uno o più slotdi un fatto. Supponiamo che sia il compleanno di Andrew. Se definiamo la regola

(defrule birthday
?birthday <- (birthday ?name)
?data-fact <- (personal-data (name ?name) (age ?age))
=>
(modify ?data-fact (age (+ ?age 1)))
(retract ?birthday)
)

dunque asserendo il fatto (birthday Andrew) modificheremo l’età di Andrew lasciando intatta tutto il resto dell’informazione.

I fatti Template possono essere usati nelle regole come i fatti ordinari:

(defrule lardy-bugger
(personal-data (name ?name) (weight ?weight))
(test (> ?weight 100))
=>
(printout t ?name ” weighs ” ?weight ” kg - the fat sod.” crlf)
)

ma,aspetta! cos’è tutto ciò (test (> ?weight 100)) ? E’ ciò che si definisce un elemento condizionale. Permette alle regole di valutare funzioni nella parte sinistra.In questo caso, farà matching con i fatti che hanno il valore dello slot weight maggiore di 100 kg. Ci sono svariati modi di coinvolgere elementi condizionali nelle regole. La prima è quella banale in cui vengono usati due pattern nella parte sinistra della regola:

(defrule print-ages
(personal-data (name ?name) (age ?age))
=>
(printout t ?name ” is ” ?age ” years old.” crlf)
)

La regola dice: “se c’è un fatto con un nome e un’età, allora visualizzalo”. Il connettivo AND può essere anche espresso in maniera esplicita se necessario:

(defrule print-ages
(and
(personal-data (name ?name) (age ?age))
(personal-data (name ?name) (weight ?weight))
)

=>
(printout t ?name ” weighs ” ?weight ” at ” ?age ” years old.” crlf)
)

Nonostante il connettivo AND sia superfluo nell’esempio sopra, esso è molto utile in espressioni logiche piu complesse, come vedremo. Possiamo anche usare il connettivo OR:

(defrule take-an-umbrella
(or
(weather raining)
(weather snowing)
)
=>
(printout t “Take an umbrella” crlf)
)

Che significa “se piove o nevica, prendi un ombrello”. Nota che sia OR che AND sono operatori prefissi. L’altro connettore logico tradizionale è NOT, che semplicemente nega un predicato:

(defrule not-birthday
(personal-data (name ?name) (weight ?weight))
(not (birthday ?name))
=>
(printout t “It’s not ” ?name “’s birthday” crlf)
)

Ora, torniamo a quell’elemento test. L’uso di test permette di valutare qualsiasi condizione nella parte sinistra di una regola, non semplicemente dei fatti. Dunqueo, we potremmo scrivere una regola che (stupidamente) controlla se 6 è maggiore di 5:

(defrule pointless
(test (> 6 5))
=>
(printout t “Six is indeed greater than five” crlf)
)

Gli ultimi due elementi condizionali sono exists e forall. exists è soddisfatto se ci sono uno o più fatti che “matchano” il suo predicato; la regola che lo contiene si attiva una sola volta a prescindere da quanti fatti “matcha”. forall, daltra parte, si attiva una volta sola se tutti i fatti “matchano” il pattern. Assicurati di avere i dati personali di qualche persona nella memoria di lavoro, dopo prova queste due regole:

(defrule person-exists
(personal-data (name ?name))
=>
(printout t “Rule person exists reports there is a person called ” ?name crlf)
)

(defrule are-there-people
(exists (personal-data (name ?name)))
=>
(printout t “Rule are-there-people reports there is at least one person” crlf)
)

(defrule check-each-person
(forall (personal-data (name ?name))
(
)
=>
(printout t “Rule check-each-person reports that all persons have a name” crlf)
)

Ora prova a creare una persona senza un nome, e eseguile di nuovo. La regola check-each-person non verrà attivata perchè non è vero che ogni fatto “matcha” il suo pattern.
CLIPS Tutorial 5 - Verità e controllo
Questa parte del tutorial utilizzà i dati seguenti. Puoi copiarli e incollarli nell’editor CLIPS.

(deftemplate personal-data
(slot name)
(slot age)
(slot weight)
(slot smoker)
(multislot date_of_birth)
)

(deffacts people
(personal-data (name adam) (weight 60) (age 30)
(smoker no) (date-of-birth 18 06 1970))
(personal-data (name brenda) (weight 120) (age 45)
(smoker yes) (date-of-birth 18 06 1955))
(personal-data (name charles) (weight 120) (age 60)
(smoker yes)(date-of-birth 18 06 1940))
)

(deffacts data
(date 18 06 2000)
)

Nel capitolo precedente, abbiamo imparato ad usare alcuni elementi condizionali: and, or, not, test, forall and exists. Ne esiste ancora un altro: LOGICAL. L’elemento condizionale logical ci aiuta con il problema del mantenimento della verità. Inserisci e avvia la seguente regola, usando i fatti definiti sopra.

(defrule cardiac-risk
(person (name ?name) (smoker yes) (weight ?weight))
(test (> ?weight 100))
=>
(assert (cardiac-risk ?name))
)

Questa regola testa se una persona è sovrappeso e fuma. Se lo è, allora asserisce un fatto di avvertimento, che dice che il suo cuore è a rischio. Ora cosa succede se la persona smette di fumare o dimagrisce? Il fatto di avvertimento continuerà ad esistere, perchè non è collegato in nessuna maniera alla regola che lo ha generato. Ciò significa che la base dei fatti non è più consistente con la realtà. Possiamo superare il problema usando l’elemento logical. Modifica la regola che hai appena inserito in questa maniera:

(defrule cardiac-risk
(logical (person (name ?name) (smoker yes) (weight ?weight)))
(logical (test (> ?weight 100)))
=>
(assert (cardiac-risk ?name))
)

Quando eseguiamo questa regola, i risultati sono identici a quelli generati dalla prima versione. La differenza occorre quando cambiamo i dati iniziali. Assicurati che la finestra dei fatti sia aperta, dopo aver eseguito la regola, localizza l’indice del fatto Brenda’s personal data (sul mio sistema è 2) e digita sulla riga di comando

CLIPS> (modify 2 (weight 80))

Come per magia, il fatto (cardiac-risk brenda) scompare dalla lista dei fatti. Usando la parola chiave logical, abbiamo creato un collegamento tra il fatto asserito e le premesse su cui esso si basa. Quando le premesse cambiano, i loro risultati non sono più validi, dunque vengono rimossi.Dunque perchè non rendere tutto logical in questo modo? Per prima cosa, ciò incrementa il tempo di computazione e la memoria necessaria - in un sistema complesso ci possono essere molti collegamenti da verificare. Inoltre, ciò non è sempre opportuno - per molte applicazioni, i fatti standard sono sufficienti.

Come adesso sai, l’ordine in cui le regole vengono attivate in CLIPS non è facilmente controllabile. Ogni regola che “matcha” un fatto potrebbe essere posizionata nell’agenda in una qualsiasi posizione. Come, dunque, possiamo noi stabilire una sorta di controllo sui nostri sistemi esperti? Ci sono due suluzioni principali a questo problema - usando fatti di controllo, oppure le salience. Ritorneremo sulle salience più tardi. Un fatto di controllo è uno di quei fatti il cui unico scopo è di dirigere il controllo del programma piùttosto che esprimere conoscenza sul dominio del problema. Includendo questi fatti di controllo nella parte sinistra delle regole, possiamo controllare quando le regole saranno attivate (fired). Per esempio, si supponga di voler controllare tutti i record dei nostri dati personali per vedere chi compie gli anni oggi, e aggiornare la sua età di conseguenza. Se abbiamo un fatto nella forma (date 18 6 2000) che rappresenta la data di oggi, allora la regola

(defrule birthdays-today
?person <- (personal-data (age ?age) (date-of-birth ?day ?month ?))
(date ?day ?month ?)
=>
(modify ?person (age (+ ?age 1)))
)

potrebbe sembrare una maniera appropriata di fare ciò. Provala, e vedi cosa succede. Premendo ctrl-break il programma CLIPS si ferma. Perchè ciò non funziona? La ragione è che si crea un loop infinito perchè la funzione modify ritratta il fatto personal-data e ne asserisce uno nuovo con la data aggiornata. Questo fatto successivamente “matcha” la stessa regola, che lo modifica nuovamente, e cosi via all’infinito. La maniera per ovviare a questo problema è dividere il problema in fasi distinte e usare dei fatti di controllo per eseguirle in ordine. In particolare ci sono due fasi in questo problema - identificare tutte le persone che compiono oggi gli anni, e successivamente aggiornare le loro età. Possiamo realizzare tutto ciò con le quattro regole:

(defrule birthdays-today
(check-birthdays)
(personal-data (name ?name) (date-of-birth ?day ?month ?))
(date ?day ?month ?)
=>
(assert (birthday ?name))
)

(defrule done-checking-birthdays
?check-birthday-fact <- (check-birthdays)
(forall (and (personal-data (name ?name) (date-of-birth ?day ?month ?))
(date ?day ?month ?)
)
(birthday ?name)
)

=>
(retract ?check-birthday-fact)
(assert (update-ages))
)

(defrule update-ages
?person<-(personal-data (name ?name) (age ?age) (date-of-birth ?day ?month ?year))
?birthday-fact<-(birthday ?name)
(update-ages)
=>
(modify ?person (age (+ ?age 1)))
(retract ?birthday-fact)
)

(defrule done-updating-ages
?update-age-fact<-(update-ages)
(not (birthday ?))
=>
(retract ?update-age-fact)
)

Le fasi sono controllate dai due fatti: (check-birthdays) e (update-ages). Nella fase 1, che è attivata dall’esistenza del fatto (check-birthdays), la regola birthdays-today asserisce un nuovo fatto per ogni persona il cui compleanno è oggi. La regola done-checking-birthdays rileva quando questo processo è finito, ritratta (check-birthdays) e asserisce (update-ages). Ora comincia la seconda fase, e la regola update-ages modifica a turno ogni persona, ritrattando i fatti birthday. Quando non ci sono più fatti birthday, la regola done-updating-ages viene attivata e ritratta (update-ages) facendo pulizia nella memoria di lavoro. Tutto ciò potrebbe sembrare un po’ complesso per quello che dovrebbe essere un task piuttosto semplice, e infatti lo è. Tuttavia illustra adeguatamente l’uso dei fatti di controllo.

L’altra maniera per controllare l’attivazione delle regole fa uso della proprietà salience delle regole. Quando una regola è dichiarata, gli può essere assegnato un valore di salience compreso tra -10000 e 10000, il valore di default è 0. Le regole con salience maggiore vengono attivate prima di quelle con salience minore. Cosidera le seguenti due regole:

(defrule poke-fun-at-smokers
(personal-data (name ?name) (smoker yes))
=>
(printout t ?name ” is a fool.” crlf)
)

(defrule worry-about-thin-people
(personal-data (name ?name) (weight ?weight))
(test (< ?weight 80))
=>
(printout t ?name ” is looking a bit thin.” crlf)
)

Cosi come stanno le cose, se mandi in run esse verranno entrambe attivate, ma l’ordine di attivazione dipenderà escliusivamente dall’ordine in cui sono stati creati i fatti da cui esse dipendono. Se modifichi le due regole così:

(defrule poke-fun-at-smokers
(declare (salience 10))
(personal-data (name ?name) (smoker yes))
=>
(printout t ?name ” is a fool.” crlf)
)

(defrule worry-about-thin-people
(declare (salience 20))
(personal-data (name ?name) (weight ?weight))
(test (< ?weight 80))
=>
(printout t ?name ” is looking a bit thin.” crlf)
)

la maggiore salience della regola worry-about-thin-people ci assicura che essa sarà attivata per prima. Come regola generale, prova ad usare le salience con moderazione. Se ti accorgi che più livelli di salience sono necessari (più di quattro per esempio) allora forse dovresti pensare di scrivere il programma in un linguaggio che ti dia il livello di controllo di cui hai bisogno, oppure riscrivere il programma affinchè si adegui al paradigma di programmazione a produzioni. Esiste una terza maniera di controllare l’esecuzione di regole o gruppi di regole, collezionandole in moduli, di cui solo uno è attivo in un dato momento.
CLIPS Tutorial 6 - Altra roba
Oltre ai fatti e alle regole, CLIPS offre un set completo di funzioni associate ai ‘normali’ linguaggi procedurali. Stiamo per dimostrare alcune di esse programmando “male” CLIPS per impostare le carte del gioco pontoon. Useremo il seguente pezzo di codice per iniziare:

(defglobal ?*shuffleswaps* = 150)

(deffacts cards
(card-names ace two three four five six seven eight nine ten jack queen king)
(card-values 1 2 3 4 5 6 7 8 9 10 10 10 10)
(card-suits hearts clubs diamonds spades)
)

Il promo compito è di settare il mazzo di carte che useremo per giocare. Questo si fa con le due regole:

(defrule go
(initial-fact)
=>
(assert (state create-pack))
)

(defrule create-cards
?old-state <- (state create-pack)
(card-names $?names)
(card-suits $?suits)
=>
(bind ?number 0)
(loop-for-count (?suit 1 4) do
(loop-for-count (?name 1 13) do

(bind ?number (+ ?number 1))
(assert (draw-pile (nth$ ?name ?names) (nth$ ?suit ?suits) ?number))
)
)
(assert (top-card 1))
(retract ?old-state)
(assert (state shuffle-pack))
)

La prima regola è solamente una regola di controllo; la seconda effettua il lavoro. Utilizza un costrutto non ancora trattato - l’iteratore loop-for-count. é molto simile a un ciclo for in C o BASIC. Si da una variabile, un limite inferiore e uno superiore, ed esso ripete ogni cosa presente tra il do e la parentesi di chiusura, incrementando la variabile dal limite inferiore fino al limite superiore. Inoltre esiste la funzione while, che è simile. Un’altra nuova funzione usata è nth$, che ritorna uno il valore in una specifica posizione in un campo multifield. Dunque se abbiamo una variabile ?a che ha il valore multiplo (dog cat fish), allora (nth$ 2 ?a) ritornerà il valore (fish). dunque quando questa regola ha finito, abbiamo un mazzo di carte, ma esse sono in ordine perfetto. è necessario mischiarle. Le seguenti due regole fanno ciò:

(defrule start-shuffle
(state shuffle-pack)
(not (swap-count ?))
=>
(seed (round (time)))
(assert (swap-count 1))
(assert (swap-position1 (round (+ (* (/ (random) 32767) 51) 1))))
(assert (swap-position2 (round (+ (* (/ (random) 32767) 51) 1))))
)

(defrule shuffle-pack
(state shuffle-pack)
?swapcard1 <- (swap-position1 ?cp1)
?swapcard2 <- (swap-position2 ?cp2)
?swapcount <- (swap-count ?cc)
(test (< ?cc ?*shuffleswaps*))
?card1 <- (draw-pile ?name1 ?suit1 ?cp1)
?card2 <- (draw-pile ?name2 ?suit2 ?cp2)

=>
(retract ?card1)
(retract ?card2)
(retract ?swapcount)
(retract ?swapcard1)
(retract ?swapcard2)
(assert (draw-pile ?name1 ?suit1 ?cp2))
(assert (draw-pile ?name2 ?suit2 ?cp1))
(assert (swap-count (+ ?cc 1)))

(assert (swap-position1 (round (+ (* (/ (random) 32767) 51) 1))))
(assert (swap-position2 (round (+ (* (/ (random) 32767) 51) 1))))
)

(defrule pack-shuffled
?state <- (state shuffle-pack)
?swapcount <- (swap-count ?cc)
(test (= ?cc ?*shuffleswaps*))
=>
(retract ?swapcount)
(retract ?state)
(assert (state print-deck))
)

L’algoritmo che le mischia, non è brillante. Tutto ciò che fa è prendere due carte random e scambiare le loro posizioni nel mazzo. Nulla ci garantisce che il mazzo sarà mischiato in maniera adeguata. Le sole due cose nuove in questa regola sono (seed (round (time))) e (random).

La seguente parte di programma visualizza a video le carte nell’ordine in cui sono state posizionate.

(defrule start-printdeck
(state print-deck)
(not (next-card ?))
(top-card ?topcard)
=>
(assert (next-card ?topcard))
)

(defrule printdeck
(state print-deck)
?nextcard <- (next-card ?number)
(draw-pile ?name ?suit ?number)
=>
(printout t ?number “: ” ?name ” of ” ?suit ” has value ” (card-value ?name) crlf)
(retract ?nextcard)
(assert (next-card (+ ?number 1)))
)

Questa regola usa una funzione (card-value)che non è parte di CLIPS, ma è definita dall’utente (user-defined). Di seguito, la sua dichiarazione:

(deffunction card-value
(?card-name)
(switch ?card-name
(case ace then (bind ?return-value 1))
(case two then (bind ?return-value 2))
(case three then (bind ?return-value 3))
(case four then (bind ?return-value 4))
(case five then (bind ?return-value 5))
(case six then (bind ?return-value 6))
(case seven then (bind ?return-value 7))
(case eight then (bind ?return-value 8))
(case nine then (bind ?return-value 9))
(case ten then (bind ?return-value 10))
(case jack then (bind ?return-value 10))
(case queen then (bind ?return-value 10))
(case king then (bind ?return-value 10))
(default (bind ?return-value 0))

)
(return ?return-value)
)

La funzione prende il nome di una carta (ace, two, king etc) e ritorna il suo valore numerico. Usa lo statement switch per fare ciòwe
.

Nessun commento »

Non c’è ancora nessun commento.

RSS feed dei commenti a questo articolo. TrackBack URI

Lascia un commento

ruldrurd
© JoUjOu Blob , Web Design by Laurentiu Piron
Entries (RSS) and Comments (RSS)