Inteligenta Artificiala

Nu esti logat.

Laboratorul 1

Quiz

Nu ai dat quizul pentru laboratorul 1

Plan de lectie

  1. Mod de punctare. Regulament (pe care trebuie sa il semneze)
  2. Introducere in inteligenta artificiala. Domenii si subdomenii. Aplicatii. Ce vom invata acest semestru.
  3. Programarea bazata pe predicate. Ce e o propozitie logica. Ce e un predicat. De ce invatam tocmai prolog.
  4. Consola sicstus. Exercitiul cu culorile. Reamintesc modul de upload si faptul ca trebuie sa aiba fisiere diferite pentru exercitii
  5. Diferenta intre operatori aritmetici si generali.
  6. Backtracking. Exercitiul cu echipele. Corectat codul pentru predicatul colegi. Aratat cum functioneaza pentru adversari(ionel, gigel), apoi pentru adversari(ionel, danel), apoi pentru adversari(X,Y)
  7. Operatori logici
  8. Pauza
  9. Termeni simpli si compusi
  10. Variabila anonima
  11. Predicatele true si fail. Afisarea tuturor solutiilor pentru un predicat.
  12. Recursivitate. Cele doua metode pentru exercitiul cu suma
  13. Operatorul de unificare
  14. Predicate pentru identificarea tipului de date.
  15. Munca individuala

De ce prolog?

Bune de citit:

Introducere in Prolog

Limbaje de programare folosite in domeniul inteligentei artificiale: LISP, Prolog, STRIPS etc.

Prolog - Programare logica, bazata pe predicate.


Notiuni de baza:

Crearea unui program (sau mai corect spus, baza de cunostinte) in prolog

Programul se va scrie intr-un fisier text care se va salva cu extensia pl. Se poate folosi orice editor de texte, inclusiv notepad insa va recomand Notepad++ sau SWI-Prolog-Editor deoarece acesta afiseaza in culori diferite diversele elemente de sintaxa facand mai usoara atat citirea programului cat si depistarea erorilor.

Pana acum programele scrise de voi erau practic un set ordonat de instructiuni prin care ii spuneati calculatorului ce sa faca si mai ales cum sa realizeze ce avea de facut, ii dadeati indicatii precise asupra actiunilor pe care trebuia sa le indeplineasca. Insa in Prolog nu mai avem instructiuni ci predicate, ii indicam calculatorului ce sa faca (anume care e scopul la care sa ajunga, pe care sa-l indeplineasca) dar nu-i mai indicam efectiv si cum sa faca asta. Fisierele pe care le scriem in prolog sunt de fapt baze de cunostinte. O baza de cunostinte e un set de fapte si reguli care vor ajuta la realizarea rationamentelor.

Pentru a putea folosi baza de cunostinte, aceasta va trebui mai intai incarcata in memorie; aceasta se va face cu ajutorul instructiunii consult
| ?-consult('cale_fisier').

Calea poate fi absoluta sau relativa la active directory.

Pentru a vedea toate faptele si regulile incarcate in memorie, se foloseste predicatul fara parametri: listing.

Daca dorim sa afisam doar faptele si regulile corespunzatoare unui anume predicat folosim: listing(nume_predicat).


Comentarii

%pana la sfarsitul liniei
/* ... */intre cele doua semne.

Specificatii predicate

Atunci cand vrem sa folosim un predicat trebuie sa stim clar care sunt parametrii sai de intrare (ce argumente trebuie date) si care sunt parametrii de iesire (cei informatii stie sa ofere/calculeze).

De exemplu avem un predicat care calculeaza dublul unui numar. dublu(N, D):-number(N), D is 2*N.

Din modul de declarare al regulii ne dam seama ca N este numarul dat si D este dublul lui (calculat).

Totusi pentru un predicat cufoarte multe clauze si multi parametri ar fi complicat sa-i citim definitia de fiecare data cand avem nevoie sa il folosim. In plus poate dorim sa folosim predicate dintr-un modul de biblioteca, la al carui cod efectiv nu avem acces.

