Sunteți pe pagina 1din 241

Cuprins

Capitolul 1. Noțiuni fundamentale de programare funcțională


1. Obiective ..............................................................................................................9
2. Programarea funcțională ...............................................................................9
3. Imutabilitate .................................................................................................... 11
4. Recursivitate ................................................................................................... 12
5. Funcții ca obiecte ........................................................................................... 14
5.1. Delegatul Func .................................................................................... 14
5.2. Expresii lambda ................................................................................. 16
5.3. Funcții locale....................................................................................... 17
6. Alte aspecte ...................................................................................................... 18
6.1. Efecte secundare ............................................................................... 18
6.2. Metode și clase statice..................................................................... 18
7. Închideri (closures) ....................................................................................... 19
8. Aplicații ............................................................................................................. 21

Capitolul 2. Operații cu liste și tuple


1. Obiective ........................................................................................................... 25
2. Crearea listelor ............................................................................................... 25
3. Funcții de lucru cu liste ............................................................................... 27
3.1. Funcții de transformare ................................................................. 28
3.2. Funcții de ordonare a elementelor............................................. 31
3.3. Funcții de filtrare și combinare ................................................... 31
3.4. Funcții statistice ................................................................................ 32
3.5. Funcții de căutare și adăugare ..................................................... 33
4. Tuple ................................................................................................................... 33
5. Aplicații ............................................................................................................. 34

Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 3. Compunerea funcțiilor și evaluarea pasivă
1. Obiective ........................................................................................................... 37
2. Înlănțuirea funcțiilor .................................................................................... 37
3. Compunerea funcțiilor ................................................................................ 39
4. Evaluarea pasivă ............................................................................................ 40
5. Aplicații ............................................................................................................. 41

Capitolul 4. Functori și monade. Tipul Option


1. Obiective ........................................................................................................... 47
2. Tipul Option ..................................................................................................... 47
3. Functori ............................................................................................................. 50
3.1. Legile functorilor............................................................................... 51
4. Monade .............................................................................................................. 51
4.1. Legile monadelor .............................................................................. 53
5. Aplicații ............................................................................................................. 54

Capitolul 5. Potrivirea modelelor


1. Obiective ........................................................................................................... 59
2. Potrivirea modelelor cu switch – when ................................................. 59
3. Potrivirea modelelor cu algoritmul Rete .............................................. 61
3.1. Algoritmul Rete și limbajul CLIPS ............................................... 61
3.2. Fapte și variabile ............................................................................... 61
4. Potrivirea modelelor pe liste .................................................................... 62
5. Potrivirea modelelor generală ................................................................. 64
5.1. Utilizarea multiplă a unei variabile............................................ 64
5.2. Constrângeri ....................................................................................... 67
6. Sisteme expert ................................................................................................ 70
7. Aplicații ............................................................................................................. 71

Capitolul 6. Algoritmul A*
1. Obiective ........................................................................................................... 77
2. Algoritmul A* .................................................................................................. 77
3. Aplicații ............................................................................................................. 80

Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 7. Jocuri. Algoritmul minimax
1. Obiective ........................................................................................................... 95
2. Jocuri .................................................................................................................. 95
3. Algoritmul minimax...................................................................................... 96
4. Retezarea alfa-beta ....................................................................................... 99
5. Aplicație ......................................................................................................... 103

Capitolul 8. Algoritmi evolutivi


1. Obiective ........................................................................................................ 117
2. Structura unui algoritm evolutiv .......................................................... 117
3. Tipuri de codificare.................................................................................... 119
3.1. Codificarea binară.......................................................................... 119
3.2. Codificarea reală............................................................................. 120
3.3. Construirea funcției de adaptare ............................................. 120
4. Pseudocodul general ................................................................................. 121
5. Operatori genetici....................................................................................... 123
5.1. Selecția ............................................................................................... 123
5.1.1. Selecția de tip ruletă ......................................................... 123
5.1.2. Selecția bazată pe ranguri .............................................. 124
5.1.3. Selecția de tip competiție ............................................... 125
5.1.4. Elitismul ................................................................................ 125
5.2. Încrucișarea ..................................................................................... 125
5.2.1. Încrucișarea binară........................................................... 126
5.2.2. Încrucișarea reală.............................................................. 127
5.3. Mutația ............................................................................................... 128
5.3.1. Mutația binară .................................................................... 128
5.3.2. Mutația reală ....................................................................... 129
6. Aplicații .......................................................................................................... 129

Capitolul 9. Rezoluția propozițională


1. Obiective ........................................................................................................ 139
2. Forma normal conjunctivă...................................................................... 139
3. Algoritmul de rezoluție propozițională ............................................. 141
4. Demonstrarea automată a propozițiilor............................................ 144
5. Decidabilitatea ............................................................................................. 145

Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
6. Aplicații .......................................................................................................... 146

Capitolul 10. Inferența vagă (fuzzy)


1. Obiective ........................................................................................................ 159
2. Mulțimi fuzzy................................................................................................ 159
2.1. Noțiuni fundamentale .................................................................. 161
2.2. Numere fuzzy .................................................................................. 162
3. Inferența Mamdani (max-min) cu o singură regulă ...................... 163
3.1. Modus Ponens generalizat ......................................................... 163
3.2. Defuzzificarea .................................................................................. 164
4. Inferența Mamdani cu reguli multiple................................................ 165
4.1. Crearea regulilor fuzzy ................................................................ 166
4.2. Fuzzificarea ...................................................................................... 167
4.3. Combinarea antecedenților multipli ...................................... 167
4.4. Calcularea consecvenților........................................................... 168
4.5. Agregarea ieșirilor ......................................................................... 168
4.6. Defuzzificarea .................................................................................. 168
5. Aplicații .......................................................................................................... 169

Capitolul 11. Rețele bayesiene


1. Obiective ........................................................................................................ 183
2. Probabilități condiționate. Teorema lui Bayes................................ 183
3. Rețele bayesiene ......................................................................................... 185
4. Inferența prin enumerare ....................................................................... 188
5. Aplicații .......................................................................................................... 190

Capitolul 12. Rețele neuronale: regiuni de decizie


1. Obiective ........................................................................................................ 193
2. Caracteristici ale rețelelor neuronale artificiale............................. 193
3. Perceptronul cu un singur strat ............................................................ 194
4. Perceptronul multi-strat.......................................................................... 198
5. Aplicații .......................................................................................................... 201

Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 13. Rețele neuronale: algoritmul de retro-propagare
(backpropagation)
1. Obiective ........................................................................................................ 213
2. Descrierea algoritmului ........................................................................... 213
3. Considerente practice ............................................................................... 217
4. Aplicație ......................................................................................................... 220

Referințe .................................................................................................................... 237

Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 1

Noțiuni fundamentale de
programare funcțională

1. Obiective

Obiectivele acestui capitol sunt legate de prezentarea unor


caracteristici fundamentale ale paradigmei de programare funcțională:
imutabilitate, recursivitate, diferite moduri de declarare a unor obiecte-
funcții, trimiterea unor funcții ca parametri pentru alte funcții, închideri
(closures).

2. Programarea funcțională

Programarea funcțională este o paradigmă de programare, cum este


și programarea orientată pe obiecte. Aceste idei sunt destul de vechi: primul
limbaj funcțional, Lisp, a apărut în 1958 și este încă utilizat. Deși limbajele
funcționale sunt succinte și expresive, până recent nu au fost folosite pe
scară largă, ci în general pentru aplicații specifice de inteligență artificială.
Însă în zilele noastre există noi provocări. Trebuie prelucrate mulțimi
mari de date în paralel și într-o manieră scalabilă. Programele trebuie testate
cât mai ușor și ar fi de dorit ca logica acestora să fie exprimată clar, într-un
mod ușor de înțeles, care să se axeze în special pe rezultatele obținute (ce se
dorește) și nu pe detaliile de execuție (cum se obțin rezultatele).
De aceea, multe limbaje de programare importante au început să
includă caracteristici funcționale, de exemplu tipuri generice, metode
anonime și expresii lambda. LINQ din platforma .NET este bazat pe
concepte funcționale. De asemenea, limbajele funcționale propriu-zise au
început să se bucure de o mai mare atenție și sunt folosite din ce în ce mai
mult pentru aplicații comerciale.
9
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În general, programarea funcțională se studiază folosind un limbaj
conceput special pe principiile funcționale, de exemplu Scheme (un dialect
Lisp), Haskell (un limbaj funcțional „pur”), Scala (compatibil cu Java VM)
sau F# (compatibil cu platforma .NET). Dezavantajul acestei abordări este
că trebuie învățate atât conceptele programării funcționale, destul de diferite
de cele ale programării imperative, cât și sintaxa unui limbaj nou, de obicei
la fel de diferită de sintaxa limbajelor apropiate de C.
De aceea, în această lucrare, vom folosi limbajul C# și ne vom
concentra doar pe aspectele funcționale. După cum se va vedea, chiar
folosind o sintaxă cunoscută, programele vor arăta destul de diferit față de
cele tipice din programarea orientată pe obiecte.
Un alt avantaj este folosirea numeroaselor funcții din bibliotecile
disponibile pentru platforma .NET și interoperabilitatea deplină cu alte
programe .NET.
Datorită sintaxei concise, programele funcționale sunt în general mai
compacte decât programele imperative echivalente. Să considerăm un
program care calculează suma pătratelor numerelor de la 1 la n.

Imperativ Funcțional
private int Square(int x) public static int SumOfSquares(int n)
{ {
return x * x; int Square(int x) => x * x;
} int SumSq(int m) => Range(1, m).Select(x =>
Square(x)).Sum();
public int SumOfSquares(int n) return SumSq(n);
{ }
int sum = 0;
for (int i = 1; i <= n; i++)
sum += Square(i);
return sum;
}

Nu este necesară înțelegerea codului funcțional acum. Putem spune


doar că ideea de bază este transformarea succesivă a unei liste de numere de
la 1 la n într-o listă cu pătratele lor și apoi sumarea acestor elemente. Se
poate observa înlănțuirea apelurilor de funcții. De asemenea, funcția de
ridicare la pătrat este definită foarte simplu, aproape de echivalentul său
matematic.

10
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3. Imutabilitate

Una din caracteristicile programării funcționale este imutabilitatea


(immutability). Odată inițializat, un obiect nu mai poate fi modificat. Acest
lucru poate părea ciudat pentru un programator obișnuit cu ideea de
variabilă din programarea imperativă, al cărei sens este tocmai cel de a se
modifica. Totuși, imutabilitatea este prezentă în platforma .NET. De
exemplu, obiectele string sunt imutabile (immutable). Toate operațiile asupra
unui string returnează alte string-uri, nu modifică obiectul inițial.
Valorile imutabile sunt asemănătoare celor din matematică. Datorită
lor, programul poate deveni mai clar. Un avantaj important apare în cazul
aplicațiilor concurente, unde sincronizarea se simplifică mult prin
renunțarea la conceptul de stare partajată (shared state). De asemenea,
imutabilitatea simplifică testarea aplicațiilor, deoarece starea internă a
obiectelor nu se schimbă în timpul execuției programului și deci un test se
va comporta la fel indiferent de operațiile anterioare efectuate.
În momentul de față, limbajul C# nu are suport implicit pentru valori
imutabile. Totuși, programatorul poate folosi proprietăți read-only de acces
la starea internă a obiectului și poate crea doar metode care nu schimbă
starea obiectului, ci instanțiază un obiect nou cu starea modificată, ca în
exemplul următor.

public class Ellipse


{
// proprietățile Rx, Ry și Area nu se pot modifica din exterior
public readonly double Rx, Ry;
public double Area { get { return Rx * Ry * Math.PI; } }

public Ellipse(double rx, double ry)


{
Rx = rx;
Ry = ry;
}

public Ellipse Stretch(double factor)


{
// se returnează o nouă elipsă, nu se modifică Rx din obiectul curent
return new Ellipse(Rx * factor, Ry);
}

11
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public Ellipse ModifyWith(double newRx = 0, double newRy = 0)
{
if (newRx == 0) newRx = Rx;
if (newRy == 0) newRy = Ry;
return new Ellipse(newRx, newRy);
}

public override string ToString()


{
return $"Ellipse: rx = {Rx:F2}, ry = {Ry:F2}, a = {Area:F2}";
}
}

public class MainEllipse


{
public static void Test()
{
var e = new Ellipse(1, 2); // Ellipse: rx = 1.00, ry = 2.00, a = 6.28
e = e.Stretch(3); // Ellipse: rx = 3.00, ry = 2.00, a = 18.85
e = e.ModifyWith(newRx: 5); // Ellipse: rx = 5.00, ry = 2.00, a = 31.42
e = e.ModifyWith(newRy: 6); // Ellipse: rx = 5.00, ry = 6.00, a = 94.25
}
}

4. Recursivitate

În programarea funcțională pură nu există bucle, deoarece acestea


presupun actualizarea unui contor. Valorile fiind imutabile, acest lucru nu
este posibil. De aceea, echivalentul funcțional al buclelor este reprezentat de
funcțiile recursive.
Orice program iterativ care implică bucle poate fi convertit într-o
variantă recursivă și viceversa. De obicei, transformarea se face ca mai jos:

Varianta iterativă Varianta recursivă


program-iterativ(parametri) program-recursiv(parametri)
cod-înainte-de-buclă cod-înainte-de-buclă
while(condiție) return funcție-recursivă(parametri, variabile-în-cod-
cod-buclă înainte-de-buclă)
return rezultat

12
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
funcție-recursivă(parametri, variabile)
if(not condiție)
return rezultat
cod-buclă
return funcție-recursivă(parametri, variabile-
modificate)

Transformarea este posibilă și în cazul în care bucla conține


instrucțiuni de tip break, continue sau return. De exemplu, funcția recursivă
poate returna, pe lângă rezultatul propriu-zis calculat, și o etichetă de tip șir
de caractere, enumerare sau întreg, care să corespundă acestor instrucțiuni.
Prin testarea etichetei, se pot trata și aceste situații speciale.
Mai jos este prezentat algoritmul lui Euclid pentru determinarea
celui mai mare divizor comun pentru două numere întregi, în cele două
variante.

Varianta iterativă (pseudocod) Varianta recursivă (C#)


procedure gcd(a, b) public static int Gcd(int a, int b)
while b ≠ 0 {
t←b if (b == 0)
b ← a modulo b return a;
a←t else
return a return Gcd(b, a % b);
}

Următorul exemplu arată o funcție de calcul al factorialului:

public static BigInteger Factorial(int n) // structura BigInteger se găsește în System.Numerics


{
if (n <= 1)
return 1;
else
return n * Factorial(n - 1);
}

sau, mai compact:

public static BigInteger Factorial(int n)


{
return (n <= 1) ? 1 : (n * Factorial(n - 1));
}

13
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Atunci când se folosesc funcții recursive, parametrii sunt memorați
pe stivă, iar dacă nivelul de recursivitate este foarte mare, acesta poate
produce o excepție de memorie insuficientă. Pentru a evita acest lucru, în
unele limbaje funcționale se folosește metoda tail recursion. Mai jos este
rescrisă funcția de calcul al factorialului în acest mod. Rezultatul este
calculat în acumulatorul acc și transmis ca parametru în funcția recursivă.

public static BigInteger Factorial(int n, BigInteger acc)


{
if (n < 2)
return acc;
return Factorial(n - 1, n * acc);
}

Tail recursioneste o metodă de optimizare foarte utilă în aplicațiile


din „viața reală”, care permite compilatorului să transforme, intern, funcția
recursivă într-o buclă. Acest procedeu este recunoscut de compilator, de
exemplu, în limbajul F#, dar nu și în C#, unde se poate folosi în mod natural
o buclă. Totuși, considerăm că această metodă nu ține de filosofia
recursivității, ci este o modalitate de optimizare practică și de aceea nu vom
insista asupra ei în restul capitolului.
De multe ori, funcțiile de ordin superior, pe care le vom prezenta în
capitolul 2, pot fi folosite în loc de recursivitate sau bucle, iar această
modalitate este mai simplă și mai clară decât recursivitatea, dar și mai
elegantă decât folosirea buclelor.

5. Funcții ca obiecte

5.1. Delegatul Func

Limbajul C# permite lucrul cu funcții la fel cum se lucrează și cu


alte tipuri de obiecte. Acesta se realizează cu ajutorul conceptului de delegat
(delegate), care poate fi văzut ca un pointer la o funcție și care poate
referenția o metodă cu aceeași semnătură. Delegații se folosesc în mod
curent pentru a trata evenimente, de exemplu, evenimentele controalelor
Windows Forms, sau alte metode de tip callback.
14
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Pentru a simplifica lucrul cu funcții, există o serie de delegați
generici predefiniți, dintre care noi vom studia delegatul Func. Fie codul de
mai jos:

private static double Div(int x, int y)


{
return (double)x / (double)y;
}

public static void Test1()


{
Func<int, int, double> Operation = Div;
double result = Operation(10, 10);
Console.WriteLine(result);
}

Argumentele din Func reprezintă, în ordine, tipurile parametrilor


funcției, iar ultimul este tipul de return. În cazul nostru, sunt două
argumente de tip int, iar tipul de return este double.
Următorul exemplu definește o funcție fără niciun parametru, care
întoarce un int:

private static Random _rand = new Random();

private static int Rand()


{
return _rand.Next(100);
}

public static void Test2()


{
Func<int> FuncRand = Rand;
double result = FuncRand();
Console.WriteLine(result);
}

Obiectele de tip Func pot fi folosite ca orice alt obiect, adică pot fi
atribuite cu diferite valori, trimise ca parametru și apelate când este nevoie:

15
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
private static int Add(int x, int y)
{
return x + y;
}

private static int Mul(int x, int y)


{
return x * y;
}

private static int PerformOperation(Func<int, int, int> operation, int x, int y)


{
return operation(x, y);
}

public static void Test3()


{
int result = PerformOperation(Add, 5, 5);
Console.WriteLine(result);
result = PerformOperation(Mul, 5, 5);
Console.WriteLine(result);
}

5.2. Expresii lambda

Expresiile lambda sunt funcții anonime, pentru care nu se definește


un nume. În C#, funcțiile pot primi ca argumente alte funcții. Atunci când
aceste funcții-argument sunt foarte simple sau sunt utilizate într-un singur
loc, este utilă exprimarea lor mai succintă. De exemplu, expresiile lambda
sunt foarte utile când se dorește executarea unor operații asupra listelor sau a
altor tipuri de colecții, după cum vom vedea în capitolul 2.
Să considerăm funcția de ordin superior PerformOperation(Func<int,
int, int> operation, int x, int y) din exemplul de mai sus, care primește ca
argument o funcție și o aplică asupra a două alte argumente.
Dacă funcțiile Add și Mul sunt definite doar pentru a fi folosite de
către PerformOperation, atunci este mai simplu să scriem:

16
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static void Test4()
{
int result = PerformOperation((x, y) => x + y, 5, 5);
Console.WriteLine(result);
result = PerformOperation((x, y) => x * y, 5, 5);
Console.WriteLine(result);
}

O expresie lambda folosește operatorul => pentru a separa lista de


parametri de codul executabil. Următoarele exemple arată diferite moduri de
definire a unor expresii lambda:

Func<int, int> Square = x => x * x;


Func<int, int, int> Add = (x, y) => x + y;
Func<string, int, bool> IsTooLong = (string s, int maxLen) => s.Length > maxLen;
Func<int, int, int> Max = (x, y) => { if (x > y) return x; else return y; }; // sau:
Func<int, int, int> Max = (x, y) => (x > y) ? x : y;

Definițiile de mai sus sunt echivalente cu următoarele:

int Square(int x) => x * x;


int Add(int x, int y) => x + y;
bool IsTooLong(string s, int maxLen) => s.Length > maxLen;
int Max(int x, int y) => (x > y) ? x : y;

5.3. Funcții locale

Începând cu versiunea 7, C# permite definirea unor funcții în cadrul


altor funcții. Acestea sunt locale și nu pot fi apelate din exterior. De
exemplu:

17
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static void Test5()
{
// Add și Mul sunt funcții locale definite în interiorul funcției Test5

int Add(int a, int b) // definire clasică


{
return a + b;
}

int Mul(int x, int y) => x * y; // definire ca expresie lambda

Console.WriteLine(Add(3, 4));
Console.WriteLine(Mul(3, 4));
}

6. Alte aspecte

6.1. Efecte secundare

În programarea funcțională pură, funcțiile trebuie să primească niște


parametri, să-i prelucreze și să returneze rezultatul. Efectele secundare (side
effects) apar atunci când o funcție realizează și altceva decât returnarea unei
valori. Însă programele reale au deseori astfel de scopuri: afișarea unor date,
salvarea acestora pe hard disk, comunicații în rețea etc.
Prin urmare, se recomandă izolarea funcțiilor cu efecte secundare de
funcțiile care realizează prelucrări. În această lucrare, în general, vom izola
funcțiile care afișează rezultatele de funcțiile care le calculează.

6.2. Metode și clase statice

În programarea funcțională în limbajul C#, în general clasele vor


avea doar metode statice, deoarece toate datele se transmit ca parametri sau
sunt returnate de funcții/metode. Prin urmare, clasele nu vor avea stare
internă (câmpuri), ca în programarea orientată pe obiecte. Aceste metode
statice pot fi, după caz, publice (accesibile din exterior) sau private (folosite
de alte metode doar în cadrul unei clase).

18
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Începând cu versiunea 6, limbajul C# a introdus directiva using
static, care permite accesarea membrilor statici ai unui tip fără specificarea
numelui tipului. De exemplu:

// directivă inclusă înainte de declararea namespace-ului, în zona celorlalte directive using


using static System.Console;
// instrucțiune normală din cadrul programului, într-o metodă dintr-o clasă
WriteLine("mesaj");

using static System.Convert;


int n = ToInt32(ReadLine()));

using static System.Math;


double a = Sin(30.0 / 180.0 * PI); // sin(30°)

Folosirea acestei directive în măsura potrivită simplifică programul


și îi crește claritatea. Nu trebuie însă abuzat de această facilitate, deoarece
dacă se folosesc în acest mod multe clase, se poate crea confuzie.

7. Închideri (closures)

Când se declară în mod normal o variabilă locală, aceasta are un


domeniu în care poate fi utilizată: este accesibilă în cadrul blocului în care
este declarată. Când se încearcă accesarea unei variabile locale,
compilatorul o caută în blocul curent, apoi mai sus în blocurile-părinte până
la blocul de pe nivelul cel mai de sus. Când fluxul de control părăsește un
bloc, variabilele sale locale nu mai sunt accesibile și de obicei memoria
ocupată de ele este eliberată de garbage collector.
O închidere (closure) este un domeniu persistent care menține
variabilele locale chiar și după ce fluxul de control a ieșit din blocul de cod
respectiv. Domeniul și variabilele sale locale sunt menținute prin legătura cu
o funcție.
Acest mecanism este prezentat în exemplul următor:

public static void TestClosure()


{
var uc = UpdateCount();
WriteLine(uc()); // 1

19
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
WriteLine(uc()); // 2
WriteLine(uc()); // 3
}

private static Func<int> UpdateCount()


{
int counter = 0;

int IncrementCounter()
{
return ++counter;
}

return IncrementCounter; // se returnează funcția locală care incrementează variabila locală


}

Mai succint, metoda UpdateCount poate fi scrisă și astfel:

private static Func<int> UpdateCount()


{
int counter = 0;
// se returnează o expresie lambda care nu are parametri de intrare și care
// incrementează contorul
return () => ++counter;
}

O închidere este într-un fel echivalentul unei variabile globale:

private static int counter = 0;

private static int UpdateCount()


{
return ++counter;
}

Închiderile permit lucrul cu stări globale, fără a utiliza însă variabile


globale. În timp ce expresiile lambda nu au stare, închiderile au. Acestea
reprezintă o modalitate mai elegantă de a încapsula și menține stări globale
în programarea funcțională.

20
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
8. Aplicații

8.1. Introduceți de la tastatură trei numere întregi reprezentând


lungimile laturilor unui triunghi și verificați dacă acesta este un triunghi
valid: laturile sunt pozitive și respectă inegalitatea în triunghi, adică orice
latură este mai mică decât suma celorlalte două. Definiți două funcții
separate prin expresii lambda: una care să testeze inegalitatea în triunghi și
una care să testeze dacă un număr este strict pozitiv. Folosiți directive using
static pentru clasele System.Console, System.Convert și System.Math.

Exemple:

Introduceti latura 1: 2
Introduceti latura 2: 3
Introduceti latura 3: 4
Reprezinta un triunghi valid

Introduceti latura 1: 1
Introduceti latura 2: -1
Introduceti latura 3: 1
Nu reprezinta un triunghi valid

Introduceti latura 1: 1
Introduceti latura 2: 2
Introduceti latura 3: 3
Nu reprezinta un triunghi valid

8.2. Se dă mai jos varianta iterativă a unui algoritm pentru calcularea


radicalului unui număr real pozitiv. Implementați varianta sa recursivă.

procedure SqrtIterative(x)
if x < 0.0 then
return nan // not a number
else
b←x
while abs (b * b - x) > 1e-12 do
b ← ((x / b) + b) / 2.0
return b

21
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
8.3. Realizați un joc simplu de ghicire a unui număr de către
utilizator. Mai întâi, programul va alege în mod aleatoriu un număr natural
de la 1 la 10, după care utilizatorul va introduce succesiv diferite valori,
până la ghicirea numărului.
Numărul aleatoriu se generează cu ajutorul clasei .NET Random.
Implementați funcția Guess în manieră recursivă.

public static void Problem3()


{
var r = new Random();
var n = r.Next(10) + 1;
Guess(n);
}

Exemplu:

Introduceti numarul: 5
Numarul corect este mai mare
Introduceti numarul: 7
Numarul corect este mai mare
Introduceti numarul: 9
Numarul corect este mai mic
Introduceti numarul: 8
Corect!

8.4. Realizați o funcție care primește o altă funcție f : ℝ → ℝ și un


număr real x și calculează derivata f '(x) folosind metoda diferenței centrale:

f (x   )  f (x   )
f ' ( x) 
2

unde ε este un număr real pozitiv mic, de exemplu 10-6.

Exemplu de apel:

public static void Problem4()


{
double y = Derivative(Sin, 0);
WriteLine($"sin(0) = 0\tsin'(0) = {y:F0}"); // sin(0) = 0 sin'(0) = 1

22
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
y = Derivative(x => x * x, 3);
WriteLine($"x^2(3) = 9\t(x^2)'(3) = {y:F0}"); // x^2(3) = 9 (x^2)'(3) = 6
}

8.5. Afișați zece termeni ai șirului lui Fibonacci, în care termenul


tn = tn–1 + tn–2, folosind o închidere (clojure). Pentru simplitate, considerăm
că funcția generează termenii începând cu t2, întrucât t0 = 0 și t1 = 1 pot fi
folosiți ca valori inițiale în închidere.

Exemplu de apel:

public static void Problem5()


{
var fibo = Fibonacci(); // funcția Fibonacci trebuie implementată
for (int i = 0; i < 10; i++)
Write($"{fibo()} "); // rezultat: 1 2 3 5 8 13 21 34 55 89
WriteLine();
}

23
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
24
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 2

Operații cu liste și tuple

1. Obiective

Obiectivele acestui capitol sunt următoarele:

 descrierea tipului IEnumerable, care poate fi considerat echivalent al


listelor din limbajele funcționale și prezentarea numeroaselor metode
LINQ pentru diverse prelucrări;
 introducerea unei biblioteci, FunCs, care include anumite facilități
de programare funcțională în C#;
 descrierea lucrului cu tuple.

2. Crearea listelor

Lista este structura de date fundamentală în programarea funcțională.


Majoritatea operațiilor de prelucrare a datelor presupun transformări ale
unor liste în altele, precum și determinarea unor valori singulare din
elementele unor liste. De multe ori, și funcțiile recursive lucrează cu liste, pe
care le construiesc în mod dinamic.
În C#, echivalentele acestor tipuri de funcții care acționează asupra
listelor sunt implementate în LINQ (pronunțat [linc]) – Language-Integrated
Query. Deși există mai multe variante ale acestei tehnologii (LINQ to Objects,
LINQ to SQL, LINQ to XML), noi ne vom concentra doar asupra primei variante,
LINQ to Objects. Tipul de bază cu care se lucrează aici este IEnumerable,
care în general expune un enumerator al unei colecții de date.
Există mai multe metode de definire a unor astfel de obiecte:

25
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
int[] array = new int[] { 1, 2 };
IEnumerable<int> intList1 = array.AsEnumerable();

List<int> list = new List<int> { 1, 2, 3 };


IEnumerable<int> intList2 = list.AsEnumerable();

Introducem acum biblioteca FunCs (https://github.com/florinleon/


FunCs), cu facilități de programare funcțională în C#. Vom folosi această
bibliotecă și în capitolele următoare. Biblioteca este reprezentată de un dll,
care trebuie adăugat la proiect. Este recomandată apoi includerea directivei
using FunCs în lista de namespace-uri utilizate în program.
Cu ajutorul FunCs, se pot declara mai ușor liste de tip int, double și
string. De asemenea, sunt implementate și metode pentru afișarea acestora.
Numele metodelor specifice bibliotecii FunCs sunt colorate cu mov. De
exemplu:

var intList3 = "[ 1 2 3 4 ]".ToIntEnumF();

WriteLine(intList1.ToStringF()); // [ 1 2 ]
WriteLine(intList2.ToStringF()); // [ 1 2 3 ]
WriteLine(intList3.ToStringF()); // [ 1 2 3 4 ]

var doubleList = "[ 1.1 2 3 4.2 ]".ToDoubleEnumF();


WriteLine(doubleList.ToStringF(1)); // argumentul ToStringF este numărul de zecimale ⇒ [
1.1 2.0 3.0 4.2 ]

var strList = "[ a b c ]".ToStringEnumF();


WriteLine(strList.ToStringF()); // [ "a" "b" "c" ]

O listă poate fi creată și din elementele returnate cu instrucțiunea


yield. Funcția următoare creează o listă cu numerele prime mai mici decât
un număr dat:

private static IEnumerable<int> CreatePrimeList(int n)


{
for (int i = 2; i < n; i++)
{
bool prime = true;
for (int j = 2; j <= Sqrt(i); j++)
if (i % j == 0)
{

26
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
prime = false;
break;
}
if (prime)
yield return i;
}
}

public static void PrimesYield()


{
var primeList = CreatePrimeList(100);
WriteLine(primeList.ToStringF());
// [ 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ]
}

3. Funcții de lucru cu liste

LINQ are implementate o multitudine de funcții pentru lucrul cu


liste, pe care le vom prezenta în cele ce urmează. Deși unele funcții realizează
operații specifice programării funcționale, numele lor în LINQ este inspirat
din interogarea bazelor de date, fiind diferit de numele utilizate în alte
limbaje funcționale. De aceea, biblioteca FunCs introduce niște alias-uri:

LINQ FunCs
Select Map
Aggregate Reduce
SelectMany Bind
Where Filter

Funcțiile originare LINQ nu sunt ascunse, ele pot fi folosite în


continuare în mod direct în programe. Scopul alias-urilor este însă
familiarizarea cu terminologia funcțională specifică.

27
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3.1. Funcții de transformare

Funcțiile prezentate în această secțiune sunt esențiale, fiind folosite


în majoritatea programelor funcționale, de aceea trebuie bine înțelese.
Funcția Map/Select aplică o funcție tuturor elementelor unei liste,
returnând o nouă listă. De exemplu, să presupunem că avem o listă de
numere întregi și dorim să le ridicăm pe toate la pătrat:

var list1 = "[ 2 1 3 ]".ToIntEnumF();


var list2 = list1.Map(x => x * x); // [ 4 1 9 ]

Transformările sunt generale, nu doar numerice. De exemplu, putem


aplica funcția Map pentru a determina lungimea cuvintelor dintr-o listă:

var list1 = "[ Laborator de IA ]".ToStringEnumF();


var list2 = list1.Map(s => s.Length); // [ 9 2 2 ]

sau pentru a transforma numere în șiruri de caractere etc.:

string Nume(int n)
{
if (n == 1) return "unu";
else if (n == 2) return "doi";
else if (n == 3) return "trei";
else return "";
}

var list1 = "[ 2 1 3 ]".ToIntEnumF();


var list2 = list1.Map(n => Nume(n)); // [ "doi" "unu" "trei" ]

Funcția Map/Select are și o variantă în care se pot utiliza indecșii


elementelor din listă. De exemplu:

class Country
{
public readonly string Name;
public readonly int Population;

public Country(string name, int population)


{

28
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Name = name;
Population = population;
}
}

public static void MapiExample()


{
var countries = new List<Country> {
new Country("Russia", 17098242), new Country("Ukraine", 603628),
new Country("France", 551695), new Country("Spain", 498511),
new Country("Sweden", 450295), new Country("Norway", 385178),
new Country("Germany", 357386), new Country("Finland", 338145),
new Country("Poland", 312685), new Country("Italy", 301338) };

var formatedList = countries.Map((country, index) =>


$"{index+1}. {country.Name}: {country.Population}");
foreach (string s in formatedList)
WriteLine(s);
}

cu rezultatul:

1. Russia: 17098242
2. Ukraine: 603628
3. France: 551695
4. Spain: 498511
5. Sweden: 450295
6. Norway: 385178
7. Germany: 357386
8. Finland: 338145
9. Poland: 312685
10. Italy: 301338

Funcția Reduce/Aggregate aplică succesiv o funcție asupra


elementelor unei liste, reducând în final lista la un singur element. Unul din
argumentele sale este o funcție care reduce două elemente la un singur
element.
Cu ajutorul său putem calcula, de exemplu, produsul elementelor
unei liste:

var list1 = "[ 1 2 3 4 ]".ToIntEnumF();


int p = list1.Reduce((x, y) => x * y); // 24

29
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Mai exact, în acest caz funcția lucrează astfel:

 calculează 1 * 2 și pune valoarea într-un acumulator: a = 1 * 2 = 2;


 calculează a * 3 și actualizează valoarea acumulatorului: a = a * 3 = 6;
 calculează a * 4 și actualizează valoarea acumulatorului: a = a * 4 = 24.

Dacă lista are un singur element, atunci acesta este rezultatul. Dacă
lista primită este vidă, funcția aruncă o excepție:
System.InvalidOperationException: Sequence contains no elements.
Să considerăm un alt exemplu, în care dorim să concatenăm
elementele unei liste într-un șir de caractere, în care elementele să fie
despărțite prin spațiu:

var list2 = "[ a b c d ]".ToStringEnumF();


string s = list2.Reduce((x, y) => $"{x} {y}"); // "a b c d"

Aici concatenarea de șiruri funcționează deoarece și rezultatul și


elementele sunt de același tip, string. Dacă elementele listei ar fi numere, ar
apărea o eroare la compilare: Cannot implicitly convert type 'string' to 'int'.
Într-o variantă mai generală a aceleiași funcționalități, se primește o
valoare inițială explicită pentru acumulator, nu primul element din listă:

double StDev(IEnumerable<double> list)


{
int size = list.Count();
double mean = list.Reduce(0.0, (x, y) => x + y) / (double)size;
double difSquares = list.Reduce(0.0, (sum, item) => sum + (item - mean) *
(item - mean));
double variance = difSquares / (double)size;
return Sqrt(variance);
}

var list1 = "[ 1 2 3 4 5 ]".ToDoubleEnumF();


var stdev = StDev(list1); // 1.4142135623731
WriteLine(stdev);

Reduce este o funcție foarte puternică, deoarece poate fi folosită și în


loc de recursivitate. O funcție de calcul al factorialului se poate realiza după
cum urmează, folosind și indecșii elementelor din listă:

30
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
int Factorial(int x) => Range(1, x).Reduce(1, (a, i) => a * i);
int f = Factorial(5); // 120

Metoda Range returnează o listă cu elementele de la 1 la x. Pentru a


fi utilizată ca atare, trebuie inclusă directiva using static
System.Linq.Enumerable în lista de namespace-uri ale programului.

3.2. Funcții de ordonare a elementelor

Din această categorie, menționăm funcțiile Reverse, care returnează


o nouă listă care conține elementele în ordine inversă și OrderBy, care
sortează elementele unei liste după aplicarea unei funcții asupra
elementelor:

var r = new Random();


var list1 = Range(1, 10).Map(x => r.Next(100));
var list2 = list1.OrderBy(x => x); // [ 4 4 20 31 41 45 53 55 69 87 ]

var list3 = Range(1, 10);


var list4 = list3.Reverse(); // sau: OrderBy(x => -x); ⇒ [ 10 9 8 7 6 5 4 3 2 1 ]

var list5 = "[ 1 4 8 -2 5 ]".ToIntEnumF();


var list6 = list5.OrderBy(x => Abs(x)); // [ 1 -2 4 5 8 ]

var list7 = "[ Laboratorul 2 de IA ]".ToStringEnumF();


var list8 = list7.OrderBy(s => s.Length); // [ "2" "de" "IA" "Laboratorul" ]

3.3. Funcții de filtrare și combinare

Funcția Filter/Where returnează o nouă listă care conține doar


elementele care satisfac un predicat:

var list1 = "[ cat dog chicken wolf ]".ToStringEnumF();


var list2 = list1.Filter(s => s.Length == 3); // [ "cat" "dog" ]

Funcția Zip combină două liste de aceeași dimensiune într-o singură


listă:
31
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
var list3 = "[ one two three ]".ToStringEnumF();
var list4 = "[ eins zwei drei ]".ToStringEnumF();
var translation = list3.Zip(list4, (en, de) => $"{en} = {de}");
// [ "one = eins" "two = zwei" "three = drei" ]

