Implementarea Retelelor Semantice Si a Frame Urilor In Guru
INTRODUCERE
În această lucrare ne propunem să studiem diferite aplicații care utilizează bazele de cunoștințe, de la rețele semantice până la sisteme expert. Rețeaua semantică este o metodă de reprezentare a cunoștințelor care permit efectuarea de deducții automate. În cele ce urmează vom arăta cum pot fi reprezentate cunoștințele într-o rețea semantică, cum se implementează o rețea semantică și cum se realizează deducțiile.
Metodele de reprezentare a cunoștințelor utilizează în general un anumit aparat matematic care permite formalizarea reprezentării cunoștințelor. Fiecare metodă de reprezentare a cunoștințelor are un mecanism propriu de efectuare a deducției. Acest mecanism este ilustrat din punct de vedere practic într-un program numit motor de inferență. În funcție de metoda aleasă se realizează motorul de inferență. În cele ce urmează vom înțelege prin piesă de cunoaștere o mulțime finită de propoziții sau fraze enunțate în limbaj natural. Vom presupune că ea nu conține propoziții interogative și nici exclamative.
Capitolul 1 cuprinde aspectele principale ale programării în SWI-Prolog versiunea 1.6.0.
Capitolul 2 oferă o scurtă prezentare a noțiunilor fundamentale despre rețele semantice.
Capitolul 3 prezintă un model de sistem expert.
Capitolul 1
SWI-Prolog
1.1. Despre SWI-Prolog
SWI-Prolog este o implementare a Prologului bazată pe o submulțime de instrucțiuni a lui WAM (Warren Abstract Machine). El a fost creat și implementat astfel încât poate fi ușor utilizat în programarea logică și în relațiile dintre programarea logică și alte programări procedurale (ca programarea orientată obiect).
SWI-Prolog a fost îmbogățit cu o mulțime de predicate interne și performanțe îmbunătățite care fac posibilă dezvoltarea aplicațiilor mari în el. Versiunea pe care o utilizăm noi oferă un nucleu sistem, o colecție de predicate și o interfață pentru limbajul C.
Intenția principală a fost de a crea mediul Prolog care să ofere suficientă putere și flexibilitate pentru a construi aplicații mari, dar este suficient de simplu pentru a fi modificat și optimizat prin introducerea de noi tipuri de date. Performanțele optimizării sunt limitate de obiectivele principale:
– portabilitate (SWI-Prolog este în întregime scris în limbajul C și Prolog)
– adaptabilitate
SWI-Prolog este bazat pe o formă restrânsă a lui WAM care definește numai 7 instrucțiuni. El este extins cu un set minim de instrucțiuni care îi îmbunătățesc performanțele. Această extensie include instrucțiuni speciale pentru unificare, pentru a apela alte predicate, câteva predicate interne des utilizate, etc. Predicatele pe care le vom utiliza în aplicațiile noastre vor fi explicate în continuare.
SWI-Prolog oferă o legătură strânsă cu programarea orientată obiect. Ea poate fi utilizată cu succes în SWI-Prolog, oferind posibilitatea dezvoltării și modularizării aplicațiilor foarte mari.
Apelul SWI-PROLOG de la shell-ul UNIX
Se recomandă să se instaleze SWI-Prologul ca „pl” în directorul binar local. El poate fi apoi apelat de la shell-ul UNIX cu „pl”. Sistemul va încărca fișierul de boot implicit, va executa inițializările necesare și va intra apoi în primul nivel interactiv.
După inițializările de sistem necesare sistemul consultă (vezi consult/1) fișierul de inițializare al utilizatorului. Acest fișier de inițializare trebuie să fie numit „.plrc” și se găsește ori în directorul curent ori în directorul home al utilizatorului. Dacă ambele există, va fi încărcat fișierul de inițializare de inițializare din directorul curent. Numele fișierului de inițializare poate fi schimbat cu opțiunea „-f file”. După încărcarea fișierului de inițializare SWI-Prolog execută goal-ul de inițializare de inițializare al utilizatorului. Goal-ul implicit este un predicat al sistemului care tipărește mesajul de bun venit. Acest goal poate fi modificat cu opțiunea „-g goal”. Apoi este apelat goal-ul primului nivel, care implicit este o buclă Prolog interactivă (vezi prolog/0). Utilizatorul poate redefini acest goal implicit cu ajutorul opțiunii „-t toplevel”.
Opțiunile liniei de comandă
-Lsize
dă mărimea stivei locale în Kbytes (implicit 200 K). Nu se lasă spațiu între opțiune și argument. Pentru calculatoarele cu alocare dinamică a stivei acest flag stabilește mărimea maximă posibilă la care poate ajunge stiva (implicit 2Mbytes). L0 stabilește limita la cea mai mare valoare posibilă.
-Gsize
dă mărimea stivei globale în Kbytes (implicit 100 K). Pentru calculatoarele cu alocare dinamică a stivei implicit este 4Mbytes.
-Tsize
dă mărimea stivei trail în Kbytes (implicit 50 K). Pentru calculatoarele cu alocare dinamică a stivei implicit este 4Mbytes.
-c file…
compilează fișierele într-un fișier de cod intermediar.
-o output
folosit în combinație cu –c determină fișierul de ieșire rezultat în urma compilării.
-O
compilare optimizată.
-f file
folosește file ca fișier de inițializare în locul „.plrc”. „-f none” oprește SWI-Prologul să caute un fișier de inițializare.
-g goal
goal-ul este executat înainte de a intra în primul nivel. Implicit este un predicat care tipărește mesajul de bun venit. Acest mesaj poate fi suprimat dacă se dă „-g true”. Goal poate să fie un termen complex și în acest caz trebuie inclus în ghilimele.
-t goal
folosește un goal ca prim nivel interactiv în locul goal-ului implicit prolog/0. goal poate fi un termen complex. Dacă goal-ul primului nivel reușește, SWI-Prolog-ul iese cu starea 0. Dacă eșuează starea de ieșire este 1. Acest flag determină și dacă goal-ul a început cu break/0 și abort/0. Dacă doriți să opriți utilizatorul să intre în modul interactiv începeți aplicația cu „-g goal” și dați „halt” ca prim nivel.
-x bootfile
folosește bootfile în locul fișierului de boot implicit al sistemului.
Compilarea
Colecțiile de fișiere sursă SWI-Prolog pot fi combinate într-un fișier de cod intermediar. Un fișier de cod intermediar e un fișier de date de la care poate fi lansat SWI-Prolog-ul. Comanda pentru a compila mai multe fișiere sursă este:
pl[opțiuni] [-o output] –c file…
Fișierele sursă individuale pot include la rândul lor alte fișiere folosind consult/1, ensure_loaded/1 și use_module/[1,2]. Când este omisă opțiunea –o file fișierul de cod intermediar este numit a.out.
Fișierele de cod intermediar încep cu codul #! și sunt executabile. Deci ele pot fi lansate cu comanda:
pl –o my_program –c…
…
my_program [opțiuni]
sau
pl –x my_program [opțiuni]
Fișierele compiolate cu “-c” trebuie să satisfacă următoarele restricții:
term_expansion/2 trebuie să nu folosească assert/1 și/sau retract/1.
Fișierele pot fi incluse doar cu directivele […], consult/1, ensure_loaded/1 și use_module/[1,2]. Apelurile unor predicate de încărcare definite de utilizator nu vor fi compilate.
Observații referitoare la sintaxă
SWI-Prolog utilizează sintaxa standard “Edinburgh”. Totuși sunt acceptate următoarele construcții non-standard:
0`<char> descrie valoarea ASCII a lui <char>. Pentru a testa dacă C este caracter alfabetic mic se poate folosi “between(0`a, 0`z, C)”.
/*…/*…*/…*/ se folosește dacă un bloc /*…*/ trebuie să fie comentat în exterior.
Predicate built-in
Descrierea predicatelor
Argumentele predicatelor sunt precedate de „+”, „-„ sau „?”. „+” arată că argumentul este argument de intrare pentru predicat, „-„ arată că este argument de ieșire iar „?” arată că este ori de intrare ori de ieșire. Construcții ca „op/3” arată că predicatul „op” are aritatea „3”.
Consultarea fișierelor sursă Prolog
Fișierele sursă SWI-Prolog în mod normal au extensia „.pl”. Specificarea extensiei este opțională. Toate predicatele care folosesc fișiere sursă verifică mai întâi dacă există un fișier cu extensia „.pl”. Dacă nu, se verifică dacă există întregul nume al fișierului. Astfel „foo” se referă la „foo.pl” sau „foo” în unul din directoarle bibliotecă specificate de predicatul dinamic library_directory/1.
Directivele pot fi plasate oriunde într-un fișier sursă și se pot referi la orice predicat. Ele sunt executate atunci când sunt întâlnite. Dacă directiva eșuează este tipărit un mesaj de avertisment. Directivele sunt specificate prin: -/1 sau ?-/1.
consult(+File)
citește File ca fișier sursă Prolog. File poate fi o listă de fișiere și în acest caz se consultă pe rând toți membrii. File poate începe cu secvențele ~, ~<user> și $<var>. File poate fi și library(Name) și în acest caz se execută în biblioteci un fișier cu numele specificat. consult/1 poate fi abreviat dând doar un număr de nume de fișiere într-o listă. Exemple:
?-consult(load). %consultă „load” sau „load.pl”
?-[library(bim)]. %încarcă biblioteca Bim.
ensure_loaded(+File)
este echivalent cu consult/1, dar fișierul este consultat doar dacă nu a mai fost făcut acest lucru. Se recomandă pentru a încărca fișiere din alte fișiere.
make
consultă toate fișierele sursă care au fost modificate de când au fost consultate. El verifică toate fișierele sursă încărcate: fișiere sursă încârcate cu ajutorul lui consult sau una din derivatele sale și fișierele încărcate într-o stare compilată cu ajutorul pl-c…make/0 de obicei apelat de edit/[0,1] sau ed/[0,1]. Poate fi combinat cu opțiunea –c pentru a mării viteza dezvoltării pachetelor mari:
pl –g make –o my_program –c file…
library_directory(-Atom)
predicat dinamic ce specifică directoarele bibliotecii. Implicit sunt definite ., ./lib, ~/lib/prolog și biblioteca sistemului (în această ordine).
term_expansion(+Term1, -Term2)
predicat dinamic de obicei nedefinit. Când e definit de utilizator, toți termenii citiți în timpul consultării sunt dați acestui predicat. Dacă predicatul reușește, Prolog va adăuga Term2 în baza de date înainte de a citi termenul Term1. Term2 poate fi un termen de forma „?-Goal” sau „:-Goal” și în acest caz Goal este tratat ca o directivă. Term2 poate fi și o listă iar în acest caz toți termenii listei sunt stocați în baza de date sau apelați dacă sunt directive.
Listarea predicatelor și interfața cu un editor
SWI-Prolog oferă o interfață cu editorul UNIX „vi”, cu editorul lui Richard O`Keefe „top”, cu „emacs” și „emacsclient”. Editorul folosit e determinat de variabila UNIX de mediu EDITOR, care trebuie să conțină calea către editorul respectiv. Dacă această variabilă e nedefinită se folosește „vi”.
După ce utilizatorul părăsește editorul este apelat make/0 pentru a reîncărca toate fișierele sursă ce au fost încărcate cu consult/1 și au fost modificate.
O specificare de predicate este fie un termen cu același functor și aceeași aritate ca predicatul dorit, fie un termen de forma Functor/Aritate, fie un atom singur. În ultimul caz se caută un predicat cu numele respectiv și aritate arbitrară. Dacă există mai mult de un astfel de predicat, sistemul va cere confirmarea la fiecare din predicatele ce corespund.
ed(+Pred)
editează fișierul sursă al lui Pred și caută predicatul.
edit(+File)
editează fișierul File. File e o specificare de fișier ca pentru consult/1 (dar nu o listă). Fișierul trebuie să existe.
listing(+Pred)
listează predicatele specificate (dacă este dat un atom, vor fi listate toate predicatele cu acel nume).
listing
listează toate predicatele bazei de date utilizând listing/1.
portray_clause(+Clause)
tipărește o clauză. Aceasta trebuie specificată ca un termen „Head :- Body” (se vor pune paranteze pentru a evita problemele legate de precedența operatorilor). Faptele sunt prezentate ca „Head :-true”.
Compararea și unificarea termenilor
Termenii sunt ordonați în așa numita „ordine standard”. Această ordine e definită de:
Variabile <Atomi <String-uri <Numere <Termeni
Variabilă veche <variabilă nouă
Atomii sunt comparați alfabetic.
String-urile sunt comparate alfabetic.
Numerele sunt comparate după valoare. Întregii și numerele reale sunt tratate identic.
Termenii sunt verificați mai întâi după functor (alfabetic), apoi după aritate și în final recursiv după argumente, începând cu argumentul cel mai din stânga.
+Term1 == +Term2
rerm_expansion(+Term1, -Term2)
predicat dinamic de obicei nedefinit. Când e definit de utilizator, toți termenii citiți în timpul consultării sunt dați acestui predicat. Dacă predicatul reușește, Prolog va adăuga Term2 în baza de date înainte de a citi termenul Term1. Term2 poate fi un termen de forma „?-Goal” sau „:-Goal” și în acest caz Goal este tratat ca o directivă. Term2 poate fi și o listă iar în acest caz toți termenii listei sunt stocați în baza de date sau apelați dacă sunt directive.
Listarea predicatelor și interfața cu un editor
SWI-Prolog oferă o interfață cu editorul UNIX „vi”, cu editorul lui Richard O`Keefe „top”, cu „emacs” și „emacsclient”. Editorul folosit e determinat de variabila UNIX de mediu EDITOR, care trebuie să conțină calea către editorul respectiv. Dacă această variabilă e nedefinită se folosește „vi”.
După ce utilizatorul părăsește editorul este apelat make/0 pentru a reîncărca toate fișierele sursă ce au fost încărcate cu consult/1 și au fost modificate.
O specificare de predicate este fie un termen cu același functor și aceeași aritate ca predicatul dorit, fie un termen de forma Functor/Aritate, fie un atom singur. În ultimul caz se caută un predicat cu numele respectiv și aritate arbitrară. Dacă există mai mult de un astfel de predicat, sistemul va cere confirmarea la fiecare din predicatele ce corespund.
ed(+Pred)
editează fișierul sursă al lui Pred și caută predicatul.
edit(+File)
editează fișierul File. File e o specificare de fișier ca pentru consult/1 (dar nu o listă). Fișierul trebuie să existe.
listing(+Pred)
listează predicatele specificate (dacă este dat un atom, vor fi listate toate predicatele cu acel nume).
listing
listează toate predicatele bazei de date utilizând listing/1.
portray_clause(+Clause)
tipărește o clauză. Aceasta trebuie specificată ca un termen „Head :- Body” (se vor pune paranteze pentru a evita problemele legate de precedența operatorilor). Faptele sunt prezentate ca „Head :-true”.
Compararea și unificarea termenilor
Termenii sunt ordonați în așa numita „ordine standard”. Această ordine e definită de:
Variabile <Atomi <String-uri <Numere <Termeni
Variabilă veche <variabilă nouă
Atomii sunt comparați alfabetic.
String-urile sunt comparate alfabetic.
Numerele sunt comparate după valoare. Întregii și numerele reale sunt tratate identic.
Termenii sunt verificați mai întâi după functor (alfabetic), apoi după aritate și în final recursiv după argumente, începând cu argumentul cel mai din stânga.
+Term1 == +Term2
reușește dacă Term1 e echivalent cu Term2.
+Term1 \== +Term2
echivalent cu „\+Termn1 == +Term2”
+Termn1 = +Term2
unifică Term1 cu Term2. Reușește dacă reușește unificarea.
+Termn1 \= +Term2
echivalent cu „\+Termn1 = +Term2”
+Term1 =@= +Term
reușește dacă Term1 e „structural egal” cu Term2. Echivalarea structurală e mai slabă decât echivalența (==2), dar mai puternică decât unificarea (=/2).
Doi termeni sunt structural egali dacă reprezentarea lor arborescentă este identică și dacă au același pattern pentru variabile. Exemplu:
a =@= A false
A =@= B true
X(A,A) =@= X(B,C) false
X(A,A) =@= X(B,B) true
X(A,B) =@= X(C,D) true
+Term1 \=@= +Term2
echivalent cu „\+Term1 =@= +Term2”.
+Term1 @< +Term2
reușește dacă Term1 e înaintea lui Term2 în ordine standard.
+Term1 @=< +Term2
reușește dacă ambii termeni sunt egali (==) sau dacă Term1 e înaintea lui Term2 în ordine standard.
+Term1 @> +Term2
reușește dacă Term1 e după Term2 în ordine standard.
+Term1 @<= +Term2
reușește dacă ambii termeni sunt egali (==) sau dacă Term1 e după Term2 în ordine standard.
Predicate de control
fail
eșuează întotdeauna.
true
reușește întotdeauna.
repeat
reușește întotdeauna, furnizând un număr infinit de puncte de alegere.
!
tăietura. Reprezintă o „barieră” la o nouă încercare de satisfacere pentru condițiile anterioare tăieturii. Deoarece structurile de control ;/2, I/2, ->/2 și \+/1 sunt de obicei manipulate de compilator, tăietura poate opera prin aceste predicate. Exemplu:
t1 :- (a , ! , fail ; b). % taie a/0 și t1/0
t2 :- (a->b , ! ; c). % taie b/0 și t2/0
t3(G) :- a , call(G) , fail. % dacă „G=!” taie a/0 și t3/0
t4(G) :- a , call(G) , fail. % dacă „G=!” tăietura nu are efect
t5 :- call (a , ! , fail ; b). % tăietura nu are efect
t6 :- \+(a , ! , fail ; b). % taie a/0 și t6/0
Deoarece argumentul lui call/1 e evaluat mai întâi de predicat și apoi de compilator tăietura nu are efect.
+Goal1 , +Goal2
Conjuncția. Reușește dacă „Goal1” și „Goal2” pot fi demonstrate:
Goal1, Goal2 :- Goal1, Goal2.
+Goal1 ; +Goal2
Disjuncția. Este definită ca:
Goal1 ; _Goal2 :- Goal1.
_Goal1 ; Goal2 :- Goal2.
+Condition -> +Action
If – Then și If – Then – Else. Este implementat ca:
If -> Then ; _Else :- If ,!, Then.
If -> _Then ; Else :-! , Else.
If -> Then :- If ,!, Then.
\+ +Goal
reușește dacă „Goal” nu poate fi demonstrat.
Declararea proprietăților predicatelor
Această secțiune descrie directivele care manipulează atributele definițiilor predicatelor. În SWI-Prolog toate aceste directive sunt predicate, ceea ce sunt predicate, ceea ce implică faptul că ele pot fi apelate de un program.
dynamic +Functor/+Arity, …
Informează interpretorul că definiția predicatului (predicatelor) poate fi schimbată în timpul execuției (folosind assert/1 și/sau retract/1).
multifile +Functor/+Arity, …
informează sistemul că pentru predicatele specificate clauzele ar putea să nu se afle împreună în fișierul sursă.
Operații de I/O
Swi-Prolog furnizează două pachete diferite pentru intrări și ieșiri. Primul pachet folosește noțiunea de „curent-input” și „curent-output”. Predicatele de citire și scriere se referă implicit la aceste streamuri. În al doilea pachet e folosit ca argument în predicatele de citire și scriere pentru a specifica sursa sau destinația.
I/O folosind surse și destinații implicite
Predicatele de citire și scriere se referă în acest caz la streamul input și output curent. Inițial aceste streamuri sunt conectate la terminal. Streamul de ieșire (output) curent poate fi schimbat folosind tell/1 sau append/1. Streamul de intrare (input) curent poate fi schimbat folosind see/1. Valoarea curentă a streamului poate fi obținută folosind telling/1 pentru output și seeing/1 pentru input.
Sursa și destinația sunt ori un fișier, ori unul din cuvintele rezervate de mai jos, ori terminalul „pipe(Comandă)”.
Exemple de specificări de surse și destinații:
?-see(data). % începe să citească din fișierul „data”
?-tell(stderr). % începe să scrie în streamul de eroare
?-tell(pipe(lpr)). % începe să scrie la imprimantă.
see(+SrcDest)
stabilește ca stream de intrare curent SrcDest. Dacă SrcDest a fost deja deschis pentru citire cu see/1 și nu a fost închis de atunci, citirea va fi reluată. Astfel, SrcDest va fi deschis și pointerul de fișier va fi poziționat la începutul fișierului.
tell(+Srcdest)
Stabilește ca stream de ieșire curent SrcDest. Dacă SrcDest a fost deja deschis pentru scriere cu tell/1 sau append/1 și nu a fost închis, scrierea va fi reluată. Altfel, fișierul este creat sau, dacă există, conținutul său este șters.
append(+File)
similar cu tell/1, dar poziționează pointerul fișierului la sfârșit fără a-i șterge conținutul, dacă fișierul există. Construcția cu „pipe” nu e acceptată de acest predicat.
seeing(-SrcDest)
unifică numele streamului de intrare curent cu SrcDest.
telling(-SrcDest)
unifică numele streamului de ieșire curent cu SrcDest.
Exemplu: obținerea directorului curent.
seen
închide streamul de intrare curent. Noul stream de intrare devine user.
told
închide streamul de ieșire curent. Noul stream de ieșire devine user.
Streamuri de I/O explicite
În acest pachet streamurile sunt explicit create folosind predicatul open/3. Identificatorul streamului rezultat este apoi dat ca parametru în predicatele de citire și scriere pentru a specifica sursa sau destinația datelor.
open(+SrcDest, +Mode, ?Stream)
SrcDest este ori un atom ce specifică un fișier Unix, ori termenul „pipe(Comandă)”, ca în see/1 sau tell/1. Mode poate fi read, write sau append. Stream e fie o variabilă care e legată la un întreg mic ce va identifica streamul, fie un atom care va fi în acest caz chiar identificatorul streamului. În acest ultim caz atomul nu poate fi un identificator de stream deja existent. Exemple:
?-open(data,read,Stream). % deschide „data” pentru citire
?-open(pipe(lpr),write,printer). % „printer” e un stream la „lpr”
close(+Stream)
închide streamul specificat. Dacă Stream nu e deschis va apare un mesaj de eroare. Dacă streamul închis este streamul input sau output curent, atunci va fi stabilit terminalul ca stream input sau output curent.
current_stream(?File, ?Mode, ?Stream)
e adevărat dacă este deschis un stream având ca specificator de fișier File, modul Mode și identificatorul de stream Stream. Streamurile rezervate user și user-error nu sunt generate de acest predicat. Dacă un stream a fost deschis cu modul append, acest predicat va genera modul write.
Trecerea de la I/O implicite la I/O explicite și invers
Predicatele următoare pot fi folosite pentru comutarea între streamurile de I/O implicite și explicite.
set_input(+Stream)
stabilește ca stream curent de intrare Stream. Astfel open(file,read,Stream), set_input(Stream) e echivalent cu see(file).
set_output(+Stream)
stabilește ca stream curent de ieșire Stream.
current_input(-Stream)
returnează streamul de intrare curent.
current_output(-Stream)
returnează streamul de ieșire curent.
Operații de intrare/ieșire cu caractere
nl
scrie un caracter linie nouă în streamul curent de ieșire. Este echivalent cu put(10).
nl(+Stream)
scrie un caracter linie nouă în Stream.
put(+Char)
scrie Char în streamul de ieșire curent. Char e fie o Expresie întreagă având valoarea 0 Char 255, fie un atom cu un caracter.
put(+Stream, +Char)
scrie Char în Stream.
tab(+Amount)
scrie Amount spații în streamul curent de ieșire. Amount trebuie să fie o expresie care se evaluează la un întreg pozitiv.
tab(+Amount, +Stream)
scrie Amount spații în Stream.
get0(-Char)
citește streamul de intrare curent și unifică următorul caracter cu Char. Char e unificată cu –1 la sfârșitul fișierului.
get0(+Stream, -Char)
citește următorul caracter din Stream.
get(-Char)
citește streamul de intrare curent și unifică următorul caracter non-blanc (nevid) cu Char. Char e unificată cu –1 la sfârșitul fișierului.
get(+Stream, -Char)
citește următorul caracter non-blanc din Stream.
get_single_char(-Char)
returnează un singur caracter din streamul de intrare „user” (fără a lua în considerare streamul de intrare curent). Caracterul este returnat fără ecou la terminal. Dacă SWI-Prolog a fost lansat cu flagul –tty acest predicat citește o întreagă linie de intrare și returnează primul caracter non-blanc din această linie sau codul ASCII de linie nouă (10) dacă întreaga linie constă din blancuri.
Citirea și scrierea termenilor
display(+Term)
scrie Term în streamul curent de ieșire utilizând notația standard cu paranteze rotunde.
display(+Stream, +Term)
scrie Term în Stream.
displayq(+Term)
scrie Term în streamul de ieșire curent utilizând notația standard cu paranteze rotunde. Atomii care au nevoie de ghilimele sunt scriși cu ghilimele. Termenii scriși cu acest predicat pot fi întotdeauna citiți înapoi, fără să se țină cont de declarațiile operatorului curent.
displayq(+Stream, +Term)
scrie Term în Stream.
write(+Term)
scrie Term în streamul de ieșire curent utilizând paranteze drepte și operatori când e necesar.
write(+Stream, +Term)
scrie Term în Stream.
writeq(+Term)
scrie Term la ieșirea curentă folosind paranteze drepte și operatori când e necesar. Atomii care au nevoie de ghilimele sunt scriși cu ghilimele. Termenii scriși cu acest predicat pot fi întotdeauna citiți înapoi cu read/1.
writeq(+Stream, +Term)
scrie Term în Stream inserând ghilimelele.
print(+Term)
tipărește Term în streamul de ieșire curent la fel ca write/1, dar pentru fiecare (sub)termen al lui Term este apelat mai întâi predicatul dinamic portray/1. Dacă acest predicat reușește, atunci print presupune că (sub)termenii au fost scriși. Aceasta permite utilizatorului să definească scrierea termenilor.
print(+Stream, +Term)
tipărește Term în Sream.
portray(+Term)
este un predicat dinamic care poate fi folosit de utilizator pentru a schimba comportarea lui print/1 pentru (sub)termeni. Pentru fiecare subtermen întâlnit care nu e variabilă print/1 apelează mai întâi portray/1 folosind termenul ca argument. Pentru liste, doar lista ca întreg poate fi folosită de portry/1. Dacă portray reușește atunci print/1 presupune că termenii au fost scriși.
read(-Term)
citește următorul termen Prolog de la streamul de intrare curent și îl unifică cu Term. Când întâlnește sfârșit de fișier Term e unificat cu atomul end_of_file.
read(+Stream, -Term)
citește Term din Stream.
read_clause(-Term)
echivalent cu read/1 dar avertizează utilizatorul dacă varaibilele apar o singură dată într-un termen (variabile singleton) care nu încep cu „_” dacă style_check(singleton) este activ (implicit). Se folosește pentru a citi fișierele sursă Prolog (vezi consult/1).
read_clause(+Stream, -Term)
citește o clauză din Stream.
read_variables(-Term, -Bindings)
similar cu read/1 dar Bindings e unificat cu o listă de tupluri „Nume = Var” furnizând astfel accesul la numele actuale ale variabilelor.
read_variables(+Stream, -Term, -Bindings)
citire, returnând termenii și legăturile din Stream.
Analiza și construcția termenilor
functor(?Term, ?Functor, ?Arity)
reușește dacă Term e un termen cu functorul Functor și aritate Arity. Dacă Term e o variabilă atunci e unificat cu un nou termen ce va conține numai varaibile.
arg(+Arg, +Term, ?Value)
Term trebuie să fie instanțiat cu un termen, Arg cu un întreg între 1 și aritatea lui Term. Value este unificată cu al Arg argument al lui Term.
+Term=..?List
List este o listă al cărei cap (head) este functorul lui Term iar celelalte argumente(tail) sunt argumentele termenului. Fiecare din cele două argumente poate fi variabilă, dar nu amândouă. Acest predicat e numit „Univ”. Exemple:
?-foo(hello, X)=..List.
List = [foo, hello, X].
?-Term =..[baz, foo(1)].
Term = baz(foo(1)).
numbervars(+Term, +Functor, +Start, -End)
unifică variabilele libere ale lui Term cu un termen construit cu atomul Functor cu un argument. Argumentul este numărul variabilei. Numărătoarea începe cu Start. End e unificat cu numărul ce trebuie să dea următoarea variabilă. Exemple:
?-numbervars(foo(A, B, A), this_is_a_variable, 0, End).
A=this_is_a_varaible(0)
B=this_is_a_varaible(1)
End=2
Analiza și construcția atomilor
name(? Atom, ?String)
String e o listă de valori ASCII descriind Atom. Fiecare din cele două argumente poate fi variabilă, dar nu amândouă. Când String e legat la o listă de valori ASCII ce descrie un întreg și Atom e o variabilă, Atom va fi unificat cu valoarea întregului descris de String (de exemplu „name(N, „300”), 400 is N + 100” reușește).
int_to_atom(+Int, +Base, -Atom)
convertește Int într-o reprezentare ASCII folosind Base și unifică rezultatul cu Atom. Dacă Base 10 baza va fi adăugată ca prefix la Atom. Base = 10 va încerca să interpreteze Int ca o valoare ACSII și returnează 0c. Altfel 2 Base 36. Exemple:
int_to_atom(45,2,A) A = 2101101
int_to_atom(97,0,A) A = 0a
int_to_atom(56,10,A) A = 56
term_to_atom(?Term, ?Atom)
reușește dacă Atom descrie un termen care e unificat cu Term. Când e Atom instanțiat, el e convertit și apoi unificat cu Term. Altfel Term e „scris” în Atom folosind write/1.
atom_to_term(+Atom, -Term, -Bindings)
folosește Atom ca intrare în read_variables/2 și returnează termenul citi în Term și legăturile variabilei în Bindings. Bindings e o listă de cupluri Nume = Var, furnizând astfel accesul la numele actuale ale variabilelor.
concat(?Atom1, ?Atom2, ?Atom3)
Atom3 e alcătuit prin concatenarea lui Atom1 și Atom2. Cel puțin două din argumente trebuie să fie instanțiate cu atomi, întregi sau numere reale.
concat_atom(+List, -Atom)
List e o listă de atomi, întregi sau numere reale. Reușește dacă Atom poate fi unificat cu elementele concatenate ale List. Dacă List are exact 2 elemente e echivalent cu concat/3.
atom_length(+Atom, -Length)
reușește dacă Atom e un atom de Length caractere lungime. Se poate folosi și pentru întregi și reali furnizând numărul de caractere de ieșire când e folosit cu write/1.
Reprezentarea unui text cu șiruri
SWI-Prolog acceptă date de tip string. Reprezentarea unui text ca o listă de valori ASCII este, din punct de vedere logic, cea mai bună soluție. Această soluție are totuși două neajunsuri:
lista nu poate fi distinsă de o listă de întregi (mici);
se consumă 12 bytes pentru fiecare caracter stocat.
Cu stringuri fiecare caracter necesită doar 1 bytes pentru stocare. Stringurile se memorează în stiva globală.
string_to_atom(?String, ?Atom)
conversie logică între un string și un atom. Cel puțin unul din cele două argumente trebuie să fie instanțiat. Atom poate fi și un întreg sau un număr real.
string_to_list(?String, ?List)
conversie logică între un string și o listă de caractere ASCII. Cel puțin unul din cele două argumente trebuie să fie instanțiat.
string_length(+String, -Length)
unifică Length cu numărul caracterelor din Sting. Acest predicat este echivalent funcțional cu atom_length/2 și acceptă și atomi, întregi și reali ca prim argument.
substring(+String, +Start, +Length, -Sub)
creează un subșir al lui String care începe cu caracterul Start și are Length caractere. Unifică acest subșir cu Sub.
Operatori
op(+Precedence, +Type, +Name)
declară Name ca fiind un operator de tipul Type ce precedența Precedence. Name poate fi și o listă de nume și în acest caz toate elementele listei sunt declarate ca fiind operatori identici. Precedence e un întreg între 0 și 1200. Precedența 0 elimină declarația. Type poate fi: xf, yf, xfx, yfx, yfy, fy sau fx. f indică poziția functorului iar x și y indică poziția argumentelor. y trebuie să fie interpretat ca „în această poziție trebuie să avem un termen cu precedență mai mică sau egală decât precedența functorului”. Pentru x precedența argumentului trebuie să fie strict mai mică. Precedența unui termen e 0, în afară de cazul în care principalul său functor e un operator, în acest caz precedența fiind precedența operatorului. Un termen inclus în paranteze ((…)) are precedența 0.
În tabelul 2.1 avem lista operatorilor predefiniți. Toți operatorii pot fi redefiniți de utilizator.
current_op(? Precedence, ?Type, ?Name)
reușește dacă Name e curent definit ca un operator de tipul Type cu precedența Precedence (vezi și op/3).
Predicate aritmetice
Argumentele predicatelor aritmetice sunt expresii. O expresie e fie un număr simplu, fie o funcție. Argumentele unei funcții sunt expresii.
between(+Low, +High, ?Value)
Low și High sunt întregi, High Low. Dacă Value e un număr întreg atunci Low Value High. Dacă Value e variabilă atunci ea e succesiv legată la toți întregii cuprinși între Low și High.
succ(?Int1, ?Int2)
reușește dacă Int2 = Int1 + 1. Cel puțin unul din cele două argumente trebuie să fie instanțiat cu un întreg.
plus(?Int1, ?Int2, ?Int3)
reușește dacă Int3 = Int1 + Int2. Cel puțin două din cele trei argumente trebuie să fie instanțiate cu întregi.
+Expr1 > +Expr2
reușește când Expr1 reprezintă un număr mai mare ca Expr2.
+Expr1 < +Expr2
reușește când Expr1 reprezintă un număr mai mic ca Expr2.
+Expr1 >= +Expr2
reușește când Expr1 reprezintă un număr mai mare sau egal cu Expr2.
+Expr1 <= +Expr2
reușește când Expr1 reprezintă un număr mai mic sau egal cu Expr2.
+Expr1 =\= +Expr2
reușește când Expr1 reprezintă un număr diferit de Expr2.
+Expr1 =:= +Expr2
reușește când Expr1 reprezintă un număr egal cu Expr2.
-Number is +Expr
reușește când Number a fost cu succes unificat cu un număr ce reprezintă evaluarea lui Expr.
Funcții aritmetice
Funcțiile aritmetice sunt termeni ce sunt evaluați de predicatele aritmetice descrise mai sus. SWI-Prolog încearcă să ascundă diferența dintre un număr întreg și un număr real față de utilizator. Operațiile aritmetice sunt executate cu întregi atât cât este posibil și sunt convertite apoi la numere reale atunci când unul din argumente sau o combinație a acestora o cere. Astfel, dacă o funcție returnează o valoare reală care e întregă, e automat transformată într-un întreg. Există trei tipuri de argumente pentru funcții:
Expr Expresie arbitrară, returnând fie o valoare reală, fie un întreg.
IntExpr Expresie arbitrară care trebuie evaluată la un întreg.
Int Un întreg.
Dacă adunarea, scăderea și înmulțirea conduc la un întrewg overflow atunci operanzii sunt automat convertiți la numere reale.
-+Expr
Rezultat =-Expr
+Expr1 ++ Expr2
Rezultat = Expr1 + Expr2
+Expr1 -+ Expr2
Rezultat = Expr1 – Expr2
+Expr1 *+ Expr2
Rezultat = Expr1* Expr2
+Expr1 / +Expr2
Rezultat =
+IntExpr1 mod +IntExpr2
Rezultat = IntExpr1 mod IntExpr2 (rezultatul împărțirii)
+IntExpr1 // +IntExpr2
Rezultat = IntExpr1 div IntExpr2 (împărțire întreagă)
abs(+Expr)
evaluează Expr și returnează valoarea sa absolută.
max(+Expr1, +Expr2)
returnează maximul dintre Expr1 și Expr2.
min(+Expr1, +Expr2)
returnează minimul dintre Expr1 și Expr2.
integer(+Expr)
evaluează Expr și rotunjește rezultatul la cel mai apropiat întreg.
floor(+Expr)
evaluează Expr și returnează cel mai mare întreg mai mare sau egal cu rezultatul evaluării.
ceil(+Expr)
evaluează Expr și returnează cel mai mic întreg mai mare sau egal cu rezultatul evaluării.
+IntExpr1 >> +IntExpr2
deplasează IntExpr1 la dreapta cu IntExpr2 biți. Întregii au doar 27 biți.
+IntExpr1 << +IntExpr2
deplasează IntExpr1 la stânga cu IntExpr2 biți.
+IntExpr1 +IntExpr2
sau logic pe biți între IntExpr1 și IntExpr2.
+IntExpr1 +IntExpr2
și logic pe biți între IntExpr1 și IntExpr2.
+IntExpr1 xor +IntExpr2
sau exclusiv logic pe biți între IntExpr1 și IntExpr2.
sqrt(+Expr)
Rezultat =
sin(+Expr)
Rezultat = sin Expr. Expr este unghiul în radiani.
cos(+Expr)
Rezultat = cos Expr. Expr este unghiul în radiani.
tan(+Expr)
Rezultat = tan Expr. Expr este unghiul în radiani.
asin(+Expr)
Rezultat = arcsin Expr. Expr este unghiul în radiani.
acos(+Expr)
Rezultat = arccos Expr. Expr este unghiul în radiani.
atan(+Expr)
Rezultat = arctan Expr. Expr este unghiul în radiani.
atan(+Yexpr, +XExpr)
Rezultat = arctan . Rezultat este unghiul în radiani. Valoarea returnată este cuprinsă între -și . Se folosește pentru conversia dintre sistemul de coordonate polar și cel rectangular.
log(+Expr)
Rezutat = ln Expr.
log10(+Expr)
Rezutat = lg Expr.
exp(+Expr)
Rezutat = eExpr
+Expr1 ^ +Expr2
Rezultat = Expr1Expr2
Manipularea listelor
is_list(+Term)
reușește dacă Term e legată la o listă vidă ([]) sau un termen cu functor `.` și aritate 2.
proper_list(+Term)
echivalent cu is_list/1 dar trebuie ca și coada listei (Tail) să fie listă (recursiv). Exemple:
is_list([x | A]) % true
proper_list([x | A]) % false
append(?List1, ?List2, ?List3)
reușește când List3 e unificat cu List1 concatenat cu List2. Predicatul poate fi folosit cu orice pattern de instanțiere (chiar și trei variabile).
member(?Elem, ?List)
reușește când Elem poate fi unificat cu unul din elementele componente ale listei List. Predicatul poate fi folosit cu orice pattern de instanțiere.
delete(+List1, ?Elem, ?List2)
șterge toți membrii listei List1 care sunt simultan unificați cu Elem și unifică rezultatul cu List2.
select(?List1, ?Elem, ?List2)
selectează un element din List1 care e unificat cu Elem. List2 e unificată cu lista elementelor rămase în List1 după ștergerea elementului selectat. De obicei se utilizează cu instanțierile (+List1, -Elem, -List2) dar poate fi folosit și pentru a insera un element în listă folosind (-List1, +Elem, +List2).
nth0(?Index, ?List, ?Elem)
reușește când al Index-lea element al List e unificat cu Elem. Numărarea elementelor începe cu 1.
last(?Elem, ?List)
reușește dacă Elem e unificat cu ultimul element din List.
reverse(+List1, -List2)
inversează ordinea elementelor din List1 și unifică rezultatul cu elementele lui List2.
flatten(+List1, -List2)
transformă List1 care poate conține liste ca elemente într-o listă liniară, care se obține înlocuind fiecare listă cu elementele sale (recursiv). Unifică rezultatul cu List2. Exemplu:
?-flatten([a, [b, [c,d], e]], X).
X = [a, b, c, d, e]
length(?List, ?Int)
reușește dacă Int reprezintă numărul de elemente al listei List. Poate fi folosit pentru a crea o listă care să conțină numai variabile.
merge(+List1, +List2, -List3)
List1 și List2 sunt liste sortate în ordinea standard a termenilor. List3 va fi unificată cu o listă ordonată ce va păstra și elementele listei List1 și elementele listei List2. duplicatele nu sunt eliminate.
Manipularea mulțimilor
is_set(+Set)
reușește dacă Set e o listă propriu-zisă (vezi proper_list/1) fără duplicate (elemente care se repetă).
list_to_set(+List, -Set)
reușește dacă Set conține aceleași elemente ca List, în aceeași ordine, dar fără duplicate (vezi și sort/1).
intersection(+Set1, +Set2, -Set3)
reușește dacă Set3 e unificată cu intersecția lui Set1 cu Set2. Set1 și Set2 sunt liste fără duplicate. Nu e nevoie să fie ordonate.
subtract(+Set, +Delete, -Result)
șterge toate elementele mulțimii Delete din Set și unifică mulțimea rezultată cu Result.
union(+Set1, +Set2, -Set3)
reușește dacă Set3 e unificată cu reuniunea mulțimilor Set1 și Set2. Set1 și Set2 sunt liste fără duplicate și nu e nevoie să fie ordonate.
subset(+Subset, +Set)
reușește dacă toate elementele lui Subset sunt elemente ale lui Set.
merge_set(+Set1, +Set2, -Set3)
Set1 și Set2 sunt liste fără duplicate, sortate în ordinea standard a termenilor. Set3 e unificat cu o listă ordonată fără duplicate ce reprezintă reuniunea lui Set1 cu Set2.
Sortarea listelor
sort(+List, -Sorted)
reușește dacă Sorted poate fi unificat cu o listă având ca elemente elementele lui List sortate în ordinea standard a termenilor. Duplicatele sunt eliminate.
msort(+List, -Sorted)
echivalent cu sort/2 dar nu elimină duplicatele.
keysort(+List, -Sorted)
List e o listă de perechi Key-Value (Cheie-Valoare). Predicatul sortează List ca msort/2, dar compară doar cheile. Poate fi utilizat pentru a sorta termenii în altă ordine decât cea standard. Sortarea pe mai multe criterii poate fi realizată folosind termeni drept chei și considerând primul criteriu ca argument 1, al doilea ca argument 2, etc.
predsort(+Pred, +List, -Sorted)
sortează similar cu msort/2, dar determină ordinea a doi termeni prin aplicarea lui Pred perechilor de elemente din List. Predicatul trebuie să reușească dacă primul element este înaintea celui de-al doilea.
Găsirea tuturor soluțiilor unui Goal
findall(+Var, +Goal, -Bag)
crează o listă a instanțierilor lui Var obținute succesiv aplicând backtracking Goal-ului și unifică rezultattul cu Bag. Returnează o listă vidă, dacă Goal nu are soluții. E echivalent cu bagof/3 cu toate că variabilele libere legate cu operatorul de existență (^), dar bagof/3 eșuează când Goal-ul nu are soluții.
bagof(+Var, +Goal, -Bag)
unifică Bag cu alternativele lui Var. Dacă Goal are și alte variabile libere în afară de Var, bagof va considera prin backtracking alternativele acestor variabile libere și va unifica Bag cu alternativele corespunzătoare ale lui Var. Construcția Var^Goal îi spune lui bagof să nu lege Var în Goal. Bagof/3 eșuează dacă Goal nu are soluții. Exemplul următor ilustrează bagof/3 și operatorul ^:
2?-listing(foo).
foo(a, b, c).
foo(a, b, d).
foo(b,c,e).
foo(b, c, f).
foo(c, c, g).
Yes
3?-bagof(C, foo(A, B, C), Cs).
A = a, B = b, C = G308, cs = [c, d];
A = b, B = c, C = G308, cs = [e, f];
A = c, B = c, C = G308, cs = [g];
No
4?-bagof(C, A^foo(A, B, C), Cs).
A = G324, B = b, C = G326, cs = [c, d];
A = G324, B = c, C = G326, cs = [e, f, g];
No
setof(+Var, +Goal, -Set)
echivalent cu bagof/3, dar sortează rezultatul utilizând sort/2 obținându-se lista sortată și fără duplicate a alternativelor.
Capitolul 2
Rețele semantice
2.1. Rețele semantice simple
În vederea reprezentării sub forma unei rețele semantice simple, vom presupune că piesa de cunoaștere nu conține propoziții de forma “dacă … atunci”, pe care le vom numi reguli. Pentru a reprezenta piesa de cunoaștere, este necesar să punem în evidență pe de o parte obiectele prezentate de piesa de cunoaștere și pe de altă parte relațiile care există între acestea. Vom presupune că lucrăm numai cu relații liniare. Vom desena obiectele cu care lucrăm prin numele lor. Ca exemplu de nume de obiecte ale pieselor de cunoaștere avem: ”salariat”, “scaun”, “prieten”, “popescu”, “funcție”, “matrice”.
Pentru reprezentarea numelor proprii vom utiliza convenția Prolog, așa cum am procedat mai înainte în cazul obiectului “popescu”. Fie mulțimea obiectelor prezentate de piesa de cunoaștere și E mulțimea etichetelor care desemnează relații binare. Din punct de vedere grafic, vom reprezenta un obiect printr-o elipsă în interiorul căruia vom scrie numele obiectului:
Relația desemnată de eticheta rel va fi reprezentată printr-un arc orientat între cele două obiecte:
Definiția 1
O rețea semantică simplă este un sistem (N,E,,g,S) unde:
– N este mulțimea nodurilor rețelei, reprezentând mulțimea obiectelor
– E este mulțimea etichetelor de relații
– :EEE este o funcție parțial definită numită în continuare funcția de descompunere a relațiilor.
– g:NENS este funcția care definește semantica atașată relației existente între două noduri ale rețelei. În general, S este o mulțime de propoziții și fraze, care definesc semantica relației.
Definiția 2
În rețeaua semantică simplă (N,E,,g,S) definim funcția: deduc:NN2S în felul următor:
deduc(X,Y)=g(X,(d),Y)dD(X,Y)
unde D(X,Y) notează mulțimea tuturor drumurilor de la nodul X la nodul Y, iar funcția este extinsă astfel:
:EiE; (e1,e2,e3,…,en)= ((…(e1,e2),e3),…,en)
Remarcă
Funcția fiind o funcție parțial definită, facem următoarea precizare: în definiția de mai sus (e1,e2,e3,…,en) este un drum în sensul teoriei grafurilor pentru care se poate calcula valoarea funcției . Pentru simplificarea situației vom presupune că ori de câte ori există un drum dD(X,Y) putem calcula valoarea (d).
Definiția 3
O deducție între două noduri X și Y ale unei rețele semantice simple înseamnă calculul valorii deduc(X,Y).
Pentru exemplificare considerăm următoarea piesă de cunoaștere:
“Popescu este muncitor. Orice muncitor este salariat. Orice salariat este om. Popescu este prieten cu Ionescu. Ionescu este salariat.”
În această piesă de cunoaștere există următoarea mulțime de obiecte:
popescu,ionescu,muncitor,salariat,om
și există următoarea mulțime de relații:
inst,subc,este_prieten_cu
Grafic vom reprezenta această piesă de cunoaștere sub următoarea formă:
Vom prezenta în cele ce urmează motorul de inferențe pentru rețeaua semantică simplă:
% motorul de inferențe pentru rețele semantice simple
go(Aici,Acolo):-
drum(Aici,Acolo,[],[Aici]),
fail.
go(_,_).
drum(Nod,Nod,Ev,Nv):-
deduc(Ev,K),
concluzie(Nv,K),
write('DORITI JUSTIFICAREA? (y/n)'),
nl,
read(T),
justific(Ev,Nv,T).
drum(Nod,Virf,Ev,Nv):-
d(Nod,Y,Z),
not(member(Y,Nv)),
append(Nv,[Y],N),
append(Ev,[Z],E),
drum(Y,Virf,E,N).
deduc(Ev,K):-
length(Ev,N),
compun(N,Ev,K).
compun(1,[X],X).
compun(N,E,X):-
N>=2,
E=[Y,Z|U],
fi(Y,Z,T),
NN is N-1,
compun(NN,[T|U],X).
concluzie(Nv,K):-
Nv=[Y|L],
last(Z,L),
semantica(Y,Z,K).
% modulul de explicații
justific([X|Ev],[U|Nv],'y'):-
Nv=[V|_],
semantica(U,V,X),
justific(Ev,Nv,'y').
justific([],_,'y').
justific(_,_,_).
% inițializarea datelor pentru deducție
start:-
consult('sem1.pl'),
consult('fi1.pl'),
write('Numiti primul obiect:'),
read(X),
write('Numiti al doilea obiect:'),
read(Y),
go(X,Y).
Programul de mai sus conține de asemenea și modulul de explicații. Cunoștințele referitoare la piesa de cunoaștere se regăsesc într-o bază de date dinamică care are următorul conținut:
% baza de date pentru rețele semantice simple
fi(inst,subc,inst).
fi(subc,subc,subc).
fi(este_prieten_cu,inst,este_prieten_cu_un).
fi(este_prieten_cu_un,subc,este_prieten_cu_un).
d(popescu,muncitor,inst).
d(muncitor,salariat,subc).
d(salariat,om,subc).
d(popescu,ionescu,este_prieten_cu).
d(ionescu,om,inst).
Predicatul de trei argumente fi precizează compunerea a două etichete de relații. Astfel, prima linie din baza de cunoștințe ne arată că, compusa relațiilor inst și subc (în această ordine) este inst.
Programul de mai sus conține și semantica conținută în programul sem.pl, adică funcția g a rețelei semantice.
În cazul piesei de cunoaștere considerată, acest program arată în felul următor:
% semantica pentru rețele semantice simple
semantica(X,Y,inst):-
write(X),
write(' este '),
write(Y),
nl.
semantica(X,Y,subc):-
write('Orice '),
write(X),
write(' este '),
write(Y),
nl.
semantica(X,Y,este_prieten_cu):-
write(X),
write(' este prieten cu '),
write(Y),
nl.
semantica(X,Y,este_prieten_cu_un):-
write(X),
write(' este prieten cu un '),
write(Y),
nl.
Rețele semantice cu arce confluente
Considerăm o rețea semantică simplă (N,E,,g,S). Pentru două obiecte X și Y din rețea, dacă există un drum de la X la Y a cărui etichetă de relație este etic, vom desena drumul cu o săgeată punctată orientată de la X la Y pe care vom scrie simbolul etic:
Definiția 4
Două drumuri d1D(X,Y) și d2D(Z,Y) se numesc drumuri confluente.
Remarcă
Fie etic1 și etic2 etichetele calculate ale drumurilor d1 respectiv d2, adică (d1)=etic1 și (d2)=etic2. Între obiectele X și Z există o relație a cărei semnificație este legată de faptul că cele două drumuri sunt confluente, adică ambele drumuri conduc la același obiect. În acest caz avem următoarea reprezentare:
Pentru acest motiv este necesar să considerăm o funcție parțial definită: confluent:EEE având proprietatea că confluent(etic1,etic2) este eticheta unei relații de la nodul X la nodul Z. Atragem atenția asupra faptului că funcția confluent nu este în general simetrică.
Pentru exemplificare să considerăm următoarea piesă de cunoaștere:
“Ionescu este elev al școlii Nicolae Bălcescu. Orice elev al școlii Nicolae Bălcescu este elev. Orice elev învață matematica, româna, și poate să aleagă disciplinele de sport: tenis, baschet, fotbal. Orice elev poate să aleagă una din grupele de limbi străine:
grupa1: engleza și germana
grupa2: franceza și rusa”
Pentru reprezentarea acestei piese de cunoștere considerăm următoarele mulțimi de obiecte și relații:
N=ionescu,elev_al_sc_nb,elev,matematică,română,discipline_de_sport,
tenis,baschet,fotbal,grupele_de_limbi,grupa_1,grupa_2,engleza,
germana,franceza,rusa
E=prop,prop1,prop2,prop3,face_p,face_p1,face_p2,este_în_1,este_în_2,
este_elem_în_1,este_elem_în_2,c1,c2,c3
Reprezentarea grafică este următoarea:
Pentru a reprezenta sub formă concisă compunerea a două relații, vom face următoarele convenții: compusa etichetelor de relații a și b fiind eticheta c, vom reprezenta relația de etichetă c printr-un arc punctat, adică sub forma următoare:
Utilizând această convenție, avem următoarea reprezentare grafică a piesei de cunoaștere considerată mai înainte:
Prezentăm în continuare implementarea în Prolog a acestor calcule. Baza de date dinamică care conține datele referitoare la piesa de cunoaștere considerată este următoarea:
% baza de date pentru rețele semantice cu arce confluente
d(ionescu,elev_al_sg_nb,inst).
d(elev_al_sg_nb,elev,subc).
d(elev,discipline_de_sport,prop2).
d(tenis,discipline_de_sport,face_p).
d(baschet,discipline_de_sport,face_p).
d(fotbal,discipline_de_sport,face_p).
d(elev,grupele_de_limbi_straine,prop2).
d(grupa1,grupele_de_limbi_straine,face_p1).
d(grupa2,grupele_de_limbi_straine,face_p2).
d(engleza,grupa1,este_in1).
d(germana,grupa1,este_in1).
d(franceza,grupa2,este_in2).
d(rusa,grupa2,este_in2).
d(elev,matematica,prop1).
d(elev,romana,prop1).
fi(inst,subc,inst).
fi(subc,prop1,prop1).
fi(inst,prop1,prop).
fi(subc,prop2,prop2).
fi(inst,prop2,prop3).
fi(este_in1,face_p1,este_elem_in1).
fi(este_in2,face_p2,este_elem_in2).
confluent(prop3,este_elem_in1,prop3).
confluent(prop3,este_elem_in2,prop3).
confluent(este_elem_in1,prop3,c1).
confluent(este_elem_in2,prop3,c1).
confluent(este_elem_in1,este_elem_in2,c2).
confluent(este_elem_in2,este_elem_in1,c2).
confluent(prop3,face_p,prop3).
confluent(face_p,prop3,c1).
confluent(este_in1,estein1,c3).
confluent(este_in2,estein2,c3).
Semantica relațiilor este prezentată în programul care urmează, numir semconf.pl:
% semantica pentru retele semantice cu arce confluente
semantica(X,Y,inst):-
write(X),
write(' este '),
write(Y),
nl.
semantica(X,Y,subc):-
write('Orice '),
write(X),
write(' este '),
write(Y),
nl.
semantica(X,Y,prop):-
write(X),
write(' are proprietatea ca invata '),
write(Y),
nl.
semantica(X,Y,prop1):-
write('Orice '),
write(X),
write(' are proprietatea ca invata '),
write(Y),
nl.
semantica(X,Y,prop2):-
write('Orice '),
write(X),
write(' poate sa aleaga '),
write(Y),
nl.
semantica(X,Y,prop3):-
write(X),
write(' poate sa aleaga '),
write(Y),
nl.
semantica(X,Y,face_p):-
write(X),
write(' face parte din '),
write(Y),
nl.
semantica(X,Y,face_p1):-
write(X),
write(' face parte din '),
write(Y),
nl.
semantica(X,Y,face_p2):-
write(X),
write(' face parte din '),
write(Y),
nl.
semantica(X,Y,c1):-
write(X),
write(' poate fi aleasa de '),
write(Y),
nl.
semantica(X,Y,c2):-
write(X),
write(' si '),
write(Y),
write(' fac parte din grupe diferite '),
nl.
semantica(X,Y,c3):-
write(X),
write(' si '),
write(Y),
write(' fac parte din aceeasi grupa '),
nl.
semantica(X,Y,este_elem_in1):-
write(X),
write(' este element in '),
write(Y),
nl.
semantica(X,Y,este_elem_in2):-
write(X),
write(' este element in '),
write(Y),
nl.
semantica(X,Y,este_in1):-
write(X),
write(' face parte din '),
write(Y),
nl.
semantica(X,Y,este_in2):-
write(X),
write(' face parte din '),
write(Y),
nl.
Motorul de inferențe și interfața de comunicare sunt prezentate după cum urmează:
% motorul de inferente pentru retele semantice cu arce confluente
begin:-
consult('sem2'),
consult('fi2'),
write('Dati numele obiectului unu:'),
read(X),
write('Dati numele obiectului doi:'),
read(Y),
relatie1(X,Y).
relatie1(X,Y):-
relatie(X,Y).
relatie1(X,Y):-
relatie(Y,X).
relatie(X,Y):-
go(X,Y),
fail.
relatie(X,Y):-
cale(X,Z,Ev1,Nv1,A),
cale(Y,Z,Ev2,Nv2,B),
confluent(A,B,C),
nl,
write('Concluzia este:'),
concluzie([X,Y],C),
nl,
write('DORITI JUSTIFICARE? (Y/N)'),
nl,
read(T),
nl,
justific(Ev1,Nv1,T),
justific(Ev2,Nv2,T).
go(Aici,Acolo):-
drum(Aici,Acolo,[],[Aici]),
fail.
go(_,_).
drum(Nod,Nod,Ev,Nv):-
deduc(Ev,K),
concluzie(Nv,K),
write('DORITI JUSTIFICAREA? (Y/N)'),
nl,
read(T),
justific(Ev,Nv,T).
drum(Nod,Virf,Ev,Nv):-
d(Nod,Y,Z),
not(member(Y,Nv)),
append(Nv,[Y],N),
append(Ev,[Z],E),
drum(Y,Virf,E,N).
drum1(Nod,Nod,Ev,Nv,Ev,Nv).
drum1(Nod,Virf,Ev,Nv,A,B):-
d(Nod,Y,Z),
not(member(Y,Nv)),
append(Nv,[Y],N),
append(Ev,[Z],E),
drum1(Y,Virf,E,N,A,B).
cale(X,Y,Ev,Nv,K):-
drum1(X,Y,[],[X],Ev,Nv),
deduc(Ev,K).
deduc(Ev,K):-
length(Ev,N),
compun(N,Ev,K).
compun(1,[X],X).
compun(N,E,X):-
N>=2,
E=[Y,Z|U],
fi(Y,Z,T),
NN is N-1,
compun(NN,[T|U],X).
concluzie(Nv,K):-
Nv=[Y|L],
last(Z,L),
semantica(Y,Z,K).
% modulul de explicatii
justific([X|Ev],[U|Nv],'y'):-
Nv=[V|_],
semantica(U,V,X),
justific(Ev,Nv,'y').
justific([],_,'y').
justific(_,_,_).
2.3. Rețele semantice cu reguli
O rețea semantică cu reguli se utilizează atunci când piesa de cunoaștere conține fraze de forma:
“Dacă … atunci …”
Se ivesc două probleme: afirmațiile sunt făcute la general sau la particular. La general nu precizez obiectul. Pentru acestea extragem aceste reguli și reprezint sub formă de rețea semantică toate cunoștințele care rămân.
Pentru reprezentarea unei reguli utilizăm următoarea convenție:
– vom presupune că premisa se referă la un singur obiect: ob1
– reprezentăm elementele fundamentale cu un arc dublu de la primul obiect la al doilea, cel de-al doilea fiind extras din concluzie.
Condiția care apare este un predicat. Apere și o relație desemnată de o etichetă e. De la ob1 la ob2 o relație notată cu e dacă se îndeplinește condiția .
unde =predicat și e=etichetă.
Avem două tipuri de obiecte fundamentale cu care construim această rețea:
(1) (ob1,ob2)e dacă se îndeplinește condiția
(2) (X,Y)rel
O rețea semantică cu reguli este formată din elemente fundamentale de primul sau al doilea tip aflate în conexiune.
O rețea semantică este reprezentată sub forma unui graf etichetat. Se menține funcția g care dă semantica de la rețeaua anterioară. Ea trebuie construită imediat ce am construit piesa de cunoaștere. La noi avem programul “sementic.pl” care dă semantica rețelei:
% semantica rețelei semantice cu reguli
semantica(X,Y,inst):-
write(X),
write(" este "),
write(Y),
nl.
semantica(X,Y,subc):-
write("Orice "),
write(X),
write(" este "),
write(Y),
nl.
semantica(X,Y,prop2):-
write("Orice "),
write(X),
write(" poate sa fie "),
write(Y),
nl.
semantica(X,Y,prop3):-
write(X),
write(" poate fi "),
write(Y),
nl.
Față de rețelele semantice simple apare în plus perechea (,e). Va mai apare și semantica predicatului scrisă în modulul de program “sempred.pl”:
% semantica predicatelor rețelei semantice cu reguli
sem_pred(fi_1_p7):-
write("Punctajul este cel putin 7"),
nl.
sem_pred(fi_3_p9_10):-
write("Punctajul este intre 9 si 10"),
nl.
sem_pred(fi_3_p8_9):-
write("Punctajul este intre 8 si 9"),
nl.
sem_pred(fi3_p7_8):-
write(Punctajul este intre 7 si 8),
nl.
Motorul de inferență folosește de câte ori este nevoie un predicat sem_pred definit în acest modul.
Căutăm un drum de la X1 la Y. drumul va fi: Nv=[X1,X2,X3,X4,X5,Y]; Ev=[e1,e2,e3,e4,e5]. Facem o deducție și calculăm funcția deduc corespunzătoare acestui drum: e1e2e3e4e5=K. Se apelează apoi predicatul semantica(X1,Z,K) care ne dă semnificația acestei concluzii. Această concluzie este adevărată numai dacă sunt îndeplinite condițiile 1 și 2.
Există două tipuri de raționament pe acest drum:
– general
Se referă la două noduri din rețea ce conțin noțiuni generale (“Dacă doi copii trăiesc atunci mănâncă”). Rezultatul final va fi de forma “Dacă 12 atunci semantica(X1,Z,K)”
– particular
Verifică dacă condiția “sem_pred(1),sem_pred(2)” este îndeplinită pentru un obiect precizat.
Fie piesa de cunoaștere:
“Există un elev al școlii Nicolae Bălcescu care merge la faza finală a Olimpiadei de Matematică. Orice participant la faza finală primește o diplomă de participant. Dacă un participant întrunește mai mult de 7 puncte atunci el obține premiu. Dacă participantul X are Y puncte și Y este între 7 și 8 atunci X obține premiul III. Dacă Y este între 8 și 9 atunci X obține premiul II. Dacă Y este între 9 și 10 atunci X obține premiul I. Orice premiant al Olimpiadei finale merge în excursie peste hotare. Dacă un participant întrunește peste 9,5 puncte atunci el participă la lotul olimpic. Ionescu este participant la Olimpiada finală de Fizică. Ionescu are media 9,30.”
Cunoștințele nesubliniate sunt reguli iar cele subliniate sunt fapte.
De exemplu pentru a descrie drumul de la nodul “participant_la_faza finală_de_matematică” la nodul “participant_care_obține_premiul_I” se extrag relațiile [subc,prop2,prop2] și etichetele [1(7),3(9,10)].
subc*prop2=prop2
prop2*prop2=prop2
Concluzia este: g(_,_,prop2) dacă 1(7) 3(9,10). Se folosește o bază de date aflată în fișierul “ret1.pl” care conține predicatele: (d,fi,arc_dacă):
% baza de date pentru rețele semantice cu reguli
d("part_la_faza_fin_de_matem","part_la_faza_finala","subc")
d("part_la_faza_fin_de_fiz","part_la_faza_finala","subc")
d("part_la_faza_finala","particip_cu_punctaj_sup","prop2")
d("particip_cu_punctaj_sup","part_care_obtine_pr1","prop2")
d("particip_cu_punctaj_sup","part_care_obtine_pr2","prop2")
d("particip_cu_punctaj_sup","part_care_obtine_pr3","prop2")
d("particip_cu_punctaj_sup","participa_la_lot_olimpic","prop1")
fi("inst","subc","inst")
fi("inst","prop2","prop3")
fi("subc","prop2","prop2")
fi("prop3","prop2","prop3")
fi("prop2","prop2","prop2")
arc_daca("part_la_faza_finala","particip_cu_punctaj_sup","fi_1_p7")
arc_daca("particip_cu_punctaj_sup","part_care_obtine_pr1","fi_3_p9_p10")
arc_daca("particip_cu_punctaj_sup","part_care_obtine_pr2","fi_3_p8_p9")
arc_daca("particip_cu_punctaj_sup","part_care_obtine_pr3","fi_3_p7_p8")
Motorul de inferență se construiește în felul următor:
Se enunță cele două noduri între care se caută o relație: X și Y.
2. Caută un drum de la X la Y; dacă el nu are arce “dacă” găsesc Nv=[X,…Y]; Ev=[e1,…,ek] (listă de noduri vizitate); L=[1,…,p] (listă de arce “dacă”).
Folosim predicatul colectează ce parcurge drumul respectiv și calculează lista L. După obținerea listei are loc o simplificare a ei cu ajutorul predicatului simplifică(L,C).
% motorul de inferențe pentru rețele semantice cu reguli
relatie(X,Y,K,C):-
drum(X,Y,Ev,Nv),
colecteaza(Nv,C),
deduc(Ev,K),
!.
drum1(Nod,Nod,Ev,Nv,Ev,Nv).
drum1(Nod,Virf,Ev,Nv,A,B):-
d(Nod,Y,Z),
not(member(Y,Nv)),
append(Nv,[Y],N),
append(Ev,[Z],E),
drum1(Y,Virf,E,N,A,B).
drum(Nod,Nod,Ev,Nv):-
drum1(X,Y,[],[X],Ev,Nv).
colect([X,Nv],C,D):-
length(Nv,K),
K>=1,
Nv=[Y|_],
arc_daca(X,Y,Z),
adaug(Z,C,C1),
colect(Nv,C1,D).
colect([_|Nv],C,D):-
length(Nv,K),
K>=1,
colect(Nv,C,D).
colect(L,C,C):-
length(L,1).
colecteaza(Nv,C):-
colect(Nv,[],C2),
length(C2,N),
simplifica(N,C2,C2,C).
deduc(Ev,K):-
length(Ev,N),
compun(N,Ev,K).
compun(1,[X],X).
compun(N,E,X):-
N>=2,
E=[Y,Z|U],
fi(Y,Z,T),
NN is N-1,
compun(NN,[T|U],X).
concluzie(X,Y,K,C):-
length(C,0),
semantica(X,Y,K),
read(_).
concluzie(X,Y,K,C):-
length(C,N),
N>=1,
semantica(X,Y,K),
write("–>"),
sem_pred(X),
nl,
NN=N-1,
scrie_pred(L,NN).
Capitolul 3
Sisteme expert
3.1. Un model de sistem
Acesta este un mic exemplu prin care dorim să demonstrăm câteva din cele mai importante concepte și metode de programare pentru a construi un sistem expert. Sistemul intenționează să sfătuiască utilizatorul în alegerea celui mai economic mijloc de transport public când utilizează MTA (Sistemul de transport din Munchen). Aceasta nu este o misiune simplă pentru un străin care călătorește cu mijloacele de transport din Munchen, dar și localnicii datorită multelor variante de costuri pentru diferite clase de pasageri.
Sistemul expert întreabă posibilii pasageri despre anumite date personale și apoi recomandă cele mai ieftine bilete. Chiar și sistemele expert simple trebuie să aibă o interfață utilizator.
Se pune următoarea problemă:
Ionescu este proaspăt angajat și trăiește în Munchen, are copii prea tineri pentru a conduce, și oaspeți ocazionali din străinătate care folosesc transportul public MTA (Munich Transport Authoritz). Mta are o structură a costurilor de călătorie foarte complexă, implicând foarte multe tipuri de bilete. Singurul lucru simplu în legătură cu transportul public este acela că pasagerul, folosind un bilet necorespunzător este amendat cu 40 DM. Astfel, acest sistem expert te ajută să alegi cel mai economic mod de a călători pentru a da călătoriei o profitabilitate pentru pasageri.
Există următoarele tipuri de bilete:
– un bilet pentru o singură călătorie costă 2.30 DM și are două zone de compostat.
– un bilet pentru călătorii multiple de tip A costă 6.50 DM și are 7 zone de compostat.
– un bilet pentru călătorii multiple de tip B costă 12 DM și are 13 zone de compostat.
– un bilet pentru copii și bilet pentru o călătorie scurtă costă 5 DM și are 8 zone fiecare dintre ele costând mai puțin decât cele descrise înainte. Acest bilet este pentru copii (pasageri sub 15 ani) sau câini.
Pentru a determina dacă traseul reprezintă o călătorie acurtă, trebuie să consultăm o hartă a rețelei de transporturi, care se găsește în fiecare autobuz. Pentru scopurile noastre, orice traseu mai mic de 5 km este considerat o călătorie scurtă.
3.2. Reguli ale călătoriei
Figura 3.1. reprezintă un fragment din baza de cunoștințe despre costul călătoriei. Cu excepția regulilor pentru “bilete pentru copii”, inițial se verifică dacă viitorul pasager are suficienți bani lichizi. În cazul biletelor pentru copii, o verificare în plus se face pentru a vedea dacă ruta este într-adevăr una scurtă.
În ce privește ordonarea regulilor pentru diferite tipuri de bilete, mai întâi de pun cele mai economice, iar apoi cele mai scumpe. Biletul pentru călătorii multiple de tip A are un cost pe câmp de 0.929 DM în comparație cu biletul pentru călătorii multiple de tip B care are un cost pe câmp de 0,923 DM. În cazul unui bilet pentru o singură călătorie, un câmp costă 1,15 DM.
Această secvență joacă un rol important în strategia aplicată de către sistem atunci când examinează diferite soluții posibile.
Propozițiile din figura 3.1. diferă de cele ale aplicării finale, prin două aspecte:
– ele presupun că datele necesare respective, de exemplu vârsta și statutul financiar al solicitatorului, sunt cunoscute sistemului.
– nu oferă o explicație a întrebărilor și concluziilor sistemului.
Dacă sistemul urmează să conducă un dialog cu solicitatorul, în forma care urmează să fie prezentată, trebuie să adăugăm regulile din baza de cunoștințe, în două moduri.
Trebuie prevăzut un mod de adăugare a datelor situației, astfel încât, faptele care lipsesc să poată fi obținute de la utilizator și adăugate în baza de cunoștințe.
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(age,child).
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(distance,near),
is_a_Multi_Trip_Ticket_B_possible:-
status(cash,Money),
Money>=12,
is_a_Multi_Trip_Ticket_A_possible:-
status(cash,Money),
Money>=6.50,
is_a_Single_Trip_Ticket_possible:-
status(cash,Money),
Money>=2.30,
Figura 3.1.
Mai mult, fiecare regulă invocată trebuie înregistrată astfel încât sistemul să poată, în orice moment, să reconstruiască cum a ajuns la o anumită concluzie și astfel să dea solicitantului o explicație rezonabilă a comportamentului său.
3.3. Interfața cu utilizatorul
Sistemul de informare ar trebui, pe baza regulilor călătoriei, să indice utilizatorului cum să plice aceste informații în mod optim. Acestea apar implicit, după cum și sistemul indică pasagerului datele potrivite, pe măsură ce situația respectivă devine mai bine definită. Totuși, utilizatorul trebuie să aibă posibilitatea de a face cercetări asupra sistemului. Pentru a afla ce fel de opțiuni are pasagerul în această privință, utilizatorul poate introduce:
?- consult(a2).
Eu sunt un mic program în SWI-Prolog, numele meu este MTA.
MTA este abrevierea lui Munich Transport Authority.
Datoria mea este să te ajut să alegi un bilet pentru a calatori în zona centrala a orașului.
Trebuie să perforezi ambele zone ale biletului pentru o singură călătorie.
Următoarele bilete sunt valabile:
– Un bilet pentru o singură călătorie cu doua zone de 2.30 DM.
– Un bilet pentru călătorii multiple de tip A cu 7 zone de 6.50 DM.
– Un bilet pentru călătorii multiple de tip B cu 13 zone de 12.00 DM.
Pentru pasagerii sub 15 ani sau animale, avem bilete pentru copii, cu 8 zone de 5.00 DM.
Pasagerii care călătoresc mai puțin de 5 kilometri pot folosi bilete pentru copii indiferent de vârsta.
Daca vrei sa te ajut sa alegi un bilet, tastează "mta."
Cu "restart." poți să începi o noua sesiune, orice fapt înregistrat va fi pierdut.
a3 compiled, 0.05 sec, 0 bytes.
a4 compiled, 0.06 sec, 0 bytes.
a5 compiled, 0.11 sec, 0 bytes.
a6 compiled, 0.11 sec, 0 bytes.
a7 compiled, 0.05 sec, 0 bytes.
a2 compiled, 0.38 sec, 0 bytes.
Yes
?-assistance.
Posibilele întrebări sunt:
– mta.
– is_a_Kiddie_Ticket_possible.
– is_a_Multi_Trip_Ticket_A_possible.
– is_a_Multi_Trip_Ticket_B_possible.
– is_a_Single_Trip_Ticket_possible.
– must_one_ride_without_paying.
– must_one_walk.
– restart. -> șterge vechile date.
– tree(Step,Rule). -> afișează arborele de derivare.
Yes
?-
Vom demonstra unele din comportamentele explicatorii, adică ceea ce utilizatorul primește ca răspuns la întrebări ca “de ce?” sau “cum?”.
?- mta.
Câți bani ai? 20.
Trebuie să cumperi bilete de tipul B de 12 DM.
Și să-ți amintești să perforezi doua zone!
Yes
Ce vârstă ai? de ce.
Știu că ai mai mult de 5 DM.
În consecință, dacă ai mai puțin de 15 ani poți să cumperi un bilet pentru copii.
Deci, trebuie să știu dacă ai mai puțin de 15 ani.
Acum: Ce vârstă ai? 18.
Cât de mare este distanta? de ce.
Știu că ai mai mult de 5 DM.
În consecință, dacă faci o călătorie scurtă poți să cumperi un bilet pentru o călătorie scurtă (Bilet pentru copii).
O călătorie scurtă înseamnă mai puțin de 5 kilometri.
Deci, trebuie să știu cit de lung este traseul.
Acum: Cât de mare este distanța? 10.
Trebuie să cumperi bilete de tipul B de 12 DM.
Și să-ți amintești să perforezi două zone!
Yes
?- restart.
Yes
?- is_a_Kiddie_Ticket_possible.
Câți bani ai? de ce.
Pentru a cumpăra un bilet pentru copii trebuie:
– să ai cel puțin 5 DM si
– să ai sub 15 ani.
Deci vreau să știu dacă ai cel puțin 5 DM.
Acum: Câți bani ai?2.18.
No
?-
Următorul exemplu arată că un dialog nu se limitează la întrebări despre bani, distanțe sau vârstă.
?-mta.
Câți bani ai? nimic.
Ai curaj? de ce.
Vreau să știu dacă dorești să-ți asumi riscul de a ajunge la destinație cât mai repede?'
Acum: Ai curaj? de ce.
Se pare că nu ai destui bani pentru un bilet.
Trebuie să caut o alta soluție.
Acum: Ai curaj? nu.
Ar trebui să mergi pe jos.
Yes.
?- is_a_Kiddie_Ticket_possible.
No.
?-
Structura controlului și interpretorul regulii
Dacă ignorăm temporar aspectele colectării datelor și activitatea explicatorie procesând o întrebare directă a utilizatorului, cum ar fi:
?-is_a_Multi_Trip_Ticket_A_possible.
devine simplu – interpretorul SWI-Prologului activează regulile corespunzătoare. Fie una din reguli are succes, și SWI-Prologul răspunde cu “yes”, fie întregul efort n-are rezultat și răspunde cu “no”.
Dar dacă sistemul se va comporta mai mult ca un consultant real, ca în cazul când lansăm:
?-mta.
atunci, sistemul nostru de informații trebuie să includă elemente primare ale unui sistem de diagnosticare: trebuie să selecteze din numeroasele soluții posibile, pe cea mai potrivită pentru “problema utilizatorului”, pe baza cunoștințelor permanent introduse în baza de cunoștințe și în baza faptelor adunate de la utilizator. Astfel, acest sistem simplu diferă de sistemele de diagnosticare mai complexe, cum sunt cele folosite în domeniul medical sau tehnic.
Aspectul de diagnosticare al sistemului este introdus ca o “soluție strategie” care conține “ipoteze” specifice, cu privire la calitatea soluțiilor date, referitor la criterii specifice. În cazul unui sistem de diagnosticare medical sau tehnic, strategia include determinarea probabilității unei boli sau a unui defect, dat fiind un set de simptome cunoscute. În cazul nostru, criteriul este satisfacția utilizatorului pentru o sugestie dată, ipoteza fiind că cel mai ieftin bilet determină satisfacția cea mai mare.
Aceasta se implică prin ordinea soluțiilor alternative din cazul nostru:
– biletul cel mai ieftin pe care și-l poate permite utilizatorul.
– transportul fără plată – ca fiind cel mai scump, fiind supus unei amenzi de 40 DM dacă este prins.
– mersul pe jos, care este mai ieftin, dar dar probabil nu este preferința utilizatorului.
Deoarece SWI-Prologul procesează un set de propoziții de sus în jos, această aranjare realizează strategia dorită: mai întâi se face un test pentru a vedea dacă cel mai ieftin bilet, bilet pentru copii, este aplicabil, apoi tipurile mai scumpe, până când utilizatorul, nedorind să riște o amendă, este sfătuit să meargă pe jos.
Astfel, din punct de vedere al interpretorului de comenzi, fiecare propoziție MTA este o ipoteză referitoare la cum poate călători utilizatorul. Și prin ordonarea lor, conform cu diminuarea satisfacției, de exemplu creșterea prețului, se exploatează căutarea de sus în jos încorporată în SWI-Prolog, pentru propozițiile corespunzătoare din baza de cunoștințe, pentru a introduce strategia sistemului de informare. Prima alternativă MTA care are succes este automat cea mai bună.
3.5. Colectarea datelor
Ipotezele impuse de către predicatele MTA, de exemplu “este posibil un bilet pentru copii”, folosesc anumite informații despre situație pentru a determina aplicabilitatea unei ipoteze date. Astfel de informații sunt formulate ca fapte binare de stări:
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(age,child).
Aceasta presupune că astfel de informații există deja în baza de cunoștințe, ceea ce în general nu este de așteptat, în măsura în care aceste informații depind de cercetător. Este sarcina modului de colecționare a datelor de a strânge de la utilizator informațiile despre situație care lipsesc și să le strângă într-o bază de date dinamică. Aceasta înseamnă că sistemul trebuie să recunoască când lipsește o informație specifică. Inițializarea sistemului stabilește baza prin adăugarea situației ca fiind necunoscută. Dacă utilizatorul reinițializează sistemul cu “restart”, atunci acest fapt este restabilit, după ce toate datele colectate înainte au fost șterse din baza de cunoștințe activă folosind abolish(status,2).
Dacă o informație asupra situației nu poate fi găsită, utilizatorul trebuie întrebat, răspunsul colectat, verificat și apoi înregistrat în baza de cunoștințe. Cea mai simplă introducere integrează aceste acțiuni în reguli. Figura 3.2. ilustrază o asemenea tehnică, bazată pe ipoteza că un bilet pentru copii este posibil.
Ordinea propozițiilor din figura 3.2. produce o structură de control a fluxului, fiecare propoziție succesivă determină soluția în conformitate cu informațiile situației disponibile pentru bani, vârstă și distanță. Introducând fiecare propoziție care presupune existența unei informații despre o stare particulară, am plasat una care, chia dacă nu e încă, cunoscută, cere această informație de la utilizator prin predicatul ask. După ce ask a verificat plauzabilitatea răspunsului, asserta înregistrează informația care i-a fost transmisă prin variabilele F1, F2 sau F3. Deoarece asserta strânge noile fapte la început, putem fi siguri că vor fi înaintea faptelor anterioare, în special originalul.
După cum se poate vedea în figura 3.2., propozițiile care realizează colectarea datelor, întotdeauna se încheie cu un eșec.
is_a_Kiddie_Ticket_possible:-
status(cash,0),
ask(cash,F1),
asserta(status(cash,F1))
fail.
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(age,0),
ask(age,F2),
asserta(status(age,F2))
fail.
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(age,child).
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(distance,0),
ask(distance,F3),
asserta(status(distance,F3))
fail.
is_a_Kiddie_Ticket_possible:-
status(cash,Money),
Money>=5,
status(distance,near).
Figura 3.2.
Bazele regulii și ale faptelor includ și baza de cunoștințe a sistemului. Acest exemplu este tipic, în măsura în care baza regulii există de la început și rămâne neschimbată. Baza faptelor începe cu niște informații inițiale care sunt apoi supuse schimbării, pe baza informațiilor generate de regula interpretorului în timpul căutării de soluții acceptabile.
Se observă că valorile situației, figura 3.2., adăugate în cele din urmă la baza de fapte, prin asserta, nu erau în mod necesar, identice cu răspunsurile date de către utilizator. Termenii situației relevanți din reguli, cum ar fi: status(vârstă,copil) sau status(distanță,aproape) nu conțin valorile numerice furnizate de utilizator, ci mai degrabă, rezultatele analizei admisiei ei, de exemplu: copil sau adult, departe sau aproape. În figura 3.3. puteți vedea cum pot fi programate o astfel de verificare și de analiză. Interpretarea sistemului cu privire la vârsta utilizatorului este introdusă în consecință.
Întrebarea sistemului despre distanța ce urmează a fi parcursă prezintă un mod de a trata cu răspunsuri ca “nu știu”. În acest caz, o interpretăm ca sigură și presupunem o distanță mare. Soluțiile prezentate în figurile 3.2. și 3.3. nu sunt complete, deoarece nu oferă utilizatorului nici o explicație referitoare la întrebarea, de ce sistemul procedează așa cum este.
ask(distance,F3):-
write('Cit de mare este distanta? '),
read(Input),
test_f3(Imput,F3).
test_f3('not sure',far):-
!.
test_f3(Imput,F3):-
number(Imput),
( Imput<=5,
F3=near
: F3=far
).
test_f3(Imput,F3):-
not number(Imput),
non_interpretable,
ask(distance,F3).
non_interpretable:-
nl,
write('Nu pot sa inteleg raspunsul tau.'),
nl,
write('Te rog reintrodu raspunsul.'),
nl.
4 Aplicație
function valid(array st,k)
global array a;
global n;
local i,x;
x:=1.0;
for i:=1 to k-1 do
x:=x*a(st(i),st(i+1));
endfor
return (x);
endfunction
function solutie(array st,k)
global array ee;
global semant;
global compun;
global s1,s2;
local i,et1,et2,etc,j1,j2;
if k>2 then
et1:=ee(st(1),st(2));
et2:=ee(st(2),st(3));
obtain first from compun;
for j1:=1 to s1 do
if compun.cod1=et1 and compun.cod2=et2 then etc:=compun.codc;
else obtain next from compun;
endif;
endfor;
obtain first from semant;
if k=3 then
for j2:=1 to s2 do
if semant.code=etc then
sem(st(1),st(3),etc);
endif;
obtain next from semant;
endfor
else
for i:=3 to k-1 do
et1:=etc;
et2:=ee(st(i),st(i+1));
obtain first from compun;
for j1:=1 to s1 do
if compun.cod1=et1 and compun.cod2=et2 then etc:=compun.codc;
else obtain next from compun;
endif;
endfor
obtain first from semant;
for j2:=1 to s2 do
if semant.code=etc then
sem(st(1),st(i+1),etc);
endif;
obtain next from semant;
endfor
endfor
endif
endif
return;
endfunction
function sem(i,j,z)
global array denn;
TEST true
CASE z="a":
output "orice ",denn(i)," are ",denn(j);
endcase;
CASE z="b":
output denn(i)," este ",denn(j);
endcase;
CASE z="c" or z="e":
output denn(i)," este ",denn(j);
endcase;
CASE z="d":
output "Orice ",denn(i)," este ",denn(j);
endcase;
CASE z="f":
output denn(i)," este un fel de ",denn(j);
endcase;
CASE z="g":
output denn(i)," are ",denn(j);
endcase;
ENDTEST
endfunction
E.SUPD=true;
release all;
load udf procedur;
load udf prsem;
use compun;
use semant;
global dim a(14,14);
global array ee; dim ee(14,14);
global dim denn(14);
local dim st(16);
global n;
denn(1):="mancare";
denn(2):="miros";
denn(3):="gust";
denn(4):="branza";
denn(5):="somonul";
denn(6):="peste";
denn(7):="pasare";
denn(8):="aripi";
denn(9):="pene";
denn(10):="alba";
denn(11):="roz";
denn(12):="animal";
denn(13):="papagal";
denn(14):="Bob"
i:=1;
while (i<=14) do
j:=1;
while (j<=14) do
a(i,j):=0;
j:=j+1;
endwhile;
i:=i+1;
endwhile;
n:=13;
s1:=tblinfo(compun,2);
s2:=tblinfo(semant,2);
/*
input n num using "dd" with "Introduceti nr de arce:";
output "Introduceti orientarea arcelor:";
k:=1;
while (k<=n) do
input i num using "dd" with "Nodul sursa:";
input j num using "dd" with "Nodul destinatie:";
input et str using "a" with "Eticheta:";
a(i,j):=1;
ee(i,j):=et;
k:=k+1;
endwhile;
*/
a(1,2):=1;ee(1,2):="a";
a(1,3):=1;ee(1,3):="a";
a(4,10):=1;ee(4,10):="c";
a(4,1):=1;ee(4,1):="b";
a(6,1):=1;ee(6,1):="f";
a(6,12):=1;ee(6,12):="d";
a(5,6):=1;ee(5,6):="b";
a(5,11):=1;ee(5,11):="c";
a(14,13):=1;ee(14,13):="e";
a(13,7):=1;ee(13,7):="d";
a(7,9):=1;ee(7,9):="a";
a(7,8):=1;ee(7,8):="a";
a(7,12):=1;ee(7,12):="d";
output "Introduceti nodurile pentru care vreti sa aflati semantica:\n";
input ns num using "dd" with "Nodul sursa:";
input nf num using "dd" with "Nodul destinatie:";
k:=1;
st(k):=ns;
k:=2;
st(k):=0;
while (k>1) do
v:=0;
while st(k)<=n and v=0 do
st(k):=st(k)+1;
v=valid(st,k);
endwhile;
if v=0 then k:=k-1;
else solutie(st,k);
if st(k)=nf then k:=0;
else
k:=k+1;st(k):=0;
endif;
endif;
endwhile;
input x num using "d" with "apasati o tasta ";
Bibliografie
Țăndăreanu N. “Introducere în programarea logică. Limbajul Prolog”. Seria “IA”, vol. 1, Ed. INTARF, Craiova, 1994
Țăndăreanu N. “Baze de cunoștințe- curs anul III”, 1999
Giurcă A., Săvulea D. “Practica programării logice”. Seria “Ia”,
Ed. INTARF, Craiova, 1995
Fritz Lehmann “Semantic networks in Artificial Inteligence”
Pergamon Press, New York, 1992
Cârstoiu D. “Sisteme expert”, Ed. All, București, 1994.
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Implementarea Retelelor Semantice Si a Frame Urilor In Guru (ID: 161122)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