Cum se clarifica parametrii predicatului? Prin niste prefize specifice.

Prefixe pentru parametrii

+Parametru de intrare (argumentul transmis predicatului trebuie sa aiba valoare cunoscuta).
-Parametru de iesire (argumentul nu trebuie sa fie instantiat; va fi o variabila a carei valoare este calculata).
?Parametru care poate fi si de intrare si de iesire (argumentul dat poate sa aiba valoare cunoscuta sau sa fie o variabila neinstaniata).

Deci, pentru predicatul dublu definit mai sus specificatia ar arata astfel: %dublu(+Numar,-Dublu)
dublu(N, D):-number(N), D is 2*N.

Astfel, acum vazand specificatia stim ca modul in care s-a dorit sa se apeleze (interogheze) predicatul este: | ?- dublu(2,N).
N = 4 ?
yes

Observam totusi ca putem apela predicatul si asa: | ?- dublu(2,4).
yes
| ?- dublu(2,3).
no
| ?-

Deci ar functiona si cu parametrul a doilea cunoscut. Asta inseamna ca specificatia %dublu(+Numar,-Dublu) e gresita si ca trebuia de fapt %dublu(+Numar,?Dublu)? Adica sa aratam clar ca al doilea paramteru poate fi si dat si calculat? Nu neaparat. Specificatia e subiectiva si arata cum a intetionat autorul sa fie folosit predicatul. Nu trebuie sa acopere toate cazurile posibile de transmitere a argumentelor.

Atentie, specificatiile se pun doar in comentarii. Nu reprezinta o parte din sintaxa prolog ci sunt doar o conventie de a arata concis care sunt parametrii de intrare si iesire. O gresala frecventa pe care cei incepatori in prolog o fac e sa puna specificatiile direct in cod cand definesc regula: dublu(+N, -D):-number(N), D is 2*N.%gresit!
/*mai sus nu au fost scrise specificatiile predicatului
ci s-a precizat mai degraba ca predicatul dublu primeste ca prim paramteru
un termen compus cu operatorul unar +,
si al doilea parametru un termen compus cu operatorul - */

Operatori

Comparatii intre termeni generali

Acesti operatori pot fi folositi pe orice tip de termeni (atomi, variabile, numere, expresii compuse). Atentie insa, ei nu efecteaza calcule, de exemplu 2+3==3+2 este fals, deoarece compara expresiile, nu valorile expresiilor.

@<mai mic
@=<mai mic sau egal
@>mai mare
@>=mai mare sau egal
==egalitate intre termeni
\==termeni diferiti

Comparatii aritmetice

mai mic
=< mai mic sau egal
> mai mare
>= mai mare sau egal
=:= egalitate aritmetica
=\= inegalitate aritmetica

Observatie importanta: in cadrul comparatiilor aritmetice ambii operanzi trebuie sa contina numai variabile instantiate.

,si
;sau
\+negare


Prioritati

Atentie! Operatorul si (,) este prioritar sau-ului (;). De aceea daca avem doua sau mai multe cazuri inainte de care sau dupa care vrem sa aplicam aceeasi operatie, trebuie sa punem testarea cazurilor in paranteza:

....., (...;...;...), .... .

De exemplu, dorim sa facem un predicat afis_paritate(+Nr) care afiseaza un mesaj de genul "Nr este par/impar" sau esueaza daca argumentul dat nu e numar. Un exemplu de interogarea ar fi acesta:

| ?- afis_paritate(2).
2 este par
yes
| ?- afis_paritate(3).
3 este impar
yes
| ?- afis_paritate(a).
no
| ?-

Ne gandim sa scriem predicatul in felul urmator. Testam intai ca argumentul dat e numar intreg. Apoi daca e par setam o variabila numita Parit sa fie egala cu atomul par, iar daca e impar, setam Parit la valoarea impar. Apoi afisam rezultatul folosindu-ne de Parit.O prima incercare ar putea arata ca mai jos.

%predicatul gresit
afis_paritate(X):-integer(X), X mod 2=:=0, Parit=par; X mod 2 =\=0, Parit=impar, write(X), write(' este '), write(Parit).