Funcția Bind/SelectMany este utilizată pentru „a aplatiza” (flatten) o


listă în care fiecare element este la rândul său o listă:

int[][] lists = {
new[] {1, 2, 3},
new[] {4},
new[] {5, 6, 7, 8},
new[] {9, 10}};
var result = lists.Bind(x => x); // [ 1 2 3 4 5 6 7 8 9 10 ]

La fel ca și Map/Select, această funcție are o importanță specială în


programarea funcțională. Vom reveni asupra ei în capitolul 4.

3.4. Funcții statistice

Din acest tip de funcții, menționăm: Sum, Average, Min și Max, cu


semnificațiile obișnuite:

var list = "[ 1 2 3 4 ]".ToIntEnumF();


int sum = list.Sum(); // 10
double avg = list.Average(); // 2.5
int min = list.Min(); // 1
int max = list.Max(); // 4

Aceleași operații se pot realiza și după aplicarea unei funcții asupra


elementelor listei:

int sum2 = list.Sum(x => x * x); // 30


double avg2 = list.Average(x => x * x); // 7.5

32
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3.5. Funcții de căutare și adăugare

Pentru adăugarea unui element la începutul unei colecții


IEnumerable se folosește funcția Prepend. Pentru adăugarea la sfârșit, se
folosește funcția Append.
Biblioteca FunCs mai adaugă unele funcții de căutare. Find caută un
element după o condiție și întoarce primul element care îndeplinește
condiția respectivă. FindIndex întoarce indexul unui element căutat sau
indexul primului element care îndeplinește o anumită condiție.

var list = "[ 2 3 4 ]".ToIntEnumF();


list = list.Append(5).Prepend(1); // [ 1 2 3 4 5 ]
int i1 = list.FindIndex(3); // 2
int i2 = list.FindIndex(9); // -1

var list2 = "[ -1 2.3 4.1 -10 1 0.01 4 ]".ToDoubleEnumF();


double found = list2.Find(x => Math.Abs(x) < 0.1); // 0.01
int i3 = list2.FindIndex(x => Math.Abs(x) < 0.1); // 5

Lista tuturor funcțiilor din LINQ poate fi consultată în documentația


MSDN: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.

4. Tuple

O tuplă este o colecție ordonată de date utilizată pentru gruparea


acestora. Tuplele pot avea elemente de tipuri diferite, inclusiv alte tuple,
separate prin virgulă și așezate, opțional, între paranteze, spre deosebire de
liste, care au elemente de același tip. De exemplu:

var pair = ("unu", "doi");


var numbers = (1, 3, 5);
var mixedTuple = (1, 1.2, "abc");
var mixedTuple2 = (1.1, (3, 4, "cuvant"), new int[] { 10, 11 });

Pentru a accesa elementele unei tuple se pot folosi proprietățile


Item_i:

33
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
var first = pair.Item1; // "unu"
var second = pair.Item2; // "doi"
var third = mixedTuple.Item3; // "abc"

Elementele unei tuple se mai pot extrage printr-o atribuire multiplă


de valori:

(int x, int y, int z) = numbers; // x = 1, y = 3, z = 5

De asemenea, se pot atribui nume elementelor unei tuple, iar


extragerea valorilor se face în mod explicit:

var pair = (First: "unu", Second: "doi");


var first = pair.First;
var second = pair.Second;

5. Aplicații

5.1. Să se determine dacă o listă reprezintă un palindrom. Un


palindrom este o listă care, citită de la stânga la dreapta sau de la dreapta la
stânga, rămâne aceeași.
Pentru testarea egalității unor secvențe IEnumerable după conținut,
se poate folosi funcția SequenceEqual.

Exemple:

Lista [ 1 2 3 4 3 2 1 ] este un palindrom


Lista [ 1 2 3 ] nu este un palindrom

5.2. Realizați o funcție care duplică fiecare element al unei liste.

Exemplu:

var list = "[ 1 2 3 ]".ToIntEnumF();


var result = Duplicate(list);
WriteLine($"Lista cu elemente duplicate este: {result.ToStringF()}");

34
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Lista cu elemente duplicate este: [ 1 1 2 2 3 3 ]

5.3. Generați cu ajutorul funcțiilor Range și Map o listă de n numere


naturale aleatorii între 0 și m.

5.4. Realizați o funcție care șterge fiecare al treilea element dintr-o


listă. A nu se confunda cerința de față cu situația în care trebuie eliminate
elementele divizibile cu 3. Aici trebuie testat indexul elementelor. Se pot
folosi funcțiile Map și Filter; se poate transforma mai întâi lista inițială într-
o lista de tuple, de forma (element, index).

Exemplu:

var list = Range(2, 15);


var result = Drop3(list);
WriteLine($"Lista cu elementele eliminate este: {result.ToStringF()}");

Lista cu elementele eliminate este: [ 2 3 5 6 8 9 11 12 14 15 ]

5.5. Rezolvați problema optimizării unei funcții f : ℝ → ℝ în sensul


determinării argumentului x dintr-un interval dat D ⊂ ℝ pentru care f(x) este
minimă sau maximă.
Programul poate avea următoarea structură:

private static IEnumerable<double> DoubleRange(double min, double max, double step)


{
// de ex., pentru parametrii: min = -1, max = 1, step = 0.1, rezultatul trebuie să fie lista:
// [ -1.0 -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 ]
// se pot folosi Range+Map sau yield
}

private static double Argmin(Func<double, double> f, double min, double max, double step)
{
var range = DoubleRange(min, max, step);
// se returnează x-ul pentru care f(x) este minimă
// nu se poate folosi o buclă de tip imperativ: for, while etc.

35
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
// se poate folosi funcția Reduce sau altă abordare funcțională (o funcție recursivă etc.)
}

private static double Argmax(Func<double, double> f, double min, double max, double step)
{
var range = DoubleRange(min, max, step);
// se returnează x-ul pentru care f(x) este maximă
}

public static void Problem5()


{
double f1(double x) => x * x - x;
var xargmin = Argmin(f1, -5, 5, 0.1);
var fmin = f1(xargmin);
WriteLine($"x = {xargmin:F3} => f = {fmin:F3}"); // x = 0.500 => f = -0.250

double f2(double x) => 1 - Math.Abs(x);


var xargmax = Argmax(f2, -5, 5, 0.1);
var fmax = f2(xargmax);
WriteLine($"x = {xargmax:F3} => f = {fmax:F3}"); // x = 0.000 => f = 1.000
}

36
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 3

Compunerea funcțiilor și evaluarea pasivă

1. Obiective

Obiectivele acestui capitol sunt legate de prezentarea modului în


care funcțiile pot fi înlănțuite și compuse în LINQ și a lucrului cu secvențe
care sunt inițial doar definite și apoi evaluate numai în momentul utilizării
lor efective.

2. Înlănțuirea funcțiilor

Înlănțuirea apelurilor de funcții conduce la o structură logică mai


naturală și deci mai ușor de înțeles. Să considerăm un prim exemplu, o
funcție care calculează dimensiunea unui director:

public static long SizeOfFolder(string folder)


{
// ia toate numele de fișiere din directorul folder și subdirectoarele sale
var filesInFolder = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories);
// transformă numele fișierelor în obiectele FileInfo corespunzătoare
var fileInfos = filesInFolder.Map(file => new FileInfo(file));
// transformă vectorul de obiecte FileInfo într-un vector de dimensiuni
var fileSizes = fileInfos.Map(info => info.Length);
// sumează dimensiunile și returnează suma
return fileSizes.Sum();
}

public static void Ex1()


{
var totalSize = SizeOfFolder(@"d:\TestSize");
WriteLine($"Total size = {totalSize} bytes ({totalSize / (1024 * 1024):F1} MB)");
}

37
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În această funcție, fiecare rezultat intermediar este pasat funcției
următoare, astfel încât avem nevoie de mai multe instrucțiuni distincte. În
LINQ, metodele sunt fluente (fluent), adică pot fi înlănțuite. Acestea se
aplică unor obiecte IEnumerable și returnează tot obiecte IEnumerable.
Folosind această facilitate, funcția de mai sus poate fi rescrisă astfel:

public static long SizeOfFolderFluent(string folder)


{
return Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories)
.Map(file => new FileInfo(file))
.Map(info => info.Length)
.Sum();
}

Pe lângă beneficiile aduse de claritate și concizie, înlănțuirea


funcțiilor are totuși dezavantajul că procesul de depanare (debugging) devine
mai dificil, deoarece programatorul nu mai poate inspecta rezultatele
intermediare ale prelucrărilor.
Programatorul își poate adăuga propriile funcții de prelucrare a
colecțiilor prin așa-numitele extension methods din C#. Acestea trebuie
introduse într-o clasă statică și au drept caracteristică faptul că primul
parametru este precedat de cuvântul cheie this, iar tipul său este tipul căruia
i se va adăuga metoda.
Exemplul următor adaugă metoda Cube tipului int:

public static class Examples


{
private static int Cube(this int x) => x * x * x;
}

În continuare, fie o funcție care calculează numărul de caractere cu


care este scris cubul unui număr întreg:

private static int Cube(this int x) => x * x * x;

public static void Ex2()


{
var x = 5;
var l3 = x.Cube().ToString().Length;
WriteLine($"x = {x}, l3 = {l3}");

38
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}

Un alt exemplu este o extension method pentru tipul IEnumerable:

private static IEnumerable<int> Cube(this IEnumerable<int> list)


{
return list.Map(x => x * x * x);
}

public static void Ex3()


{
var list = "[ 1 2 3 4 ]".ToIntEnumF();
var list3 = list.Cube();
WriteLine($"{list.ToStringF()} => {list3.ToStringF()}"); // [ 1 2 3 4 ] => [ 1 8 27 64 ]
}

3. Compunerea funcțiilor

O altă caracteristică fundamentală a programării funcționale este


compunerea mai multor funcții pentru a realiza funcții mai complexe și cu o
putere de prelucrare mai mare. În biblioteca FunCs este implementată o
extension method pentru obiectele de tip funcție numită To, care creează o
nouă funcție prin compunerea a două funcții.

public static void Ex4()


{
Func<double, double> Cube = n => n * n * n;
Func<double, string> String = n => n.ToString();
Func<string, int> Length = s => s.Length;

var CubeLength = Cube.To(String).To(Length);


// echivalent cu: var CubeLength = Cube.To(String.To(Length));

double x = 5.1;
var l3 = CubeLength(x);

WriteLine($"x = {x}, l3 = {l3}");


}

39
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În acest caz, CubeLength este o funcție compusă, care primește
argumentul primit de Cube și întoarce rezultatul calculat de Length.
Apelarea succesivă a funcțiilor poate fi realizată împreună cu
funcțiile de transformare ale listelor. În exemplul următor, aplicăm în ordine
trei funcții simple asupra elementelor unei liste de numere reale:

var functions = new List<Func<double, double>>


{
Abs,
Sqrt,
x => x + 1
};

var list = "[ 1 -2.5 4 ]".ToDoubleEnumF();


var res = list.Map(x => functions.Reduce(x, (a, f) => f(a)));
WriteLine(res.ToStringF(3)); // [ 2.000 2.581 3.000 ]

4. Evaluarea pasivă

Obiectele de tip vector (array) sau listă sunt evaluate în mod activ
(eager). Când se definește, de exemplu, o listă, ea se creează efectiv în
memorie, chiar dacă nu este folosită ulterior. Există însă situații în care
dorim să evaluăm o expresie doar atunci când este nevoie. De exemplu,
putem să definim o secvență foarte mare de date, dar dorim să se calculeze
aceste valori doar în momentul în care le folosim efectiv. Acest mod de
evaluare se numește pasiv sau întârziat (lazy) și poate conduce la o scădere
foarte mare a necesarului de memorie a unui program.
Colecțiile IEnumerable sunt evaluate pasiv. Prin urmare, pe lângă
modurile de definire pe care le-am considerat până acum, se pot defini
colecții infinite. Biblioteca FunCs include funcția InfiniteF.Define care
definește o secvență infinită. În exemplul următor, se definește șirul
numerelor naturale.

var seqInfinite = InfiniteF.Define(n => n + 1);

Parametrul n din expresia lambda merge de la 0 la infinit. Expresia


definește termenul corespunzător lui n. Șirul începe de la 1, deci termenul
40
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
corespunzător lui n = 0 este t0 = 0 + 1 = 1, termenul corespunzător lui n = 1
este t1 = 1 + 1 = 2 ș.a.m.d.
O secvență (infinită sau nu) poate fi parcursă în maniera dorită cu
ajutorul funcțiilor Skip, care permite saltul peste un număr de elemente, și
Take, care evaluează un număr de elemente:

var list = seqInfinite.Skip(3).Take(4);


WriteLine($"{list.ToStringF()}"); // [ 4 5 6 7 ]

O colecție poate fi construită în mod recursiv. Următoarea funcție


întoarce toate fișierele dintr-un director și subdirectoarele acestuia:

private static IEnumerable<string> AllFilesUnder(string basePath)


{
foreach (var file in Directory.GetFiles(basePath)) // toate fișierele din directorul de bază
yield return file;

foreach (var subdir in Directory.GetDirectories(basePath))


foreach (var file in AllFilesUnder(subdir)) // toate fișierele din subdirectoare
yield return file;
}

public static void Ex5()


{
var allFiles = AllFilesUnder(@"d:\TestDir");
foreach (var file in allFiles)
WriteLine(file);
}

5. Aplicații

5.1. La un magazin, dacă un client cumpără cel puțin 3 produse, i se


acordă o reducere de 10% pentru al doilea cel mai scump produs și o
reducere de 20% pentru al treilea cel mai scump produs. Să se calculeze
suma totală pe care trebuie să o plătească clientul.
Pentru rezolvare, nu extrageți primele două elemente pentru a le
prelucra separat, ci operați o transformare asupra întregii liste de prețuri.

41
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Definiți funcția DiscountedSum ca o extension method pentru
IEnumerable, apelabilă la fel ca funcția standard Sum.

Exemplu:

var list = "[ 20 10 30 ]".ToDoubleEnumF(); WriteLine($"Preturile articolelor fara reduceri:


{list.ToStringF(2)}");
double sum = list.Sum(); WriteLine($"Suma fara reduceri: {sum:F2}");
double discSum = list.DiscountedSum(); WriteLine($"Suma finala: {discSum:F2}");

list = "[ 10.5 20.5 ]".ToDoubleEnumF(); WriteLine($"\r\nPreturile articolelor fara reduceri:


{list.ToStringF(2)}");
sum = list.Sum(); WriteLine($"Suma fara reduceri: {sum:F2}");
discSum = list.DiscountedSum(); WriteLine($"Suma finala: {discSum:F2}");

Preturile articolelor fara reduceri: [ 20.00 10.00 30.00 ]


Suma fara reduceri: 60.00
Suma finala: 56.00

Preturile articolelor fara reduceri: [ 10.50 20.50 ]


Suma fara reduceri: 31.00
Suma finala: 31.00

5.2. Realizați un program care calculează valoarea reală a unei fracții


continue. O fracție continuă este o expresie obținută în urma unui proces
iterativ de reprezentare a unui număr ca suma unor numere întregi și inverse
ale unor întregi, de forma:

[ ]

unde și , .

a) Folosiți mai întâi abordarea imperativă, cu o buclă for.


b) Rezolvați aceeași problemă în mod funcțional, folosind funcția
Reduce.

Exemplu:

42
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
WriteLine($"Valoarea lui pi este:\t{Math.PI}");

double pi1 = ContFractionI(new int[] { 3, 7, 15 });


WriteLine($"Valoarea fractiei este:\t{pi1} (i)");

double pi2 = ContFractionF(new int[] { 3, 7, 15 });


WriteLine($"Valoarea fractiei este:\t{pi2} (f1)");

double pi3 = ContFractionF(new int[] { 3, 7, 15, 1, 292, 1 });


WriteLine($"Valoarea fractiei este:\t{pi3} (f2)");

Valoarea lui pi este: 3.14159265358979


Valoarea fractiei este: 3.14150943396226 (i)
Valoarea fractiei este: 3.14150943396226 (f1)
Valoarea fractiei este: 3.14159265392142 (f2)

5.3. Funcția exponențială poate fi calculată cu ajutorul unei serii


Taylor:

Definiți o secvență infinită ai cărei termeni corespund termenilor


seriei de mai sus. Realizați o funcție care aproximează funcția exponențială
cu o precizie dorită, precizia însemnând în acest caz faptul că termenii seriei
mai mici decât această valoare sunt ignorați.

Indicație: Pentru definirea secvenței infinite, observați că termenul


generic corespunzător lui n poate fi scris recursiv:

De exemplu, factorialul poate fi scris recursiv ca:


iar funcția care îl calculează este:

43
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static BigInteger Factorial(int n) => (n <= 1) ? 1 : (n * Factorial(n - 1));

Realizați o funcție similară pentru termenul generic al seriei de mai


sus, fără a folosi separat funcția de calcul al factorialului. La rezolvare, se
poate utiliza funcția TakeWhile.

Exemplu:

var x = 2.0; // exponentul din e^x


var p = 1e-6; // precizia

var e1 = ExpF(x, p);


WriteLine($"Serie:\t{e1:F6}"); // 7.389056

var e2 = Math.Exp(x);
WriteLine($"Exact:\t{e2:F6}"); // 7.389056

5.4. Date fiind toate tipurile de bancnote ale unei țări și o valoare,
determinați numărul minim de bancnote necesar pentru plata acelei valori.
Nu folosiți construcții imperative (de exemplu, bucle for sau while).
Folosiți secvențe IEnumerable, recursivitatea și/sau funcția Reduce. Alte
funcții utile din IEnumerable pot fi First și Append.
Algoritmul de descompunere este greedy: se sortează descrescător
tipurile de bancnote, se calculează numărul maxim de bancnote de tipul cel
mai mare care pot fi utilizate la un moment dat, după care se scade suma
identificată din suma inițială și se trece la următorul tip de bancnotă.

Exemplu: descompunerea valorii 2778 cu tipurile de bancnote: 500,


200, 100, 50, 10, 5, 1.

2778 / 500 = 5 rest 278


278 / 200 = 1 rest 78
78 / 100 = 0 rest 78
78 / 50 = 1 rest 28
28 / 10 = 2 rest 8
8/5 = 1 rest 3
3/1 = 3 rest 0

44
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
var amount = 2778;
var money = "[ 500 200 100 50 10 5 1 ]".ToIntEnumF();
var result = Decompose(amount, money); // [ 5 1 0 1 2 1 3 ]

45
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
46
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 4

Functori și monade. Tipul Option

1. Obiective

În acest capitol, vom prezenta conceptele de functor și monadă,


tipice programării funcționale, împreună cu un tip special de obiecte
(Option), care poate încapsula sau nu o valoare.

2. Tipul Option

În multe situații apare necesitatea de a trata valori nedefinite.


Majoritatea limbajelor imperative folosesc în acest sens cuvântul cheie null
sau unul echivalent. Să considerăm ca exemplu o problemă des întâlnită:
citirea unui șir de caractere de la tastatură și convertirea lui într-un număr
întreg pozitiv. Conversia este posibilă doar dacă șirul de caractere reprezintă
un număr valid. În C#, conversia se poate face astfel:

private static int? ToNatural(string s) // int? = Nullable<int>


{
bool ok = int.TryParse(s, out int result);
if (ok && result >= 0)
return result;
else
return null;
}

sau folosind excepții:

private static int ToNatural(string s)


{
bool ok = int.TryParse(s, out int result);

47
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
if (ok && result >= 0)
return result;
else
throw new Exception($"Argument {s} is not a valid natural number");
}

Începând cu versiunea 8, în C# au fost adăugate o serie de metode


pentru a identifica explicit situațiile în care un obiect, atât de tip valoare cât
și de tip referință, poate fi nul, astfel încât compilatorul să le recunoască prin
analiză statică și să scadă incidența erorilor legate de obiecte nule.
În multe limbaje funcționale, se folosește tipul Option sau echivalent
(Maybe, Optional), cu două posibile valori: Some (Just) și None (Nothing).
Some poate conține la rândul său orice tip de valoare.
În biblioteca FunCs este definit tipul OptionF, care poate fi utilizat
ca în exemplul de mai jos, echivalentul funcțional al metodei de conversie a
unui string într-un număr natural:

private static OptionF<int> ToNatural(string s)


{
bool ok = int.TryParse(s, out int result);
if (ok && result >= 0)
return OptionF<int>.Some(result); // crearea unui obiect de tip Some, cu valoarea
result
else
return OptionF<int>.None(); // crearea unui obiect de tip None
}

private static OptionF<int> ReadNatural()


{
Write("Input a positive integer: ");
var s = ReadLine();
return ToNatural(s);
}

public static void TestOption()


{
var rez = ReadNatural();
if (rez.IsSome) // test dacă opțiunea are o valoare
WriteLine($"Some: {rez.Value}"); // regăsirea valorii încapsulate
if (rez.IsNone) // test dacă opțiunea nu conține nicio valoare
WriteLine("None");
}

48
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Se poate observa că tipul OptionF este un container (sau wrapper)
care poate conține orice alt tip de obiect. De asemenea, se poate testa în mod
explicit dacă un obiect de tip opțiune conține sau nu un obiect „valoare”.
În metoda următoare, se prezintă mai multe operații cu obiecte
IEnumerable și OptionF:

public static void TestListOfOptions()


{
IEnumerable<int> l1 = "[ 1 2 2.5 abc -5 14 ]".ToStringEnumF(' ')
// se convertesc elementele string în elemente OptionF<int>
.Map(x => ToNatural(x))
// se rețin doar elementele de tip Some
.Filter(x => x.IsSome)
// se transformă OptionF<int> în valorile corespunzătoare int
.Map(x => x.Value);
WriteLine(l1.ToStringF()); // [ 1 2 14 ]

OptionF<int> o1 = ToNatural("1");
WriteLine(o1); // Some(1)

OptionF<int> o2 = ToNatural("abc");
WriteLine(o2); // None

// obiectele OptionF pot conține în Some alte obiecte de tip OptionF


OptionF<OptionF<int>> o3 = OptionF<OptionF<int>>.Some(o1);
WriteLine(o3); // Some(Some(1))

// Bind aplatizează obiectele cu valori imbricate/nested


OptionF<int> o4 = o3.Bind(x => x);
WriteLine(o4.ToStringF()); // [ 1 ]

// pot exista colecții IEnumerable de obiecte OptionF și invers


IEnumerable<OptionF<int>> l2 = new OptionF<int>[] { o1, o2, o1, o2 }.AsEnumerable();
// se rețin doar elementele de tip Some, iar din acestea doar valorile
IEnumerable<int> l3 = l2.FilterSome();
WriteLine(l3.ToStringF()); // [ 1 1 ]
}

În secvența de cod următoare, se observă cum se poate lucra cu


funcția Map din OptionF:

49
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
private static OptionF<string> StringNotNumber(string s)
{
if (!double.TryParse(s, out double d)) // s nu reprezintă un număr
return OptionF<string>.Some(s);
else
return OptionF<string>.None();
}

public static void TestOptionMap()


{
OptionF<string> optStr = StringNotNumber("abc");
// se transformă OptionF<string> în OptionF<int>
OptionF<int> optLen = optStr.Map(x => x.Length);
WriteLine($"{optStr} -> {optLen}"); // Some(abc) -> Some(3)

optStr = StringNotNumber("123");
optLen = optStr.Map(x => x.Length);
WriteLine($"{optStr} -> {optLen}"); // None -> None
}

3. Functori

Atât listele (IEnumerable) cât și opțiunile (OptionF) sunt obiecte-


container, care conțin alte obiecte de orice tip. O listă conține o serie de
obiecte, iar o opțiune conține un singur obiect, în Some. Aceste tipuri de
obiecte-container respectă o serie de proprietăți care le fac să aparțină unor
categorii speciale definite în teoria programării funcționale.
Am văzut că Map aplică o funcție asupra elementelor interne ale
unei liste sau opțiuni, rezultând o altă listă sau opțiune. Să notăm cu F<T>
tipul acestui obiect-container care conține elemente de tip T. Putem nota
funcția care transformă elementele drept T → R. Tipul obiectului-container
rezultat după aplicarea funcției Map va fi F<R>. Map trebuie să aplice
funcția elementelor interne din F și să nu aibă efecte secundare.
Un astfel de tip F pentru care este definită o funcție Map se numește
functor.
Figura următoare prezintă în mod succint structura unui astfel de
obiect.

50
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3.1. Legile functorilor

1. Păstrarea identității: dacă valorile interne ale unui functor sunt


transformate în ele însele, rezultatul trebuie să fie același functor.

// functor.Map(x => x) == functor

OptionF<int> functor1 = OptionF<int>.Some(3); // Some(3)


OptionF<int> functor2 = functor1.Map(x => x); // Some(3)

2. Păstrarea compunerii: rezultatul a două operații succesive de


transformare trebuie să fie același cu rezultatul aplicării primei funcții
asupra rezultatului celei de a doua.

// functor.Map(x => (f o g)(x))) == functor.Map(f).Map(g)

OptionF<int> functor = OptionF<int>.Some(3);


Func<int, int> f = x => x * x;
Func<int, int> g = x => x + 1;
OptionF<int> r1 = functor.Map(x => (f.To(g))(x)); // Some(10)
OptionF<int> r2 = functor.Map(x => f(x)).Map(x => g(x)); // Some(10)

4. Monade

În exemplele prezentate în capitolele 2 și 4, am văzut cum poate fi


folosită funcția Bind pentru a evita lucrul cu obiecte imbricate (nested), de
exemplu, liste de liste sau opțiuni cu valori de tip opțiune.

51
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Să notăm cu M<T> tipul unui astfel de obiect-container care conține
elemente de tip T. Bind folosește o funcție de transformare de tip T → M(R).
Tipul obiectului-container rezultat după aplicarea funcției Bind va fi M<R>.
Să mai considerăm o funcție simplă numită Return, care
încapsulează o valoare T într-un obiect-container M<T>. Ca exemple de
funcții Return putem menționa orice funcție care creează o enumerare
populată de obiecte de un anumit tip, precum:

IEnumerable<int> list = "[ 1 2 3 ]".ToIntEnumF();

sau o funcție care creează o opțiune setându-i valoarea internă:

OptionF<int> opt = OptionF<int>.Some(1);

Un tip M pentru care sunt definite funcțiile Return și Bind se


numește monadă.
Figura următoare prezintă în mod succint structura unui astfel de
obiect.

52
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
4.1. Legile monadelor

1. Identitate la stânga

// Return(t).Bind(f) == f(t)

int t = 3;
Func<int, OptionF<int>> Return = x => OptionF<int>.Some(x);
Func<int, OptionF<int>> f = x => OptionF<int>.Some(x * x);
var r1 = Return(t).Bind(f); // Some(9)
var r2 = f(t); // Some(9)

2. Identitate la dreapta

// m == m.Bind(Return)

OptionF<int> m1 = OptionF<int>.Some(3); // Some(3)


Func<int, OptionF<int>> Return = x => OptionF<int>.Some(x);
var m2 = m1.Bind(Return); // Some(3)

3. Asociativitate

// m.Bind(f).Bind(g) == m.Bind(x => f(x).Bind(g))

OptionF<int> m = OptionF<int>.Some(3);
Func<int, OptionF<int>> f = x => OptionF<int>.Some(x * x);
Func<int, OptionF<int>> g = x => OptionF<int>.Some(x + 1);
OptionF<int> r1 = m.Bind(f).Bind(g); // Some(10)
OptionF<int> r2 = m.Bind(x => f(x).Bind(g)); // Some(10)

Folosirea functorilor și/sau monadelor permite lucrul la un nivel


superior de abstractizare, care nu mai depinde de tipurile concrete ale
obiectelor încapsulate. Pe lângă liste sau opțiuni, există și alte monade
folosite în mod curent în programare. De exemplu, Func<T> nu este un T, ci
o expresie care trebuie evaluată pentru a obține un T. De asemenea, Task<T>
nu este un T, ci o promisiune că la un moment dat în viitor vom avea
disponibil un T.

53
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5. Aplicații

5.1. Realizați o funcție care testează dacă un an este bisect și