Totusi observam ca nu ne da rezultatul dorit:

| ?- afis_paritate(3).
3 este impar
yes
| ?- afis_paritate(2).
yes
| ?- afis_paritate(a).
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal: a mod 2=\=0
| ?-

Numai pentru numere impare functioneaza asa cum ne-am fi asteptat. De ce? Pentru ca si-ul este prioritar sau-ului. Deci practic el grupeaza testele din afis_paritate in doua cazuri legate prin si:

Deci observam ca pe cazul 1 nu mai face afisarea, iar pe cazul 2 nu mai testeaza ca e numar intreg ca sa poata fi sigur ca mod e o operatie valida, asa cum se vede si in trace-ul de mai jos, unde, dupa ce esueaza integer, in loc sa iasa din predicat cu no, asa cum am fi dorit, continua pe cazul 2:

| ?- trace.
% The debugger will first creep -- showing everything (trace)
yes
% trace
| ?- afis_paritate(a).       
        1      1 Call: afis_paritate(a) ?
        2      2 Call: integer(a) ?
        2      2 Fail: integer(a) ?
        3      2 Call: a mod 2=\=0 ?
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal:  a mod 2=\=0
        3      2 Exception: a mod 2=\=0 ?
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal:  a mod 2=\=0
        1      1 Exception: afis_paritate(a) ?
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal:  a mod 2=\=0
% trace
| ?-

Tot ce trebuie sa facem e sa grupam intr-o paranteza cazurile astfel incat sa se aplice penru ambele cazuri si testele de dinaintea lor si testele de dupa

afis_paritate_corect(X):-integer(X), (X mod 2=:=0, Parit=par; X mod 2 =\=0, Parit=impar), write(X), write(' este '), write(Parit).

E practic ideea de la adunare si inmultire: 2*(3+5)*7=2*3*7+2*5*7. Aici prin desfacerea parantezei s-ar ajunge la cazurile:

Astfel, pe ambele cazuri se testeaza daca X e intreg (si deci e valida operatia mod), si pe ambele cazuri se afiseaza mesajul.

Cum arata o baza de cunostinte

Sa presupunem faptul ca stim ca autobuzul, troleibuzul, tramvaiul, metroul si caruta sunt mijloace de transport. Autobuzul, troleibuzul, tramvaiul si caruta sunt de suprafata, insa metroul e subteran. Autobuzul si troleibuzul au cate 100 locuri, tramvaiul 150, iar metroul are trei sute.

Vrem sa definim si un predicat numit mai_incapator(X,Y) care primeste doua nume de mijloace de transport si se termina cu succes daca X are mai multe locuri decat Y.

%mijloc_de transport(nume, tip)
mijloc_de_transport(autobuz, suprafata).
mijloc_de_transport(troleibuz, suprafata).
mijloc_de_transport(tramvai, suprafata).
mijloc_de_transport(caruta, suprafata).
mijloc_de_transport(metrou, subteran).

numar_locuri(autobuz,100).
numar_locuri(troleibuz,100).
numar_locuri(tramvai,150).
numar_locuri(metrou,300).

%daca am avea si o informatie despre alt tip de obiect stocata in acelasi predicat, precum o sala de seminar? numar_locuri(sala_seminar,40).

mai_incapator(X,Y):-mijloc_de_transport(X, _),mijloc_de_transport(Y, _), X\==Y, numar_locuri(X,LX), numar_locuri(Y,LY),LX>LY.

Diverse predicate de testare a tipului

Lista completa se gaseste in documentatia pentru sicstus prolog

Observatie:Daca avem un atom a carui forma ne permite sa il folosim fara apostroafe (de exemplu: abc), forma lui cu apostroafe, (adica, pentru exemplul nostru: 'abc'), reprezinta aceeasi valoare.

| ?- abc=='abc'.
yes

Atentie: nu se intampla acelasi lucru si pentru numere. Daca scriem 1 fara apostroafe, il vede ca numar, pe cand forma '1' reprezinta un atom, deci valorile sunt diferite.