returnează Some dacă este bisect și None dacă nu este.
În calendarul gregorian, majoritatea anilor multipli de 4 sunt bisecți. În fiecare an
bisect, luna februarie are 29 de zile în loc de 28. Adăugarea unei zile suplimentare la fiecare
an compensează faptul că perioada de 365 de zile este mai scurtă decât un an tropic cu
aproape 6 ore. Sunt necesare unele excepții de la această regulă simplă, întrucât durata
anului tropical este puțin mai mică decât 365.25 zile. Eroarea se acumulează și pe o
perioadă de 4 secole ea ajunge la 3 zile. Pentru aceasta, calendarul gregorian renunță la trei
ani bisecți în 400 de ani. Aceasta se face prin renunțarea la ziua de 29 februarie în anii
multipli de 100 și care nu sunt și multipli de 400. Anii 2000 și 2400 sunt ani bisecți, dar
1800, 1900, 2100, 2200, 2300 și 2500 nu sunt. (https://ro.wikipedia.org/wiki/An_bisect)

Exemplu:

public static void Problem1()


{
var list = "[ 1900 2000 2017 2020 ]".ToIntEnumF()
.Map(y => LeapYear(y))
.FilterSome();
WriteLine(list.ToStringF()); // [ 2000 2020 ]
}

5.2. Se dă o listă de studenți cu rezultatele obținute la un examen:

private class Student


{
public readonly string Name;
public readonly string Result;

public Student(string name, string result)


{
Name = name;
Result = result;
}
}

54
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static void Problem2()
{
var students = new List<Student>
{
new Student("Alex Neagoe", "10"),
new Student("Carla Stoenescu", "3.50"),
new Student("Flavius Predoiu", "absent"),
new Student("Doina Vasiliu", "9.75"),
new Student("Dan Draghicescu", "5"),
new Student("Maria Raducanu", "7.25")
};
}

Completați funcția de mai sus cu o singură instrucțiune cu funcții


înlănțuite, care să transforme și să filtreze lista students astfel încât să poată
fi afișați în final doar studenții cu medii mai mari sau egale cu 5, iar pentru
aceștia, mediile să fie rotunjite la cel mai apropiat întreg:

Alex Neagoe - 10
Doina Vasiliu - 10
Dan Draghicescu - 5
Maria Raducanu - 7

Lucrați cu opțiuni. În funcțiile înlănțuite, puteți folosi tuple în loc de


obiecte de tip Student. Puteți adăuga și alte funcții în program.

5.3. Fie următorul program, proiectat într-o manieră imperativă, care


realizează diferite operațiuni financiare:

namespace OptionAccountI
{
public class Bank
{
private Dictionary<int, Account> _accounts;

55
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public Bank()
{
_accounts = new Dictionary<int, Account>();
}

public void AddAccount(int accountNumber, double initialAmount)


{
_accounts[accountNumber] = new Account(initialAmount);
}

public Account GetAccount(int accountNumber)


{
if (!_accounts.ContainsKey(accountNumber))
return null;

return _accounts[accountNumber];
}
}

public class Account


{
private Balance _balance;

public Account(double initialAmount)


{
_balance = new Balance(initialAmount);
}

public Balance GetBalance()


{
if (_balance.GetValue() < 0)
return null;

return _balance;
}

public void Withdraw(int amount)


{
_balance = new Balance(_balance.GetValue() - amount);
}
}

public class Balance


{
private double _value;

56
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public Balance(double value)
{
_value = value;
}

public double GetValue()


{
return _value;
}
}

public class Operations


{
public static void Test()
{
var bank = new Bank();
bank.AddAccount(1, 100);
bank.AddAccount(2, 10);
PrintAmount(bank, 1);
bank.GetAccount(1).Withdraw(1000); PrintAmount(bank, 1);
PrintAmount(bank, 10);
}

private static void PrintAmount(Bank bank, int accountNumber)


{
var account = bank.GetAccount(accountNumber);

bool ok = false;
if (account != null)
{
var balance = account.GetBalance();
if (balance != null)
{
double a = balance.GetValue();
WriteLine($"Soldul este pozitiv: {a}");
ok = true;
}
}

if (!ok)
WriteLine("Soldul este negativ sau nu poate fi interogat");
}
}
}

57
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Refactorizați programul într-o manieră funcțională:

 Clasele Bank, Account și Balance vor conține doar proprietățile


(câmpurile), cu vizibilitate publică (fără alte metode). În mod
normal, proprietățile ar trebui să fie read-only, însă în acest caz, vom
face o excepție și le vom defini ca read-write (get-set). În cadrul
operațiilor pe care va trebui să le efectuăm, vom modifica
proprietățile obiectelor imbricate (de tipul a.B.C). În programarea
funcțională, aceste operațiuni se fac cu așa-numitele lentile (lenses).
Pentru aplicația de față, această metodă ar fi prea complexă. În plus,
în C# modificarea proprietăților se face ușor și natural, spre
deosebire de limbajele în care imutabilitatea este implicită. Deci
trebuie să reținem că aici vom încălca recomandările programării
funcționale în beneficiul simplității;
 Toate metodele de procesare vor fi statice și vor fi mutate într-o
clasă distinctă, de exemplu, chiar în clasa Operations. Dacă în
abordarea POO o metodă era în clasa X, în abordarea PF metoda va
primi un prim argument de tipul X (obiectul pentru care trebuie să
realizeze prelucrările);
 Constructorii vor fi înlocuiți de metode statice de tipul CreateX, care
returnează obiectele create de tipul corespunzător (X);
 Operațiile care pot întoarce sau nu rezultate valide (GetAccount,
GetBalance) vor întoarce obiecte de tip OptionF;
 Metoda de test PrintAmount(Bank bank, int accountNumber) trebuie
să conțină o singură operațiune cu funcții înlănțuite în care să se
acceseze contul, obiectul de sold (balance) și valoarea sa. În final,
rezultatul va fi de tip OptionF, cu IsSome dacă toate informațiile și
prelucrările au fost corecte și IsNone în caz contrar. Aici se pot
folosi funcțiile tipice functorilor și monadelor, Map și Bind.

58
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 5

Potrivirea modelelor

1. Obiective

Potrivirea modelelor este echivalentă cu utilizarea unor instrucțiuni


condiționale mai avansate și mai flexibile. Reprezintă verificarea unei
secvențe de simboluri pentru a determina dacă conține anumite elemente
într-o anumită ordine, adică un model. În acest capitol, vom prezenta
modalitatea de lucru cu un algoritm de potrivire a modelelor care poate
aplica eficient reguli sau modele multiple pentru o mulțime de obiecte sau
fapte dintr-o bază de cunoștințe.

2. Potrivirea modelelor cu switch – when

Potrivirea modelelor (pattern matching) reprezintă verificarea unei


secvențe de simboluri pentru a determina dacă conține anumite elemente
într-o anumită ordine, adică un model. O potrivire poate reprezenta și
deconstrucția secvenței în părțile sale constitutive, de exemplu identificarea
primului element al unei liste și restul listei, elementele distincte ale unei
tuple etc.
La un nivel simplificat, potrivirea modelelor este asemănătoare unei
instrucțiuni switch, însă este mai puternică. Începând cu versiunea 7, C#
permite, pe lângă instrucțiunea switch clasică și testarea unor condiții
suplimentare cu instrucțiunea when. Pentru a prezenta sintaxa unei astfel de
expresii, să considerăm un exemplu simplu, în care se identifică un județ pe
baza indicativului de pe plăcuța de înmatriculare:

59
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static string Judet1(string cod)
{
switch (cod)
{
case "IS": return "Iasi";
case "SV": return "Suceava";
case "B": return "Bucuresti";
default: return "Alt judet";
}
}

public static string Judet2(string cod)


{
var d = new Dictionary<string, string>
{
{ "IS", "Iasi" },
{ "SV", "Suceava" },
{ "B", "Bucuresti" }
};

switch (cod)
{
case string c when d.ContainsKey(c): // c ia valoarea lui cod
return d[c];

case string c when c.Length != 2:


return "Cod invalid";

default:
return "Alt judet";
}
}

public static void Ex1()


{
WriteLine(Judet1("IS")); // Iasi
WriteLine(Judet1("VS")); // Alt judet
WriteLine(Judet1("Arad")); // Alt judet

WriteLine(Judet2("IS")); // Iasi
WriteLine(Judet2("VS")); // Alt judet
WriteLine(Judet2("Arad")); // Cod invalid
}

60
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3. Potrivirea modelelor cu algoritmul Rete

3.1. Algoritmul Rete și limbajul CLIPS

În cele ce urmează, vom prezenta niște scenarii de potrivire a


modelelor mai puternice. Deoarece acestea nu sunt incluse în platforma
.NET, vom folosi facilitățile din biblioteca FunCs. Aceste metode de
potrivire a modelelor se bazează pe algoritmul Rete, implementat de CLIPS,
un instrument pentru crearea sistemelor expert, dezvoltat de Software
Technology Branch, NASA Lyndon B. Johnson Space Center, cu scopul
facilitării realizării de programe care modelează cunoașterea și experiența
umană. CLIPS este un acronim pentru C Language Integrated Production
System.
Rete este un algoritm de potrivire a modelelor pentru implementarea
sistemelor bazate pe reguli. Algoritmul a fost dezvoltat pentru a aplica
eficient multe reguli sau modele la multe obiecte sau fapte dintr-o bază de
cunoștințe. Permite, de asemenea, găsirea unor soluții multiple pentru astfel
de probleme.

3.2. Fapte și variabile

Faptele cu care vom lucra sunt constituite din zero sau mai multe
câmpuri separate de spații, de exemplu: "1 2 3", "abc efg", "a 1.4 15 -6
alte cuvinte".
Elementele a căror valoare dorim să o determinăm prin potrivirea
modelelor sunt echivalente variabilelor și se notează cu un semn de
întrebare pus înaintea numelui, de exemplu: ?varsta, ?loc_nastere, ?specie.
Aceste variabile lor lua valoarea unui singur câmp dintr-un fapt.
Dacă dorim ca o variabilă să poată conține orice număr de câmpuri
(zero, unu sau mai multe), numele acesteia trebuie precedat și de simbolul
dolar, de exemplu: $?text.
Uneori, dorim să identificăm unul sau mai multe câmpuri dintr-un
fapt, dar nu ne interesează valorile lor specifice. În această situație se
folosesc variabile libere, notate cu ? sau $? și fără un nume de variabilă.

61
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Să presupunem că avem un fapt care reprezintă numele unei
persoane, compus din numele de familie, inițiala tatălui și unul sau mai
multe prenume și că dorim să aflăm numele de familie. Faptele pot fi de
tipul: "Ionescu T. George" sau "Popescu S. Andrei Sebastian". Potrivirea se
face cu modelul "?nume $?", unde variabila nume va lua valorile Ionescu,
respectiv Popescu.
Dacă dorim să identificăm primul prenume al unei persoane, vom
folosi modelul "? ? ?prenume $?", unde variabila prenume va lua valorile
George, respectiv Andrei.
Dacă se folosesc în același timp două variabile libere multi-câmp,
rezultatele pot fi multiple. De exemplu, cu faptul "1 2 3" și modelul
"$? ?x $?", variabila x va lua trei valori: 1, 2, respectiv 3, deoarece prima
variabilă liberă poate înlocui zero sau mai multe câmpuri, iar a doua poate și
ea înlocui zero sau mai multe câmpuri. În acest caz, numărul de soluții va fi
egal cu numărul de câmpuri din fapt.

4. Potrivirea modelelor pe liste

Operațiile pe care le-am prezentat mai sus pot fi aplicate pe colecții


de tip IEnumarable în C#, cu ajutorul bibliotecii FunCs. Mai jos se prezintă
câteva exemple de utilizare:

private static void MatchList()


{
var list = "[ 1 2 3 4 5 6 7 8 9 10 ]".ToStringEnumF();
list.MatchF("?head ? $?rest ? ?last", out var head, out var rest, out var last);
WriteLine($"{head} - {rest} - {last}"); // 1 - 3 4 5 6 7 8 - 10

list.MatchF("?x $? ?y", out Dictionary<string, string> matches);


WriteLine($"{matches["?x"]} {matches["?y"]}"); // 1 10

list.MatchF("?x ?y $?w ?z", out var x, out var y, out var w, out var z);
WriteLine($"{x} - {y} - {w} - {z}"); // 1 - 2 - 3 4 5 6 7 8 9 - 10
}

Un alt exemplu este determinarea combinărilor de n elemente luate


câte k. Pentru soluții multiple, se instanțiază un obiect de tip ExpertMatchF,

62
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
iar rezultatele multiple sunt disponibile într-o listă de dicționare, câte un
dicționar pentru fiecare soluție. Valoarea fiecărei variabile dintr-un dicționar
se poate regăsi folosind numele acesteia:

public static void Combinations()


{
var em = new ExpertMatchF("1 2 3 4 5"); // "[" și "]" nu sunt necesare aici
var patterns = new List<string> { "$? ?x $? ?y $?" }; // combinări de 5 luate câte 2
em.Match(patterns, out var matchList); // List<Dictionary<string, string>> matchList
foreach (var m in matchList)
WriteLine($"{m["?x"]} - {m["?y"]}");

// Rezultat:
// 1 - 2
// 1 - 3
// 1 - 4
// 1 - 5
// 2 - 3
// 2 - 4
// 2 - 5
// 3 - 4
// 3 - 5
// 4 - 5
}

Întrucât listele sunt unele dintre cele mai folosite structuri de date în
programarea funcțională, potrivirea modelor pentru liste este de asemenea
foarte des întâlnită, mai ales în contextul funcțiilor recursive. De obicei, se
identifică primul element din listă (head) și lista de elemente următoare (tail).
În FunCs, această facilitate este implementată pentru tipul
IEnumerable generic. Celelalte tipuri de potrivire de modele se pot utiliza
doar pentru colecții de tip string.
Exemplul următor prezintă o funcție care calculează recursiv suma
elementelor dintr-o listă:

private static int MySum(IEnumerable<int> list)


{
if (list.Count() == 0)
return 0;
list.MatchHeadTailF(out int head, out var tail);
return head + MySum(tail);
}

63
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static void MySumTest()
{
var list = "[ 1 2 3 4 5 ]".ToIntEnumF();
int sum = MySum(list); // 15
}

Exemplul următor prezintă o funcție care aplică altă funcție f,


primită ca parametru, asupra tuturor elementelor dintr-o listă. Funcția
aceasta este echivalentă de fapt cu funcția Map:

private static IEnumerable<int> MyMap(this IEnumerable<int> list, Func<int, int> f)


{
if (list.Count() == 0)
return "[]".ToIntEnumF(); // o listă goală, fără elemente
list.MatchHeadTailF(out int head, out var tail);
return tail.MyMap(f).Prepend(f(head));
}

public static void MyMapTest()


{
var list = "[ 3 6 2 ]".ToIntEnumF();
var list2 = list.MyMap(n => n * n); // [ 9 36 4 ]
var list3 = list.MyMap(n => -n); // [ -3 -6 -2 ]
}

5. Potrivirea modelelor generală

5.1. Utilizarea multiplă a unei variabile

O proprietate foarte utilă si importantă pe care o au variabilele este


aceea că pot fi folosite în mai multe modele, însă o variabilă are o singură
valoare, indiferent în câte modele apare.
Fie următorul exemplu, în care avem o bază de fapte cu relațiile de
rudenie între mai multe persoane:

64
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Kinship.csv

father Christopher Arthur // Christopher este tatăl lui Arthur


father Christopher Victoria
father Andrew James
father Andrew Jennifer
father James Colin
father James Charlotte
father Roberto Emilio
father Roberto Lucia
father Pierro Marco
father Pierro Angela
father Marco Alfonso
father Marco Sophia
mother Penelope Arthur
mother Penelope Victoria
mother Christine James
mother Christine Jennifer
mother Victoria Colin
mother Victoria Charlotte
mother Maria Emilio
mother Maria Lucia
mother Francesca Marco
mother Francesca Angela
mother Lucia Alfonso
mother Lucia Sophia

Să determinăm ce persoane sunt părinții aceluiași copil. Se observă


că variabilele corespund exact locului din listă unde se află un anumit nume.
De exemplu, pentru perechea de fapte:

father Marco Sophia


mother Lucia Sophia

folosind variabilele ?f pentru tată (father), ?m pentru mamă (mother) și ?c


pentru copil (child), ?f va fi Marco, ?m va fi Lucia și ?c va fi Sophia. Legarea
celor două fapte se face prin utilizarea aceleiași variabile ?c, care trebuie să
ia același nume, în acest caz Sophia.
În același mod, regula descoperă și celelalte perechi de fapte father și
mother, care au același copil.

65
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public static void Parents()
{
// încărcarea faptelor din fișier
var sr = new StreamReader("kinship.csv");
var lines = sr.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
sr.Close();

var em = new ExpertMatchF(lines.ToList());

// găsirea mamei unui anumit copil în fapte de tip:


// mother Penelope Arthur
string child = "Arthur";
em.MatchVar($"mother ?m {child}", out var mother);
WriteLine(mother); // Penelope

// găsirea părinților aceluiași copil

var patterns = new List<string>


{
"father ?f ?c",
"mother ?m ?c"
};
em.Match(patterns, out var matches);
foreach (var m in matches)
WriteLine($"Father {m["?f"]}, mother {m["?m"]}, child {m["?c"]}");

// Rezultat:
// Father Marco, mother Lucia, child Sophia
// Father Marco, mother Lucia, child Alfonso
// Father Pierro, mother Francesca, child Angela
// Father Pierro, mother Francesca, child Marco
// Father Roberto, mother Maria, child Lucia
// Father Roberto, mother Maria, child Emilio
// Father James, mother Victoria, child Charlotte
// Father James, mother Victoria, child Colin
// Father Andrew, mother Christine, child Jennifer
// Father Andrew, mother Christine, child James
// Father Christopher, mother Penelope, child Victoria
// Father Christopher, mother Penelope, child Arthur
}

66
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5.2. Constrângeri

Limbajul CLIPS, al cărui motor de inferență este încapsulat în


biblioteca FunCS, permite și specificarea unor constrângeri asupra valorilor
variabilelor din modele. Constrângerile sunt implementate ca funcții
predicative în format prefix, operatorul fiind plasat în fața operanzilor.
Dintre aceste funcții, menționăm: and, or, not, eq (egal), neq
(diferit) pentru valori generale și = (egal), <> (diferit), <, <=, >, >= pentru
valori numerice.
Pentru exemplul anterior, să determinăm acum ce persoane sunt
bunic și nepot:

public static void GrandParents()


{
// încărcarea faptelor din fișier
...
var em = new ExpertMatchF(lines.ToList());
var patterns = new List<string>
{
"father ?g ?p",
"?r ?p ?c"
// ?r este o relație asupra căreia se va aplica o constrângere:
// să fie de tip father sau mother
};

var constraint = "(or (eq ?r father) (eq ?r mother))";

em.Match(patterns, constraint, out var matches);


foreach (var m in matches)
WriteLine($"Grandfather {m["?g"]}, {m["?r"]} {m["?p"]}, child {m["?c"]}");

// Rezultat:
// Grandfather Roberto, mother Lucia, child Sophia
// Grandfather Roberto, mother Lucia, child Alfonso
// Grandfather Christopher, mother Victoria, child Charlotte
// Grandfather Christopher, mother Victoria, child Colin
// Grandfather Pierro, father Marco, child Sophia
// Grandfather Pierro, father Marco, child Alfonso
// Grandfather Andrew, father James, child Charlotte
// Grandfather Andrew, father James, child Colin
}

67
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Tot un mecanism de constrângeri poate fi folosit împreună cu
soluțiile multiple pentru a determina permutările elementelor unei liste:

public static void Permutations()


{
var facts = new List<string> { "1", "2", "3" };
var em = new ExpertMatchF(facts);
var patterns = new List<string> { "?o1", "?o2", "?o3" };
var constraint = "(and (neq ?o1 ?o2) (neq ?o1 ?o3) (neq ?o2 ?o3))";
em.Match(patterns, constraint, out var matches);
foreach (var m in matches)
WriteLine($"{m["?o1"]} - {m["?o2"]} - {m["?o3"]}");

// Rezultat:
// 3 - 2 - 1
// 3 - 1 - 2
// 1 - 3 - 2
// 2 - 3 - 1
// 1 - 2 - 3
// 2 - 1 - 3
}

Un alt exemplu în care avem soluții multiple și constrângeri este


problema damelor, care constă în așezarea pe tabla de șah a unui număr de
dame, fără ca acestea să intre în conflict. Nu vom putea avea deci două
dame pe aceeași linie, coloană sau diagonală. Constrângerile generale sunt:

linie1  linie 2

coloana1  coloana 2
 linie  linie  coloana  coloana
 1 2 1 2

Constrângerile pot include și funcții matematice elementare. Aici,


pentru determinarea valorii absolute vom utiliza funcția abs. De exemplu, în
CLIPS, (abs (- 3 7)) returnează 4.

public static void MatchQueens4()


{
var em = new ExpertMatchF("n 1 2 3 4");

68
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
var patterns = new List<string>
{
"n $? ?row $?",
"n $? ?col $?"
};

var queens = new List<string>();


em.Match(patterns, out var matchList);
foreach (var m in matchList)
queens.Add($"queen {m["?row"]} {m["?col"]}");

em = new ExpertMatchF(queens);

patterns = new List<string>


{
"queen 1 ?c1",
"queen 2 ?c2",
"queen 3 ?c3",
"queen 4 ?c4"
};

var constraints = "(and " +


"(neq (abs (- ?c1 ?c2)) 1)" +
"(neq (abs (- ?c1 ?c3)) 2)" +
"(neq (abs (- ?c1 ?c4)) 3)" +
"(neq (abs (- ?c2 ?c3)) 1)" +
"(neq (abs (- ?c2 ?c4)) 2)" +
"(neq (abs (- ?c3 ?c4)) 1)" +
"(neq ?c1 ?c2)" +
"(neq ?c1 ?c3)" +
"(neq ?c1 ?c4)" +
"(neq ?c2 ?c3)" +
"(neq ?c2 ?c4)" +
"(neq ?c3 ?c4)" +
")";

em.Match(patterns, constraints, out matchList);


foreach (var m in matchList)
WriteLine($"{m["?c1"]} {m["?c2"]} {m["?c3"]} {m["?c4"]}");

// Rezultat:
// 2 4 1 3
// 3 1 4 2
}

69
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
După cum se poate vedea urmărind rezultatele programului de mai
sus, presupunând că prima damă se află pe linia 1, a doua pe linia 2 ș.a.m.d.,
există două soluții:

Dama 1: linia 1 coloana 2


Dama 2: linia 2 coloana 4
Dama 3: linia 3 coloana 1
Dama 4: linia 4 coloana 3

Dama 1: linia 1 coloana 3


Dama 2: linia 2 coloana 1
Dama 3: linia 3 coloana 4
Dama 4: linia 4 coloana 2

6. Sisteme expert

Sistemele expert sunt programe concepute pentru a raționa în scopul


rezolvării problemelor pentru care în mod obișnuit se cere o expertiză
umană considerabilă. Un sistem expert încorporează o bază de cunoștințe
sau fapte și un motor de inferență utilizat pentru soluționarea problemelor.
Cunoașterea într-un sistem expert este organizată într-o manieră care
separă cunoștințele despre domeniul problemei de alte tipuri de cunoștințe,
precum cele despre algoritmii de căutare pentru rezolvarea problemelor
specifice și cele despre interacțiunea cu utilizatorul.
Sistemele expert înmagazinează cunoștințe specializate, introduse de
experți. De cele mai multe ori, baza de cunoștințe este foarte mare, de aceea
este foarte importantă modalitatea de reprezentare a cunoașterii. Baza de
cunoștințe a sistemului trebuie separată de program, care la rândul său
trebuie să fie cât mai stabil.
În cele ce urmează, vom prezenta un exemplu de sistem expert bazat
pe arbori de decizie, care realizează clasificarea mișcărilor unui punct
material. Sunt luate în considerare doar cele mai importante caracteristici ale
mișcării: vectorul viteză și vectorul accelerație. În funcție de valorile
acestora se realizează o clasificare primară a mișcării. Deși arborele de
decizie reprezentat mai jos nu este foarte mare, odată cu introducerea altor
parametri, dimensiunile acestuia pot crește foarte mult.

70
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
7. Aplicații

7.1. Realizați un program care afișează numerele de la 1 la 20.


Pentru multiplii de 3, afișați Fizz în loc de număr, pentru multiplii de 5,
afișați Buzz, iar pentru multiplii atât de 3 cât și de 5, afișați FizzBuzz.
Folosiți metoda bazată pe instrucțiuni switch cu when.

Exemplu:

1 Fizz 11 16
2 7 Fizz 17
Fizz 8 13 Fizz
4 Fizz 14 19
Buzz Buzz FizzBuzz Buzz

7.2. Fie următorul arbore genealogic. Să se afișeze verii unei


anumite persoane date.

71
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Indicații: Urmărind arborele, putem vedea că verii lui x au același
bunic ca și x, dar părinți diferiți. Se dă următoarea funcție, care trebuie
completată:

public static void Problem2()


{
var facts = new List<string>
{
"parent Liviu children Vlad Maria Nelu",
"parent Vlad [de completat]",
[de completat]
};

var em = new ExpertMatchF(facts);


string searchFor = "Sorin"; // se caută verii lui Sorin

var patterns = new List<string>


{
[de completat]
};

string constraint = [de completat];

Write($"Verii lui {searchFor} sunt: ");


em.Match(patterns, constraint, out var matches);
foreach (var m in matches)
Write($"{m["?c"]} ");
WriteLine();
}

Exemplu:

Verii lui Sorin sunt: Oana Mircea Dan

72
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
7.3. Realizați un sistem expert bazat pe un arbore de decizie generic,
reprezentând raționamentul pentru rezolvarea unei anumite probleme.
Problema va fi rezolvată interactiv, prin întrebări adresate utilizatorului. Se
pleacă din rădăcina arborelui și apoi, în funcție de răspunsurile date, se
coboară în arbore până se atinge o frunză, corespunzătoare unei concluzii
(soluții).
Pentru fiecare nod neterminal, se definesc următoarele caracteristici:

 O întrebare de al cărei răspuns depinde alegerea următorului nod;


 O listă de răspunsuri acceptabile;
 O mulțime de reguli care determină nodul următor, în funcție de un
anumit răspuns.

În acest scop, se utilizează fapte de tipul:

regulă <nod_curent> <răspuns> <nod_următor>


întrebare <nod_curent> <text_întrebare>
răspunsuri <nod_curent> <listă_răspunsuri_acceptabile>
concluzie <nod_terminal> <text_concluzie>

Se dă următoarea bază de cunoștințe, inclusă în fișierul viteza.kbf,


care definește un arbore de decizie pentru a determina tipul de mișcare al
unui punct material:

Viteza.kbf

root n11
question n11 Viteza isi pastreaza directia?
answers n11 da nu nu_stiu
rule when n11 if nu then n21
rule when n11 if da then n22
rule when n11 if nu_stiu then n23
conclusion n23 Informatii insuficiente.
question n21 Modulul vectorului viteza este constant?
answers n21 da nu
rule when n21 if nu then n31
rule when n21 if da then n32
conclusion n31 Miscare curbilinie neuniforma.
conclusion n32 Miscare curbilinie uniforma.
question n22 Vectorul acceleratie este nul?

73
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
answers n22 da nu
rule when n22 if nu then n33
rule when n22 if da then n34
conclusion n34 Miscare rectilinie uniforma.
question n33 Acceleratie constanta?
answers n33 da nu
rule when n33 if nu then n41
rule when n33 if da then n42
conclusion n41 Miscare rectilinie neuniforma.
question n42 Acceleratie pozitiva?
answers n42 da nu
rule when n42 if nu then n51
rule when n42 if da then n52
conclusion n51 Miscare rectilinie uniform incetinita.
conclusion n52 Miscare rectilinie uniform accelerata.

De exemplu, pentru nodul rădăcină (notat n11) vom avea


următoarele fapte:

question n11 Viteza isi pastreaza directia?


answers n11 da nu nu_stiu
rule when n11 if nu then n21 // răspuns = if, nod următor = then
rule when n11 if da then n22
rule when n11 if nu_stiu then n23

Trebuie subliniat faptul că programul este independent de arborele


de decizie pe care lucrează. Același program poate fi aplicat pentru alte
probleme fără modificări suplimentare.

Exemplu:

Viteza isi pastreaza directia? (da/nu/nu_stiu): da


Vectorul acceleratie este nul? (da/nu): nu
Acceleratie constanta? (da/nu): da
Acceleratie pozitiva? (da/nu): nu
Miscare rectilinie uniform incetinita.

Indicații: Se dau următoarele funcții, care trebuie completate:

public static void Problem3 ()


{
var facts = ReadFromFile("viteza.kbf");

74
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
var em = new ExpertMatchF(facts);

// root n11
em.MatchVar("root ?r", out string currentNode);

while (true) // până când se ajunge la o concluzie


{
// conclusion n51 Miscare rectilinie uniform incetinita.
em.MatchVar($"conclusion {currentNode} $?c", out string conclusion);

if (conclusion != "")
{
WriteLine(conclusion);
break; // se afișează concluzia și se părăsește bucla while
}

// se găsește întrebarea din nodul curent


// se găsesc răspunsurile permise din nodul curent
// se afișează întrebarea, se citește și se validează răspunsul utilizatorului
// se găsește regula corespunzătoare răspunsului dat și se trece la următorul nod
// în arbore, adică se actualizează variabila currentNode
}
}

private static List<string> ReadFromFile(string fileName)


{
var sr = new StreamReader(fileName);
var lines = sr.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
sr.Close();
return lines.ToList();
}

75
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
76
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 6

Algoritmul A*

1. Obiective

Obiectivele acestui capitol sunt înțelegerea și implementarea


algoritmului de căutare a căilor A*.

2. Algoritmul A*

O euristică este o metodă care furnizează rapid o soluție, nu neapărat


optimă. Este o metodă aproximativă, spre deosebire de un algoritm exact
optim. Deși nu garantează găsirea soluției optime, metodele euristice găsesc
de obicei o soluție acceptabilă și deseori chiar soluția optimă.
Metodele euristice de căutare utilizează cunoștințe specifice fiecărei
probleme pentru a ordona nodurile, astfel încât cele mai „promițătoare”
noduri sunt plasate la începutul frontierei.
Funcții utilizate în general:

 f(n) este un cost estimat. Cu cât este mai mic f(n), cu atât este mai
bun nodul n;
 g(n) este costul căii de la nodul inițial la n. Este cunoscută;
 h(n) este estimarea costului căii de la n la un nod scop. Este o
estimare euristică.

Tipuri de căutare:

 Căutarea de cost uniform (neinformată): f(n) = g(n);


 Căutarea greedy: f(n) = h(n);

77
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
 Căutarea A*: f(n) = g(n) + h(n). Reunește ideile căutărilor de cost
uniform și greedy.

Pentru a garanta găsirea soluției optime, funcția h trebuie să fie


admisibilă: niciodată mai mare decât costul real. Pentru metodele euristice
de căutare, problema principală este găsirea celei mai bune funcții h, care să
dea estimări cât mai apropiate de realitate. Cu cât h este mai bună, numărul
de noduri expandate este mai mic.
Algoritmul A* folosește două liste: Open (frontiera) și Closed
(nodurile deja vizitate). Într-o iterație, se preia primul nod din lista Open (cu
cel mai mic f). Dacă este un scop, căutarea se termină cu succes. Altfel,
nodul se expandează. Pentru fiecare succesor, se calculează f = g + h.
Succesorul se ignoră dacă în listele Open și Closed există un nod cu aceeași
stare, dar cu g mai mic. Altfel, se introduce succesorul în lista Open. Nodul
curent (care a fost expandat) se introduce în lista Closed, iar dacă acolo
există deja un nod cu aceeași stare, acesta este înlocuit. Dacă lista Open
devine vidă, nu există soluție.
O funcție euristică h este monotonă sau consistentă dacă respectă
inegalitatea în triunghi. O euristică monotonă devine din ce în ce mai
precisă cu cât înaintează în adâncimea arborelui de căutare. În multe cazuri
(dar nu întotdeauna), monotonia euristicii accelerează găsirea soluției.
Ecuația pathmax face ca valorile lui f să fie monoton
nedescrescătoare pe căile traversate din arborele de căutare: la generarea
unui nod fiu c al lui p: f(c) = max( f(p), g(c) + h(c) ).
Algoritmul A* este complet și optim dacă nodurile care revizitează
stările nu sunt eliminate. A* este optim eficient: niciun alt algoritm de
același tip nu garantează expandarea unui număr mai mic de noduri, dar
doar dacă euristica este monotonă.
Pseudocodul algoritmului A* este prezentat mai jos:

PROBLEMĂ =
{
Stare-inițială : STARE
» starea inițială a problemei
Acțiuni(s : STARE) : ACȚIUNE*
» o funcție care întoarce lista de acțiuni care se pot efectua în starea s
Stare-succesoare(s : STARE, a : ACȚIUNE) : STARE
» o funcție care întoarce starea în care se ajunge aplicând acțiunea a în starea s

78
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Cost(s1 : STARE, s2 : STARE) : NUMĂR-REAL-POZITIV
» o funcție care întoarce costul/distanța dintre 2 stări vecine s1 și s2
Euristică(s : STARE) : NUMĂR-REAL-POZITIV
» o funcție care întoarce estimarea euristică a costului/distanței din starea s în starea scop
Este-scop(s : STARE) : BOOLEAN
» o funcție care verifică dacă starea s este starea scop
}

NOD =
{
Stare » starea problemei corespunzătoare nodului
Părinte » părintele nodului
Acțiune » acțiunea prin care a rezultat nodul
G » distanța cunoscută parcursă din starea inițială până la starea curentă
H » estimarea euristică a distanței din starea curentă până la starea scop
F » suma G + H
}

CĂUTARE-A-STEA
intrări: problemă : PROBLEMĂ
ieșiri: soluție : NOD » nod care conține starea scop
------------------------------------------------------------------------------------------------------------------------
soluție ← Nul
rădăcină ← NOD(Stare ← problemă.Stare-inițială, Nul, Nul, G ← 0, H ← problemă.Euristică(Stare),
F ← G + H)
lista-deschisă ← [ rădăcină ] » nodurile care vor fi expandate
lista-închisă ← [ ] » nodurile deja vizitate

repetă cât-timp lista-deschisă ≠ [ ] » cât timp lista deschisă nu este vidă


nod-curent ← nodul cu valoarea F minimă din lista-deschisă
lista-deschisă.Elimină(nod-curent)

dacă problemă.Este-scop(nod-curent.Stare) atunci


întoarce nod-curent
altfel
pentru-fiecare acțiune din problemă.Acțiuni(nod-curent.Stare)
nod-fiu ← NOD(Stare ← problemă.Stare-succesoare(nod.Stare, acțiune),
Părinte ← nod-curent, Acțiune ← acțiune)

nod-fiu.G ← nod-curent.G + problemă.Cost(nod-curent.Stare, nod-fiu.Stare)


nod-fiu.H ← problemă.Euristică(nod-fiu.Stare)
nod-fiu.F ← nod-fiu.G + nod-fiu.H

» dacă în aceeași stare s-a ajuns deja pe o cale mai scurtă, nod-fiu este ignorat
dacă lista-deschisă nu conține un nod cu starea nod-fiu.Stare și G mai mic decât nod-fiu.G și
lista-închisă nu conține un nod cu starea nod-fiu.Stare și G mai mic decât nod-fiu.G
atunci
lista-deschisă.Adaugă(nod-fiu)

dacă lista-închisă conține un nod nli cu starea nod-curent.Stare atunci


» prin nod-curent s-a ajuns în aceeași stare pe o cale mai scurtă

79
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
lista-închisă.Înlocuiește(nli, nod-curent)
altfel
lista-închisă.Adaugă(nod-curent)

întoarce Nul » nu s-a găsit o soluție

Variante:

» lista-deschisă poate fi o listă sortată: primul element va fi întotdeauna nodul cu


» valoarea F minimă
lista-deschisă ← LISTĂ-SORTATĂ([ rădăcină ], F)
...
nod-curent ← lista-deschisă.Primul-element()

» folosirea ecuației pathmax


nod-fiu.F ← Max(nod-curent.F, nod-fiu.G + nod-fiu.H)

» se pot ignora succesorii care au aceeași stare ca părintele nodului curent


» (s-ar reveni în starea imediat anterioară)
dacă lista-deschisă.NuConține(n : (n.Stare = nod-fiu.Stare) și (n.G < nod-fiu.G)) și
lista-închisă.NuConține(n : (n.Stare = nod-fiu.Stare) și (n.G < nod-fiu.G)) și
(nod-fiu.Stare ≠ nod-curent.Părinte.Stare : nod-curent.Părinte ≠ Nul)
atunci
lista-deschisă ← lista-deschisă {+} nod-fiu » adaugă nod-fiu în listă

3. Aplicații

Fie următoarele probleme de căutare:

80
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Problema 1

Problema 2

81
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Problema 3

Problema 4

82
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
4.1. Rezolvați pe hârtie problema 2 sau problema 3, la alegere.
Arătați cum evoluează lista deschisă, ce noduri sunt scoase din listă, ce
noduri sunt expandate, ce valori f au fiii etc.

4.2. Implementați algoritmul A* pentru o problemă generică de


căutare a căilor. Se dă un prototip de aplicație, în care există o interfață
IProblem, care trebuie respectată atunci când se definește o problemă de
căutare. Soluția mai conține implementările celor 4 probleme prezentate mai
sus. Diagrama UML de clase a soluției complete este următoarea:

Trebuie completată clasa AstarAlgorithm, cu metoda Solve și cele


trei metode de test pentru optimizarea căutării. Se va folosi și ecuația
pathmax.

Program.cs

using System;

namespace Astar
{
public class Program
{

83
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
private static void Main(string[] args)
{
Console.WriteLine("Problema 1");
Console.WriteLine(AstarAlgorithm.Solve(new Problem1()));

Console.WriteLine("\r\nProblema 2");
Console.WriteLine(AstarAlgorithm.Solve(new Problem2()));

Console.WriteLine("\r\nProblema 3");
Console.WriteLine(AstarAlgorithm.Solve(new Problem3()));

Console.WriteLine("\r\nProblema 4");
Console.WriteLine(AstarAlgorithm.Solve(new Problem4()));

Console.WriteLine();
Console.ReadLine();
}
}
}

IProblem.cs

namespace Astar
{
public interface IProblem
{
double GetEdgeCost(string state1, string state2);

double GetHeuristic(string state);

string GetRoot();

string[] GetSuccessors(string state);

bool IsGoal(string state);


}
}

Problem1.cs

using System;

namespace Astar
{

84
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public class Problem1 : IProblem
{
public string GetRoot()
{
return "A";
}

public bool IsGoal(string state)


{
if (state == "E") return true;
else return false;
}

public double GetEdgeCost(string state1, string state2)


{
string[] temp = new string[] { state1, state2 };
Array.Sort(temp);
state1 = temp[0];
state2 = temp[1];

if (state1 == "A" && state2 == "B") return 1;


if (state1 == "A" && state2 == "C") return 2;

if (state1 == "B" && state2 == "D") return 1;

if (state1 == "C" && state2 == "D") return 2;

if (state1 == "D" && state2 == "E") return 100;

return double.PositiveInfinity;
}

public double GetHeuristic(string state)


{
switch (state)
{
case "A": return 0;
case "B": return 100;
case "C": return 1;
case "D": return 90;
case "E": return 0;
}

return double.PositiveInfinity;
}

85
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public string[] GetSuccessors(string state)
{
switch (state)
{
case "A": return new string[] { "B", "C" };
case "B": return new string[] { "A", "D" };
case "C": return new string[] { "A", "D" };
case "D": return new string[] { "B", "C", "E" };
case "E": return new string[] { "D" };
}

return null;
}
}
}

Problem2.cs

using System;

namespace Astar
{
public class Problem2 : IProblem
{
public string GetRoot()
{
return "Buzau";
}

public bool IsGoal(string state)


{
if (state == "Iasi") return true;
else return false;
}

public double GetEdgeCost(string state1, string state2)


{
string[] temp = new string[] { state1, state2 };
Array.Sort(temp);
state1 = temp[0];
state2 = temp[1];

if (state1 == "Bacau")
{
if (state2 == "Iasi")

86
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
return 128;
if (state2 == "Focsani")
return 106;
if (state2 == "Galati")
return 188;
}
else if (state1 == "Buzau")
{
if (state2 == "Focsani")
return 77;
if (state2 == "Galati")
return 133;
if (state2 == "Constanta")
return 231;
}
else if (state1 == "Constanta")
{
if (state2 == "Tulcea")
return 123;
}
else if (state1 == "Focsani")
{
if (state2 == "Vaslui")
return 138;
}
else if (state1 == "Galati")
{
if (state2 == "Vaslui")
return 155;
}
else if (state1 == "Iasi")
{
if (state2 == "Vaslui")
return 72;
}

return double.PositiveInfinity;
}

public double GetHeuristic(string state)


{
switch (state)
{
case "Iasi": return 0;
case "Bacau": return 82;
case "Vaslui": return 58;
case "Focsani": return 165;

87
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
case "Galati": return 195;
case "Buzau": return 231;
case "Constanta": return 342;
case "Tulcea": return 239;
}

return double.PositiveInfinity;
}

public string[] GetSuccessors(string state)


{
switch (state)
{
case "Iasi": return new string[] { "Bacau", "Vaslui" };
case "Bacau": return new string[] { "Iasi", "Focsani", "Galati" };
case "Vaslui": return new string[] { "Iasi", "Focsani", "Galati" };
case "Focsani": return new string[] { "Bacau", "Vaslui", "Buzau" };
case "Galati": return new string[] { "Vaslui", "Bacau", "Buzau" };
case "Buzau": return new string[] { "Focsani", "Galati", "Constanta" };
case "Constanta": return new string[] { "Buzau", "Tulcea" };
case "Tulcea": return new string[] { "Constanta" };
}

return null;
}
}
}

Problem3.cs

using System;

namespace Astar
{
public class Problem3 : IProblem
{
public string GetRoot()
{
return "1";
}

public bool IsGoal(string state)


{
if (state == "6") return true;
else return false;

88
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}

public double GetEdgeCost(string state1, string state2)


{
string[] temp = new string[] { state1, state2 };
Array.Sort(temp);
state1 = temp[0];
state2 = temp[1];

if (state1 == "1" && state2 == "2") return 4;


if (state1 == "1" && state2 == "3") return 1;
if (state1 == "1" && state2 == "4") return 2;

if (state1 == "2" && state2 == "5") return 1;

if (state1 == "3" && state2 == "5") return 2;


if (state1 == "3" && state2 == "6") return 6;

if (state1 == "4" && state2 == "6") return 5;

if (state1 == "5" && state2 == "6") return 3;

return double.PositiveInfinity;
}

public double GetHeuristic(string state)


{
switch (state)
{
case "1": return 4;
case "2": return 3;
case "3": return 3;
case "4": return 3;
case "5": return 2;
case "6": return 0;
}

return double.PositiveInfinity;
}

public string[] GetSuccessors(string state)


{
switch (state)
{
case "1": return new string[] { "2", "3", "4" };
case "2": return new string[] { "1", "5" };
case "3": return new string[] { "1", "5", "6" };

89
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
case "4": return new string[] { "1", "6" };
case "5": return new string[] { "2", "3", "6" };
case "6": return new string[] { "3", "4", "5" };
}

return null;
}
}
}

Problem4.cs

using System;

namespace Astar
{
public class Problem4 : IProblem
{
public string GetRoot()
{
return "1";
}

public bool IsGoal(string state)


{
if (state == "7") return true;
else return false;
}

public double GetEdgeCost(string state1, string state2)


{
string[] temp = new string[] { state1, state2 };
Array.Sort(temp);
state1 = temp[0];
state2 = temp[1];

if (state1 == "1" && state2 == "2") return 4;


if (state1 == "1" && state2 == "3") return 1;
if (state1 == "1" && state2 == "4") return 2;

if (state1 == "2" && state2 == "5") return 1;

if (state1 == "3" && state2 == "5") return 2;


if (state1 == "3" && state2 == "6") return 6;

90
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
if (state1 == "4" && state2 == "6") return 5;

if (state1 == "5" && state2 == "6") return 3;

return double.PositiveInfinity;
}

public double GetHeuristic(string state)


{
switch (state)
{
case "1": return 4;
case "2": return 3;
case "3": return 3;
case "4": return 3;
case "5": return 2;
case "6": return 0;
}

return double.PositiveInfinity;
}

public string[] GetSuccessors(string state)


{
switch (state)
{
case "1": return new string[] { "2", "3", "4" };
case "2": return new string[] { "1", "5" };
case "3": return new string[] { "1", "5", "6" };
case "4": return new string[] { "1", "6" };
case "5": return new string[] { "2", "3", "6" };
case "6": return new string[] { "3", "4", "5" };
}

return null;
}
}
}

Node.cs

namespace Astar
{
public class Node
{

91
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public string State { get; set; }

public Node Parent { get; set; }

public string Operator { get; set; }

public int Depth { get; set; }

public double F { get; set; }

public double G { get; set; }

public double H { get; set; }

public Node(string state, string @operator, Node parent)


{
State = state;
Parent = parent;
Operator = @operator;

if (parent == null) // root


Depth = 0;
else
Depth = parent.Depth + 1;
}

public override string ToString()


{
if (F - (int)F < 1e-6) // is int
return $"{State} ({G:F0}+{H:F0}:{F:F0})";
else
return $"{State} ({G:F2}+{H:F2}:{F:F2})";
}
}
}

SortedNodeList.cs

using System.Collections.Generic;

namespace Astar
{
public class SortedNodeList
{
private List<Node> _list;

92
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public SortedNodeList()
{
_list = new List<Node>();
}

public Node First => (_list.Count > 0) ? _list[0] : null;

public int Count => _list.Count;

public List<Node> Items => _list;

public void Add(Node n)


{
for (int i = 0; i < _list.Count; i++)
{
Node listNode = _list[i];
if (n.F <= listNode.F)
{
_list.Insert(i, n);
return;
}
}
_list.Add(n);
}

public void Remove(Node n) => _list.Remove(n);


}
}

AstarAlgorithm.cs

using System;
using System.Collections.Generic;

namespace Astar
{
public class AstarAlgorithm
{
public static string Solve(IProblem problem)
{
var openList = new SortedNodeList();
var closedList = new Dictionary<string, Node>();

var root = new Node(problem.GetRoot(), "", null);


root.G = 0;

93
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
root.H = problem.GetHeuristic(root.State);
root.F = root.G + root.H;

openList.Add(root);

throw new Exception("Aceasta metoda trebuie completata");


}

private static bool IsCaseForParentPruning(Node successorNode, Node currentNode)


{
throw new Exception("Aceasta metoda trebuie implementata");
}

private static bool IsInOpenWithSmallerG(Node successorNode,


SortedNodeList openList)
{
throw new Exception("Aceasta metoda trebuie implementata");
}

private static bool IsInClosedWithSmallerG(Node successorNode,


Dictionary<string, Node> closedList)
{
throw new Exception("Aceasta metoda trebuie implementata");
}

private static string BuildSolution(Node goal)


{
if (goal.Parent != null)
return $"{BuildSolution(goal.Parent)} - {goal.State}";
else if (goal != null)
return goal.State;
else
return "";
}
}
}

94
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 7

Jocuri. Algoritmul minimax

1. Obiective

Obiectivele acestui capitol sunt legate de implementarea unui joc


simplu între om și calculator, folosind algoritmul minimax cu retezarea alfa-
beta.

2. Jocuri

Un joc reprezintă o succesiune de decizii (acțiuni) luate de părți ale


căror interese sunt opuse. Jocurile pot fi clasificate după:

 Numărul de jucători: 1, 2, n ;
 Natura mutărilor: există jocuri cu mutări libere (mutarea este aleasă
conștient, dintr-o mulțime de acțiuni posibile, de exemplu, șahul) și
jocuri cu mutări aleatorii (mutarea este dictată de un factor aleatoriu:
zaruri, cărți de joc, monede etc.). Pot exista jocuri cu ambele tipuri
de mutări;
 Cantitatea de informație de care dispune un jucător (un jucător de
șah vede configurația completă a pieselor adversarului, pe când un
jucător de cărți nu are acces la configurațiile celorlalți jucători).

Există două motive pentru care jocurile sunt un bun domeniu de


explorat pentru inteligența artificială:

 Au o organizare clară, în care e foarte ușor de măsurat succesul sau


eșecul;

95
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
 Nu necesită cantități mari de cunoștințe inițiale. Multe jocuri sunt
concepute astfel încât să poată fi rezolvate prin metode de căutare
din starea inițială până în poziția câștigătoare. Bineînțeles, acest
lucru este adevărat numai pentru jocurile simple. Un contraexemplu
este jocul de șah, care presupune un arbore de căutare cu factorul de
ramificare 35, adică în fiecare moment există (în medie) 35 de
mutări disponibile. Având în vedere că un jucător face (tot în medie)
aproximativ 50 de mutări, putem trage concluzia că pentru un joc
trebuie examinate 35250  2,55 10154 variante.

Din cauza numărului mare de posibilități, trebuie să existe euristici


de căutare. De obicei, se utilizează o funcție de evaluare statică, care
folosește informațiile disponibile la un moment dat pentru a evalua pozițiile
care au probabilitate mare de a conduce la atingerea scopului, câștigarea
jocului. În absența informațiilor complete, se alege euristic poziția cea mai
promițătoare. „Inteligența” cu care joacă calculatorul depinde în mare
măsură de această funcție de evaluare.
Există jocuri pentru care avem și alte tehnici, în afară de căutare. De
exemplu, în șah („drosophila IA”, după cum îl considera Kronod),
deschiderile și finalurile sunt deseori memorate sub formă de modele în
baze de date. Aici pot fi combinate tehnicile de căutare cu tehnici mai
directe de genul celor menționate mai sus.

3. Algoritmul minimax

Minimax este un algoritm de căutare limitată în adâncime (depth-


first, depth-limited). Se pleacă de la poziția curentă și se generează o mulțime
de poziții următoare posibile. În acest scop, structura de date cel mai des
folosită este arborele.
Se presupune că jocul este de sumă nulă, deci se poate folosi o
singură funcție de evaluare pentru ambii jucători. Dacă f(n) > 0, poziția n
este bună pentru calculator și rea pentru om, iar dacă f(n) < 0, poziția n este
rea pentru calculator și bună pentru om. Funcția de evaluare este stabilită de
proiectantul aplicației.

96
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Pentru aplicarea algoritmului, se selectează o limită de adâncime și o
funcție de evaluare. Se construiește arborele până la limita de adâncime. Se
calculează funcția de evaluare pentru frunze și se propagă evaluarea în sus,
selectând minimele pe nivelul minimizant (decizia omului) și maximele pe
nivelul maximizant (decizia calculatorului).
Să considerăm mai întâi căutarea pe un singur nivel (engl. ply, pliu,
cută, strat; în teoria jocurilor, această denumire sugerează numărul de
niveluri în arbore care se studiază):

B C D

8 3 -1

Primul nivel este un nivel maximizant, de aceea valoarea rădăcinii


arborelui este maximul dintre valorile fiilor. Aici, valoarea lui A este 8,
deoarece valoarea lui B (8) este valoarea maximă.
Algoritmul minimax presupune căutarea alternativă pe niveluri
maximizante și niveluri minimizante. Dacă vom cerceta două niveluri,
primul nivel va fi maximizant, iar al doilea minimizant.
Căutarea pe două niveluri este de forma:

-2 A MAX

-6 B -2 C -4 D MIN

E F G H I J

9 -6 0 -2 -4 -3

97
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În nodul B, vom avea valoarea minimă a fiilor săi (–6). La fel în C și
D. În A, vom prelua valoarea maximă dintre B, C și D, adică –2.
Aceste valori numerice sunt date de funcția de evaluare statică.
Deoarece această estimare poate fi imprecisă, e importantă căutarea pe cât
mai multe niveluri, pentru a crește numărul de posibilități avute în vedere, și
deci și șansele de a determina mutarea optimă.
Pseudocodul algoritmului minimax este următorul:

JOC =
{
Stare-curentă : STARE
» starea în care trebuie să mute jucătorul care utilizează algoritmul
Acțiuni(s : STARE) : ACȚIUNE*
» o funcție care întoarce lista de acțiuni (mutări) care se pot efectua în starea s
Stare-succesoare(s : STARE, a : ACȚIUNE) : STARE
» o funcție care întoarce starea în care se ajunge aplicând acțiunea a în starea s
Este-terminală(s : STARE) : BOOLEAN
» o funcție care verifică dacă starea s este o stare terminală (câștig, pierdere sau
» remiză)
Evaluare(s : STARE) : NUMĂR-REAL
» funcția de evaluare statică, care calculează valoarea poziției s
» joc de sumă nulă cu doi jucători: cu cât valoarea funcției este mai mare,
» cu atât poziția este mai bună pentru jucătorul care utilizează algoritmul
» și mai rea pentru adversar
}

MINIMAX
intrări: joc : JOC
stare : STARE » în apelul inițial: joc.Stare-curentă
este-nivel-maximizant : BOOLEAN » în apelul inițial: Adevărat
adâncime-curentă : NUMĂR-NATURAL » în apelul inițial: 0
adâncime-maximă : NUMĂR-NATURAL
ieșiri: acțiune : ACȚIUNE
valoare : NUMĂR-REAL
------------------------------------------------------------------------------------------------------------------------
dacă adâncime-curentă ≥ adâncime-maximă sau joc.Este-terminală(stare) atunci
întoarce (Nul, joc.Evaluare(stare))

pentru-fiecare acțiune din joc.Acțiuni(stare) execută


(_, valori[acțiune]) ← MINIMAX(joc, joc.Stare-succesoare(stare, acțiune),
nu este-nivel-maximizant, adâncime-curentă + 1, adâncime-maximă))
» acțiunea contează doar în rădăcină
» pentru celelalte niveluri contează doar valorile
» nivelul următor este de tip opus celui curent (maximizant, minimizant)

98
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
dacă este-nivel-maximizant atunci
întoarce (joc.Acțiuni(stare)[valori.Argmax()], valori.Max()) » acțiunea cu valoare maximă
altfel
întoarce (joc.Acțiuni(stare)[valori.Argmin()], valori.Min()) » acțiunea cu valoare minimă

4. Retezarea alfa-beta

Eficiența procedurilor depth-first poate fi mărită prin tehnici branch-


and-bound, în care pot fi abandonate mai repede soluțiile parțiale care sunt
în mod evident inferioare altor soluții deja cunoscute. După cum minimax
necesită gestionarea straturilor minimizante și maximizante, și strategia
branch-and-bound trebuie modificată pentru a include două limite, una
pentru fiecare jucător. Această strategie modificată se numește retezare alfa-
beta (alpha-beta pruning). Ea necesită menținerea a două valori prag, una
reprezentând limita inferioară a valorii unui nod care realizează o
maximizare (alfa), iar cealaltă limita superioară a valorii unui nod care
realizează o minimizare (beta).
Retezarea alfa-beta este o optimizare a algoritmului minimax, care
întrețese generarea arborelui cu propagarea valorilor. Unele valori obținute
în arbore furnizează informații privind redundanța altor părți, care nu mai
trebuie generate. În acest caz, se generează arborele în adâncime, de la
stânga la dreapta și se propagă valorile finale ale nodurilor ca estimări
inițiale pentru părinții lor. De fiecare dată când alfa ≥ beta, se oprește
generarea nodurilor descendente.
Pseudocodul algoritmului este următorul:

MINIMAX-CU-ALFA-BETA
intrări: joc : JOC
adâncime-maximă : NUMĂR-NATURAL
ieșiri: acțiune : ACȚIUNE
------------------------------------------------------------------------------------------------------------------------
(acțiune, _) ← VALOARE-MAXIMĂ(joc, joc. Stare-curentă, alfa ← –∞, beta ← ∞, adâncime-curentă ← 0,
adâncime-maximă)
întoarce acțiune

VALOARE-MAXIMĂ
intrări: joc : JOC
stare : STARE
alfa : NUMĂR-REAL
beta : NUMĂR-REAL

99
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
adâncime-curentă : NUMĂR-NATURAL
adâncime-maximă : NUMĂR-NATURAL
ieșiri: acțiune : ACȚIUNE
valoare : NUMĂR-REAL
------------------------------------------------------------------------------------------------------------------------
dacă adâncime-curentă ≥ adâncime-maximă sau joc.Este-terminală(stare) atunci
întoarce joc.Evaluare(stare)

val-max ← –∞, acțiune-optimă ← Nul


pentru-fiecare acțiune din joc.Acțiuni(stare) execută
(act, val) ← VALOARE-MINIMĂ(joc, joc.Stare-succesoare(stare, acțiune), alfa, beta,
adâncime-curentă + 1, adâncime-maximă)
dacă val-max < val atunci
val-max ← val, acțiune-optimă ← act
dacă val-max ≥ beta atunci » alfa ≥ beta, nu se mai încearcă celelalte acțiuni
întoarce (acțiune-optimă, val-max)
alfa ← Max(val-max, alfa)
întoarce (acțiune-optimă, val-max)

VALOARE-MINIMĂ
intrări: joc : JOC
stare : STARE
alfa : NUMĂR-REAL
beta : NUMĂR-REAL
adâncime-curentă : NUMĂR-NATURAL
adâncime-maximă : NUMĂR-NATURAL
ieșiri: acțiune : ACȚIUNE
valoare : NUMĂR-REAL
------------------------------------------------------------------------------------------------------------------------
dacă adâncime-curentă ≥ adâncime-maximă sau joc.Este-terminală(stare) atunci
întoarce joc.Evaluare(stare)

val-min ← ∞, acțiune-optimă ← Nul


pentru-fiecare acțiune din joc.Acțiuni(stare) execută
(act, val) ← VALOARE-MAXIMĂ (joc, joc.Stare-succesoare(stare, acțiune), alfa, beta,
adâncime-curentă + 1, adâncime-maximă)
dacă val-min > val atunci
val-min ← val, acțiune-optimă ← act
dacă alfa ≥ val-min atunci » alfa ≥ beta, nu se mai încearcă celelalte acțiuni
întoarce (acțiune-optimă, val-min)
beta ← Min(val-min, beta)
întoarce (acțiune-optimă, val-min)

În exemplul următor, după ce examinăm nodul F, știm că adversarul


(omul) are garantat un câștig de –5 sau mai puțin în C, de vreme ce
adversarul este jucătorul minimizant. Dar mai știm de asemenea că noi
(calculatorul) avem garantat un câștig de 3 sau mai mult în nodul A, care
poate fi obținut dacă mutăm în B. Orice altă mutare care produce un câștig
mai mic de 3 este mai proastă decât mutarea în B și poate fi ignorată. Deci,
100
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
după ce am examinat doar nodul F, suntem siguri că mutarea în C este
greșită (va fi mai mică sau egală cu –5), indiferent de câștigul în G. De
aceea nu mai avem nevoie să explorăm nodul G.

3 A MAX

3 B  -5 C MIN

D E F G

3 5 -5

Această metodă este utilă atunci când arborele de căutare este


explorat pe mai multe niveluri. În acest caz, eliminând un nod pe un anumit
nivel, eliminăm tot subarborele corespunzător acelui nod desfășurat pe
nivelurile inferioare.
În exemplul următor, se prezintă o situație mai compexă, în care
praguri alfa și beta nu sunt folosite doar pentru niveluri succesive în arbore.
Trebuie remarcat în primul rând faptul că factorul de ramificare nu este
constant, deoarece numărul de mutări posibile din configurații diferite nu
este constant.

101
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3 A MAX

3 B 2 C MIN

D E F G H MAX

3 5 (4)

0 I J M N MIN

2 (7) (8)

K L

0 (7)

Dacă explorăm tot subarborele cu rădăcina B, descoperim că în A ne


putem aștepta la un câștig de minim 3. Când această valoare alfa este pasată
în jos către F, ea ne va permite să evităm explorarea lui L. După ce K este
examinat, observăm că în I este garantat un câștig de 0, ceea ce înseamnă că
în F este garantat un minim de 0. Dar această valoare este mai mică decât
valoarea alfa = 3, astfel încât nu mai trebuie să evaluăm alte ramificații
pornind din I. Jucătorul maximizant deja știe că nu trebuie să aleagă mutarea
în C și apoi în I, de vreme ce prin această mutare câștigul rezultant nu va fi
mai mare de 0 și un câștig de 3 se poate obține mutând în B. Apoi este
examinat J, ceea ce determină o valoare de 2, care îi este atribuită lui F, de
vreme ce este maximul dintre 0, în I, și 2, în J. Această valoare devine
pragul beta în nodul C, indicând faptul că în C este garantat un câștig mai
mic sau egal cu 2. În acest moment, alfa =3 și beta = 2, se îndeplinește din
nou condiția alfa ≥ beta și în consecință, nu mai este necesară explorarea
nodurilor G și H.
Nodurile care nu mai sunt expandate au valoarea funcției de evaluare
notată între paranteze.

102
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5. Aplicație

Realizați o variantă simplificată a unui joc de dame, cu următoarele


reguli:

 există doi jucători;


 piesele sunt dispuse pe o tablă de 4x4 careuri;
 configurația (tabla) inițială este:

 fiecare jucător are ca scop să ajungă pe ultima linie cu toate piesele


(în acest caz, a câștigat omul):

103
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
 mutări posibile: fiecare piesă poate fi mutată în orice direcție, într-un
careu liber adiacent.

5.1. Implementați căutarea minimax cu cel puțin două niveluri (două


mutări pentru fiecare jucător).

5.2. Adăugați retezarea alfa-beta pentru optimizarea căutării. Ce se


poate spune despre timpul necesar calculatorului să determine o mutare?

Indicații. Se dă un prototip de aplicație, în care sunt implementate


toate clasele necesare pentru rezolvare și interfața grafică cu utilizatorul.
Diagrama UML de clase a aplicației complete este următoarea:

104
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Trebuie implementate doar următoarele metode:

Metoda EvaluationFunction() din clasa Board


Calculează funcția de evaluare statică pentru configurația (tabla)
curentă. Pentru un joc oarecare, programatorul trebuie să imagineze funcția
de evaluare. Pot exista mai multe funcții posibile pentru un joc. Cu cât
funcția aceasta este mai „inteligentă”, cu atât calculatorul va juca mai bine.
De exemplu, în cazul nostru, o funcție de evaluare poate lua în calcul
diferență dintre cât au avansat piesele calculatorului și cât au avansat piesele
omului. Cu cât funcția este mai mare, cu atât poziția este mai bună pentru
calculator, adică aceasta este mai aproape de configurația câștigătoare.
În program, coordonatele x și y ale pieselor sunt considerate astfel:

105
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3
2
1
0
0 1 2 3

Să presupunem că alegem funcția:

F   dyc   dyo ,
c o

unde dyc reprezintă avansul pieselor calculatorului, iar dyo, ale omului.
Funcția va fi 0 când niciun jucător nu are vreun avantaj, de exemplu:

3
2 C C O O
1 O O C C
0
0 1 2 3

Aici, primele două piese ale calculatorului au avansat o căsută pe axa


y, de la 3 la 2, iar ultimele două au avansat două căsuțe, de la 3 la 1. Analog
pentru om, de la 0 la 1, respectiv de la 0 la 2:

F = (1+1+2+2) – (1+1+2+2) = 0.

Funcția va fi mare când calculatorul are un avantaj, de exemplu:

3
2
1 O O O O
0 C C C C
0 1 2 3

F = (3+3+3+3) – (1+1+1+1) = 8.

106
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Funcția va fi mică când omul are un avantaj, de exemplu:

3 O O O O
2 C C C C
1
0
0 1 2 3

F = (1+1+1+1) – (3+3+3+3) = –8.

Se poate verifica faptul că această funcție poate fi scrisă mai


compact, luând în calcul direct coordonatele y ale pieselor și nu cât au
avansat acestea:

F  12   y c   y o ,
c o

unde yc reprezintă coordonatele y ale pieselor calculatorului, iar yo, ale


omului.
Am putea alege și o funcție mai simplă, care să ia în considerare
numai piesele calculatorului, însă în acest caz calculatorul ar încerca doar să
avanseze și nu ar putea încerca să blocheze avansarea omului. De asemenea,
se pot imagina și funcții mai complexe, care să ia în calcul pozițiile libere de
pe linia scop, pozițiile relative ale pieselor adversarului etc.

Metoda ValidMoves(Board currentBoard) din clasa Piece


Returnează lista tuturor mutărilor permise pentru piesa curentă în
configurația currentBoard.
Folosește metoda IsValidMove.

Metoda IsValidMove(Board currentBoard, Move move) din clasa


Piece
Testează dacă o mutare este permisă în configurația curentă. O
mutare este invalidă dacă: nu mută nicio piesă, nu mută într-un careu
adiacent ci sare peste mai multe careuri, mută în afara tablei sau mută într-
107
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
un careu ocupat de altă piesă. Această metodă validează atât mutările
calculatorului (în metoda ValidMoves din clasa Piece) cât și mutările omului
(în evenimentul pictureBoxBoard_MouseUp din clasa MainForm).

Metoda FindNextBoard(Board currentBoard) din clasa Minimax


Implementează algoritmul minimax cu retezarea alfa-beta. Primul
nivel corespunde mutării calculatorului, deci este maximizant. Transformă
mutările valide ale tuturor pieselor din currentBoard într-o listă de
configurații, folosind metoda MakeMove din clasa Board. Apoi selectează
din această listă configurația cu funcția de evaluare statică maximă. Dacă
există mai multe configurații cu funcția de evaluare maximă, se alege
aleatoriu una dintre ele. Căutarea pe arbore se face recursiv.

MainForm.cs

using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace SimpleCheckers
{
public partial class MainForm : Form
{
private Board _board;
private int _selected; // indexul piesei selectate
private PlayerType _currentPlayer; // om sau calculator
private Bitmap _boardImage;

public MainForm()
{
InitializeComponent();
try
{
_boardImage = (Bitmap)Image.FromFile("board.png");
}
catch
{
MessageBox.Show("Nu se poate incarca board.png");
Environment.Exit(1);

108
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}

_board = new Board();


_currentPlayer = PlayerType.None;
_selected = -1; // nicio piesa selectata

this.ClientSize = new System.Drawing.Size(927, 600);


this.pictureBoxBoard.Size = new System.Drawing.Size(500, 500);

pictureBoxBoard.Refresh();
}

private void pictureBoxBoard_Paint(object sender, PaintEventArgs e)


{
Bitmap board = new Bitmap(_boardImage);
e.Graphics.DrawImage(board, 0, 0);

if (_board == null)
return;

int dy = 500 - 125 + 12;


SolidBrush transparentRed = new SolidBrush(Color.FromArgb(192, 255, 0, 0));
SolidBrush transparentGreen = new SolidBrush(Color.FromArgb(192, 0, 128, 0));
SolidBrush transparentYellow = new SolidBrush(Color.FromArgb(192, 255, 255, 0));

foreach (Piece p in _board.Pieces)


{
SolidBrush brush = transparentRed;
if (p.Player == PlayerType.Human)
{
if (p.Id == _selected)
brush = transparentYellow;
else
brush = transparentGreen;
}

e.Graphics.FillEllipse(brush, 12 + p.X * 125, dy - p.Y * 125, 100, 100);


}
}

private void pictureBoxBoard_MouseUp(object sender, MouseEventArgs e)


{
if (_currentPlayer != PlayerType.Human)
return;

int mouseX = e.X / 125;


int mouseY = 3 - e.Y / 125;

109
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
if (_selected == -1)
{
foreach (Piece p in _board.Pieces.Where(a => a.Player == PlayerType.Human))
{
if (p.X == mouseX && p.Y == mouseY)
{
_selected = p.Id;
pictureBoxBoard.Refresh();
break;
}
}
}
else
{
Piece selectedPiece = _board.Pieces[_selected];

if (selectedPiece.X == mouseX && selectedPiece.Y == mouseY)


{
_selected = -1;
pictureBoxBoard.Refresh();
}
else
{
Move m = new Move(_selected, mouseX, mouseY);
if (selectedPiece.IsValidMove(_board, m))
{
_selected = -1;
Board b = _board.MakeMove(m);
AnimateTransition(_board, b);
_board = b;
pictureBoxBoard.Refresh();
_currentPlayer = PlayerType.Computer;

CheckFinish();

if (_currentPlayer == PlayerType.Computer) // jocul nu s-a terminat


ComputerMove();
}
}
}
}

private void ComputerMove()


{
Board nextBoard = Minimax.FindNextBoard(_board);
AnimateTransition(_board, nextBoard);
_board = nextBoard;

110
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
pictureBoxBoard.Refresh();

_currentPlayer = PlayerType.Human;

CheckFinish();
}

private void CheckFinish()


{
bool end; PlayerType winner;
_board.CheckFinish(out end, out winner);

if (end)
{
if (winner == PlayerType.Computer)
{
MessageBox.Show("Calculatorul a castigat!");
_currentPlayer = PlayerType.None;
}
else if (winner == PlayerType.Human)
{
MessageBox.Show("Ai castigat!");
_currentPlayer = PlayerType.None;
}
}
}

private void AnimateTransition(Board b1, Board b2)


{
Bitmap board = new Bitmap(_boardImage);
int dy = 500 - 125 + 12;
SolidBrush transparentRed = new SolidBrush(Color.FromArgb(192, 255, 0, 0));
SolidBrush transparentGreen = new SolidBrush(Color.FromArgb(192, 0, 128, 0));

Bitmap final = new Bitmap(500, 500);


Graphics g = Graphics.FromImage(final);

int noSteps = 50;

for (int j = 1; j < noSteps; j++)


{
g.DrawImage(board, 0, 0);

for (int i = 0; i < b1.Pieces.Count; i++)


{
double avx = (j * b2.Pieces[i].X + (noSteps - j) * b1.Pieces[i].X) / (double)noSteps;
double avy = (j * b2.Pieces[i].Y + (noSteps - j) * b1.Pieces[i].Y) / (double)noSteps;

111
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
SolidBrush brush = transparentRed;
if (b1.Pieces[i].Player == PlayerType.Human)
brush = transparentGreen;

g.FillEllipse(brush, (int)(12 + avx * 125), (int)(dy - avy * 125), 100, 100);


}

Graphics pbg = pictureBoxBoard.CreateGraphics();


pbg.DrawImage(final, 0, 0);
}
}

private void jocNouToolStripMenuItem_Click(object sender, System.EventArgs e)


{
_board = new Board();
_currentPlayer = PlayerType.Computer;
ComputerMove();
}

private void despreToolStripMenuItem_Click(object sender, EventArgs e)


{
const string copyright =
"Algoritmul minimax\r\n" +
"Inteligenta artificiala, Laboratorul 8\r\n" +
"(c)2016-2020 Florin Leon\r\n" +
"http://florinleon.byethost24.com/lab_ia.htm";

MessageBox.Show(copyright, "Despre jocul Dame simple");


}

private void iesireToolStripMenuItem_Click(object sender, System.EventArgs e)


{
Environment.Exit(0);
}
}
}

Minimax.cs

using System;

namespace SimpleCheckers
{

/// <summary>

112
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// Implementeaza algoritmul de cautare a mutarii optime
/// </summary>
public class Minimax
{
private static Random _rand = new Random();
/// <summary>
/// Primeste o configuratie ca parametru, cauta mutarea optima si returneaza configuratia
/// care rezulta prin aplicarea acestei mutari optime
/// </summary>
public static Board FindNextBoard(Board currentBoard)
{
throw new Exception("Aceasta metoda trebuie implementata");
}
}
}

Board.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleCheckers
{
/// <summary>
/// Reprezinta o configuratie a jocului (o tabla de joc) la un moment dat
/// </summary>
public class Board
{
public int Size { get; set; } // dimensiunea tablei de joc
public List<Piece> Pieces { get; set; } // lista de piese, atat ale omului cat si ale calculatorului
public Board()
{
Size = 4;
Pieces = new List<Piece>(Size * 2);

for (int i = 0; i < Size; i++)


Pieces.Add(new Piece(i, Size - 1, i, PlayerType.Computer));

for (int i = 0; i < Size; i++)


Pieces.Add(new Piece(i, 0, i + Size, PlayerType.Human));
}

113
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public Board(Board b)
{
Size = b.Size;
Pieces = new List<Piece>(Size * 2);

foreach (Piece p in b.Pieces)


Pieces.Add(new Piece(p.X, p.Y, p.Id, p.Player));
}

/// <summary>
/// Calculeaza functia de evaluare statica pentru configuratia (tabla) curenta
/// </summary>
public double EvaluationFunction()
{
throw new Exception("Aceasta metoda trebuie implementata");
}

/// <summary>
/// Creeaza o noua configuratie aplicand mutarea primita ca parametru in configuratia curenta
/// </summary>
public Board MakeMove(Move move)
{
Board nextBoard = new Board(this); // copy
nextBoard.Pieces[move.PieceId].X = move.NewX;
nextBoard.Pieces[move.PieceId].Y = move.NewY;
return nextBoard;
}

/// <summary>
/// Verifica daca configuratia curenta este castigatoare
/// </summary>
/// <param name="finished">Este true daca cineva a castigat si false altfel</param>
/// <param name="winner">Cine a castigat: omul sau calculatorul</param>
public void CheckFinish(out bool finished, out PlayerType winner)
{
if (Pieces.Where(p => p.Player == PlayerType.Human && p.Y == Size - 1).Count() == Size)
{
finished = true;
winner = PlayerType.Human;
return;
}

if (Pieces.Where(p => p.Player == PlayerType.Computer && p.Y == 0).Count() == Size)


{
finished = true;
winner = PlayerType.Computer;
return;

114
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}

finished = false;
winner = PlayerType.None;
}
}
}

Piece.cs

using System;
using System.Collections.Generic;

namespace SimpleCheckers
{
public enum PlayerType { None, Computer, Human };

/// <summary>
/// Reprezinta o piesa de joc
/// </summary>
public class Piece
{
public int Id { get; set; } // identificatorul piesei
public int X { get; set; } // pozitia X pe tabla de joc
public int Y { get; set; } // pozitia Y pe tabla de joc
public PlayerType Player { get; set; } // carui tip de jucator apartine piesa (om sau calculator)

public Piece(int x, int y, int id, PlayerType player)


{
X = x;
Y = y;
Id = id;
Player = player;
}

/// <summary>
/// Returneaza lista tuturor mutarilor permise pentru piesa curenta (this)
/// in configuratia (tabla de joc) primita ca parametru
/// </summary>
public List<Move> ValidMoves(Board currentBoard)
{
throw new Exception("Aceasta metoda trebuie implementata");
}

115
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Testeaza daca o mutare este valida intr-o anumita configuratie
/// </summary>
public bool IsValidMove(Board currentBoard, Move move)
{
throw new Exception("Aceasta metoda trebuie implementata");
}
}
}

Move.cs

namespace SimpleCheckers
{
/// <summary>
/// Reprezinta mutarea unei singure piese
/// </summary>
public class Move
{
public int PieceId { get; set; } // id-ul piesei mutate
public int NewX { get; set; } // noua pozitie X
public int NewY { get; set; } // noua pozitie Y

public Move(int pieceId, int newX, int newY)


{
PieceId = pieceId;
NewX = newX;
NewY = newY;
}
}
}

116
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 8

Algoritmi evolutivi

1. Obiective

Obiectivele acestui capitol sunt următoarele:

 prezentarea structurii generale a unui algoritm evolutiv, împreună cu


operatorii specifici;
 rezolvarea a două probleme de optimizare bazate pe valori reale,
folosind același algoritm.

2. Structura unui algoritm evolutiv

Un algoritm evolutiv este o metodă de optimizare care funcționează


prin analogie cu evoluția biologică. Pentru găsirea soluției, se utilizează o
populație de soluții potențiale, care evoluează prin aplicarea iterativă a unor
operatori stohastici. Elementele populației reprezintă soluții potențiale ale
problemei.
Pentru a ghida căutarea către soluția problemei, asupra populației se
aplică transformări specifice selecției naturale:

 Selecția. Determină părinții care se vor reproduce pentru a forma


următoarea generație. Indivizii mai adaptați din populație, care se
apropie mai mult de soluția problemei, sunt favorizați, în sensul ca
au mai multe șanse de reproducere;
 Încrucișarea. Pornind de la doi părinți, se generează copíi;
 Mutația. Pentru a asigura diversitatea populației, se aplică
transformări cu un caracter aleatoriu asupra copiilor nou generați,

117
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
permițând apariția unor trăsături care nu ar fi apărut în cadrul
populației doar prin selecție și încrucișare.

Figura 8.1. Structura generală a unui algoritm evolutiv

Structura generală a unui astfel de algoritm este cea din figura 8.1.
Codificarea diferă de la problemă la problemă, cele mai des folosite
metode fiind codificările binară și reală. Condiția de stop se poate referi la
evoluția pentru un anumit număr de generații sau la proprietățile populației
curente, de exemplu convergența populației către o anumită valoare,
pierderea diversității indivizilor din populație etc.
Dimensiunea populației poate fi în jur de 50. Pentru probleme
simple poate fi mai mic (30), iar pentru probleme dificile poate fi mai mare
(100). Dacă sunt prea puțini cromozomi, algoritmul nu are diversitatea
necesară găsirii soluției. Dacă sunt prea mulți, algoritmul va fi mai lent, fără
a se îmbunătăți însă calitatea soluției.
Numărul maxim de generații variază de obicei între 100-1000, în
funcție de dificultatea problemei.

118
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3. Tipuri de codificare

Modul în care o posibilă soluție este codificată într-un cromozom


depinde de problema concretă care trebuie rezolvată.

3.1. Codificarea binară

Este varianta clasică a algoritmilor genetici. În acest caz,


cromozomii sunt șiruri de n biți, n fiind numărul de gene. Este adecvată
pentru problemele de optimizare combinatorială în care configurațiile pot fi
specificate ca vectori binari.

Exemplul 3.1.1. Problema rucsacului


Se consideră o mulțime de n obiecte caracterizate prin greutățile
w1, ... , wn și prin valorile v1, ... , vn. Se pune problema determinării unei
submulțimi de obiecte pentru a fi introduse într-un rucsac de capacitate C
astfel încât valoarea obiectelor selectate să fie maximă. O soluție a acestei
probleme poate fi codificată ca un șir de n valori binare în felul următor:
si = 1 dacă obiectul i este selectat, respectiv si = 0 dacă obiectul nu este
selectat.
Pentru 5 obiecte posibile de introdus în rucsac, o genă reprezintă un
bit (xi), în timp ce cromozomul este o soluție potențială (de exemplu, 01101
înseamnă că vor fi incluse articolele 2, 3 și 5).
Funcția de adaptare (fitness) arată cât de bună este o soluție, cât este
de adaptat un cromozom.
De exemplu, presupunem că la un moment dat avem combinația:

i 1 2 3 4 5
wi 70 55 40 15 5
vi 40 15 5 30 10

Atunci, pentru cromozomul 01101, constrângerea este satisfăcută:


w = 55 + 40 + 5 = 100 ≤ C = 100, iar funcția de adaptare este: F = 15 + 5 +
10 = 30.

119
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Exemplul 3.1.2. Problema împachetării
Se consideră un set de n obiecte caracterizate prin dimensiunile
d1, d2, ..., dn și o mulțime de m cutii având capacitățile c1, c2, ..., cm . Se pune
problema plasării obiectelor în cutii astfel încât capacitatea acestora să nu
fie depășită, iar numărul de cutii utilizate să fie cât mai mic. O posibilă
reprezentarea binară pentru această problemă este următoarea: se utilizează
o matrice cu n linii și m coloane iar elementul Mij are valoarea 1 dacă
obiectul i este plasat în cutia j și 0 în caz contrar. Prin liniarizarea matricii se
ajunge ca fiecare soluție potențială să fie reprezentată printr-un cromozom
cu m · n gene.

3.2. Codificarea reală

Este adecvată pentru problemele de optimizare pe domenii continue.


În acest caz, cromozomii sunt vectori cu elemente reale din domeniul de
definiție al funcției. Avantajul acestei reprezentări este faptul că este
naturală și nu necesită proceduri speciale de codificare/decodificare.

Exemplul 3.2.1. Funcția Rastrigin generalizată


Presupune minimizarea funcției următoare:

n
f (x)  n  A   xi2  A cos(  xi )
i 1

Pentru A = 10 și  = 2, funcția are minimul f(0) = 0.


Domeniul variabilelor este: –5,12 < xi < 5,12.
În acest caz, cromozomii vor fi vectori cu n elemente din acest
interval.

3.3. Construirea funcției de adaptare

Funcția de adaptare definește problema de optimizare. În formularea


standard, scopul algoritmilor evolutivi este maximizarea acestei funcții.

120
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Orice problemă de minimizare poate fi transformată într-una de
maximizare prin schimbarea semnului funcției obiectiv. Pentru o problemă
de minimizare în care dorim minimizarea unei funcții g(x), funcția de
adaptare va fi F(x) = –g(x).

4. Pseudocodul general
PROBLEMĂ =
{
Funcție-de-adaptare(x : NUMĂR-REAL*) : NUMĂR-REAL
» funcția care definește problema de optimizare (funcția de fitness)
Număr-gene : NUMĂR-NATURAL
» dimeniunea problemei, numărul de parametri de intrare ai funcției de adaptare
Domeniu(i : NUMĂR-NATURAL) : NUMĂR-REAL*
» această definiție este pentru codare reală, pentru codare binară domeniul este {0,1}
» întoarce o listă cu 2 elemente: domeniul genei cu indexul i,
» adică al parametrului de intrare xi al funcției de adaptare: [xi;min, xi;max]
}

PARAMETRI =
{
Număr-cromozomi : NUMĂR-NATURAL
» dimensiunea populației
Probabilitate-încrucișare : NUMĂR-REAL([0, 1])
» probabilitatea (rata) de încrucișare (crossover)
Probabilitate-mutație : NUMĂR-REAL([0, 1])
» probabilitatea (rata) de mutație
Număr-maxim-generații : NUMĂR-NATURAL
» criteriu de terminare
}

CROMOZOM =
{
Valori : NUMĂR-REAL*
» o listă de intrări pentru funcția de adaptare de dimensiune Număr-gene
Adaptare : NUMĂR-REAL
» valoarea funcției de adaptare pentru cromozomul curent
}

ALGORITM-EVOLUTIV
intrări: problemă : PROBLEMĂ
parametri : PARAMETRI
ieșiri: soluție : CROMOZOM » individul care maximizează funcția de adaptare
------------------------------------------------------------------------------------------------------------------------
populație ← INIȚIALIZARE(problemă, parametri) » listă de cromozomi
populație ← CALCUL-ADAPTARE(populație, problemă)

121
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
repetă de parametri.Număr-maxim-generații ori
populație-nouă ← [ ]
părinți ← SELECȚIE(populație, problemă) » listă de dimensiune 2 * parametri.Număr-cromozomi

repetă de parametri.Număr-cromozomi ori


(p1, p2) ← primele două elemente din lista părinți
părinți.Elimină(p1, p2)
(c1, c2) ← ÎNCRUCIȘARE(p1, p2, parametri.Probabilitate-încrucișare) » copiii
c1 ← MUTAȚIE(c1, parametri.Probabilitate-mutație)
c2 ← MUTAȚIE(c2, parametri.Probabilitate-mutație)
populație-nouă.Adaugă(c1, c2) » adaugă copiii în populația nouă

populație-nouă ← CALCUL-ADAPTARE(populație-nouă, problemă)


populație ← populație-nouă

soluție ← CEL-MAI-ADAPTAT(populație)
întoarce soluție

INIȚIALIZARE
intrări: problemă : PROBLEMĂ
parametri : PARAMETRI
ieșiri: populație : CROMOZOM*
------------------------------------------------------------------------------------------------------------------------
pentru-fiecare cromozom din [0 .. parametri.Număr-cromozomi]
pentru-fiecare genă din [0 .. problemă.Număr-gene]
(xmin, xmax) ← problemă.Domeniu(genă)
populație[cromozom].Valori[genă] ← Număr-aleatoriu-uniform(xmin, xmax)
întoarce populație