| ?- 1=='1'.
no
| ?-

Unificare. Operatorul de unificare. Functii matematice.

Operatorul is se foloseste pentru a calcula expresii numerice si pentru a aloca valoarea respectiva unei variabile. O lista cu operatorii si functiile matematice suportate de Sicstus Prolog gasiti in documentatie.
Unificarea se realizeaza cu ajutorul operatorului =. Pentru termeni simpli exista mai multe cazuri:

Operatorul \= este negatia unificarii.

Variabila anonima

Se noteaza cu _ si reprezinta o variabila a carei valoare nu ne intereseaza, in principiu putem avea acolo "orice". Se foloseste pentru variabile care altfel ar aparea drept singleton, acestea sunt variabile care apar doar o singura data in cadrul unei reguli, prin urmare nu exista restrictii asupra lor. Desi mesajul de singleton variables e doar un warning e bine sa il evitati in program pentru a avea un cod mai usor de urmarit.

Structuri(termeni compusi)

O structura se aseamana unui predicat din punct de vedere sinctactic, in sensul ca are aceeasi forma: nume_structura(elem1,elem2,...). Insa, spre deosebire de predicate, o structura nu e vazuta ca avand valoare de adevar, e doar un termen care grupeaza mai multe date. Pentru a testa daca un termen este compus sau nu, se folosesc predicatele:

Comparatii intre termeni compusi

Se realizeaza comparatiile obisnuite pe termeni.

| ?- om(ion, popescu)@<persoana(ion, popescu).
yes
| ?- om(ion, popescu)==om(ion, popescu).
yes
| ?- om(ion, popescu)==pisica('Mitzi', Y).
no

Unificarea in cazul termenilor compusi.

Atunci cand unul din termeni e neinstantiat evident va primi ca valoare acea structura.

| ?- X=om(ion,popescu).
X = om(ion,popescu) ?
yes

Daca ambii termeni ai unificarii sunt instantiati cu o anumita structura, pentru a se realiza unificarea, trebuie in primul rand ca numele structurilor si numarul de parametri sa coincida, apoi practic se va realiza unificarea pentru fiecare componenta a structurii. Mai jos in primul caz raspunsul e no pentru ca numele termenilor compusi nu coincid, iar in al doilea caz numarul de parametri nu coincide:

| ?- ceva(1,2)=altceva(1,2,X).
no
| ?- ab(X,Y)=ab(X,Y,Z).
no

Daca structurile sunt compatibile, ca in exemplul de mai jos, se realizeaza unificarea pe fiecare componenta a structurii:

| ?- pisica(X,Y)=pisica('Mitzi',persana).
X = 'Mitzi',
Y = persana ?
| ?- pisica(X,persana,Y)=pisica('Mitzi',persana,varsta(2,ani)).
X = 'Mitzi',
Y = varsta(2,ani) ?

Mai jos structurile nu sunt compatibile deoarece difera a doua componenta:

| ?- pisica(X,angora,Y)=pisica('Mitzi',persana,varsta(2,ani)).
no

Predicatele true si fail

Predicatele true si fail nu au argumente si au proprietatea ca true este intotdeauna adevarat si fail este intotdeauna fals. | ?- true.
yes
| ?- fail.
no
In Sicstus Prolog exista si echivalentele: otherwise si false, insa e mai bine sa folositi true si fail deoarece acestea sunt definite si in standardul ISO.

Recursivitate

Recursivitatea apare atunci cand valoarea de adevar a unui predicat cu anumiti parametri depinde de valoarea de adevar a aceluiasi predicat (dar, bineinteles, cu alti parametri). Practic e vorba de un predicat care se apeleaza pe el insusi (direct sau indirect), de exemplu:
p(...) :- ..., p(...), ... .
sau:
p1(...) :- ..., p2(...), ... .
p2(...) :- ..., p1(...), ... .


Teme

Rezolvati teme astfel incat sa ajungi la un punctaj aproximativ de 0.5. Este indicat sa lucrati astfel incat sa obtineti un pic mai mult de 0.5 deoarece pot exista penalizari pentru greselile din teme.