CALCUL-ADAPTARE
intrări: populație : CROMOZOM*
problemă : PROBLEMĂ
ieșiri: populație : CROMOZOM*
------------------------------------------------------------------------------------------------------------------------
pentru-fiecare cromozom din populație
cromozom.Adaptare ← problemă.Funcție-de-adaptare(cromozom.Valori)
întoarce populație

CEL-MAI-ADAPTAT
intrări: populație : CROMOZOM*
ieșiri: soluție : CROMOZOM
------------------------------------------------------------------------------------------------------------------------
întoarce populație[populație.Argmax(cr : cr.Adaptare)]
» cromozomul cu funcția de adaptare maximă

122
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5. Operatori genetici

5.1. Selecția

Selecția are ca scop determinarea părinților care vor genera


elementele populației din următoarea generație. Criteriul de selecție se
bazează pe valoarea funcției de adaptare. Procesul de selecție nu depinde de
modul de codificare a elementelor populației (binar, real etc.).

5.1.1. Selecția de tip ruletă

Ideea de bază a acestui tip de selecție este că probabilitatea unui


individ de a fi selectat este proporțională cu funcția sa de adaptare. Ruleta se
învârte de n ori pentru a se alege n indivizi.

Figura 8.2. Selecția de tip ruletă

SELECȚIE-RULETĂ
intrări: populație : CROMOZOM*
ieșiri: părinți : CROMOZOM*
------------------------------------------------------------------------------------------------------------------------
valori-adaptare ← populație.Transformă(cr : cr.Adaptare)
» lista de valori ale funcțiilor de adaptare ale tuturor cromozomilor din populație
opțiuni ← EȘANTIONARE-PROPORȚIONALĂ(valori-adaptare, 2 * populație.Dimensiune)
părinți ← [ ]
pentru-fiecare o din opțiuni
părinți.Adaugă (populație[o])
întoarce părinți

EȘANTIONARE-PROPORȚIONALĂ
intrări: valori : NUMĂR-REAL-POZITIV*
n : NUMĂR-NATURAL » numărul de opțiuni dorite

123
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
ieșiri: opțiuni : NUMĂR-NATURAL* » indecșii opțiunilor selectate
------------------------------------------------------------------------------------------------------------------------
sumă ← valori.Sumă()

sume-parțiale[0] ← valori[0] » primul element


pentru-fiecare i din [1 .. valori.Dimensiune – 1] » restul elementelor
sume-parțiale[i] ← sume-parțiale[i – 1] + valori[i]

opțiuni ← [ ]
repetă de n ori
na ← Număr-aleatoriu-uniform(0, sumă)
is ← indexul primului element s ≥ na din sume-parțiale
opțiuni.Adaugă(is)
întoarce opțiuni

5.1.2. Selecția bazată pe ranguri

Se ordonează crescător valorile funcției de adaptare pentru toate


elementele populației. Se rețin valorile distincte și li se asociază câte un
rang: cea mai mică valoare are rangul 0 sau 1, iar cea mai mare are rangul
maxim, n sau n – 1, unde n este dimensiunea populației. Probabilitatea de
selecție este în acest caz proporțională cu rangul individului.

SELECȚIE-RANGURI
intrări: populație : CROMOZOM*
ieșiri: părinți : CROMOZOM*
------------------------------------------------------------------------------------------------------------------------
populație-sortată ← populație.Sortează-după(cr : cr.Adaptare, Crescător)
ranguri ← populație.Transformă(cr : populație-sortată.Index(cr))
» cromozomul cel mai adaptat va avea rangul (populație.Dimensiune – 1), iar
» cromozomul cel mai slab adaptat va avea rangul 0

opțiuni ← EȘANTIONARE-PROPORȚIONALĂ(ranguri, 2 * populație.Dimensiune)


părinți ← [ ]
pentru-fiecare o din opțiuni
părinți:Adaugă(populație[o])
întoarce părinți

124
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5.1.3. Selecția de tip competiție

Se aleg aleatoriu k membri din populație și se determină cel mai


adaptat dintre aceștia. De obicei, k = 2. Procedura se repetă pentru a selecta
mai mulți părinți. Această metodă este mai rapidă decât selecțiile prin ruletă
sau ranguri.

SELECȚIE-COMPETIȚIE
intrări: populație : CROMOZOM*
problemă : PROBLEMĂ
dimensiune-competiție : NUMĂR-ÎNTREG
ieșiri: părinți : CROMOZOM*
------------------------------------------------------------------------------------------------------------------------
părinți ← [ ]
repetă de (2 * populație.Dimensiune) ori
competiție ← [ ]
repetă de dimensiune-competiție ori
competiție.Adaugă(un element aleatoriu din populație care nu există în competiție)
» competiție conține doar cromozomi diferiți
părinți.Adaugă(CEL-MAI-ADAPTAT(competiție))
întoarce părinți

5.1.4. Elitismul

Cel mai adaptat individ este copiat direct în noua populație. Asigură
faptul că niciodată nu se pierde soluția cea mai bună.

populație-nouă ← [ CEL-MAI-ADAPTAT(populație) ]
repetă de (parametri.Număr-cromozomi – 1) ori
aplică SELECȚIE, ÎNCRUCIȘARE și MUTAȚIE pentru a genera restul cromozomilor din populație-nouă

5.2. Încrucișarea

Încrucișarea (crossover) permite combinarea informațiilor provenite


de la doi părinți pentru generarea unuia sau a doi urmași. Vom considera
doar cazul a doi părinți (notați cu x și y) care generează doi urmași (notați cu
x' și y' ). Încrucișarea (numită uneori și recombinare) depinde de modul de
codificare al datelor, aplicându-se direct asupra cromozomilor.

125
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5.2.1. Încrucișarea binară

Cea mai des folosită este încrucișarea cu un punct. Reamintim că


aici cromozomul este un șir de n biți. Se alege în mod aleatoriu un
k  { l , ..., n – 1 }, numit punct de tăietură, și se construiesc urmașii în modul
următor:

x' = ( x1, ..., xk, yk+1, ..., yn )


y' = ( y1 ... , yk, xk+1, ..., xn )

Figura 8.3. Încrucișarea binară cu un punct

ÎNCRUCIȘARE-BINARĂ
intrări: p1 : CROMOZOM » primul părinte
p2 : CROMOZOM » al doilea părinte
probabilitate : NUMĂR-REAL([0, 1]) » probabilitatea de încrucișare
ieșiri: c1 : CROMOZOM » primul copil
c2 : CROMOZOM » al doilea copil
------------------------------------------------------------------------------------------------------------------------
dacă probabilitate < Număr-aleatoriu-uniform(0, 1) atunci
» nu se face încrucișare, se copiază părinții
dacă Număr-aleatoriu-uniform(0, 1) < 0.5 atunci
întoarce (p1, p2)
altfel
întoarce (p2, p1)

punct-divizare ← Rotunjește(Număr-aleatoriu-uniform(1, p1.Valori.Dimensiune))


c1.Valori ← p1.Valori[ .. punct-divizare ] + p2.Valori[ punct-divizare + 1 .. ]
» concatenarea primelor elemente din p1.Valori, până la punct-divizare
» cu ultimele elemente din p2.Valori de la punct-divizare + 1
» valorile cromozomilor sunt biți
c2.Valori ← p2.Valori[ .. punct-divizare ] + p1.Valori[ punct-divizare + 1 .. ]
întoarce (c1, c2)

126
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5.2.2. Încrucișarea reală

Încrucișarea aritmetică intermediară combină materialul genetic al


părinților x și y (vectori de numere reale) aplicând următoarele reguli pentru
a obține copiii x' și y':

xi' = ai · xi + (1 – ai) · yi
yi' = (1 – ai) · xi + ai · yi

Vectorul a are aceeași lungime ca și cromozomii, iar elementele sale


sunt cuprinse în intervalul (0, 1) sau (–0,25, 1,25). În ultimul caz, urmașii se
vor găsi în interiorul unui hipercub puțin mai mare decât hipercubul
delimitat de valorile genelor părinților.
Încrucișarea aritmetică liniară combină materialul genetic al
părinților x și y, aplicând regulile:

xi' = a · xi + (1 – a) · yi
yi' = (1 – a) · xi + a · yi

În acest caz, a este un scalar ales aleatoriu din intervalul (0, 1) sau
(–0,25, 1,25). Astfel, copiii vor fi plasați pe un segment de dreaptă
determinat de genele părinților.

Figura 8.4. Încrucișarea reală aritmetică liniară

Încrucișarea se are loc cu o anumită probabilitate, numită


probabilitate de încrucișare și notată cu pc. Aceasta trebuie să fie în general
mare, de exemplu 0,9.
Din punct de vedere al implementării, se generează un număr
aleatoriu r între 0 și 1. Dacă r < pc, se aplică încrucișarea. Dacă nu, se
generează copii egali cu părinții.

127
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
ÎNCRUCIȘARE-REALĂ
intrări: p1 : CROMOZOM » primul părinte
p2 : CROMOZOM » al doilea părinte
probabilitate : NUMĂR-REAL([0, 1]) » probabilitatea de încrucișare
ieșiri: c1 : CROMOZOM » primul copil
c2 : CROMOZOM » al doilea copil
------------------------------------------------------------------------------------------------------------------------
dacă probabilitate < Număr-aleatoriu-uniform(0, 1) atunci
» nu se face încrucișare, se copiază părinții
dacă (Număr-aleatoriu-uniform(0, 1) < 0.5) atunci
întoarce (p1, p2)
altfel
întoarce (p2, p1)

a ← Număr-aleatoriu-uniform(0, 1)
c1.Valori ← a * p1.Valori + (1 – a) * p2.Valori
c2.Valori ← (1 – a) * p1.Valori + a * p2.Valori
» formulele de mai sus sunt vectoriale: calculele se realizează pentru fiecare dimensiune/genă
» în parte; se aplică încrucișarea aritmetică, valorile genelor sunt numere reale

întoarce (c1, c2)

5.3. Mutația

Mutația asigură modificarea valorilor unor gene pentru a evita


situațiile în care o anumită alelă (valoare posibilă a unei gene) nu apare în
populație deoarece nu a fost generată de la început. În felul acesta poate fi
crescută diversitatea populației și se poate evita convergența într-un optim
local.
Mutația se efectuează asupra copiilor generați după aplicarea
operatorului de încrucișare.

5.3.1. Mutația binară

Cel mai simplu și des utilizat tip de mutație binară este cel în care se
selectează o genă iar valoarea acesteia este negată (0 devine 1 iar 1 devine
0) cu o anumită probabilitate, numită probabilitate de mutație și notată cu
pm. Aceasta trebuie să fie în general mică, de exemplu 0,02.

128
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
MUTAȚIE-BINARĂ
intrări: c : CROMOZOM » cromozomul (copilul) înainte de mutație
probabilitate : NUMĂR-REAL([0, 1]) » probabilitatea de mutație
ieșiri: c : CROMOZOM » cromozomul (copilul) după mutație
------------------------------------------------------------------------------------------------------------------------
pentru-fiecare genă din c.Valori.Dimensiune
dacă probabilitate ≥ Număr-aleatoriu-uniform(0, 1) atunci
c.Valori[genă] ← 1 – c.Valori[genă] » 0 devine 1 și 1 devine 0
întoarce c

5.3.2. Mutația reală

Pentru codificarea reală, se folosește de obicei resetarea valorii unei


gene la un număr aleatoriu din domeniul ei de definiție. Rata de mutație
reală poate fi mai mare decât la mutația binară, deoarece numărul valorilor
reale este mai mic decât numărul de biți, de exemplu 0,1.

MUTAȚIE-REALĂ
intrări: c : CROMOZOM » cromozomul (copilul) înainte de mutație
probabilitate : NUMĂR-REAL([0, 1]) » probabilitatea de mutație
problemă : PROBLEMĂ » parametru suplimentar față de varianta binară
ieșiri: c : CROMOZOM » cromozomul (copilul) după mutație
------------------------------------------------------------------------------------------------------------------------
pentru-fiecare genă din c.Valori.Dimensiune
dacă probabilitate ≥ Număr-aleatoriu-uniform(0, 1) atunci
(xmin, xmax) ← problemă.Domeniu(genă)
c.Valori[genă] ← Număr-aleatoriu-uniform(xmin, xmax)
întoarce c

6. Aplicații

6.1. Determinați soluția reală a ecuației: x5 – 5x + 5 = 0. (Soluția


este: x = –1,680494.)

Indicații. Trebuie să exprimăm cerința sub forma unei probleme de


optimizare, adică de găsire a maximului (sau minimului) unei funcții. Dacă
trebuie să determinăm x astfel încât f(x) = y, înseamnă că trebuie să
minimizăm diferența dintre f(x) și y: min |f(x) – y|. Minimul așteptat ar trebui
să fie 0. Pentru ecuația noastră, va trebui să determinăm: min |x5 – 5x + 5|.
Deoarece algoritmul evolutiv standard găsește maximul unei funcții, nu
129
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
minimul ei, trebuie să transformăm problema într-una de maximizare:
max –|x5 – 5x + 5|. Prin urmare, funcția de adaptare corespunzătoare
problemei este: F(x) = –|x5 – 5x + 5|.

6.2. Un fermier are 100 m de gard cu care vrea să împrejmuiască un


teren dreptunghiular care se învecinează cu un râu. Latura vecină cu râul nu
are nevoie de gard. Care sunt dimensiunile terenului cu arie maximă care
poate fi împrejmuit în acest fel?

Problema de optimizare formalizată este maximizarea ariei A = x · y,


respectând constrângerea 2 x + y = 100. (Soluția este: x = 25 și y = 50, deci
A = 1250).

Indicații. Aici funcția de adaptare este direct cea exprimată de


problemă: F(x, y) = x · y. Dificultatea apare din existența constrângerii.
Întrucât foarte puțini indivizi ar putea respecta constrângerea, o soluție
inteligentă este „repararea” acestora, adică forțarea respectării constrângerii.
De exemplu, un individ are genele x și y, care nu respectă constrângerea.
Genele sale se pot scala proporțional cu un factor r, păstrând informațiile
utile pentru căutare și diversitate genetică:

r = 100 / (2x + y)
x' = x · r
y' = y · r

130
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Astfel, 2x' + y' va fi întotdeauna 100 (constrângerea va fi satisfăcută
de toți indivizii), iar optimizarea se va face după funcția de adaptare
F(x', y') = x' · y'.

Se dă un prototip de aplicație, în care sunt implementate toate clasele


necesare pentru rezolvare. Diagrama UML de clase a aplicației complete
este următoarea:

Trebuie implementate doar următoarele metode:

Metoda Tournament(Chromosome[] population) din clasa Selection


Alege în mod aleatoriu doi indivizi/cromozomi diferiți din populație,
le compară valorile funcției de adaptare și îl returnează pe cel mai adaptat.

Metoda GetBest(Chromosome[] population) din clasa Selection


Returnează individul din populație cu funcția de adaptare maximă.

Metoda Arithmetic(Chromosome m, Chromosome f, double rate)


din clasa Crossover
Aplică încrucișarea artimetică între cei doi părinți, cu probabilitatea
rate.

Metoda Reset(Chromosome child, double rate) din clasa Mutation


Aplică asupra copilului child mutația prin resetare, cu probabilitatea
rate.

131
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Metoda Solve(...) din clasa EvolutionaryAlgorithm
Trebuie completată bucla principală, cu apelul operatorilor genetici.

Metoda ComputeFitness(Chromosome c) din clasa Equation


Funcția de adaptare pentru prima problemă.

Metoda ComputeFitness(Chromosome c) din clasa Fence


Funcția de adaptare pentru a doua problemă. Aici trebuie satisfăcută
și constrângerea. Fezabilitatea soluțiilor se poate asigura prin repararea
cromozomilor.

Metoda Main() din clasa Program


Pentru ambele probleme, trebuie să găsiți, prin încercări repetate,
valorile parametrilor algoritmului (dimensiunea populației, numărul maxim
de generații, ratele de încrucișare și mutație) care dau cele mai bune
rezultate.

Program.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Programul principal care apeleaza algoritmul
/// </summary>
public class Program
{
private static void Main(string[] args)
{
throw new Exception("Aceasta metoda trebuie completata");

EvolutionaryAlgorithm ea = new EvolutionaryAlgorithm();

// Chromosome solution = ea.Solve(new Equation(), ...);


// de completat parametrii algoritmului
// se foloseste -solution.Fitness pentru ca algoritmul evolutiv maximizeaza,
// iar aici avem o problema de minimizare
// Console.WriteLine("{0:F6} -> {1:F6}", solution.Genes[0], -solution.Fitness);
// solution = ea.Solve(new Fence(), ...); // de completat parametrii algoritmului

132
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
// Console.WriteLine("{0:F2} {1:F2} -> {2:F4}", solution.Genes[0], solution.Genes[1],
// solution.Fitness);
}
}
}

EvolutionaryAlgorithm.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Clasa care implementeaza algoritmul evolutiv pentru optimizare
/// </summary>
public class EvolutionaryAlgorithm
{
/// <summary>
/// Metoda de optimizare care gaseste solutia problemei
/// </summary>
public Chromosome Solve(IOptimizationProblem p, int populationSize,
int maxGenerations, double crossoverRate, double mutationRate)
{
throw new Exception("Aceasta metoda trebuie completata");

Chromosome[] population = new Chromosome[populationSize];


for (int i = 0; i < population.Length; i++)
{
population[i] = p.MakeChromosome();
p.ComputeFitness(population[i]);
}

for (int gen = 0; gen < maxGenerations; gen++)


{
Chromosome[] newPopulation = new Chromosome[populationSize];
newPopulation[0] = Selection.GetBest(population); // elitism

for (int i = 1; i < populationSize; i++)


{
// selectare 2 parinti: Selection.Tournament
// generarea unui copil prin aplicare crossover: Crossover.Arithmetic
// aplicare mutatie asupra copilului: Mutation.Reset
// calculare fitness pentru copil: ComputeFitness din problema p
// introducere copil in newPopulation
}

133
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
for (int i = 0; i < populationSize; i++)
population[i] = newPopulation[i];
}

return Selection.GetBest(population);
}
}
}

Selection.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Clasa care reprezinta operatia de selectie
/// </summary>
public class Selection
{
private static Random _rand = new Random();

public static Chromosome Tournament(Chromosome[] population)


{
throw new Exception("Aceasta metoda trebuie implementata");
}

public static Chromosome GetBest(Chromosome[] population)


{
throw new Exception("Aceasta metoda trebuie implementata");
}
}
}

Crossover.cs

using System;

namespace EvolutionaryAlgorithm
{

134
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Clasa care reprezinta operatia de incrucisare
/// </summary>
public class Crossover
{
private static Random _rand = new Random();

public static Chromosome Arithmetic(Chromosome mother, Chromosome father,


double rate)
{
throw new Exception("Aceasta metoda trebuie implementata");
}
}
}

Mutation.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Clasa care reprezinta operatia de mutatie
/// </summary>
public class Mutation
{
private static Random _rand = new Random();

public static void Reset(Chromosome child, double rate)


{
throw new Exception("Aceasta metoda trebuie implementata");
}
}
}

Chromosome.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Clasa care reprezinta un individ din populatie
/// </summary>

135
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public class Chromosome
{
public int NoGenes { get; set; } // numarul de gene ale individului

public double[] Genes { get; set; } // valorile genelor

public double[] MinValues { get; set; } // valorile minime posibile ale genelor

public double[] MaxValues { get; set; } // valorile maxime posibile ale genelor

public double Fitness { get; set; } // valoarea functiei de adaptare a individului

private static Random _rand = new Random();

public Chromosome(int noGenes, double[] minValues, double[] maxValues)


{
NoGenes = noGenes;
Genes = new double[noGenes];
MinValues = new double[noGenes];
MaxValues = new double[noGenes];

for (int i = 0; i < noGenes; i++)


{
MinValues[i] = minValues[i];
MaxValues[i] = maxValues[i];

Genes[i] = minValues[i] + _rand.NextDouble() * (maxValues[i] - minValues[i]);


// initializare aleatorie a genelor
}
}

public Chromosome(Chromosome c) // constructor de copiere


{
NoGenes = c.NoGenes;
Fitness = c.Fitness;

Genes = new double[c.NoGenes];


MinValues = new double[c.NoGenes];
MaxValues = new double[c.NoGenes];

for (int i = 0; i < c.Genes.Length; i++)


{
Genes[i] = c.Genes[i];
MinValues[i] = c.MinValues[i];
MaxValues[i] = c.MaxValues[i];
}
}

136
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}
}

IOptimizationProblem.cs

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Interfata pentru problemele de optimizare
/// </summary>
public interface IOptimizationProblem
{
void ComputeFitness(Chromosome c);

Chromosome MakeChromosome();
}
}

Equation.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Clasa care reprezinta problema din prima aplicatie: rezolvarea ecuatiei
/// </summary>
public class Equation : IOptimizationProblem
{
public Chromosome MakeChromosome()
{
// un cromozom are o gena (x) care poate lua valori in intervalul (-5, 5)
return new Chromosome(1, new double[] { -5 }, new double[] { 5 });
}

public void ComputeFitness(Chromosome c)


{
throw new Exception("Aceasta metoda trebuie completata");

double x = c.Genes[0];
// c.Fitness = functia care va fi maximizata
}
}
}

137
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Fence.cs

using System;

namespace EvolutionaryAlgorithm
{
/// <summary>
/// Clasa care reprezinta problema din a doua aplicatie: maximizarea ariei terenului
/// </summary>
public class Fence : IOptimizationProblem
{
public Chromosome MakeChromosome()
{
// un cromozom are doua gene (x si y) care pot lua valori in intervalul (0, 100)
return new Chromosome(2, new double[] { 0, 0 }, new double[] { 100, 100 });
}

public void ComputeFitness(Chromosome c)


{
throw new Exception("Aceasta metoda trebuie completata");

double x = c.Genes[0];
double y = c.Genes[1];

// c.Fitness = functia care va fi maximizata


}
}
}

138
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 9

Rezoluția propozițională

1. Obiective

Obiectivele acestui capitol sunt legate de prezentarea algoritmului de


rezoluție propozițională, o metodă generală de demonstrare automată a
teoremelor în logica propozițională.

2. Forma normal conjuctivă

Vom aminti mai întâi câteva noțiuni fundamentale ale logicii


propoziționale. Un literal este o propoziție atomică sau negația unei
propoziții atomice, de exemplu p și ¬p. O clauză este un literal sau o
disjuncție de literali, de exemplu: p, ¬p, ¬p ∨ q etc.
O clauză este realizabilă (satisfiable) dacă există o interpretare, adică
o atribuire de valori de adevăr pentru literalii care o compun, care o fac
adevărată.
Clauza vidă {} este tot o clauză. Este echivalentă cu o disjuncție vidă
și deci este nerealizabilă (unsatisfiable, care nu poate fi adevărată în nicio
interpretare). După cum vom vedea, aceasta joacă un rol important în
procesul de rezoluție.
Rezoluția propozițională este o regulă puternică de inferență pentru
logica propozițională, cu ajutorul căreia se poate construi un demonstrator
de teoreme corect și complet. Ea poate fi aplicată însă doar după aducerea
premiselor și concluziei într-o formă standardizată, numită formă normal
conjunctivă.
Există o procedură simplă pentru a aduce o propoziție arbitrară în
forma normal conjunctivă, adică sub forma unei conjuncții de disjuncții (un

139
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
ȘI de SAU-uri). Regulile de conversie sunt prezentate mai jos și trebuie
aplicate în ordine.

1. Implicațiile (I):

φ ⇒ ψ → ¬φ ∨ ψ
φ ⇐ ψ → φ ∨ ¬ψ
φ ⇔ ψ → (¬φ ∨ ψ) ∧ (φ ∨ ¬ψ)

2. Negațiile (N):

¬¬φ → φ
¬(φ ∧ ψ) → ¬φ ∨ ¬ψ
¬(φ ∨ ψ) → ¬φ ∧ ¬ψ

3. Distribuția (D):

φ ∨ (ψ ∧ χ) → (φ ∨ ψ) ∧ (φ ∨ χ)
(φ ∧ ψ) ∨ χ → (φ ∨ χ) ∧ (ψ ∨ χ)
φ ∨ (φ1 ∨ ... ∨ φn) → φ ∨ φ1 ∨ ... ∨ φn
(φ1 ∨ ... ∨ φn) ∨ φ → φ1 ∨ ... ∨ φn ∨ φ
φ ∧ (φ1 ∧ ... ∧ φn) → φ ∧ φ1 ∧ ... ∧ φn
(φ1 ∧ ... ∧ φn) ∧ φ → φ1 ∧ ... ∧ φn ∧ φ

De exemplu, să considerăm următoarea conversie:

¬(g ∧ (r ⇒ f))
I ¬(g ∧ (¬r ∨ f))
N ¬g ∨ ¬(¬r ∨ f)
¬g ∨ (¬¬r ∧ ¬f)
¬g ∨ (r ∧ ¬f)
D (¬g ∨ r) ∧ (¬g ∨ ¬f)

140
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3. Algoritmul de rezoluție propozițională

Ideea de bază a acestei forme de raționament este deducerea din


două propoziții, în care unul din termeni apare cu valori de adevăr contrare,
a unei concluzii din care este eliminat termenul respectiv. Fie următorul
exemplu:

Afirmații:
Afară plouă sau este soare.
Dacă este soare, atunci este cald.

Reprezentări:
ploaie ∨ soare
soare ⇒ cald

În forma normal conjunctivă, cea de a doua reprezentare va fi:


¬soare ∨ cald

Se observă că soare apare în prima expresie afirmat și în a doua


negat. Ambele propoziții trebuie să fie adevărate. Dacă soare este adevărat,
atunci ploaie poate lua orice valoare, pe când cald trebuie să fie obligatoriu
adevărat. Dacă soare este fals, atunci ploaie trebuie să fie obligatoriu
adevărat, pe când cald poate lua orice valoare. În orice caz, una din
propozițiile ploaie sau cald trebuie să fie adevărată. Acest lucru corespunde
formulei ploaie ∨ cald.

Pe acest fapt se bazează procesul de rezoluție: din două clauze: p ∨ q


și ¬p ∨ r, rezultă: q ∨ r.

Cu alte cuvinte, din două clauze în care una conține un literal iar
cealaltă negația acelui literal, putem deduce o clauză care conține reuniunea
literalilor celor două clauze-premisă, fără perechea complementară.
Într-o formulare mai generală, rezoluția propozițională se bazează pe
următoarea regulă de inferență:

141
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
φ1 ∨ ... ∨ φi–1 ∨ χ ∨ φi+1 ∨... ∨ φn
ψ1 ∨ ... ∨ ψj–1 ∨ ¬χ ∨ ψj+1 ∨... ∨ ψm
---------------------------------------------------------------------------
φ1 ∨ ... ∨ φi–1 ∨ φi+1 ∨... ∨ φn ∨ ψ1 ∨ ... ∨ ψj–1 ∨ ψj+1 ∨... ∨ ψm

sau echivalent:

C1 = {..., χ, ...}
C2 = {..., ¬χ, ...}
-------------------------------
C1 ∖ { χ } ⋃ C2 ∖ { ¬χ }

Linia punctată reprezintă o implicație. Clauza produsă se mai


numește rezolvent.
În exemplul de mai sus, dacă avem clauzele p ∨ q, respectiv ¬p ∨ r,
putem deriva clauza q ∨ r într-un singur pas:

p∨q
¬p ∨ r
----------
q∨r

Trebuie menționat că, deoarece clauzele sunt mulțimi, un literal nu


poate apărea de două ori într-o clauză, de exemplu:

¬p ∨ q
p∨q
----------
q

Dacă o clauză-premisă este o mulțime singleton (cu un singur


element), numărul de literali din rezolvent este mai mic decât numărul de
literali din a doua premisă. De exemplu, din clauza
p ∨ q ∨ r și clauza singleton ¬p, putem deriva clauza mai scurtă q ∨ r:

142
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
p∨q∨r
¬p
-------------
q∨r

Rezolvarea a două clauze singleton produce o clauză vidă, ca mai jos.


Derivarea clauzei vide indică faptul că mulțimea de clauze, sau mulțimea de
fapte, conține o contradicție:

p
¬p
------
{}

La rezolvarea a două clauze, pot exista mai mulți rezolvenți dacă


există mai multe perechi de literali complementari:

p∨q
¬p ∨ ¬q
-----------
p ∨ ¬p
q ∨ ¬q

Atunci când două clauze conțin mai multe perechi de literali


complementari, o singură pereche poate genera un rezolvent. Exemplul
următor nu este corect:

p∨q
¬p ∨ ¬q
------------ Greșit!
{}

Dacă am permite această implicație, am trage concluzia că aceste


două clauze sunt inconsistente. Însă, este posibil ca (p ∨ q) și (¬p ∨ ¬q) să
fie adevărate simultan. De exemplu, dacă p este adevărat și q este fals,
ambele clauze sunt satisfăcute.

143
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
4. Demonstrarea automată a propozițiilor

Rezoluția nu este singura metodă de raționament din logica


propozițională. De exemplu, există și metoda raționamentului înainte, care
folosește o succesiune de implicații, de genul celor din demonstrațiile
matematice clasice: p → q, q → r, deci p → r. Prin urmare, dacă p, atunci r.
Avantajul rezoluției este că propozițiile nu trebuie să fie doar implicații. De
exemplu, p ∨ q nu poate fi pusă sub forma unei implicații.
De multe ori, procesul de rezoluție poate fi aplicat pentru a deriva
direct o concluzie din premise, de exemplu, din premisele ¬p ∨ r, ¬q ∨ r și p
∨ q, se poate deriva concluzia r:

1. ¬p ∨ r Premisă
2. ¬q ∨ r Premisă
3. p∨q Premisă
4. q∨r 1, 3
5. r 2, 4

Trebuie însă subliniat că rezoluția nu este completă din punct de


vedere generativ, adică nu se pot deriva prin rezoluție toate clauzele care
sunt implicate logic din mulțimea de premise. De exemplu, din premisele p
și q, nu se poate deriva clauza p ∨ q, chiar dacă aceasta este implicată logic
de premise.
Totuși, dacă o mulțime de clauze Δ este nerealizabilă, rezoluția
garantează derivarea clauzei vide din Δ. În exemplul următor, cele patru
premise nu pot fi adevărate simultan pentru nicio combinație de valori
logice pentru p și q:

1. p∨q Premise
2. p ∨ ¬q Premise
3. ¬p ∨ q Premise
4. ¬p ∨ ¬q Premise
5. p 1, 2
6. ¬p 3, 4
7. {} 5, 6

144
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Această relație dintre nerealizabilitate și implicația logică poate fi
folosită pentru determinarea implicațiilor logice, adică pentru demonstrarea
generală a teoremelor. Pentru aceasta, se neagă concluzia, se adaugă la
mulțimea de premise și se folosește rezoluția pentru a determina dacă
mulțimea de clauze rezultată este nerealizabilă.
O propoziție φ este demonstrabilă dintr-o mulțime Δ de propoziții
dacă și numai dacă procesul de rezoluție propozițională generează clauza
vidă din mulțimea Δ ∪ {¬φ}.
Ca exemplu, fie următoarea problemă. Se dau trei premise: p, p ⇒ q,
(p ⇒ q) ⇒ (q ⇒ r) și trebuie demonstrat r. Procesul de rezoluție este descris
mai jos. Clauzele 3 și 4 rezultă din aducerea premisei 3 în forma normal
conjunctivă: (p ∨ ¬q ∨ r) ∧ (¬q ∨ r).

1. p Premisa 1
2. ¬p ∨ q Premisa 2
3. p ∨ ¬q ∨ r Premisa 3 FNC
4. ¬q ∨ r Premisa 3 FNC
5. ¬r Concluzia negată
6. q 1, 2
7. r 4, 6
8. {} 5, 7

5. Decidabilitatea

Un sistem logic este decidabil dacă există o metodă eficientă care


determină dacă o formulă arbitrară este o teoremă a sistemului logic
considerat, adică dacă se poate stabili dacă o formulă este adevărată sau nu.
Logica propozițională este decidabilă. De exemplu, cu algoritmul de
rezoluție propozițională, o demonstrație se termină întotdeauna: deoarece
există un număr finit de clauze care pot fi generate dintr-o mulțime inițială
finită de clauze, la un moment dat algoritmul nu mai poate genera noi
clauze. Dacă până în acel moment a ajuns la o contradicție, demonstrația a
reușit. Dacă nu, concluzia propusă nu poate fi demonstrată.
Spre deosebire de logica propozițională, în logica predicativă de
ordin întâi, problema demonstrării teoremelor este semidecidabilă: se poate
145
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
afla dacă o propoziție se poate demonstra, dar nu se poate afla dacă o
propoziție nu se poate demonstra. Algoritmul de rezoluție predicativă poate
rula la infinit.

6. Aplicații

6.1a. Fie următoarele premise:

p

p  q
(q  r )  s

Demonstrați prin rezoluție propozițională adevărul propoziției s.

Indicații. În primul rând, cele trei premise trebuie aduse la forma


normal conjunctivă:

 p
p p  q
 
p  q  
(q  r )  s  q  s
  r  s

Presupunem că s este adevărat și încercăm să ajungem la o


contradicție:

146
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
6.1b. Pentru aceleași premise ca la problema 1, încercați să
demonstrați adevărul propoziției r.

6.2. Date fiind premisele p ⇒ q și r ⇒ s, demonstrați că


(p ∨ r) ⇒ (q ∨ s).
Transformați mai întâi aceste propoziții, pe hârtie, în forma normal
conjunctivă.

Indicații privind programul. Se dă un prototip de aplicație, în care


sunt implementate toate clasele necesare pentru rezolvare. Diagrama UML
de clase a aplicației complete este prezentată în pagina următoare.

Se observă următoarele clase principale:

Literal: Reprezintă un literal, de exemplu „p” sau „NOT p”.


Clause: Reprezintă o clauză, adică o disjunctie de literali, de
exemplu: „p OR NOT q OR r”. Conține o listă cu literalii din disjuncție.
FactBase: Corespunde întregii mulțimi de clauze asupra căreia se
desfășoară procesul de inferență. Conține o listă de clauze. Primele clauze,
premisele, sunt citite dintr-un fișier cu extensia .facts, care pentru prima
problemă arată astfel (ex1a.facts):

147
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
p
NOT p OR q
NOT q OR s
NOT r OR s

NOT s

Tot în lista de clauze din FactBase vor fi adăugate clauzele noi,


generate în cadrul procesului de rezoluție.

Trebuie implementate doar următoarele metode:

Metoda Prove(FactBase fb) din clasa Resolution


Metoda care implementează algoritmul. Primește o bază de fapte și
încearcă să găsească o contradicție. Apelează metoda TryResolve.

148
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Metoda TryResolve(Clause c1, Clause c2) din clasa Resolution
Metoda care încearcă să găsească literali complementari în cele două
clauze primite ca parametri. Întoarce o listă de rezolvenți, posibil vidă.

Pentru primele probleme din acest capitol, rezultatele ar trebui să fie


de forma:

ex1a.proof

0. p
1. NOT p OR q
2. NOT q OR s
3. NOT r OR s
4. NOT s
5. q <- 0+1
6. NOT p OR s <- 1+2
7. NOT q <- 2+4
8. s <- 2+5
9. NOT r <- 3+4
10. NOT p <- 4+6
11. {} <- 4+8

Demonstratie:

0. p
1. NOT p OR q
2. NOT q OR s
4. NOT s
5. q <- 0+1
8. s <- 2+5
11. {} <- 4+8

ex1b.proof

0. p
1. NOT p OR q
2. NOT q OR s
3. NOT r OR s
4. NOT r
5. q <- 0+1

149
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
6. NOT p OR s <- 1+2
7. s <- 2+5

Propozitia nu poate fi demonstrata.

Program.cs

using System;

namespace Resolution
{
public class Program
{
private static void Main(string[] args)
{
Resolution res = new Resolution();
res.Prove(new FactBase("ex1a.facts"));
Console.WriteLine("\r\n");
res.Prove(new FactBase("ex1b.facts"));
Console.WriteLine("\r\n");

// Aplicatia 2: trebuie creat fisierul "ex2.facts"


//res.Prove(new FactBase("ex2.facts"));
}
}
}

Resolution.cs

using System;
using System.Collections.Generic;

namespace Resolution
{
/// <summary>
/// Clasa care ajuta verificarea procesului de rezolutie, in care din doua clauze rezulta
una noua. S1 si S2 reprezinta indecsii clauzelor sursa.
/// O pereche de clauze trebuie tratata o singura data.
/// </summary>
public class Pair
{
public int S1 { get; set; }
public int S2 { get; set; }

150
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public Pair(int s1, int s2)
{
S1 = s1;
S2 = s2;
}
}

/// <summary>
/// Clasa corespunzatoare algoritmului de rezolutie
/// </summary>
public class Resolution
{
/// <summary>
/// Metoda care implementeaza algoritmul. Primeste o baza de fapte si incearca sa
ajunga la o contradictie
/// </summary>
public void Prove(FactBase fb)
{
List<Pair> matchedPairs = new List<Pair>();

bool fbHasChanged = true;

while (fbHasChanged)
{
// pentru toate perechile de clauze care nu au mai fost prelucrate, apeleaza TryResolve
// daca o clauza-rezolvent c este vida (lista sa de literali este vida), atunci se adauga clauza
// c in baza de fapte si se afiseaza demonstratia: fb.WriteProof(c)
// daca nu, se adauga clauza c in baza de fapte si continua procesul
// la adaugarea unei clauze c in baza de fapte, trebuie setate c.Source1 si c.Source1
// (indecsii clauzelor din care a rezultat c)
// adaugati clauza in baza de fapte cu metoda fb.AddClause(c), care face si afisarea si
// salvarea in fisierul de demonstratie
}

// daca se ajunge aici, concluzia nu poate fi demonstrata


}

/// <summary>
/// Metoda care incearca sa gaseasca literali complementari in cele doua clauze sursa.
Intoarce o lista (posibil vida) de rezolventi
/// </summary>
private List<Clause> TryResolve(Clause c1, Clause c2)
{
List<Clause> resolvents = new List<Clause>();

// se cauta literalii complementari

151
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
return resolvents;
}

/// <summary>
/// Elimina literalii duplicati din clauza
/// </summary>
private Clause RemoveDuplicates(Clause clause)
{
Clause c = new Clause();

for (int i = 0; i < clause.Literals.Count; i++)


{
Literal lit = clause.Literals[i];
if (!Contains(c, lit))
c.Literals.Add(lit);
}

return c;
}

/// <summary>
/// Testeaza daca o baza de fapte contine o clauza
/// </summary>
private bool Contains(List<Clause> fbClauses, Clause clause)
{
foreach (Clause fbc in fbClauses)
{
if (AreEqual(fbc, clause))
return true;
}

return false;
}

/// <summary>
/// Testeaza daca doua clauze sunt egale, adica au aceiasi literali, indiferent de
ordinea in care apar acestia
/// </summary>
private bool AreEqual(Clause c1, Clause c2)
{
foreach (Literal lit in c1.Literals)
{
if (!(Contains(c2, lit)))
return false;
}

return true;

152
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}

/// <summary>
/// Testeaza daca o clauza contine un literal
/// </summary>
private bool Contains(Clause clause, Literal literal)
{
foreach (Literal lit in clause.Literals)
{
if (lit.Atom == literal.Atom && lit.IsNegative == literal.IsNegative)
return true;
}

return false;
}

/// <summary>
/// Testeaza daca o lista de obiecte "Pair" contine o anumita pereche
/// </summary>
private bool Contains(List<Pair> list, Pair pair)
{
foreach (Pair p in list)
{
if (p.S1 == pair.S1 && p.S2 == pair.S2)
return true;
}
return false;
}
}
}

FactBase.cs

using System;
using System.Collections.Generic;
using System.IO;

namespace Resolution
{
/// <summary>
/// Clasa care corespunde intregii multimi de clauze asupra careia se desfasoara
procesul de inferenta
/// </summary>
public class FactBase
{

153
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Numele bazei de fapte; corespunde numelui fisierului din care se citesc premisele
/// </summary>
public string Name { get; set; }

/// <summary>
/// Lista de clauze care compun baza de fapte
/// </summary>
public List<Clause> Clauses { get; set; }

/// <summary>
/// Constructorul care initializeaza baza de fapte cu clauzele din fisierul .facts
/// </summary>
public FactBase(string fileName)
{
Name = Path.GetFileNameWithoutExtension(fileName);

File.Delete(Name + ".proof"); // demonstratia va fi salvata intr-un fisier .proof; daca


deja exista acest fisier, este sters

Clauses = new List<Clause>();

StreamReader sr = new StreamReader(fileName);

while (sr.Peek() != -1)


{
string line = sr.ReadLine().Trim();
if (line == "")
continue;

AddClause(new Clause(line));
}
sr.Close();
}

/// <summary>
/// Adauga o clauza in baza de fapte
/// </summary>
public void AddClause(Clause clause)
{
StreamWriter sw = new StreamWriter(Name + ".proof", true);
if (clause.Source1 == -1) // premisele initiale, citite din fisier
{
Console.WriteLine("{0}. {1}", Clauses.Count, clause.ToString());
sw.WriteLine("{0}. {1}", Clauses.Count, clause.ToString());
}

154
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
else // clauzele rezultate din procesul de rezolutie
{
Console.WriteLine("{0}. {1} <- {2}+{3}", Clauses.Count, clause.ToString(),
clause.Source1, clause.Source2);
sw.WriteLine("{0}. {1} <- {2}+{3}", Clauses.Count, clause.ToString(),
clause.Source1, clause.Source2);
}

sw.Close();

Clauses.Add(clause);
}

/// <summary>
/// Afiseaza intreaga demonstratie, urmarind indecsii Source1 si Source2 pana la
premise. Include doar clauzele care contribuie efectiv la demonstratie.
/// </summary>
public void WriteProof(Clause contradiction)
{
StreamWriter sw = new StreamWriter(Name + ".proof", true);

sw.WriteLine("\r\nDemonstratie:\r\n");

List<Clause> list = new List<Clause>();


list.Add(contradiction);

List<int> inProof = new List<int>();


while (list.Count > 0)
{
Clause c = list[0];
inProof.Add(Clauses.IndexOf(c));
list.RemoveAt(0);
if (c.Source1 != -1)
list.Add(Clauses[c.Source1]);
if (c.Source2 != -1)
list.Add(Clauses[c.Source2]);
}

inProof.Sort();

Console.WriteLine("\r\nDemonstratie:\r\n");
for (int i = 0; i < inProof.Count; i++)
{
Clause clause = Clauses[inProof[i]];

if (clause.Source1 != -1)
{

155
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Console.WriteLine("{0}. {1} <- {2}+{3}", Clauses.IndexOf(clause),
clause.ToString(), clause.Source1, clause.Source2);
sw.WriteLine("{0}. {1} <- {2}+{3}", Clauses.IndexOf(clause), clause.ToString(),
clause.Source1, clause.Source2);
}
else
{
Console.WriteLine("{0}. {1} ", Clauses.IndexOf(clause), clause.ToString());
sw.WriteLine("{0}. {1} ", Clauses.IndexOf(clause), clause.ToString());
}
}

sw.Close();
}
}
}

Clause.cs

using System;
using System.Collections.Generic;
using System.Text;
namespace Resolution
{
/// <summary>
/// Reprezinta o disjunctie de literali, de exemplu: "p OR NOT q OR r"
/// </summary>
public class Clause
{
/// <summary>
/// Literalii din disjunctie
/// </summary>
public List<Literal> Literals { get; set; }

/// <summary>
/// In baza de fapte, fiecare clauza are un index. In procesul de rezolutie, din doua
clauze se deduce una noua. Source1 este indexul primei clauze din care s-a dedus clauza
curenta (this).
/// </summary>
public int Source1 { get; set; }
/// <summary>
/// In baza de fapte, fiecare clauza are un index. In procesul de rezolutie, din doua
clauze se deduce una noua. Source2 este indexul celei de a doua clauze din care s-a dedus
clauza curenta (this).

156
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// </summary>
public int Source2 { get; set; }

public Clause()
{
Literals = new List<Literal>();
Source1 = Source2 = -1;
}

/// <summary>
/// Constructorul care primeste o linie din fisierul .facts si o imparte in literali
/// </summary>
public Clause(string clause)
{
Literals = new List<Literal>();

string[] toks = clause.Split(new string[] { "OR" }, StringSplitOptions.RemoveEmptyEntries);


foreach (string term in toks)
Literals.Add(new Literal(term.Trim()));

Source1 = Source2 = -1;


}

/// <summary>
/// Metoda care formateaza literalii clauzei pentru afisare si salvare in fisier
/// </summary>
public override string ToString()
{
if (Literals.Count == 0)
return "{}";
StringBuilder sb = new StringBuilder();
Literal lit;

for (int i = 0; i < Literals.Count - 1; i++)


{
lit = Literals[i];
if (lit.IsNegative)
sb.Append("NOT ");
sb.Append(lit.Atom);
sb.Append(" OR ");
}

lit = Literals[Literals.Count - 1];


if (lit.IsNegative)
sb.Append("NOT ");
sb.Append(lit.Atom);

157
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
return sb.ToString();
}
}
}

Literal.cs

namespace Resolution
{
/// <summary>
/// Reprezinta un literal, de exemplu "p" sau "NOT p"
/// </summary>
public class Literal
{
/// <summary>
/// Formula atomica a literalului, fara "NOT" daca literalul este negat. De exemplu:
pentru "p" este "p", pentru "NOT q" este "q"
/// </summary>
public string Atom { get; set; }

/// <summary>
/// Este adevarat daca literalul este negat ("NOT")
/// </summary>
public bool IsNegative { get; set; }

public Literal(string literal)


{
if (literal.StartsWith("NOT"))
{
IsNegative = true;
Atom = literal.Substring(4); // fara "NOT "
}
else
{
IsNegative = false;
Atom = literal;
}
}
}
}

158
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 10

Inferența vagă (fuzzy)

1. Obiective

Obiectivele acestui capitol sunt următoarele:

 prezentarea mulțimilor fuzzy (se pronunță [fázi]) și a procesului de


inferență fuzzy;
 implementarea unei aplicații care simulează un controler fuzzy:
controlul mișcării unui metrou care trebuie să plece dintr-o stație și
să oprească la timp în stația următoare.

2. Mulțimi fuzzy

Logica tradițională consideră că un obiect poate aparține sau nu unei


mulțimi. Logica fuzzy permite o interpretare mai flexibilă a noțiunii de
apartenență. Astfel, un obiect poate aparține mai multor mulțimi în grade
diferite.
Mulțimile fuzzy permit elementelor să aparțină parțial unei clase sau
mulțimi. Fiecărui element i se atribuie un grad de apartenență la o mulțime.
Acest grad de apartenență poate lua valori între 0 (nu aparține mulțimii) și 1
(aparține total mulțimii). În cazul în care gradul de apartenență ar fi doar 0
sau 1, mulțimea fuzzy ar fi echivalentă cu o mulțime binară.
Fie X universul discursului, cu elemente notate x. O mulțime fuzzy A
a universului de discurs X este caracterizată de o funcție de apartenență
 A (x) care asociază fiecărui element x un grad de apartenență la mulțimea
A:

 A ( x) : X  [0,1]
159
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Pentru a reprezenta o mulțime fuzzy, trebuie să-i definim mai întâi
funcția de apartenență. În acest caz, o mulțime fuzzy A este complet definită
de mulțimea tuplelor:

A  {( x,  A ( x)) | x  X }

Pentru o mulțime finită X = {x1, ... , xn}, se mai folosește notația:

A  1 / x1  ...  n / xn

De exemplu, pentru variabila lingvistică „tânăr”, avem universul


discursului X = {0, 20, 30, 50} și următoarea funcție de apartenență:
A = 0/1 + 0,9/20 + 0,7/30 + 0/50, cu semnificația: o persoană de 20 de ani
aparține mulțimii oamenilor tineri în proporție de 90%, una de 30 de ani în
proporție de 70% iar una de 50 de ani nu aparține mulțimii (gradul său de
apartenență este 0). Aceste lucruri se reprezintă grafic ca în figura 10.1.

Figura 10.1. Funcție de apartenență pentru mulțimea oamenilor tineri

Pe un univers de discurs pot fi definite mai multe submulțimi fuzzy.


De exemplu, pentru universul vârstelor unor persoane, putem defini
submulțimile oamenilor tineri, bătrâni sau de vârstă mijlocie. Aceste
submulțimi se pot intersecta (este chiar recomandat acest fapt). Aceeași
persoană poate aparține, de exemplu, submulțimii oamenilor tineri cu un
grad de 70%, submulțimii oamenilor de vârstă mijlocie cu un grad de 90%
și submulțimii oamenilor bătrâni cu un grad de 30%.
O altă situație cu mai multe submulțimi fuzzy este prezentată în
figura 10.2. Aici, submulțimile reprezintă numere negative mari, medii,
160
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
mici, negative tinzând la zero, pozitive tinzând la zero, mici, medii, și mari.
Valoarea µ reprezintă gradul de apartenență.

Figura 10.2. Submulțimi fuzzy pentru mulțimea numerelor reale


(N = negativ, P = pozitiv, L = mare, M = mediu, S = mic)

2.1. Noțiuni fundamentale

Fie A o submulțime fuzzy a universului de discurs X. Se numește


suportul lui A submulțimea strictă a lui X ale cărei elemente au grade de
apartenență nenule în A:

supp(A) = {x  X |  A ( x)  0} .

Înălțimea lui A se definește drept cea mai mare valoare a funcției de


apartenență:
h(A) = max  A ( x ) .
xX

Se numește nucleul lui A submulțimea strictă a lui X ale cărei


elemente au grade de apartenență unitare în A:

n(A) = {x  X |  A ( x)  1} .

161
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
2.2. Numere fuzzy

De multe ori, oamenii nu pot caracteriza precis informațiile


numerice, folosind formulări precum „aproape 0”, „în jur de 100” etc. În
teoria mulțimilor fuzzy, aceste numere pot fi reprezentate ca submulțimi
fuzzy ale mulțimii numerelor reale.
Un număr fuzzy este o mulțime fuzzy a mulțimii numerelor reale, cu
o funcție de apartenență convexă și continuă și suport mărginit.
O mulțime fuzzy A se numește număr fuzzy triunghiular cu centrul
c, lățimea la stânga   0 și lățimea la dreapta   0 dacă funcția sa de
apartenență are forma:

 cx
1   , c    x  c
 x  c
 A ( x)  1  ,c  x  c
 
0, altfel


Folosim notația: A  (c, ,  ) . Este evident că


supp(A)  (c   , c   ) . Semnificația unui număr fuzzy triunghiular cu
centrul c este „x este aproximativ egal cu c ”.

Figura 10.3. Număr fuzzy triunghiular

O mulțime fuzzy A se numește număr fuzzy trapezoidal cu intervalul


de toleranță [c, d], lățimea la stânga   0 și lățimea la dreapta   0 dacă
funcția sa de apartenență are forma:

162
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
 cx
1   , c    x  c
1, c  x  d
 A ( x)   x  d
1  ,d  x d 
 
0, altfel

Figura 10.4. Număr fuzzy trapezoidal

Aici notăm: A  (c, d , ,  ) , iar supp(A)  (c   , d   ) .


Semnificația unui număr fuzzy trapezoidal cu intervalul de toleranță [c, d]
este „x este aproximativ între c și d ”.

3. Inferența Mamdani (max-min) cu o


singură regulă

3.1. Modus Ponens generalizat

În logica fuzzy și raționamentul aproximativ, cea mai importantă


regulă de inferență este Modus Ponens generalizat.
În logica clasică, această regulă de inferență este de forma
( p  ( p  q))  q , adică:

regulă: dacă p, atunci q


premisă: p
concluzie: q

163
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În logica fuzzy, regula de inferență corespunzătoare este următoarea:

regulă: dacă x este A, atunci y este B


premisă (antecedent): x este A'
concluzie (consecvent): y este B', unde B'  A'( A  B) .

Dacă A' = A și B' = B, regula se reduce la Modus Ponens clasic.


Matricea A  B deseori se notează cu R. Procesul de inferență
fuzzy este văzut ca o transformare a unei mulțimi fuzzy într-o altă mulțime
fuzzy. Submulțimea indusă în B, B' se calculează astfel:
b j '  max (min( ai ' , rij )). Există mai multe metode de definire a matricei R.
i

În cele ce urmează, noi vom folosi tipul de inferență Mamdani, prin


care mulțimea B' este o variantă „retezată” a lui B, la înălțimea fixată de A'.
A' poate fi o submulțime normală a lui A, nu doar cu un singur element.

Figura 10.5. Inferență de tip Mamdani

3.2. Defuzzificarea

După ce am determinat mulțimea fuzzy indusă de o regulă de


inferență, în unele aplicații trebuie obținută o valoare singulară, strictă, pe
baza acestei mulțimi. Procesul se numește defuzzificare. Cea mai utilizată
tehnică de defuzzificare este metoda centrului de greutate (sau a
centroidului):

 x   (x )
i A i
xCG  i

  (x )
i
A i

164
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În figura 10.6, programul folosește drept universuri de discurs
intervalele (0,100). În figura din dreapta, mulțimea delimitată cu albastru
este B, cea delimitată cu roz este B' iar valoarea pe axa X indicată cu roșu
este centrul de greutate.

Figura 10.6. Inferență de tip Mamdani și defuzzificare

4. Inferența Mamdani cu reguli multiple

Un exemplu de sistem de inferență fuzzy de tip Mamdani este


prezentat în figura 10.7. Pentru a calcula ieșirea acestui sistem când se dau
intrările trebuie parcurși următorii 6 pași:

1. Se determină o mulțime de reguli fuzzy;


2. Se realizează fuzzificarea intrărilor utilizând funcțiile de apartenență;
3. Se combină intrările fuzzificate urmând regulile fuzzy pentru
stabilirea puterilor de activare ale regulilor;
4. Se calculează consecvenții regulilor prin combinarea puterilor de
activare ale regulilor cu funcțiile de apartenență ale ieșirilor;
5. Se combină consecvenții pentru a determina mulțimea de ieșire;
6. Se defuzzifică mulțimea de ieșire, doar dacă se dorește ca ieșirea să
fie strictă.

În cele ce urmează se prezintă o descriere detaliată a acestui proces.

165
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Figura 10.7. Sistem de inferență fuzzy de tip Mamdani
cu două reguli și două intrări stricte

4.1. Crearea regulilor fuzzy

Regulile fuzzy reprezintă o mulțime de afirmații care descriu modul


în care sistemul poate lua o decizie privind estimarea ieșirii. Regulile fuzzy
au următoarea formă:

DACĂ (intrarea-1 este mulțime-fuzzy-1)


ȘI/SAU (intrarea-2 este mulțime-fuzzy-2)
ȘI/SAU ...
ATUNCI (ieșirea este mulțime-fuzzy-de-ieșire).

Un exemplu de regulă scrisă în acest mod este următorul:

166
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
DACĂ finanțarea-proiectului este adecvată
ȘI numărul de angajați este redus
ATUNCI riscul-proiectului este mic.

4.2. Fuzzificarea

Scopul fuzzificării este de a transforma intrările în valori cuprinse


între 0 și 1 utilizând funcțiile de apartenență. În exemplul din figura 10.7,
există două intrări x0 și y0 prezentate în colțul din stânga jos. Pentru aceste
intrări stricte se marchează gradele de apartenență în mulțimele
corespunzătoare.

4.3. Combinarea antecedenților multipli

Pentru crearea regulilor fuzzy utilizăm operatorii ȘI, SAU și uneori


NEGAȚIE. Operatorul fuzzy ȘI este scris ca:  AB ( x)  T (  A ( x), B ( x)) unde
T este o funcție numită T-normă, µA(x) este gradul de apartenență a lui x la
mulțimea A, iar µB(x) este gradul de apartenență a lui x la mulțimea B. Cu
toate că există mai multe moduri pentru calculul funcției ȘI, cel mai des
folosit este: min(µA(x), µB(x)). Operatorul fuzzy ȘI reprezintă o generalizare
a operatorului logic boolean ȘI în sensul că valoarea de adevăr a unei
propoziții nu este doar 0 sau 1, ci poate fi cuprinsă între 0 și 1. O funcție
T-normă este monotonă, comutativă, asociativă și respectă condițiile
T(0, 0) = 0 și T(x, 1) = x.
Operatorul fuzzy SAU se scrie ca  AB  S (  A , B ) unde S este o
funcție numită T-conormă. În mod similar cu operatorul ȘI, aceasta poate fi:
max(µA(x), µB(x)). Operatorul fuzzy SAU reprezintă de asemenea o
generalizare a operatorului logic boolean SAU la valori cuprinse între 0 și 1.
O funcție T-conormă este monotonă, comutativă, asociativă și respectă
condițiile S(x, 0) = x și S(1, 1) = 1.

167
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
4.4. Calcularea consecvenților

Mai întâi, se calculează puterile de activare ale regulilor, după cum


s-a prezentat în secțiunea 4.3. În figura 10.7, se poate observa că este utilizat
operatorul fuzzy ȘI asupra funcțiilor de apartenență pentru a calcula puterile
de activare ale regulilor. Apoi, pentru un sistem de inferență fuzzy de tip
Mamdani, mulțimea de ieșire este retezată la nivelul dat de puterea de
activare a regulii, așa cum s-a arătat și în secțiunea 3.1.

4.5. Agregarea ieșirilor

Ieșirile obținute după aplicarea regulilor fuzzy sunt combinate pentru


a se obține mulțimea de ieșire. De obicei, acest lucru se realizează utilizând
operatorul fuzzy SAU. În figura 10.7, funcțiile de apartenență din partea
dreaptă sunt combinate utilizând operatorul fuzzy SAU pentru a obține
mulțimea de ieșire prezentată în colțul din dreapta jos.

4.6. Defuzzificarea

De multe ori se dorește obținerea unei ieșiri stricte. De exemplu,


dacă se încearcă clasificarea literelor scrise de mână pe o tabletă, sistemul
de inferență fuzzy trebuie să genereze un număr strict care poate fi
interpretat. Acest număr se obține în urma procesului de defuzzificare. Cea
mai des utilizată metodă de defuzzificare este metoda centrului de greutate,
prezentată în secțiunea 3.2 și exemplificată și în figura 10.8.

168
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Figura 10.8. Defuzzificarea utilizând metoda centrului de greutate

5. Aplicații

În cadrul acestei aplicații, vom simula un sistem fuzzy de control al


unui metrou care trebuie să parcurgă 1 km între două stații. Scopul este ca
metroul să plece din prima stație și să oprească la timp în a doua stație.
Sistemul de control poate fi văzut ca o funcție care calculează în fiecare
moment accelerația metroului, dată fiind distanța rămasă până la destinație.
Se dă un prototip de aplicație, în care sunt implementate toate clasele
necesare pentru rezolvare și interfața grafică cu utilizatorul.
În figura 10.9 este prezentată diagrama UML de clase a aplicației
complete.

5.1. Implementați mai întâi algoritmul de inferență fuzzy cu reguli


multiple, completând metodele de mai jos.

Metoda ComputeMembership(double x) din clasa


TriangularMembershipFunction
Metoda care calculează valoarea funcției de apartenență a unui
număr fuzzy triunghiular în punctul x. Se poate completa pe baza funcției
din secțiunea 2.2 și/sau prin analogie cu expresia funcției de apartenență
pentru numerele fuzzy trapezoidale.

169
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Figura 10.9. Diagrama UML de clase a aplicației complete

170
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Metoda ComputeOutput(double distanceLeft) din clasa
FuzzyInference
Metoda care implementează procesul efectiv de inferență Mamdani
cu reguli multiple. Metoda primește ca parametru distanța rămasă și
folosește informațiile date de regulile și mulțimile fuzzy pentru a calcula
accelerația metroului.

Indicație. În program, mulțimile fuzzy trebuie reprezentate ca niște


vectori, prin eșantionare. De exemplu, în figura de mai jos este prezentată o
mulțime fuzzy eșantionată într-un vector cu 10 elemente, cu indexul plecând
de la 0:

0.75

0.5

0.25

0
0 1 2 3 4 5 6 7 8 9

Vectorul corespunzător mulțimii este: (0, 0,5, 1, 1, 0,75, 0,75, 0,75,


0,5, 0,25, 0).
În cazul general, în care universul de discurs la mulțimii fuzzy este
[xmin, xmax] și dorim să eșantionăm mulțimea într-un vector v cu n elemente,
avem următoarele corespondențe:

v[0] → μ(xmin)
v[n – 1] → μ(xmax).

Pentru un index i între 0 și n al vectorului v, corespondentul va fi o


valoare a funcției de apartenență a mulțimii fuzzy într-un punct intermediar
xi dintre xmin și xmax, după regula de trei simplă. Prin urmare, valoarea unui
element din vector va fi: v[i] = μ(xi), unde:

171
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
i
xi  xmin   xmax  xmin  .
n 1

5.2. Adăugați mulțimi și reguli fuzzy potrivite pentru a face metroul


să funcționeze corect, adică să oprească exact în stație.

MainForm.cs

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace SubwayControl
{
public partial class MainForm : Form
{
/// <summary>
/// Pozitia metroului, unde 0 este pozitia in prima statie si 1 este pozitia in a doua
statie
/// </summary>
private double _position;

public MainForm()
{
InitializeComponent();

this.ClientSize = new System.Drawing.Size(800, 285);


this.pictureBoxRoute.Size = new System.Drawing.Size(740, 50);
this.textBoxStatus.Size = new System.Drawing.Size(740, 93);

_position = 0;
}

/// <summary>
/// Evenimentul de refresh al pictureBoxRoute, care deseneaza "metroul" conform
pozitiei sale curente _position
/// </summary>
private void pictureBoxRoute_Paint(object sender, PaintEventArgs e)
{
double p = _position;
if (p < -0.01)

172
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
p = -0.01;
if (p > 1.01)
p = 1.01;

Bitmap b = new Bitmap(740, 50);


Graphics g = Graphics.FromImage(b);
g.Clear(Color.White);

g.DrawLine(Pens.Black, 20, 25, 720, 25);


int x = (int)(30 + p * 670);
g.FillRectangle(Brushes.Blue, x, 22, 15, 6);

g.FillRectangle(Brushes.DarkGray, 30, 10, 15, 10);


g.FillRectangle(Brushes.DarkGray, 30, 30, 15, 10);
g.FillRectangle(Brushes.DarkGray, 700, 10, 15, 10);
g.FillRectangle(Brushes.DarkGray, 700, 30, 15, 10);

e.Graphics.DrawImage(b, 0, 0);
}

/// <summary>
/// Evenimentul de click al butonului "Porneste", in care se desfasoara deplasarea
animata a metroului.
/// Aici se apeleaza metoda ComputeOutput din FuzzyInference.
/// </summary>
private void buttonStart_Click(object sender, EventArgs e)
{
FuzzyInference fuzzy = new FuzzyInference();

_position = 0;
double speed = 0;
double distanceLeft = 1000;

double previousDistance = distanceLeft;


bool changed = true;

while (changed)
{
double acc = fuzzy.ComputeOutput(distanceLeft);

if (double.IsNaN(acc))
throw new Exception("Nu se aplica nicio regula fuzzy!");

speed += acc; // viteza in m/s


if (speed < 0) speed = 0;
if (speed > 30) speed = 30;

173
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
previousDistance = distanceLeft;

distanceLeft -= speed;
_position = (1000 - distanceLeft) / 1000.0;

if (_position > 1.001) // a depasit statia, toleranta de 1m


break;
pictureBoxRoute.Refresh();
textBoxStatus.Text = string.Format("Distanta ramasa: {0:F2} m\r\nViteza: {1:F2}
m/s\r\nAcceleratia: {2:F2} m/s^2\r\n", distanceLeft, speed, acc);
Application.DoEvents();
Thread.Sleep(25);

if (Math.Abs(distanceLeft - previousDistance) < 1e-6)


changed = false;
else
changed = true;
}

if (_position > 1.001) // toleranta de 1m


{
textBoxStatus.AppendText("Metroul a depasit statia!");
_position = 1.01;
}
else if (_position < 0.999) // toleranta de 1m
textBoxStatus.AppendText("Metroul nu a ajuns in statie!");
else
{
textBoxStatus.AppendText("Metroul a ajuns cu bine in statie.");
_position = 1;
}

pictureBoxRoute.Refresh();
}

private void buttonExit_Click(object sender, EventArgs e)


{
Environment.Exit(0);
}

private void buttonAbout_Click(object sender, EventArgs e)


{
const string copyright =
"Inferenta fuzzy\r\n" +
"Inteligenta artificiala, Laboratorul 10\r\n" +
"(c)2016-2020 Florin Leon\r\n" +
"http://florinleon.byethost24.com/lab_ia.htm";

174
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
MessageBox.Show(copyright, "Despre Metroul fuzzy");
}
}
}

FuzzyInference.cs

using System;
using System.Collections.Generic;

namespace SubwayControl
{
/// <summary>
/// Clasa care contine procesul de inferenta fuzzy
/// </summary>
public class FuzzyInference
{
private UniverseOfDiscourse _distance, _acceleration;
private FuzzySet _lowDistance, _mediumDistance, _highDistance;
private FuzzySet _negativeAcceleration, _zeroAcceleration, _positiveAcceleration;
private List<FuzzyRule> _rules;

public FuzzyInference()
{
CreateUniverses();
CreateSets();
CreateRules();
}

/// <summary>
/// Defineste universurile de discurs ale problemei.
/// </summary>
private void CreateUniverses()
{
_distance = new UniverseOfDiscourse("Distanta ramasa", 0, 1000, "m");
_acceleration = new UniverseOfDiscourse("Acceleratia", -1, 1, "m/s^2");
}

/// <summary>
/// Defineste multimile fuzzy, aici doar numere fuzzy triunghiulare sau trapezoidale.
/// </summary>
private void CreateSets()
{

175
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
_lowDistance = new FuzzySet("Distanta mica", new
TrapezoidalMembershipFunction(-0.01, 0, 200, 300));
_mediumDistance = new FuzzySet("Distanta medie", new
TriangularMembershipFunction(200, 500, 800));
_highDistance = new FuzzySet("Distanta mare", new
TrapezoidalMembershipFunction(700, 800, 1000, 1000.01));

_negativeAcceleration = new FuzzySet("Acceleratie negativa", new


TrapezoidalMembershipFunction(-1.01, -1, -0.5, -0.2));
_zeroAcceleration = new FuzzySet("Acceleratie zero", new
TriangularMembershipFunction(-0.5, 0, 0.5));
_positiveAcceleration = new FuzzySet("Acceleratie pozitiva", new
TrapezoidalMembershipFunction(0.2, 0.5, 1, 1.01));

// Aplicatia 2: de modificat multimile existente sau de adaugat noi multimi fuzzy


pentru ca metroul sa opreasca in statie corect
}

/// <summary>
/// Defineste regulile fuzzy care vor fi folosite in procesul de inferenta.
/// </summary>
private void CreateRules()
{
_rules = new List<FuzzyRule>();

_rules.Add(new FuzzyRule(_highDistance, _positiveAcceleration));


_rules.Add(new FuzzyRule(_mediumDistance, _zeroAcceleration));
_rules.Add(new FuzzyRule(_lowDistance, _negativeAcceleration));

// Aplicatia 2: de adaugat noi reguli daca este cazul


}

/// <summary>
/// Metoda apelata din exterior, care pentru o intrare stricta trebuie sa returneze o
iesire stricta.
/// In cazul metroului, intrarea este distanta ramasa pana la urmatoarea statie, iar
iesirea este noua valoare pentru acceleratie.
/// </summary>
public double ComputeOutput(double distanceLeft)
{
// fuzzificarea pentru antecedentul fiecarei reguli fuzzy
// reprezentarea multimilor induse in consecventi prin esantionare, de exemplu cu
100 de valori discrete
// agregarea multimilor induse prin reuniune
// defuzzificarea cu metoda centrului de greutate - aceasta este valoarea returnata
de metoda

176
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
throw new Exception("Aceasta metoda trebuie implementata.");
}
}
}

UniverseOfDiscourse.cs

namespace SubwayControl
{
/// <summary>
/// Clasa care defineste un univers de discurs, cu nume, unitate de masura (de exemplu
metri pentru distanta, m/s^2 pentru acceleratie)
/// si valorile minime si maxime ale intervalului de definitie
/// </summary>
public class UniverseOfDiscourse
{
public string Name { get; set; }

public string Unit { get; set; }

public double MinValue { get; set; }

public double MaxValue { get; set; }

public UniverseOfDiscourse(string name, double minValue, double maxValue, string


unit)
{
Name = name;
Unit = unit;
MinValue = minValue;
MaxValue = maxValue;
}
}
}

177
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
FuzzySet.cs

namespace SubwayControl
{
/// <summary>
/// Defineste o multime fuzzy, cu un nume (de exemplu "distanta mica") si o functie de
apartenenta
/// </summary>
public class FuzzySet
{
public string Name { get; set; }

public IMembershipFunction MembershipFunction { get; set; }

public FuzzySet(string name, IMembershipFunction membershipFunction)


{
Name = name;
MembershipFunction = membershipFunction;
}
}
}

FuzzyRule.cs

namespace SubwayControl
{
/// <summary>
/// Clasa care defineste o regula fuzzy, de forma: daca x este A, atunci y este B.
/// Antecedentul este multimea fuzzy A iar consecventul este multimea fuzzy B.
/// </summary>
public class FuzzyRule
{
/// <summary>
/// Antecedentul regulei (daca/if). Daca sunt mai multi antecedenti, trebuie o lista sau
un vector.
/// </summary>
public FuzzySet Antecedent { get; set; }

/// <summary>
/// Consecventul regulei (atunci/then)
/// </summary>
public FuzzySet Consequent { get; set; }

178
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public FuzzyRule(FuzzySet antecedent, FuzzySet consequent)
{
Antecedent = antecedent;
Consequent = consequent;
}
}
}

MembershipFunction.cs

using System;

namespace SubwayControl
{
public interface IMembershipFunction
{
double ComputeMembership(double x);
}
/// <summary>
/// Functia de apartenenta a unui numar fuzzy triunghiular
/// </summary>
public class TriangularMembershipFunction : IMembershipFunction
{
private double _center;
private double _leftWidth;
private double _rightWidth;

/// <summary>
/// Spre deosebire de definitia din text, cu centru, latime la stanga si latime la
dreapta,
/// aici constructorul primeste punctul minim al suportului, centrul si punctul maxim
al suportului.
/// S-a considerat ca aceasta definitie este mai intuitiva atunci cand se definesc
multimile efective pe universul de discurs.
/// </summary>
public TriangularMembershipFunction(double supportMin, double center, double
supportMax)
{
_center = center;
_leftWidth = center - supportMin;
_rightWidth = supportMax - center;

if (_leftWidth <= 0 || _rightWidth <= 0)


throw new Exception("Latime invalida");
}

179
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Calculul efectiv al functiei de apartenenta pentru un x din universul de discurs
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
public double ComputeMembership(double x)
{
throw new Exception("Aceasta metoda trebuie implementata.");
}
}

/// <summary>
/// Functia de apartenenta a unui numar fuzzy trapezoidal
/// </summary>
public class TrapezoidalMembershipFunction : IMembershipFunction
{
private double _coreMin;
private double _coreMax;
private double _leftWidth;
private double _rightWidth;

/// <summary>
/// Spre deosebire de definitia din text, cu valorile minime si maxime ale nucleului,
latime la stanga si latime la dreapta,
/// aici constructorul primeste punctele minime ale suportului si nucleului, respectiv
punctele maxime ale nucleului si suportului.
/// S-a considerat ca aceasta definitie este mai intuitiva atunci cand se definesc
multimile efective pe universul de discurs.
/// </summary>
public TrapezoidalMembershipFunction(double supportMin, double coreMin, double
coreMax, double supportMax)
{
_coreMin = coreMin;
_coreMax = coreMax;
_leftWidth = coreMin - supportMin;
_rightWidth = supportMax - coreMax;

if (_leftWidth <= 0 || _rightWidth <= 0)


throw new Exception("Latime invalida");

if (_coreMin > _coreMax)


throw new Exception("Nucleu invalid");
}

180
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
public double ComputeMembership(double x)
{
if (x >= _coreMin - _leftWidth && x <= _coreMin)
return 1.0 - (_coreMin - x) / _leftWidth;
if (x > _coreMin && x <= _coreMax)
return 1;
if (x > _coreMax && x <= _coreMax + _rightWidth)
return 1.0 - (x - _coreMax) / _rightWidth;
return 0;
}
}
}

181
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
182
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 11

Rețele bayesiene

1. Obiective

Obiectivul acestui capitol este de a prezenta structura rețelelor


bayesiene și de a descrie un algoritm exact de inferență, inferența prin
enumerare. Folosind un program de lucru cu rețele bayesiene, se vor modela
două rețele și se vor explora probabilitățile nodurilor în prezenta diferitelor
observații.

2. Probabilități condiționate. Teorema lui Bayes

Vom aminti câteva noțiuni legate de probabilitățile condiționate.


Când trebuie să definim P(A|B), presupunem că se cunoaște B și se
calculează probabilitatea lui A în această situație. Să considerăm
evenimentul D (durere de cap) și să presupunem că are, în general, o
probabilitate de 1/10. Probabilitatea de a avea gripă (evenimentul G) este de
numai 1/40. După cum se vede în figura 11.1, dacă cineva are gripă,
probabilitatea de a avea și dureri de cap este de 1/2. Deci probabilitatea
durerii de cap, dată fiind gripa, este de 1/2. Această probabilitate corespunde
intersecției celor două regiuni, cu aria egală cu jumătate din G.

Figura 11.1. Reprezentare grafică a unei probabilități condiționate

183
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Pe baza acestei relații rezultă teorema lui Bayes, care este importantă
pentru toate raționamentele probabilistice pe care le vom studia.
Considerăm formula probabilităților condiționate:

( )
( | )
( )

Putem exprima probabilitatea intersecției în două moduri și de aici


deducem expresia lui P(B|A) în funcție de P(A|B):

( ) ( | ) ( )

( ) ( | ) ( )
( | ) ( )
( | )
( )

Această ecuație reprezintă un rezultat fundamental. Mai clar, putem


considera următoarea expresie alternativă:

( | ) ( )
( | )
( )
unde I este ipoteza, E este evidența (provenind din datele observate), ( )
este probabilitatea a-priori a ipotezei, adică gradul inițial de încredere în
ipoteză, ( | ) este verosimilitatea datelor observate (likelihood), adică
măsura în care s-a observat evidența în condițiile îndeplinirii ipotezei, iar
( | ) este probabilitatea a-posteriori a ipotezei, dată fiind evidența.
Relația este importantă deoarece putem calcula astfel probabilitățile
cauzelor, date fiind efectele. Este mai simplu de cunoscut când o cauză
determină un efect, dar invers, când cunoaștem un efect, probabilitățile
cauzelor nu pot fi cunoscute imediat. Teorema ne ajută să diagnosticăm o
anumită situație sau să testăm o ipoteză.
Să considerăm următorul exemplu de diagnosticare. Știm că
probabilitatea de apariție a meningitei în populația generală este ( )
. De asemenea, probabilitatea ca o persoană să aibă gâtul înțepenit

184
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
este ( ) . Mai știm că meningita cauzează gât înțepenit în jumătate
din cazuri: ( | ) .
Dorim să aflăm următorul lucru: dacă un pacient are gâtul înțepenit,
care este probabilitatea să aibă meningită?
Aplicând teorema lui Bayes, vom avea:

( | ) ( )
( | )
( )

G este un simptom pentru M. Dacă există simptomul, care este


probabilitatea unei posibile cauze, adică P(M)? Rezultatul este 0,02%, deci
o probabilitate mică, deoarece probabilitatea meningitei înseși este foarte
mică în general.

3. Rețele bayesiene

În continuare, ne vom concentra asupra reprezentării informațiilor


legate de evenimente probabilistice, care ne va ajuta să realizăm eficient
raționamente.
Determinarea probabilității unei combinații de valori se poate realiza
astfel:

( ) ( | ) ( )

Aplicând în continuare această regulă vom obține regula de


înmulțire a probabilităților (chain rule):

( ) ( | ) ( | ) ( | ) ( )

exprimată mai concis astfel:

( ) ∏ ( | )

185
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
O rețea bayesiană arată ca în figura 11.2: este un graf orientat aciclic
(directed acyclic graph), în care evenimentele sau variabilele se reprezintă ca
noduri, iar relațiile de corelație sau cauzalitate se reprezintă sub forma
arcelor dintre noduri.

Gripă Abces

Febră

Oboseală Anorexie

Figura 11.2. Rețea bayesiană

Tabelul 11.1. Tabelele de probabilități pentru rețeaua bayesiană


P(Gripă = Da) P(Gripă = Nu)
0,1 0,9

P(Abces = Da) P(Abces = Nu)


0,05 0,95

Gripă Abces P(Febră = Da) P(Febră = Nu)


Da Da 0,8 0,2
Da Nu 0,7 0,3
Nu Da 0,25 0,75
Nu Nu 0,05 0,95

Febră P(Oboseală = Da) P(Oboseală = Nu)


Da 0,6 0,4
Nu 0,2 0,8

Febră P(Anorexie = Da) P(Anorexie = Nu)


Da 0,5 0,5
Nu 0,1 0,9

186
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
În acest exemplu, se consideră că atât gripa cât și abcesul pot
determina febra. De asemenea, febra poate cauza o stare de oboseală sau
lipsa poftei de mâncare (anorexie).
Sensul săgeților arcelor sunt dinspre părinți, cum ar fi gripa și
abcesul, înspre fii, precum febra. Deși în acest exemplu relațiile sunt
cauzale, în general o rețea bayesiană reflectă relații de corelație, adică
măsura în care aflarea unor informații despre o variabilă-părinte aduce noi
informații despre o variabilă-fiu.
Fiecare variabilă are o mulțime de valori. În cazul cel mai simplu,
variabilele au valori binare, de exemplu Da și Nu. În general însă, o
variabilă poate avea oricâte valori.
Asociate cu variabilele, o rețea bayesiană conține o serie de tabele de
probabilități, precum cele din tabelul 11.1. Pentru nodurile fără părinți se
indică probabilitățile marginale ale fiecărei valori (adică fără a lua în
considerare valorile celorlalte variabile). Pentru celelalte noduri, se indică
probabilitățile condiționate pentru fiecare valoare, ținând cont de fiecare
combinație de valori ale variabilelor părinte.
În general, o variabilă binară fără părinți va avea un singur
parametru independent, o variabilă cu 1 părinte va avea 2 parametri
independenți iar o variabilă cu n părinți va avea parametri independenți
în tabela de probabilități corespunzătoare.
Presupunerea modelului bazat pe rețele bayesiene este că o variabilă
nu depinde decât de părinții săi și deci ecuația anterioară devine:

( ) ∏ ( | ( ))

unde ( ) reprezintă mulțimea părinților variabilei , din șirul ordonat


topologic al nodurilor, în care părinții unui nod apar întotdeauna înaintea
nodului respectiv.

187
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
4. Inferența prin enumerare

Folosind acest procedeu, putem răspunde practic la orice întrebare


privind evenimentele codate în rețea. Având în vedere niște evidențe, adică
observații sau evenimente despre care știm că s-au întâmplat, putem calcula
probabilitățile tuturor celorlalte noduri din rețea.
Mai exact, scopul inferenței prin enumerare este de a calcula
probabilitatea unei variabile interogate (query), date fiind variabilele
observate (evidență).
Ideea de bază este tot calcularea unui produs de probabilități
condiționate, însă în cazul variabilelor despre care nu se cunoaște nimic (nu
sunt nici observate și nici interogate), se sumează variantele corespunzătoare
tuturor valorilor acestora.

Să considerăm următoarea întrebare: „Care este probabilitatea ca o


persoană să aibă gripă, dacă prezintă simptome de oboseală și anorexie?”

Vom calcula independent ( | ) și ( | ).


Pentru ( | ), variabilele rămase sunt Abcesul și Febra. În
consecință, vom suma probabilitățile corespunzătoare tuturor valorilor
acestor variabile: * + și * +. De asemenea, pentru a
crește eficiența calculelor, se recomandă ca variabilele rămase să fie mai
întâi sortate topologic, astfel încât părinții să apară înaintea copiilor. În acest
caz, se vor putea descompune mai ușor sumele, scoțând în față factorii care
nu depind de o anumită variabilă.

( | )

∑ ∑ ( )
* + * +

∑∑ ( ) ( ) ( | ) ( | ) ( | )

( ) ∑ ( ) ∑ ( | ) ( | ) ( | )

188
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
( ) ∑ ( ) , ( | ) ( | ) ( | )

( | ) ( | ) ( | )-
( ) * ( ) , ( | ) ( | ) ( | )
( | ) ( | ) ( | )-
( ) , ( | ) ( | ) ( | )
( | ) ( | ) ( | )-+
* , -
, -+

În exemplul de mai sus, se observă că ( ) nu depinde de f și prin


urmare, suma corespunzătoare variabilei Abces a fost scoasă în fața sumei
corespunzătoare variabilei Febră, evitându-se duplicarea unor calcule.
Nodul Abces, neavând părinți, este în fața Febrei în sortarea topologică. Se
remarcă variabila α care intervine în expresia probabilității. Vom explica
sensul acesteia imediat, după ce vom considera și calculele pentru
( | ), în mod analog:

( | )

∑ ∑ ( )
* + * +

∑∑ ( ) ( ) ( | ) ( | ) ( | )

( ) ∑ ( ) ∑ ( | ) ( | ) ( | )

( ) * ( ) , ( | ) ( | ) ( | )
( | ) ( | ) ( | )-
( ) , ( | ) ( | ) ( | )
( | ) ( | ) ( | )-+

189
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
* , -
, -+

Rolul coeficientului este de a asigura faptul că ( | )


( | ) , deoarece Da și Nu sunt singurele valori posibile pentru
Gripă. Având în vedere că ( | ) și ( | )
, există ( ) , astfel încât
suma celor două probabilități să fie 1. În consecință, rezultatul interogării
este:

( | )
( | )

5. Aplicații

5.1. Fie rețeaua bayesiană din figura 11.2, cu probabilitățile din


tabelul 11.1. Folosind algoritmul de inferență prin enumerare, răspundeți, pe
hârtie, la întrebarea: „Care este probabilitatea ca o persoană să fie obosită
dacă nu are gripă, nu are abces și nu are anorexie?”

5.2. Desenați în programul Belief and Decision Network Tool


(http://www.aispace.org/bayes/version5.1.10signed/bayes.jar) aceeași
rețea bayesiană (figura 11.2, tabelul 11.1). Răspundeți la întrebarea de la
punctul 1 cu ajutorul programului și comparați rezultatele.
Indicații. Desenarea se face în tab-ul Create. Interogările se introduc
în tab-ul Solve: Make observation pentru setarea evidențelor și Query pentru
setarea variabilei de interogare.

190
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5.3. Tot cu ajutorul programului, răspundeți la următoarele întrebări:

a) Care este probabilitatea ca o persoană să aibă febră, dacă are gripă


și abces? Cum influențează variabilele Oboseală și Anorexie aceste
probabilități?
b) Care sunt probabilitățile marginale ale nodurilor Febră, Oboseală
și Anorexie (când în rețea nu sunt noduri de evidență)?
c) Care este probabilitatea nodului Oboseală dacă Gripă are valoarea
Da?
d) Care este probabilitatea nodului Oboseală dacă Gripă are
valoarea Da și Febră are valoarea Nu? În acest caz, care sunt variabilele
irelevante pentru interogare?

5.4. Fie situația de trafic din figura 11.3. Strada incidentă în


intersecție, cu sens unic, are 4 benzi de circulație (B1, B2, B3, B4). Până la
intersecție sunt 4 sectoare de drum (S1, S2, S3, S4). Din fiecare sector, o
mașină poate merge înainte, cu probabilitatea de 50% sau la dreapta,
respectiv stânga, cu probabilitățile de 25%. De pe benzile laterale nu se
poate ieși în afara drumului, prin urmare probabilitatea de a merge înainte
este 75%. La capătul străzii, mașinile pot merge pe unul din cele
3 drumuri (Stânga, Înainte, Dreapta).
Modelați această situație cu ajutorul unei rețele bayesiene în
programul bayes.jar.

Indicație. Fiecare sector de drum poate fi modelat ca o variabilă cu 4


valori posibile, corespunzătoare celor 4 benzi. Cele 3 drumuri de după
intersecție pot fi modelate și ele ca 3 variabile distincte, de data aceasta cu
câte 2 valori, Adevărat sau Fals.

191
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Figura 11.3. Model de intersecție

Cu ajutorul programului, răspundeți la următoarele întrebări:


a) Dacă o mașină este pe segmentul S1, banda B1, care sunt
probabilitățile ca după interecție să meargă la Stânga, Înainte sau la
Dreapta? Pentru direcția Înainte, nu ne interesează pe care din cele două
benzi va merge mașina, contează doar direcția.
b) Să presupunem că o mașină merge pe segmentul S1, banda B1,
apoi pe segmentul S2, banda B4. Este posibil? Răspunsul se găsește
calculând probabilitatea evidenței (în program, P(e) Query).
c) Dacă o mașină este pe segmentul S1, banda B1 și o ia la dreapta,
care sunt probabilitățile poziției sale pe sectoarele de drum S3 și S4?
d) Dacă mașina a luat-o la Stânga în intersecție, care sunt
probabilitățile poziției sale anterioare pe sectoarele de drum incidente: S1,
S2, S3 și S4?

192
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 12

Rețele neuronale: regiuni de decizie

1. Obiective

Obiectivele acestui capitol sunt următoarele:

 descrierea caracteristicilor rețelelor neuronale de tip perceptron, cu


unul sau mai multe straturi;
 prezentarea unor funcții de activare pentru neuroni;
 descoperirea empirică a parametrilor unor rețele neuronale simple
care aproximează funcții logice elementare și reprezentarea
regiunilor de decizie ale acestora.

2. Caracteristici ale rețelelor neuronale


artificiale

Preocuparea pentru rețelele neuronale artificiale, denumite în mod


curent „rețele neuronale”, a fost motivată de recunoașterea faptului că
modul în care calculează creierul ființelor vii este complet diferit de cel al
calculatoarelor numerice convenționale. Spre deosebire de mașinile von
Neumann, unde există o unitate de procesare care execută instrucțiunile
stocate în memorie în mod serial, numai o instrucțiune la un moment dat,
rețelele neuronale utilizează în mod masiv paralelismul. Fiind modele
simplificate ale creierului uman, ele au capacitatea de a învăța, spre
deosebire de calculatoarele convenționale, care rămân totuși mai eficiente
pentru sarcinile bazate pe operații aritmetice precise și rapide. Rețelele
neuronale nu dispun de unități de procesare puternice, dimpotrivă, acestea
sunt caracterizate printr-o simplitate extremă, însă interacțiunile lor pe
ansamblu produc rezultate complexe datorită numărului mare de conexiuni.
193
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
O rețea neuronală este o arhitectură de calcul formată din elemente
de procesare simple, masiv interconectate. Ea se aseamănă cu creierul în
două privințe:

 Cunoștințele sunt căpătate de rețea printr-un proces de învățare;


 Cunoștințele sunt depozitate nu în unitățile de procesare (neuroni), ci
în conexiunile interneuronale, cunoscute drept ponderi sinaptice.

Procedura folosită pentru a executa procesul de învățare se numește


algoritm de învățare, funcția căruia este de a modifica ponderile sinaptice
ale rețelei într-un mod sistematic pentru a aproxima cât mai bine datele de
antrenare.

3. Perceptronul cu un singur strat

Începutul rețelelor neuronale artificiale este legat de problema


clasificării unor obiecte definite de o serie de atribute. Cel mai simplu model
era funcția ȘI logic între anumite atribute (prezente sau absente), care să
determine o anumită clasă. Totuși, unele clase pot avea atribute comune, iar
unele valori, în cazul în care provin dintr-un mecanism perceptual, pot fi
afectate de zgomot. Soluția s-a bazat pe faptul că unele atribute sunt mai
importante decât altele pentru determinarea unei anumite clase. O clasă era
determinată dacă sumarea valorilor ponderate depășea un anumit prag, în
concordanță cu legea biologică „totul sau nimic” (dacă un impuls nu
depășește un prag minim, el nu produce nici un răspuns).
Rosenblatt a propus un astfel de model, care rămâne până în prezent
fundamentul structural pentru majoritatea rețelelor neuronale. Fiecărei
conexiuni îi corespunde o valoare reală, numită pondere sinaptică, care
determină efectul intrării respective asupra nivelului de activare a
neuronului. Suma ponderată a intrărilor poartă denumirea de intrare netă
(net input). În figura 12.1, xi reprezintă intrările, wi ponderile sinaptice, f o
funcție de activare, θ valoarea prag iar y ieșirea, care se calculează după
formula:

194
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
 n 
y  f   wi xi    .
 i1 

Figura 12.1. Modelul unui neuron

Rosenblatt a imaginat și un algoritm de învățare pentru așa-numitul


perceptron (figura 12.1). Ideea principală este de a face mici ajustări ale
ponderilor pentru a reduce diferența dintre ieșirea reală a perceptronului și
ieșirea dorită. Ponderile inițiale sunt inițializate aleatoriu (în general în
intervalul [-0,5, 0,5]) și apoi actualizate treptat astfel încât ieșirea să se
apropie de valorile dorite. Exemplele de antrenare sunt prezentate succesiv,
în orice ordine. Dacă în pasul p ieșirea reală este y(p) iar ieșirea dorită este
yd (p), atunci eroarea este:

e(p) = yd(p) – y(p).

Dacă eroarea e este pozitivă, trebuie să mărim y ; dacă este negativă,


y trebuie micșorat. Având în vedere că fiecare intrare are contribuția xi  wi ,
atunci dacă xi este pozitivă, o creștere a ponderii wi va avea ca efect o
creștere a ieșirii perceptronului. Invers, dacă xi este negativă, creșterea
ponderii wi va determina scăderea ieșirii y. De aici poate fi stabilită regula
de învățare a perceptronului:

wi ( p  1)  wi ( p)    e( p)  xi ( p) ,

unde   (0, 1) este numită rata de învățare.

195
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Cu ajutorul perceptronului pot fi învățate de exemplu funcții binare
elementare, precum ȘI, SAU etc. (figura 12.2). Pe abscisă și ordonată sunt
reprezentate valorile celor două intrări, iar culoarea cercurilor reprezintă
rezultatul operației (alb = 0, negru = 1). Perceptronul împarte planul în două
regiuni de decizie (datorită pragului funcției de activare). În cazul
n-dimensional, spațiul soluțiilor va fi divizat tot în două regiuni de un
hiperplan. Acestea sunt probleme liniar separabile. Aici poate fi observată
și utilitatea pragului: în lipsa acestuia, hiperplanul separator ar trece
întotdeauna prin origine, ceea ce nu este de dorit în orice situație.

Figura 12.2. Probleme liniar separabile: ȘI, SAU

Algoritmul de antrenare garantează clasificarea corectă a două clase


pe baza setului de antrenare, cu condiția ca acele clase să fie liniar
separabile.
Algoritmul de antrenare al perceptronului este prezentat mai jos:

se inițializează toate ponderile wi cu 0 sau cu valori aleatorii din intervalul [–0.5, 0.5]
se inițializează rata de învățare eta cu o valoare din intervalul (0, 1], de exemplu 0.1
se inițializează numărul maxim de epoci P, de exemplu 100
p = 0 // numărul epocii curente
erori = true // un flag care indică existența erorilor de antrenare

repetă cât timp p < P și erori == true


{
erori = false
pentru fiecare vector de antrenare xi cu i = 1..N
{

196
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
yi = F(sum(xij * wj)) cu j = 1..n+1
dacă (yi != ydi)
{
e = ydi – yi
erori = true
pentru fiecare intrare j = 1..n+1
wj = wj + eta * xij * e
}
}
p=p+1
}

Foarte multe probleme sunt însă neseparabile liniar. De exemplu,


funcția XOR (figura 12.3) nu poate fi învățată de un perceptron simplu.
Minsky și Papert au demonstrat limitările serioase ale rețelelor de tip
perceptron în aceste situații. De asemenea, ei au studiat posibilitatea
utilizării perceptronilor pentru calculul predicatelor, demonstrându-le
limitele în comparație cu mașina Turing. Concluzia cărții lor a determinat
practic pierderea interesului pentru cercetările în domeniul rețelelor
neuronale pentru aproape 20 de ani, de la începtulul anilor ’70 până în 1986,
când a fost găsită o soluție fezabilă pentru problemele neseparabile liniar.
Trebuie totuși menționat faptul că funcția XOR multidimensională (suma
modulo 2 a argumentelor) continuă să fie o funcție greu de învățat și pentru
rețelele neuronale actuale.

Figura 12.3. Problemă liniar neseparabilă: XOR

197
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
4. Perceptronul multi-strat

Încercările de rezolvare a problemelor neseparabile liniar au condus


la diverse variante privind numărul de straturi de neuroni și funcțiile de
activare utilizate. Perceptronul multi-strat (multilayer perceptron, MLP) este
tipul de rețea neuronală cel mai cunoscut și mai des folosit. De cele mai
multe ori, semnalele se transmit în interiorul rețelei într-o singură direcție:
de la intrare spre ieșire. Nu există bucle, ieșirea fiecărui neuron neafectând
neuronul respectiv. Această arhitectură se numește cu propagare înainte
(feed-forward) (figura 12.4). Straturile care nu sunt conectate direct la mediu
se numesc ascunse. În cele ce urmează, vom număra numai straturile
formate din neuroni propriu-ziși, însă vom spune că intrările sunt grupate în
stratul de intrare.

Figura 12.4. Rețea neuronală feed-forward multi-strat

Există și rețele recurente (cu feedback), în care impulsurile se pot


transmite în ambele direcții, datorită unor conexiuni de reacție în rețea.
Aceste tipuri de rețele sunt foarte puternice și pot avea structuri complexe.
Sunt dinamice, starea lor schimbându-se permanent, până când rețeaua
ajunge la un punct de echilibru iar căutarea unui nou echilibru are loc la
fiecare schimbare a intrării.

198
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Introducerea mai multor straturi a fost determinată de necesitatea
creșterii complexității regiunilor de decizie. După cum am arătat în
secțiunea 3, un perceptron cu un singur strat și o ieșire generează regiuni de
decizie de forma unor semiplane. Adăugând încă un strat, fiecare neuron se
comportă ca un perceptron standard asupra ieșirii neuronilor din stratul
anterior și astfel ieșirea rețelei poate aproxima regiuni de decizie convexe,
rezultate din intersecția semiplanelor generate de neuroni. La rândul său, un
perceptron cu trei straturi poate genera zone de decizie arbitrare (figura
12.5).

Figura 12.5. Regiunile de decizie ale perceptronilor multi-strat

199
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Din punct de vedere al funcției de activare a neuronilor, rețelele
multi-strat nu asigură o creștere a puterii de calcul în raport cu rețelele cu un
singur strat dacă funcțiile de activare sunt liniare, deoarece o funcție liniară
de funcții liniare este tot o funcție liniară. Puterea perceptronului multi-strat
provine tocmai din funcțiile de activare neliniare. Aproape orice funcție
neliniară poate fi folosită în acest scop, cu excepția funcțiilor polinomiale.
Însă funcțiile cele mai des utilizate pentru rețele MLP clasice sunt sigmoida
unipolară (sau logistică), afișată în figura 12.6:

1
f ( s) 
1  e s

și sigmoida bipolară (tangenta hiperbolică), afișată în figura 12.7:

1  e 2 s
f ( s)  .
1  e 2 s

0.5

0
-8 0 8

Figura 12.6. Funcția de activare sigmoidă unipolară

-1
-8 0 8

Figura 12.7. Funcția de activare sigmoidă bipolară

200
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Se poate constata că funcțiile sigmoide se comportă aproximativ
liniar pentru valori absolute mici ale argumentului și se saturează, preluând
oarecum rolul de prag, pentru valori absolute mari ale argumentului.
O aproximare mai simplă a sigmoidei este funcția semiliniară,
definită astfel:

 1, s  1

f ( s)  s, s  (1, 1) .

1, s  1

O rețea neuronală cu un singur strat ascuns, cu un număr posibil


infinit de neuroni, poate aproxima orice funcție reală continuă. Această
propoziție definește proprietatea de aproximator universal a perceptronului.

5. Aplicații

Se dă un prototip de aplicație pentru implementarea unui perceptron


cu un singur strat și a unui perceptron cu un strat ascuns, având configurația
din figura următoare:

Prototipul include interfața grafică cu utilizatorul și desenarea


regiunilor de decizie.

201
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Întrucât aici se presupune că ieșirile neuronilor aparțin intervalului
[0,1], se folosesc următoarele expresii pentru funcțiile de activare:

 funcția prag:

0, s  0
f ( s)  
1, s  0

 funcția semiliniară:

0, s  0

f ( s )  s, s  (0, 1)
1, s  1

 funcția sigmoidă unipolară:

1
f ( s) 
1  e s

La fel de bine, se pot proiecta rețele în care ieșirile aparțin


intervalului [–1, 1].

202
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Scopul aplicațiilor este determinarea ponderilor conexiunilor dintre
neuroni și a valorilor prag astfel încât rețeaua să aproximeze câteva funcții
binare elementare. De exemplu:

Funcția de Funcția de w13 w23 w14 w24 w35 w45 θ3 θ4 θ5


activare aproximat
prag nand 1 0 0 1 -0.5 -0.5 0.5 0.5 -0.5
semiliniară or 1 1 1 1 0.5 0.5 0 0 0
sigmoidă and 0 -2 -6 -6 0 -5 0 -9 -3
sigmoidă or -5 -5 6 6 -3 3 -3 3 0
sigmoidă xor -7 -7 -9 -9 5 -5 -10 -5 2

Scopul aplicațiilor este de a încerca determinarea prin încercări a


parametrilor pentru un perceptron cu un singur strat, pentru probleme liniar
separabile și apoi determinarea automată a acestora cu ajutorul algoritmului
de antrenare al perceptronului. Pentru un perceptron cu un singur strat,
valorile ponderilor determină rotația dreptei de separare a celor două clase,
iar pragul controlează apropierea acestei drepte de origine.
Pentru perceptronul multi-strat și probleme neseparabile liniar, se
poate folosi algoritmul backpropagation (inclus într-un dll în care codul este
bazat pe cel din biblioteca NeuronDotNet, https://sourceforge.net/
projects/neurondotnet). Acest algoritm va fi tratat în capitolul 13.

203
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
5.1. Completați metodele care calculează cele trei funcții de activare
și metodele care calculează ieșirile perceptronilor: StepActivation,
SemiliniarActivation, SigmoidActivation, respectiv SLP și MLP.

5.2. Completați metoda corespunzătoare antrenării perceptronului cu


un singur strat: buttonTrainSLP_Click.

5.3. Determinați parametrii corespunzători pentru aproximarea


funcției SAU, folosind funcția de activare prag pentru un perceptron cu un
singur strat și pentru perceptronul multi-strat din figura de mai sus.

5.4. Aproximați funcția NON-XOR (XNOR) cu perceptronul multi-


strat și funcția de activare sigmoidă.

5.5. Aproximați funcția NON-ȘI (NAND) folosind funcția de activare


semiliniară și cele două tipuri de perceptron.

MainForm.cs

using System;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

namespace DecisionRegions
{
public partial class MainForm : Form
{
/// <summary>
/// Functie care calculeaza iesirea retelei in functie de tipul ei: perceptron cu un singur
strat sau perceptron multistrat
/// </summary>
private Func<double, double, double> NetworkFunction;

204
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Functie care reprezinta functia de activare a retelei: prag, semiliniara sau sigmoida
unipolara
/// </summary>
private Func<double, double> ActivationFunction;

private double w13, w23, w14, w24, t3, t4, w35, w45, t5; // ponderile si pragurile

public MainForm()
{
InitializeComponent();

comboBoxType.SelectedIndex = 0;
comboBoxActivation.SelectedIndex = 0;

ActivationFunction = StepActivation; // functia de activare implicita este functia prag


NetworkFunction = SLP; // reteaua implicita este perceptronul cu un singur strat
}

/// <summary>
/// Functia de activare prag
/// </summary>
private double StepActivation(double x)
{
throw new Exception("Aceasta metoda trebuie implementata.");
}

/// <summary>
/// Functia de activare semiliniara
/// </summary>
private double SemiliniarActivation(double x)
{
throw new Exception("Aceasta metoda trebuie implementata.");
}

/// <summary>
/// Functia de activare sigmoida unipolara
/// </summary>
private double SigmoidActivation(double x)
{
throw new Exception("Aceasta metoda trebuie implementata.");
}

/// <summary>
/// Citeste valorile ponderilor si pragurilor din interfata grafica
/// </summary>
private void ReadParametersFromGUI()

205
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
{
if (comboBoxType.SelectedIndex == 0) // SLP
{
w13 = ConvertToDouble(textBoxSw13.Text);
w23 = ConvertToDouble(textBoxSw23.Text);
t3 = ConvertToDouble(textBoxSt3.Text);
}
else if (comboBoxType.SelectedIndex == 1) // MLP
{
w13 = ConvertToDouble(textBoxMw13.Text);
w23 = ConvertToDouble(textBoxMw23.Text);
w14 = ConvertToDouble(textBoxMw14.Text);
w24 = ConvertToDouble(textBoxMw24.Text);
t3 = ConvertToDouble(textBoxMt3.Text);
t4 = ConvertToDouble(textBoxMt4.Text);

w35 = ConvertToDouble(textBoxMw35.Text);
w45 = ConvertToDouble(textBoxMw45.Text);
t5 = ConvertToDouble(textBoxMt5.Text);
}
}

/// <summary>
/// Converteste numerele citite ca text din interfata in valori reale
/// </summary>
private double ConvertToDouble(string s)
{
if (s.Contains(","))
MessageBox.Show("Folositi punctul ca separator! (" + s + ")");

try
{
CultureInfo ci = (CultureInfo)(CultureInfo.CurrentCulture.Clone());
ci.NumberFormat.NumberDecimalSeparator = ".";
return Convert.ToDouble(s, ci);
}
catch
{
MessageBox.Show("Numar invalid: " + s);
return 0;
}
}

206
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Metoda care calculeaza iesirea perceptronului cu un singur strat pentru intrarile x si y
/// </summary>
private double SLP(double x1, double x2)
{
// double s = suma ponderata a intrarilor cu scaderea pragului, conform primei
ecuatii din text
// se folosesc ponderile w13, w23 si pragul t3
// in functie de alegerea utilizatorului, ActivationFunction poate fi: StepActivation, S
emiliniarActivation sau SigmoidActivation
// functia de activare este setata in evenimentul comboBoxActivation_SelectedInde
xChanged
// in metoda curenta se lucreaza in mod generic doar cu "ActivationFunction": retur
n ActivationFunction(s);

throw new Exception("Aceasta metoda trebuie implementata.");


}

/// <summary>
/// Antreneaza perceptronul cu un singur strat si seteaza ponderile in interfata
/// </summary>
private void buttonTrainSLP_Click(object sender, EventArgs e)
{
comboBoxType.SelectedIndex = 0;
comboBoxActivation.SelectedIndex = 0;

// algoritmul de antrenare al perceptronului

Random rand = new Random();

int trainingSetSize = 4;
int noInputs = 2;
double alpha = 0.1; // rata de invatare

int[,] inputs = new int[trainingSetSize, noInputs + 1]; // intrarea -1 pentru prag


int[] outputs = new int[trainingSetSize];
double[] weights = new double[noInputs + 1];

inputs[0, 0] = 0; inputs[0, 1] = 0; inputs[0, 2] = -1;


inputs[1, 0] = 0; inputs[1, 1] = 1; inputs[1, 2] = -1;
inputs[2, 0] = 1; inputs[2, 1] = 0; inputs[2, 2] = -1;
inputs[3, 0] = 1; inputs[3, 1] = 1; inputs[3, 2] = -1;

outputs[0] = Convert.ToInt32(textBox00.Text);
outputs[1] = Convert.ToInt32(textBox01.Text);
outputs[2] = Convert.ToInt32(textBox10.Text);
outputs[3] = Convert.ToInt32(textBox11.Text);

207
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
weights[0] = rand.Next(10) / 10.0 - 0.5;
weights[1] = rand.Next(10) / 10.0 - 0.5;
weights[2] = rand.Next(10) / 10.0 - 0.5;

int maxEpochs = 100;


int[] actualOutputs = new int[outputs.Length];

// aici trebuie inclus algoritmul de antrenare pentru perceptronul cu un singur strat

// weight fiind double, pot aparea erori de ordinul 1e-8-1e-


10, care la rotunjire sa schimbe rezultatul cand linia de separare este foarte apropiata de u
n punct
// la sfarsitul unei epoci de antrenare, se recomanda includerea urmatoarei secvent
e, care rotunjeste toate ponderile la o zecimala
// for (int j = 0; j < noInputs + 1; j++)
// weights[j] = Math.Round(weights[j], 1);

textBoxSw13.Text = weights[0].ToString("F1");
textBoxSw23.Text = weights[1].ToString("F1");
textBoxSt3.Text = weights[2].ToString("F1");

throw new Exception("Aceasta metoda trebuie completata.");


}

/// <summary>
/// Metoda care calculeaza iesirea perceptronului cu doi neuroni in stratul ascuns pen
tru intrarile x si y
/// </summary>
private double MLP(double x1, double x2)
{
// pentru fiecare neuron, se calculeaza suma intrarilor ponderate, se scade pragul si
se aplica functia de activare
// neuronul 5 are ca intrari iesirile neuronilor 3 si 4
// iesirea neuronului 5 este iesirea retelei

throw new Exception("Aceasta metoda trebuie implementata.");


}

208
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Antreneaza perceptronul multistrat si seteaza ponderile in interfata
/// </summary>
private void buttontrainMLP_Click(object sender, EventArgs e)
{
comboBoxType.SelectedIndex = 1;
comboBoxActivation.SelectedIndex = 2;

// apel algoritm backpropagation in dll

double[] y = new double[] {


Convert.ToDouble(textBox00.Text),
Convert.ToDouble(textBox01.Text),
Convert.ToDouble(textBox10.Text),
Convert.ToDouble(textBox11.Text) };

BPLib.Backprop.Train(y, out double[] w);

textBoxMw13.Text = w[0].ToString("F6");
textBoxMw23.Text = w[1].ToString("F6");
textBoxMt3.Text = w[2].ToString("F6");
textBoxMw14.Text = w[3].ToString("F6");
textBoxMw24.Text = w[4].ToString("F6");
textBoxMt4.Text = w[5].ToString("F6");
textBoxMw35.Text = w[6].ToString("F6");
textBoxMw45.Text = w[7].ToString("F6");
textBoxMt5.Text = w[8].ToString("F6");
}

/// <summary>
/// Selecteaza tipul de perceptron atunci cand utilizatorul il alege din combobox
/// </summary>
private void comboBoxType_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxType.SelectedIndex == 0)
{
groupBoxSLP.Enabled = true;
groupBoxMLP.Enabled = false;
NetworkFunction = SLP;
}
else if (comboBoxType.SelectedIndex == 1)
{
groupBoxSLP.Enabled = false;
groupBoxMLP.Enabled = true;
NetworkFunction = MLP;
}
}

209
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Selecteaza functia de activare atunci cand utilizatorul o alege din combobox
/// </summary>
private void comboBoxActivation_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxActivation.SelectedIndex == 0)
ActivationFunction = StepActivation;
else if (comboBoxActivation.SelectedIndex == 1)
ActivationFunction = SemiliniarActivation;
else if (comboBoxActivation.SelectedIndex == 2)
ActivationFunction = SigmoidActivation;
}

/// <summary>
/// Calculeaza si afiseaza iesirile retelei pentru combinatiile de intrari (0,0), (0,1), (1,0)
si (1,1)
/// </summary>
private void ComputeResults()
{
textBoxResults.Clear();
for (int x1 = 0; x1 <= 1; x1++)
for (int x2 = 0; x2 <= 1; x2++)
{
double y = NetworkFunction(x1, x2);
textBoxResults.AppendText(string.Format("{0} {1} {2:F3}\r\n", x1, x2, y));
}
}

/// <summary>
/// Evenimentul de click al butonului "Calculeaza"
/// </summary>
private void buttonCompute_Click(object sender, EventArgs e)
{
ReadParametersFromGUI();
ComputeResults();
pictureBoxRegions.Refresh();
}

210
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Desenarea regiunilor de decizie pentru retea in picturebox. Intrarile retelei sunt aic
i numere reale din intervalul [-0.5, 1.5]
/// </summary>
private void pictureBoxRegions_Paint(object sender, PaintEventArgs e)
{
int size = pictureBoxRegions.Width; // patrat
Bitmap b = new Bitmap(size, size);
Graphics g = Graphics.FromImage(b);
g.Clear(Color.White);

for (double x1 = -0.5; x1 < 1.5; x1 += 0.01)


for (double x2 = -0.5; x2 < 1.5; x2 += 0.01)
{
double y = NetworkFunction(x1, x2);
int gray = (int)(y * 255);
Color c = Color.FromArgb(gray, gray, gray);

int xScreen = (int)(size * (x1 + 0.5) / 2.0);


int yScreen = size - (int)(size * (x2 + 0.5) / 2.0);

g.DrawLine(new Pen(c), xScreen, yScreen, xScreen, yScreen + 1);


}

for (int x1 = 0; x1 <= 1; x1++)


for (int x2 = 0; x2 <= 1; x2++)
{
int xScreen = (int)(size * (x1 + 0.5) / 2.0);
int yScreen = size - (int)(size * (x2 + 0.5) / 2.0);
g.DrawEllipse(Pens.Red, xScreen - 2, yScreen - 2, 4, 4);
}

e.Graphics.DrawImage(b, 0, 0);
}

/// <summary>
/// Testeaza daca doua numere reale sunt egale (in reprezentarea double pot aparea
erori mici care fac ca testarea normala a egalitatii sa esueze)
/// </summary>
/// <returns></returns>
private bool AreEqual(double x, double y)
{
if (Math.Abs(x - y) < 1e-8)
return true;
else
return false;
}

211
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Informatii despre program si autor
/// </summary>
private void buttonAbout_Click(object sender, EventArgs e)
{
const string copyright =
"Retele neuronale - Regiuni de decizie\r\n" +
"Inteligenta artificiala, Laboratorul 12\r\n" +
"(c)2016-2020 Florin Leon\r\n" +
"http://florinleon.byethost24.com/lab_ia.htm";

MessageBox.Show(copyright, "Despre Retele neuronale");


}

/// <summary>
/// Inchiderea programului
/// </summary>
private void buttonExit_Click(object sender, EventArgs e)
{
Environment.Exit(0);
}
}
}

212
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 13

Algoritmul de retro-propagare
(backpropagation)

1. Obiective

Obiectivele acestui capitol sunt legate de prezentarea algoritmului de


retro-propagare (backpropagation), utilizat pentru antrenarea rețelelor
neuronale de tip perceptron multi-strat.

2. Descrierea algoritmului

Rețelele neuronale au capacitatea de a învăța, însă modalitatea


concretă în care se realizează acest proces este dată de algoritmul folosit
pentru antrenare. O rețea se consideră antrenată dacă aplicarea unui vector
de intrare conduce la obținerea unei ieșiri dorite, sau foarte apropiate de
aceasta.
Antrenarea constă în aplicarea secvențială a diferiți vectori de intrare
și ajustarea ponderilor din rețea cu ajutorul unui algoritm de antrenare. În
acest timp, ponderile conexiunilor converg gradual spre anumite valori
pentru care fiecare vector de intrare produce vectorul de ieșire dorit. După
aplicarea unei intrări, se compară ieșirea calculată cu ieșirea dorită, după
care diferența este folosită pentru modificarea ponderilor cu scopul
minimizării erorii la un nivel acceptabil.
Algoritmul backpropagation este cel mai cunoscut și utilizat
algoritm de antrenare pentru rețele neuronale de tip perceptron multi-strat.
Numit și algoritmul delta generalizat, el se bazează pe minimizarea
diferenței dintre ieșirea dorită și ieșirea reală, prin metoda gradientului
descendent. Se definește gradientul unei funcții F(x,y,z,...) drept:

213
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
F F F
F  i j k  ...
x y z

Gradientul ne spune cum variază funcția în diferite direcții. Ideea


algoritmului este găsirea minimului funcției de eroare în raport cu ponderile
conexiunilor. Eroarea este dată de diferența dintre ieșirea dorită și ieșirea
efectivă a rețelei. Cea mai utilizată funcție de eroare este eroarea medie
pătratică (mean squared error, MSE). Dacă avem în mulțimea de antrenare K
vectori iar rețeaua are N ieșiri, atunci:

1 1
  ekn2    yknd  ykn  ,
2
MSE 
KN k n KN k n

d
unde ykn este valoarea dorită la ieșirea n a rețelei pentru vectorul k, iar ykn
este ieșirea efectivă a rețelei.
Algoritmul pentru un perceptron multi-strat cu un singur strat ascuns
este următorul.

Pasul 1: Inițializarea

Toate ponderile și pragurile rețelei sunt inițializate cu valori aleatorii


nenule, distribuite uniform într-un mic interval, de exemplu (– 0.1, 0.1).
Dacă valorile acestea sunt 0, gradienții care vor fi calculați pe
parcursul antrenării vor fi tot 0 (dacă nu există o legătură directă între intrare
și ieșire) și rețeaua nu va învăța. Este chiar indicată încercarea mai multor
antrenări, cu ponderi inițiale diferite, pentru găsirea celei mai bune valori
pentru funcția de cost (minimul erorii). Dimpotrivă, dacă valorile inițiale
sunt mari, ele tind să satureze unitățile respective. În acest caz, derivata
funcției sigmoide este foarte mică. Ea acționează ca un factor de
multiplicare în timpul învățării și deci unitățile saturate vor fi aproape
blocate, ceea ce face învățarea foarte lentă.

214
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Pasul 2: O nouă epocă de antrenare

O epocă reprezintă prezentarea tuturor exemplelor din mulțimea de


antrenare. În majoritatea cazurilor, antrenarea rețelei presupune mai multe
epoci de antrenare.
Pentru ca antrenarea să nu fie influențată de ordinea de prezentare a
vectorilor de antrenare, ponderile vor fi ajustate numai după ce toți vectorii
sunt aplicați rețelei. În acest scop, corecțiile ponderilor trebuie memorate și
ajustate după prezentarea fiecărui vector din mulțimea de antrenare. La
sfârșitul unei epoci de antrenare, se modifică ponderile o singură dată.
Există și varianta online, mai simplă, în care ponderile sunt actualizate
direct. În acest caz, poate conta ordinea în care sunt prezentați rețelei
vectorii de antrenare.
Se inițializează corecțiile ponderilor și eroarea curentă cu 0: wij  0
și E = 0.

Pasul 3: Propagarea semnalului înainte

3.1. La intrările rețelei se aplică un vector din mulțimea de antrenare.

3.2. Se calculează ieșirile neuronilor din stratul ascuns:

 n 
y j  f   xi  wij   j  ,
 i 1 

unde n este numărul de intrări ale neuronului j din stratul ascuns, iar f este
funcția de activare sigmoidă.
Pentru simplitate, pragul este considerat ca fiind ponderea unei
conexiuni suplimentare a cărei intrare este întotdeauna egală cu 1. Expresia
de mai sus devine acum:

 n 1 
y j  f   xi  wij  .
 i 1 

215
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
3.3. Se calculează ieșirile reale ale rețelei:

 m1 
yk  f   y j  w jk  ,
 j 1 

unde m este numărul de intrări ale neuronului k din stratul de ieșire.

3.4. Se actualizează eroarea medie pătratică pe epocă:

1 1
  ekn2    yknd  ykn  .
2
MSE 
KN k n KN k n

Pasul 4: Propagarea erorilor înapoi și ajustarea ponderilor

4.1. Se calculează gradienții erorilor pentru neuronii din stratul de


ieșire:

 k  f ' ek ,

unde f ' este derivata funcției de activare iar eroarea ek  ykd  yk .


Dacă folosim sigmoida unipolară, derivata acesteia este:

ex
f ' ( x)   f ( x)  1  f ( x)  .
1  e 
x 2

Vom avea:

 k  yk  1  yk  ek .

4.2. Se actualizează corecțiile ponderilor dintre stratul ascuns și


stratul de ieșire:

w jk  w jk    y j   k ,

216
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
unde α este rata de învățare.

4.3. Se calculează gradienții erorilor pentru neuronii din stratul


ascuns:

l
 j  y j  1  y j     k  w jk ,
k 1

unde l este numărul de ieșiri ale rețelei.

4.4. Se actualizează corecțiile ponderilor dintre stratul de intrare și


stratul ascuns:

wij  wij    xi   j .

Pasul 5. O nouă iterație

Dacă mai sunt vectori de test în epoca de antrenare curentă, se trece


la pasul 3.
Dacă nu, se actualizează ponderile tuturor conexiunilor pe baza
corecțiilor ponderilor:

wij  wij  wij .

Dacă s-a încheiat o epocă, se testează dacă s-a îndeplinit criteriul de


terminare (E < Emax sau atingerea unui număr maxim de epoci de antrenare).
Dacă nu, se trece la pasul 2. Dacă da, algoritmul se termină.

3. Considerente practice

În implementarea algoritmului, apar o serie de probleme practice,


legate în special de alegerea parametrilor antrenării și a configurației rețelei.
În primul rând, o rată de învățare mică determină o convergență lentă
a algoritmului, pe când una prea mare poate cauza eșecul (algoritmul va
217
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
„sări” peste soluție). Pentru probleme relativ simple, o rată de învățare
α = 0,5 este acceptabilă, însă în general pentru rețele clasice, nu profunde, se
recomandă rate de învățare în jur de 0,1 – 0,2.
Este recomandată preprocesarea și postprocesarea datelor, astfel
încât rețeaua să opereze cu valori scalate, de exemplu în intervalul [0,1, 0,9]
pentru sigmoida unipolară.
O altă problemă caracteristică acestui mod de antrenare este dată de
minimele locale. Într-un minim local, gradienții erorii devin 0 și învățarea
nu mai continuă. O soluție este încercarea mai multor antrenări
independente, cu ponderi inițializate diferit la început, ceea ce crește
probabilitatea găsirii minimului global. Pentru probleme mari, acest lucru
poate fi greu de realizat și atunci pot fi acceptate și minime locale, cu
condiția ca erorile să fie suficient de mici. De asemenea, se pot încerca
diferite configurații ale rețelei, cu un număr mai mare de neuroni în stratul
ascuns sau cu mai multe straturi ascunse, care în general conduc la minime
locale mai mici. Totuși, deși minimele locale sunt într-adevăr o problemă, în
practică nu reprezintă dificultăți de nesoluționat.
O chestiune importantă este alegerea unei configurații cât mai bune
pentru rețea din punct de vedere al numărului de neuroni în straturile
ascunse. În multe situații, un singur strat ascuns este suficient. Nu există
niște reguli precise de alegere a numărului de neuroni. În general, rețeaua
poate fi văzută ca un sistem în care numărul vectorilor de test înmulțit cu
numărul de ieșiri reprezintă numărul de ecuații iar numărul ponderilor
reprezintă numărul necunoscutelor. Ecuațiile sistemului sunt în general
neliniare și foarte complexe și deci este foarte dificilă rezolvarea lor exactă
prin mijloace convenționale. Algoritmul de antrenare urmărește tocmai
găsirea unor soluții aproximative care să minimizeze erorile.
O rețea neuronală trebuie să fie capabilă de generalizare. Dacă
rețeaua aproximează bine mulțimea de antrenare, aceasta nu este o garanție
că va găsi soluții la fel de bune și pentru datele din altă mulțime, cea de test.
Generalizarea presupune existența în date a unor regularități, a unui model
care poate fi învățat. În analogie cu sistemele liniare clasice, aceasta ar
însemna niște ecuații redundante. Astfel, dacă numărul de ponderi este mai
mic decât numărul de vectori de test, pentru o aproximare corectă rețeaua
trebuie să se bazeze pe modelele intrinseci din date, modele care se vor
regăsi și în datele de test. O regulă euristică afirmă că numărul de ponderi

218
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
trebuie să fie în jur sau sub o zecime din produsul dintre numărul de vectori
de antrenare și numărul de ieșiri. În unele situații însă (de exemplu, dacă
datele de antrenare sunt relativ puține), numărul de ponderi poate fi chiar
jumătate din produs.
Pentru un perceptron multi-strat se consideră că numărul de neuroni
dintr-un strat trebuie să fie suficient de mare pentru ca acest strat să
furnizeze trei sau mai multe laturi pentru fiecare regiune convexă
identificată de stratul următor. Deci numărul de neuroni dintr-un strat ar
trebui să fie aproximativ de trei ori mai mare decât cel din stratul următor.
După cum am menționat, un număr insuficient de ponderi conduce la
„sub-potrivire” (underfitting), în timp ce un număr prea mare de ponderi
conduce la „supra-potrivire” (overfitting), fenomene prezentate în figura de
mai jos. Același lucru apare dacă numărul de epoci de antrenare este prea
mic sau prea mare.

O metodă de rezolvare a acestei probleme este oprirea antrenării în


momentul în care se atinge cea mai bună generalizare. Pentru o rețea
suficient de mare, eroarea de antrenare scade în mod continuu pe măsură ce
crește numărul epocilor de antrenare. Totuși, pentru date diferite de cele din
mulțimea de antrenare, adică pentru mulțimea de test, se constată că eroarea
scade la început până la un punct în care începe din nou să crească. De
aceea, oprirea antrenării trebuie făcută în momentul când eroarea pentru
mulțimea de test este minimă.
Cea mai simplă modalitate de a estima capacitatea de generalizare
este împărțirea mulțimii de date disponibile în 2/3 pentru antrenare și 1/3
pentru testare. Antrenarea se oprește atunci când începe să crească eroarea
219
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
pentru mulțimea de validare, moment numit „punct de maximă
generalizare”. În funcție de performanțele rețelei în acest moment, se pot
încerca apoi diferite configurații, crescând sau micșorând numărul de
neuroni din stratul (sau straturile) ascuns(e).
O metodă mai complexă, dar care este cea mai utilizată pentru
evaluarea și compararea performanțelor modelelor de învățare, este
validarea încrucișată (cross-validation), în care datele sunt împărțite în n
grupuri (de obicei 10), n – 1 grupuri se folosesc pentru antrenare, iar al n-lea
pentru testare. Procesul se repetă de n ori, schimbând grupul de test. În final,
se face media performanțelor obținute pentru cele n grupuri de test.

4. Aplicație

Implementați algoritmul backpropagation pentru o rețea neuronală de


tip perceptron multi-strat, cu un singur strat ascuns și cu un număr variabil
de unități în stratul ascuns.
Se dă un prototip de aplicație care își propune recunoașterea cifrelor
formate de utilizator pe un afișaj cu 7 led-uri.
Mulțimea de antrenare, citită din fișierul segments.data, conține 7
intrări (starea celor 7 led-uri), 10 ieșiri (1 pentru cifra formată, 0 în rest) și
10 vectori (cifrele 0-9):

Segments.data

7 10 10
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0
1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0
1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0
0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0
1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0
1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0
1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0
1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1

220
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Studiați structura prototipului de aplicație. Diagrama UML de clase
a programului complet este următoarea:

221
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Implementați următoarele metode, corespunzătoare algoritmului
backpropagation:

Metoda Train(double learningRate, int maxEpochs, double


maxError) din clasa BackPropAlgorithm
Metoda de antrenare a rețelei. Antrenarea se realizează până la
atingerea unui număr maxim de epoci (maxEpochs) sau până la atingerea
unei erori acceptabile (maxError). Apelează metoda TrainOneEpoch.

Metoda TrainOneEpoch() din clasa BackPropAlgorithm


Reprezintă o epocă de antrenare pentru toți vectorii de antrenare.
Calculează și eroarea medie pătratică (MSE) obținută la finalul epocii.

Metoda ForwardPass(double[] inputVector) din clasa


NeuralNetwork
Pasul de propagare înainte al algoritmului.

222
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Metoda BackwardPass(double[] inputVector, double[]
desiredOutputs) din clasa BackPropAlgorithm
Pasul de propagare înapoi al algoritmului.

MainForm.cs

using System;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

namespace Backpropagation
{
public partial class MainForm : Form
{
private Dataset _dataset;
private NeuralNetwork _nn;
private BackPropAlgorithm _backProp;

public MainForm()
{
InitializeComponent();

// multimea de antrenare, cu 10 vectori (cifrele 0-9), 7 intrari (starea celor 7 led-


uri) si 10 iesiri (1 pentru cifra formata, 0 in rest)
_dataset = new Dataset("segments.data");

// scalarea datelor in intervalul (0.1, 0.9), pentru a aplica sigmoida unipolara


_dataset.ScaleData();
}

#region CheckBoxes Segments

private void checkBox0_CheckedChanged(object sender, System.EventArgs e)


{
checkBox0.BackColor = checkBox0.Checked ? Color.Red : SystemColors.Control;
}

private void checkBox1_CheckedChanged(object sender, System.EventArgs e)


{
checkBox1.BackColor = checkBox1.Checked ? Color.Red : SystemColors.Control;
}

private void checkBox2_CheckedChanged(object sender, System.EventArgs e)

223
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
{
checkBox2.BackColor = checkBox2.Checked ? Color.Red : SystemColors.Control;
}

private void checkBox3_CheckedChanged(object sender, System.EventArgs e)


{
checkBox3.BackColor = checkBox3.Checked ? Color.Red : SystemColors.Control;
}

private void checkBox4_CheckedChanged(object sender, System.EventArgs e)


{
checkBox4.BackColor = checkBox4.Checked ? Color.Red : SystemColors.Control;
}

private void checkBox5_CheckedChanged(object sender, System.EventArgs e)


{
checkBox5.BackColor = checkBox5.Checked ? Color.Red : SystemColors.Control;
}

private void checkBox6_CheckedChanged(object sender, System.EventArgs e)


{
checkBox6.BackColor = checkBox6.Checked ? Color.Red : SystemColors.Control;
}

#endregion CheckBoxes Segments

/// <summary>
/// Verifica starea checkbox-urilor care simuleaza display-
ul si returneaza vectorul corespunzator.
/// </summary>
/// <returns></returns>
private double[] GenerateTestVector()
{
double[] testVector = new double[_dataset.NoInputs]; // 7 led-uri

if (checkBox0.Checked)
testVector[0] = 1;

if (checkBox1.Checked)
testVector[1] = 1;

if (checkBox2.Checked)
testVector[2] = 1;

if (checkBox3.Checked)
testVector[3] = 1;

224
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
if (checkBox4.Checked)
testVector[4] = 1;

if (checkBox5.Checked)
testVector[5] = 1;

if (checkBox6.Checked)
testVector[6] = 1;

return testVector;
}

/// <summary>
/// Antreneaza reteaua si afiseaza rezultatele.
/// </summary>
private void buttonTrain_Click(object sender, System.EventArgs e)
{
CultureInfo ci = (CultureInfo)(CultureInfo.CurrentCulture.Clone());
ci.NumberFormat.NumberDecimalSeparator = ".";

int maxEpochs = Convert.ToInt32(textBoxNoEpochs.Text);


int noHiddenNeurons = Convert.ToInt32(textBoxNoHidden.Text);
double learningRate = Convert.ToDouble(textBoxLearningRate.Text, ci);
double maxError = Convert.ToDouble(textBoxMaxError.Text, ci);

_nn = new NeuralNetwork(_dataset.NoInputs, noHiddenNeurons, _dataset.NoOutp


uts);

_backProp = new BackPropAlgorithm(_nn, _dataset);

_backProp.Train(learningRate, maxEpochs, maxError);

richTextBoxTraining.Text = string.Format("MSE = {0:F3}\r\n\r\n", _backProp.MeanS


quareError);

richTextBoxTraining.Text += "Rezultatele de dupa antrenare:\r\n\r\n";

for (int i = 0; i < _dataset.NoVectors; i++)


{
string s = "";
double[] outputs = _nn.Propagate(GetVectorFromDataset(i));
_dataset.RescaleVector(outputs);
for (int j = 0; j < _dataset.NoOutputs; j++)
s += outputs[j].ToString("F2") + " ";
richTextBoxTraining.Text += s + "\r\n";
}

225
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
richTextBoxTraining.Focus();
}

/// <summary>
/// Returneaza vectorul cu indexul i din multimea de antrenare.
/// </summary>
private double[] GetVectorFromDataset(int i)
{
double[] v = new double[_dataset.NoInputs];
for (int k = 0; k < _dataset.NoInputs; k++)
v[k] = _dataset.Data[i, k];
return v;
}

/// <summary>
/// Foloseste reteaua pentru a recunoaste o cifra formata de utilizator pe display.
/// </summary>
private void buttonPropagate_Click(object sender, EventArgs e)
{
richTextBoxTesting.Clear();

double[] testVector = GenerateTestVector();

string s = "Vectorul semnului format:\r\n";


for (int i = 0; i < _dataset.NoInputs; i++)
s += string.Format("{0:F0} ", testVector[i]);
richTextBoxTesting.Text += s + "\r\n\r\n";

double max = double.MinValue;


int maxIndex = -1;

_dataset.ScaleVector(testVector);
double[] outputs = _nn.Propagate(testVector);
_dataset.RescaleVector(outputs);

s = "Iesirea retelei:\r\n";
for (int i = 0; i < _dataset.NoOutputs; i++)
{
s += string.Format("{0:F2} ", outputs[i]);
if (outputs[i] > max)
{
max = outputs[i];
maxIndex = i;
}
}

richTextBoxTesting.Text += s + "\r\n\r\n";

226
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
richTextBoxTesting.Text += string.Format("Cifra recunoscuta: {0}", maxIndex);

richTextBoxTesting.Focus();
}

private void buttonAbout_Click(object sender, EventArgs e)


{
const string copyright =
"Retele neuronale - Algoritmul backpropagation\r\n" +
"Inteligenta artificiala, Laboratorul 13\r\n" +
"(c)2016-2020 Florin Leon\r\n" +
"http://florinleon.byethost24.com/lab_ia.htm";

MessageBox.Show(copyright, "Despre Algoritmul backpropagation");


}

private void buttonExit_Click(object sender, EventArgs e)


{
Environment.Exit(0);
}
}
}

NeuralNetwork.cs

using System;
namespace Backpropagation
{
/// <summary>
/// Clasa corespunzatoare unei retele neuronale de tip perceptron multistrat cu un sing
ur strat ascuns.
/// </summary>
public class NeuralNetwork
{
/// <summary>
/// Numarul de intrari
/// </summary>
public int NoInputs { get; private set; }

/// <summary>
/// Numarul de neuroni din stratul ascuns
/// </summary>
public int NoHidden { get; private set; }

227
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Numarul de iesiri
/// </summary>
public int NoOutputs { get; private set; }

/// <summary>
/// Neuronii din stratul ascuns
/// </summary>
public Neuron[] HiddenLayer { get; private set; }

/// <summary>
/// Neuronii din stratul de iesire
/// </summary>
public Neuron[] OutputLayer { get; private set; }

/// <summary>
/// Constructorul clasei.
/// </summary>
/// <param name="noHidden">Numarul de neuroni din stratul ascuns</param>
public NeuralNetwork(int noInputs, int noHidden, int noOutputs)
{
NoInputs = noInputs;
NoHidden = noHidden;
NoOutputs = noOutputs;

HiddenLayer = new Neuron[NoHidden];


for (int j = 0; j < NoHidden; j++)
HiddenLayer[j] = new Neuron(NoInputs + 1); // + "bias"

OutputLayer = new Neuron[NoOutputs];


for (int k = 0; k < NoOutputs; k++)
OutputLayer[k] = new Neuron(NoHidden + 1); // + "bias"
}

/// <summary>
/// Metoda care calculeaza iesirile retelei pentru un vector primit ca parametru.
/// </summary>
public double[] Propagate(double[] vector)
{
ForwardPass(vector);

double[] y = new double[NoOutputs];


for (int k = 0; k < NoOutputs; k++)
y[k] = OutputLayer[k].Activation;

return y;
}

228
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <summary>
/// Pasul de propagare inainte al algoritmului.
/// </summary>
private void ForwardPass(double[] inputVector)
{
// se calculeaza activarile neuronilor din stratul ascuns si apoi din stratul de iesire

throw new Exception("Aceasta metoda trebuie implementata.");


}
}
}

Neuron.cs

using System;

namespace Backpropagation
{
/// <summary>
/// Clasa corespunzatoare unui neuron din retea.
/// </summary>
public class Neuron
{
/// <summary>
/// Ponderile conexiunilor de intrare ale neuronului.
/// </summary>
public double[] Weights { get; set; }

/// <summary>
/// Valoarea functiei de activare. Valoarea se calculeaza cu metoda ComputeActivatio
n, dar este pastrata
/// in proprietatea Activation. In algoritmul backpropagation, functiile de activare se c
alculeaza
/// doar in pasul "inainte", iar in pasul "inapoi" se folosesc valorile de activare calculat
e deja.
/// </summary>
public double Activation { get; private set; }

private int _noWeights;


private static Random _r = new Random();

/// <summary>
/// Constructorul clasei.
/// </summary>

229
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// <param name="noWeightsIncludingBias">Numarul de ponderi (egal cu numarul d
e intrari) + 1. Valoarea 1 corespunde termenului "bias" (prag), o conexiune conectata mer
eu la o intrare egala cu 1</param>
public Neuron(int noWeightsIncludingBias)
{
_noWeights = noWeightsIncludingBias;

Weights = new double[_noWeights]; // ultima este "bias"-ul (bias = -prag)


for (int i = 0; i < _noWeights; i++)
Weights[i] = InitWeight();
}

/// <summary>
/// Initializeaza ponderea cu un numar aleatoriu din intervalul [-0.1, 0.1), diferit de 0.
/// </summary>
private double InitWeight()
{
double w = 0;
while (Math.Abs(w) < 1e-6)
w = _r.NextDouble() / 5.0 - 0.1;
return w;
}

/// <summary>
/// Calculeaza functia de activare pentru un vector de intrare si memoreaza valoarea i
n proprietatea Activation.
/// </summary>
/// <param name="x">Vectorul de intrare</param>
public void Activate(double[] x)
{
double s = 0;

for (int i = 0; i < _noWeights - 1; i++)


s += Weights[i] * x[i];

s += Weights[_noWeights - 1] * 1.0; // "bias"-ul

Activation = SigmoidFunction(s);
}

/// <summary>
/// Functia sigmoida unipolara.
/// </summary>
private double SigmoidFunction(double x)
{
return 1.0 / (1.0 + Math.Exp(-x));
}

230
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}
}

Dataset.cs

using System;
using System.IO;

namespace Backpropagation
{
/// <summary>
/// Clasa corespunzatoare unei multimi de date de antrenare.
/// </summary>
public class Dataset
{
/// <summary>
/// Datele propriu-
zise memorate in forma unei matrice, in care numarul de linii este numarul de vectori de a
ntrenare,
/// iar numarul de coloane este suma dintre numarul de intrari si numarul de iesiri.
/// De exemplu, pentru problema binara XOR, multimea de antrenare are 4 vectori, 2 i
ntrari si 1 iesire si este de forma:
/// 0 0 0 / 0 1 1 / 1 0 1 / 0 0 0
/// </summary>
public double[,] Data { get; private set; }

public int NoVectors { get; private set; }

public int NoOutputs { get; private set; }

public int NoInputs { get; private set; }

/// <summary>
/// Multimea de antrenare este citita dintr-un fisier.
/// </summary>
public Dataset(string fileName)
{
StreamReader sr = new StreamReader(fileName);

string[] toks = sr.ReadLine().Split(" \t\r\n,;".ToCharArray(), StringSplitOptions.Remo


veEmptyEntries);

NoInputs = Convert.ToInt32(toks[0]);
NoOutputs = Convert.ToInt32(toks[1]);
NoVectors = Convert.ToInt32(toks[2]);

231
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Data = new double[NoVectors, NoInputs + NoOutputs];

for (int i = 0; i < NoVectors; i++)


{
toks = sr.ReadLine().Split(" \t\r\n,;".ToCharArray(), StringSplitOptions.RemoveEm
ptyEntries);
for (int j = 0; j < toks.Length; j++)
Data[i, j] = Convert.ToDouble(toks[j]);
}

sr.Close();
}

/// <summary>
/// Pentru a putea fi invatate mai usor de reteaua neuronala, datele pot fi scalate.
/// In cazul de fata, datele de antrenare sunt intre 0 si 1, iar scalarea se face automat
/// intre 0.1 si 0.9, pentru a aplica sigmoida unipolara.
/// Atentie! Metoda aceasta nu este generala si nu poate fi aplicata pentru orice date
de antrenare.
/// </summary>
public void ScaleData()
{
for (int i = 0; i < NoVectors; i++)
for (int j = 0; j < NoInputs + NoOutputs; j++)
Data[i, j] = Data[i, j] * 0.8 + 0.1;
}

/// <summary>
/// Scaleaza elementele unui vector din domeniul [0, 1] in domeniul [0.1, 0.9].
/// </summary>
public void ScaleVector(double[] vector)
{
for (int i = 0; i < vector.Length; i++)
vector[i] = vector[i] * 0.8 + 0.1;
}

/// <summary>
/// Rescaleaza elementele unui vector de intrare din domeniul modificat [0.1, 0.9] in d
omeniul initial [0, 1].
/// </summary>
public void RescaleVector(double[] vector)
{
for (int i = 0; i < vector.Length; i++)
{
vector[i] = (vector[i] - 0.1) / 0.8;
if (vector[i] > 1)

232
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
vector[i] = 1;
if (vector[i] < 0)
vector[i] = 0;
}
}
}
}

BackPropAlgorithm.cs

using System;
namespace Backpropagation
{
/// <summary>
/// Algoritmul backpropagation pentru antrenarea unei retele neuronale.
/// </summary>
public class BackPropAlgorithm
{
private NeuralNetwork _nn; // reteaua neuronala care este antrenata
private Dataset _dataset; // multimea de antrenare
private double _learningRate; // rata de invatare
private double[] _errors; // erorile dintre iesirile dorite si iesirile reale ale retelei
private double[,] _dwij, _dwjk; // corectiile ponderilor delta-w

/// <summary>
/// Eroarea medie patratica din epoca de antrenare curenta.
/// </summary>
public double MeanSquareError { get; private set; }

public BackPropAlgorithm(NeuralNetwork nn, Dataset dataset)


{
_dataset = dataset;
_nn = nn;

// stratul de intrare are indicele i, stratul ascuns are indicele j, stratul de iesire are in
dicele k
_dwij = new double[_nn.NoInputs + 1, _nn.NoHidden]; // inclusiv "bias"-ul
_dwjk = new double[_nn.NoHidden + 1, _nn.NoOutputs]; // inclusiv "bias"-ul
}

/// <summary>
/// Metoda de antrenare a retelei folosind algoritmul backpropagation. Antrenarea se
realizeaza
/// pana la atingerea unui numar maxim de epoci sau pana la atingerea unei erori acc
eptabile.

233
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
/// </summary>
public void Train(double learningRate, int maxEpochs, double maxError)
{
_learningRate = learningRate;

MeanSquareError = double.MaxValue;

int currentEpoch = 1;

// cata vreme currentEpoch < maxEpochs si MeanSquareError > maxError, apeleaza


TrainOneEpoch

throw new Exception("Aceasta metoda trebuie implementata.");


}

/// <summary>
/// Metoda care implementeaza o epoca de antrenare si seteaza eroarea medie patra
tica.
/// </summary>
private void TrainOneEpoch()
{
// se reseteaza corectiile ponderilor (metoda este deja implementata si trebuie doa
r apelata)

double totalSquareError = 0;
_errors = new double[_dataset.NoOutputs];

for (int v = 0; v < _dataset.NoVectors; v++)


{
double[] inputVector = new double[_dataset.NoInputs];
for (int i = 0; i < _dataset.NoInputs; i++)
inputVector[i] = _dataset.Data[v, i];

_nn.Propagate(inputVector);

double[] outputVector = new double[_nn.NoOutputs];


for (int k = 0; k < _dataset.NoOutputs; k++)
outputVector[k] = _dataset.Data[v, _nn.NoInputs + k];

BackwardPass(inputVector, outputVector);

for (int k = 0; k < _dataset.NoOutputs; k++)


totalSquareError += _errors[k] * _errors[k];
}

// se actualizeaza ponderile (metoda este deja implementata si trebuie doar apelat


a)

234
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
// se seteaza eroarea medie patratica pentru aceasta epoca de antrenare
// se folosesc: totalSquareError, _dataset.NoVectors, _dataset.NoOutputs

throw new Exception("Aceasta metoda trebuie completata.");


}

/// <summary>
/// Pasul de propagare inapoi al algoritmului.
/// </summary>
private void BackwardPass(double[] inputVector, double[] desiredOutputs)
{
// se calculeaza erorile pentru neuronii din stratul de iesire
// eroarea este diferenta dintre iesirea dorita si iesirea retelei (din reteaua _nn)

// se calculeaza gradientii de eroare pentru neuronii din stratul de iesire


// se actualizeaza corectiile ponderilor (_dwjk), inclusiv "bias"-
ul, considerand si rata de invatare

// se calculeaza gradientii de eroare pentru neuronii din stratul ascuns


// se actualizeaza corectiile ponderilor (_dwij), inclusiv "bias"-
ul, considerand si rata de invatare

throw new Exception("Aceasta metoda trebuie implementata.");


}

private void ResetDeltaWs()


{
for (int i = 0; i < _nn.NoInputs; i++)
for (int j = 0; j < _nn.NoHidden; j++)
_dwij[i, j] = 0;

for (int j = 0; j < _nn.NoHidden; j++)


for (int k = 0; k < _nn.NoOutputs; k++)
_dwjk[j, k] = 0;
}

private void UpdateWeights()


{
for (int j = 0; j < _nn.NoHidden; j++)
for (int i = 0; i < _nn.NoInputs; i++)
_nn.HiddenLayer[j].Weights[i] += _dwij[i, j];

for (int k = 0; k < _nn.NoOutputs; k++)


for (int j = 0; j < _nn.NoHidden; j++)
_nn.OutputLayer[k].Weights[j] += _dwjk[j, k];
}

235
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
}
}

236
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Referințe

Capitolele 1-4

Buonanno, E. (2018). Functional Programming in C#, Manning Publications, Shelter


Island, USA
Leon, F. (2020). FunCs – A Functional Programming Library for .NET,
https://github.com/florinleon/FunCs
Microsoft (2020). Enumerable Class, https://docs.microsoft.com/en-us/dotnet/api/
system.linq.enumerable?view=netframework-4.7.2
Microsoft (2020). Language Integrated Query (LINQ), https://docs.microsoft.com/en-us/
dotnet/csharp/programming-guide/concepts/linq/
Smith, C. (2013). Programming F# 3.0, Second Edition, O’Reily Media, Sebastopol,
California, USA

Capitolul 5

Cârstoiu, D. I. (1994). Sisteme expert, Ed. All, București


Gâlea, D., Costin, M., Zbancioc, M. (2001). Aplicații de inteligență artificială în CLIPS,
Ed. Tehnopress, Iași.
Gâlea, D., Costin, M., Zbancioc, M. (2001). Programarea în CLIPS prin exemple, Ed.
Tehnopress, Iași.
Hinton, G. E. (1986). Learning Distributed Representations of Concepts, Proceedings of
the Eighth Annual Conference of the Cognitive Science Society, CogSci 1986, pp. 1-12
Hinton, G. E. (1990). Kinship Data Set, https://archive.ics.uci.edu/ml/datasets/Kinship
Leon, F. (2020). FunCs – A Functional Programming Library for .NET,
https://github.com/florinleon/FunCs

Capitolul 6

Hart, P. E., Nilsson, N. J., Raphael, B. (1968). A Formal Basis for the Heuristic
Determination of Minimum Cost Paths, IEEE Transactions on Systems Science and
Cybernetics, vol. 4, no. 2, pp. 100-107
Leon, F. (2020). Inteligență artificială. Note de curs: Metode de căutare,
http://florinleon.byethost24.com/curs_ia.htm

237
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași
Russell, S. J., Norvig, P. (2002). Artificial Intelligence: A Modern Approach, Prentice Hall,
2nd Edition

Capitolul 7

Edwards, D. J., Hart, T. P. (1961). The Alpha-beta Heuristic, Report no. AIM-030,
Massachusetts Institute of Technology, https://dspace.mit.edu/handle/1721.1/6098
Leon, F. (2020). Inteligență artificială. Note de curs: Jocuri. Satisfacerea constrângerilor,
http://florinleon.byethost24.com/curs_ia.htm
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași
Rich, E. (1983). Artificial Intelligence, Mc Graw-Hill Book Company, New York
Russell, S. J., Norvig, P. (2002). Artificial Intelligence: A Modern Approach, Prentice Hall,
2nd Edition

Capitolul 8

Baeck, T., Fogel, D. B., Michalewicz, Z., eds. (1997). Handbook of Evolutionary
Computation, Institute of Physics Publishing Publishing and Oxford University Press
Dolan, R. (2016). Optimization (Solutions), https://robert-dolan.grad.uconn.edu/wp-content/
uploads/sites/1419/2016/06/Lecture-4.7-Solutions.pdf
Holland, J. H. (1975). Adaptation in Natural and Artificial Systems, University of Michigan
Press, Ann Arbor, Michigan, USA.
Leon, F. (2020). Inteligență artificială. Note de curs: Metode de optimizare (I),
http://florinleon.byethost24.com/curs_ia.htm
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași

Capitolul 9

Drakos, N., Moore, R. (2001). Conjunctive Normal Form, http://www.mathcs.duq.edu/


simon/Fall04/notes-6-20/node2.html
Leon, F. (2020). Inteligență artificială. Note de curs: Metode de inferență în logica
propozițională și predicativă, http://florinleon.byethost24.com/curs_ia.htm
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași
Robinson, J. A. (1965). A Machine-Oriented Logic Based on the Resolution Principle,
Journal of the ACM, vol. 12, no. 1, pp. 23-41
Smith, J. R. (2001). Conjunctive Normal Form, http://vorpal.math.drexel.edu/course/
founds/proving/node1.html

238
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Capitolul 10

Knapp, B. (2004). Fuzzy Inference Systems, https://www.cs.princeton.edu/courses/archive/


fall07/cos436/HIDDEN/Knapp/fuzzy004.htm
Leon, F. (2006). Agenți inteligenți cu capacități cognitive, Ed. Tehnopress, Iași
Leon, F. (2020). Inteligență artificială. Note de curs: Logica vagă (fuzzy),
http://florinleon.byethost24.com/curs_ia.htm
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași
Zadeh, L. A. (1965). Fuzzy Sets, Information and Control, vol. 8, no. 3, pp. 338-353

Capitolul 11

AIspace Group (2016). Belief and Decision Network Tool, http://www.aispace.org/bayes/


version5.1.10signed/bayes.jar
Leon, F. (2012). Inteligență artificială: raționament probabilistic, tehnici de clasificare,
Ed. Tehnopress, Iași.
Leon, F. (2020). Inteligență artificială. Note de curs: Rețele bayesiene,
http://florinleon.byethost24.com/curs_ia.htm
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași
Pearl J. (1986). Fusion, Propagation, and Structuring in Belief Networks, Artificial
Intelligence, vol. 29, no. 3, pp. 241-288
Russell, S. J., Norvig, P. (2002). Artificial Intelligence: A Modern Approach, Prentice Hall,
2nd Edition

Capitolele 12-13

Caudill, M. (1987). Neural Network Primer: Part I, AI Expert, vol. 2, no. 12, pp. 46-52
Cybenko, G. (1989). Approximation by Superpositions of a Sigmoidal Function,
Mathematics of Control, Signals and Systems, vol. 2, pp. 303-314
Leon, F. (2006). Agenți inteligenți cu capacități cognitive, Ed. Tehnopress, Iași
Leon, F. (2014). Inteligență artificială: mașini cu vectori suport, Ed. Tehnopress, Iași
Leon, F. (2020). Inteligență artificială. Note de curs: Rețele neuronale (I),
http://florinleon.byethost24.com/curs_ia.htm
Leon, F. (2020). Sinteze de inteligență artificială, Ed. Tehnopress, Iași
Minsky, M., Papert, S. (1969). Perceptrons, The MIT Press, Cambridge, Massachusetts,
USA
Negnevitsky, M. (2004). Artificial Intelligence: A Guide to Intelligent Systems, 2nd Edition,
Addison Wesley

239
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com
Rosenblatt, F. (1958) The Perceptron: A Probabilistic Model for Information Storage and
Organization in the Brain, Psychological Review, vol. 65, no. 6, pp. 386-408
Rumelhart, D. E., Hinton, G. E., Williams, R. J. (1986). Learning Internal Representations
by Error Propagation, în D. E. Rumelhart, J. L. McClelland (eds.): Parallel Distributed
Processing: Explorations in the Microstructure of Cognition, vol. 1, pp. 318-362, The MIT
Press, Cambridge, Massachusetts, USA
Vijeth, D. (2015). NeuronDotNet, https://sourceforge.net/projects/neurondotnet

240
Florin Leon (2020). Aplicatii de inteligenta artificiala in C#, Tehnopress, Iasi, ISBN 978-606-687-428-1
http://florinleon.byethost24.com

S-ar putea să vă placă și