Sunteți pe pagina 1din 27

9

PROGRAMARE EVOLUTIVĂ
ŞI PROGRAMARE GENETICĂ

Programarea evolutivă şi Programarea genetică lucrează cu


populaţii care nu mai sunt reprezentate prin şiruri binare sau reale, ca
în cazul algoritmilor genetici şi al strategiilor evolutive, ci prin
structuri mai complicate: programe, automate finite, etc. Din punct de
vedere al operatorilor folosiţi, programarea evolutivă este mai
apropiată de strategiile evolutive (foloseste mutaţia ca operator
principal, încrucişarea fiind foarte rar sau deloc folosită) în timp ce
programarea genetică este mai apropiată de algoritmii genetici
(operatorul principal este cel de mutaţie).

9.1. Programare evolutivă


9.1.1. Generalităţi

Programarea evolutivă a fost iniţiată de Fogel [26, 28]cu


scopul de a genera un comportament inteligent pentru un sistem
artificial. Comportamentul inteligent se referă la abilitatea sistemului
206
de a realiza predicţii asupra mediului informaţional în care se află.
Sistemele sunt modelate prin automate Turing iar mediul
informaţional este reprezentat printr-o succesiune de simboluri de
intrare.

Un automat Turing este un automat finit înzestrat cu o bandă


de ieşire cu următoarea funcţionare: aflându-se în starea p şi citind
simbolul de intrare x , va trece într-o altă stare q şi va înscrie pe
banda de ieşire un simbol y . Prin acest mecanism, automatul Turing
transformă o secvenţă de simboluri de intrare într-o secvenţă de
simboluri de ieşire.
Comportamentul automatului este considerat inteligent dacă
poate prezice simbolul următor. Populaţia este dată de diagramele de
tranziţie ale automatului iar gradul de adecvare al unui individ este cu
atât mai mare cu cât şirul de simboluri produs de automat este mai
aproape de un şir „ţintă”. Diagrama de tranziţie a unui automat finit
determinist este reprezentată printr-un multigraf orientat şi etichetat,
în care nodurile sunt etichetate cu stările automatului iar arcele
reprezintă tranziţiile şi sunt etichetate cu simbolul de intrare şi cel de
ieşire corespunzător.
Ca exemplu, să considerăm automatul din Figura 9.1, care
verifică dacă un şir de biţi conţine un număr par sau impar de poziţii
egale cu 1. Alfabetul de intrare este 0 , 1 iar ieşirea unei tranziţii va fi
0 sau 1, după cum şirul conţine un număr par respectiv impar de cifre
207
egale cu 1. Mulţimea stărilor este mulţimea  par , impar  iar starea
iniţială este par .

Figura 9.1

9.1.2. Funcţionarea automatului Turing

Populaţia este formată din   1 indivizi, fiecare fiind un


automat Turing. Să considerăm exemplul din figura următoare.

0/c

B
0/b
0/b
1/a
A C
1/c

Figura 9.2
208

Automatul are stările S   A , B , C  , alfabetul de intrare I  0 , 1 ,


alfabetul de ieşire O  a , b , c  . Tranziţia între două stări este data de
funcţia
 :S  I  S O
definită printr-o eticheta de forma i / o care apare pe o latură între
două stări s k şi s l , însemnând că
   s k , i    s l , o  ;

adică dacă maşina este în starea s k şi primeşte la intrare simbolul


i  I atunci ea trece în starea s l şi produce la ieşire simbolul o  O .

Prin acest mecanism, automatul transformă un şir format din


simboluri de intrare ( interpretat ca mediul maşinii) într-un şir format
din simboluri de ieşire. Performanţa automatului în raport cu mediul
poate fi măsurată pe baza capacităţii predictive a ei: se compară
fiecare simbol de ieşire cu următorul simbol de intrare şi se măsoară
valoarea predicţiei cu ajutorul unei funcţii de câştig.

Paradigma programării evolutive a fost implementată de Fogel


lucrând cu o populaţie de   1 părinţi care generează  descendenţi
prin mutaţii asupra fiecărui părinte. Mutaţia a fost implementată ca o
schimbare aleatoare a componentelor automatului; o schimbare se
poate realiza în cinci moduri: schimbarea unui simbol de ieşire,
modificarea unei tranziţii, adăugarea/eliminarea unei stări,
modificarea stării iniţiale.
209
Pentru fiecare individ al populaţiei se alege uniform şi aleator
unul din cei cinci operatori de mutaţie. Este, însă, posibil ca asupra
aceluiaşi individ să se aplice mai mulţi operatori de mutaţie, numărul
mutaţiilor putând să fie fix sau ales conform unei distribuţii de
probabilitate. După evaluarea descendenţilor se selectează cei mai
buni  indivizi dintre părinţi şi descendenţi; deci se efectuează o
selecţie de tip      .

Fogel nu a folosit încrucişarea, de aceea mulţi cercetători din


domeniul algoritmilor genetici au criticat metoda lui, considerând că
nu e suficient de puternică. Totuşi, rezultatele teoretice şi empirice din
ultimii 30 de ani au arătat că rolul mutaţiei în algoritmi genetici a fost
subestimat iar cel al încrucişării a fost supraestimat [3, 19, 21, 50].

9.1.3. Optimizare folosind programarea


evolutivă

Variantele curente de programare evolutivă folosite în


probleme de optimizare cu parametri continui au multe lucruri în
comun cu strategiile evolutive, în special în privinţa reprezentării
indivizilor, a modului de efectuare a mutaţiei şi a autoadaptării
parametrilor.
210
Initial programarea evolutivă a lucrat cu spaţii mărginite
n

 u i , v i   R
n
, cu u i  v i .
i 1

Mai târziu domeniul de căutare a fost extins la I  R n , un individ


fiind un vector a  x  I . În [22] se introduce conceptul de
metaprogramare evolutivă, care presupune un mecanism de auto-
adaptare similar celui de la strategii evolutive. Pentru a încorpora
v  R , spaţiul indivizilor este extins la
n
vectorul varianţelor

I  R  R  . Funcţia de evaluare   a  se obţine din funcţia obiectiv


n n

f ( x ) prin scalare la valori pozitive şi, eventual, prin impunerea unor

modificări aleatoare k ale parametrilor; deci  ( a )    f  x , k  , unde


 este functia de scalare. În cazul programării evolutive standard
mutaţia transformă pe x în x ' , x '  mut  1 , ,  n , 1 ,  ,  n ( x) , după regula

x ' i  x i   i N i ( 0 , 1)

i   i   x    i

unde constantele de proporţionalitate  i şi  i sunt alese în funcţie de


problema de rezolvat; totuşi, de obicei se consideră  i  1 şi  i  0 ,
astfel că mutaţia devine

x 'i  x i   ( x )  N i ( 0 , 1) .
211
În cazul meta-programării evolutive individul a  ( x , v ) se
transformă prin mutaţie în a '  mut  ( a )  ( x ' , v ' ) astfel:

x 'i  x i  v i  N i ( 0 , 1)

v 'i  v i    v i  N i 0 , 1 

unde  are rolul de a asigura că v ' i primeşte o valoare pozitivă.


Totuşi, dacă varianţa devine negativă sau zero atunci i se atribuie o
valoare mică   0 .

Se consideră că în programarea evolutivă se codifică mai


degrabă specii decât indivizi; şi cum încrucişarea nu acţionează la
nivelul speciilor, programarea evolutivă nu foloseşte acest tip de
operator. După crearea a  descendenţi din  părinţi, prin aplicarea
mutaţiei o singură dată asupra fiecărui părinte, se selectează 
indivizi din mulţimea părinţilor P (t ) reunită cu cea a descendenţilor
P ' ( t ) . Se utilizează o variantă stochastică a selecţiei turneu cu

parametrul q 1 care constă în: pentru fiecare individ


a k  P ( t )  P ' ( t ) se selectează aleator q indivizi din P ( t )  P ' ( t ) şi

se compară evaluarea lui cu cea a lui a k . Numărul w k  0 , 1,  , q  al


indivizilor mai puţin performanţi decât a k constituie scorul lui a k .
Formal, acesta se poate scrie
q
1 dac ă  a i    a   
wi  
i

j 10 altfel
212
unde indicii  j  1, 2 ,  , 2   sunt valori aleatoare uniforme,
calculate pentru fiecare comparare. După ce se efectuează această
operaţie pentru toţi cei 2  indivizi, ei se ordonează descrescător după
scorul w i , 1  i  2  , şi se aleg cei mai buni  indivizi care vor
forma generaţia următoare P ( t  1) . Rezultă următorul algoritm

begin
t : 0

iniţializează P ( 0 ) : a1 ( 0 ),  , a  0  I  ,

unde I  R n  R n , a i  ( x i , v i )  i  1,  , n 
evaluează P ( 0 ) :  a1 ( 0 ) ,  ,  a  0 
unde  a j 0     f x j 0 , k j 
while ( T  P t   true ) do
begin
aplică mutaţia: a ' i ( t ) : mut  a i t   i  1,  ,  
evaluează P ' ( t ) : a '1 ( t ),  , a '  t  calculând
 a '1 ( t ),  ,  a '  t  cu  a ' t     f  x ' t , k 
i i i

selectează P ( t  1) : turn q  P ( t )  P ' ( t ) 


t : t  1
end
end
213
Am evidenţiat anterior similaritatea dintre strategiile evolutive şi
programarea evolutivă. Există, totuşi, şi diferenţe, cele mai evidente
fiind la nivelul:

• codificării: strategiile evolutive codifică indivizi iar programarea


evolutivă codifică specii

• selecţiei: strategiile evolutive aleg cei mai buni indivizi dintre


părinţi şi descendenţi, pe când programarea evolutivă face această
alegere dintr-un număr de indivizi selectaţi anterior din populaţia
curentă reunită cu cea a descendenţilor.

Programarea evolutivă are numeroase aplicaţii, dintre care


amintim: optimizarea numerică continuă, dezvoltarea sistemelor de
clasificare, antrenarea reţelelor neuronale, proiectarea sistemelor de
control ce pot fi modelate prin automate finite, controlul deplasării
roboţilor.

9.2. Programare genetică

Programarea genetică reprezintă o nouă direcţie în cadrul


calculului evolutiv, dezvoltată de către J. Koza [50] în jurul anilor
1990. Programarea genetică este, de fapt, o variantă a algoritmilor
genetici care operează cu populaţii constituite din „structuri de
calcul”, din acest punct de vedere fiind similară programării evolutive.
214
Structurile care constituie populaţia sunt programe care atunci când
sunt executate sunt soluţii candidat ale problemei. Programarea
genetică a fost dezvoltată iniţial cu scopul de a genera automat
programe care să rezolve (aproximativ) anumite probleme.

Ulterior aria de aplicaţii s-a extins către proiectarea evolutivă,


un domeniu aflat la intersecţia dintre calculul evolutiv, proiectarea
asistată de calculator şi biomimetism (= subdomeniu al biologiei care
studiază procese imitative din natură). Programarea genetică urmează
structura generală a unui algoritm genetic, folosind încrucişarea ca
operator principal şi mutaţia ca operator secundar. Particularităţile
programării genetice sunt legate de modul de reprezentare a
indivizilor, fapt ce necesită şi alegerea adecvată a operatorilor.

9.2.1 Reprezentarea indivizilor

În programarea genetică indivizii sunt văzuţi nu ca o


succesiune de linii de cod ci ca arbori de derivare asociaţi
„cuvântului” pe care îl reprezintă în limbajul formal asociat limbajului
de programare utilizat. În practică se lucrează cu limbaje restrânse,
bazate pe o mulţime mică de simboluri asociate variabilelor şi o
mulţime de operatori; în aceste condiţii, orice program este de fapt o
expresie în sens general. Alegerea simbolurilor şi a operatorilor este
strâns legată de problema de rezolvat. Această alegere determină
esenţial rezultatele ce se vor obţine; nu există, însă, reguli generale
care să stabilească legătura dintre o problemă de rezolvat şi mulţimile
215
de simboluri şi operatori folosite, rolul important revenindu-i
programatorului.

Koza a propus ca modalitate de reprezentare scrierea prefixată


a expresiilor, care corespunde parcurgerii în preordine a arborelui de
structură al expresiei. Pentru a simplifica descrierea, considerăm că se
operează cu „programe” care sunt expresii ce conţin operatori
aritmetici, relaţionali şi logici precum şi apeluri ale unor funcţii
matematice. În acest caz, limbajul formal asociat este independent de
context şi fiecărui cuvânt (expresie) i se poate asocia un arbore de
descriere. Nodurile interioare ale arborelui sunt etichetate cu operatori
sau nume de funcţii iar cele terminale sunt etichetate cu nume de
variabile sau constante. De exemplu, expresia max( x  y , x  5  y )
va fi reprezentata prin

max

* +

x y x
*

5 y

Figura 9.3
216

Mulţimea nodurilor interioare se numeşte mulţimea funcţiilor


F  { f1 , f 2 , , f n } ; în exemplul nostru F  {max,  , } . Fiecare
f

funcţie fi  F are aritatea (numărul argumentelor) cel puţin 1.


Funcţiile din F pot fi de diferite tipuri:
• aritmetic:  ,  , , /
• matematic: sin, cos, exp, log
• boolean: AND , OR , NOT
• conditional: if  then  else
• repetitiv: for , while , repeat

Mulţimea nodurilor terminale din arborele de derivare se numeşte


muţtimea terminalelor 
T  t1 , t 2 ,  , t n ;
t
 în exemplul nostru

T  x , y , 5  . Mulţimile F şi T pot fi reunite într-un grup uniform


C FT , dacă se consideră că terminalele sunt funcţii de aritate
zero. Pentru ca programarea genetică să funcţioneze eficient,
mulţimile F şi T trebuie să respecte două condiţii:

• cea de închidere, adică fiecare funcţie din F este aptă să accepte ca


argument orice valoare sau tip de dată ce poate fi returnat de orice
funcţie din C ; această proprietate previne erorile din timpul rulării

• cea de suficienţă, care cere ca funcţiile din C să poată exprima


soluţiile problemei, adică cel puţin o soluţie aparţine mulţimii tuturor
compunerilor posibile ale funcţiilor din C .
217
Câteva exemple de mulţimi C închise sunt:
• C   AND , OR , NOT , x , y , true  , unde x şi y sunt variabile
booleene
• C   ,  ,  , x , y , 1, 0  , cu x şi y variabile întregi
• C   ,  , sin, cos, exp, x, y  , cu x şi y variabile reale.

Există mulţimi pentru care proprietatea de închidere nu este


verificată; de exemplu:
• C   ,  ,  , /, x , y , 1, 0  , cu x şi y variabile reale, nu este închisă
x x y
deoarece se pot genera împărţiri prin zero, cum sunt , , etc
0 xx
• C   ,  , sin, cos, log, x, y  , cu x şi y variabile întregi, nu este
închisă deoarece se pot genera valori negative pentru logaritm; de
exemplu, log(  x ) .

Mulţimea C   ,  , 
, x poate fi închisă sau nu, în funcţie de

domeniul valorilor lui x . Închiderea poate fi forţată prin folosirea


funcţiilor „protejate”. Astfel de funcţii sunt:
x 1 dac ă y  0
•  , operaţie ce va fi notată în continuare cu
y x y dac ă y  0

div

0 dac ă x  0
• log( x )  
 log  x  altfel

• x  x .
218
Dacă nu vrem să folosim funcţii de protecţie atunci trebuie
redusă foarte mult adecvarea (fitness-ul) expresiilor nevalide;
problema este similară funcţiilor de penalizare din cazul problemelor
de optimizare.

Suficienţa este garantată numai pentru unele probleme, când


teoria sau alte metode ne spun că o soluţie poate fi obţinută
combinând elementele lui C . De exemplu, logica ne spune că
C   AND , OR , NOT , x , y  permite implementarea oricărei funcţii
booleene, deoarece conţine o multime completă de conectori. Dacă C
nu este suficientă, programarea genetică poate doar să dezvolte
programe care să realizeze o aproximare cât mai bună. De exemplu,
mulţimea C   ,  ,  , /, x , 0 , 1, 2 nu poate să dea decât o aproximare
pentru exp( x ) , ştiind că aceasta este o funcţie trascedentală (nu poate
fi aproximată exact cu ajutorul unor expresii algebrice finite). În acest
caz programarea genetică nu poate decât să realizeze aproximări
algebrice finite de tipul
exp( x )  1

exp( x )  1  x

x
exp( x )  1  x 
2
2
x 1 3
exp( x )  1  x   x
2 1 1 2  2
219
9.2.2 Populaţia iniţială

Arborii iniţiali sunt generaţi alegând aleator funcţii din C . De


exemplu, pentru C   ,  ,  , /, x , y , 0 , 1, 2 , 3 putem avea

* *

+
2
2 x

x 3

* *

- / /
x

2 3 1 - +

x y 0

Figura 9.4

Dimensiunea şi forma programelor iniţiale sunt controlate


selectând noduri din F şi T , în funcţie de adâncimea lor în arbore.
Arborii pot fi reprezentaţi şi ca liste de liste. De exemplu, pentru
primii doi arbori anteriori avem
220
 2 x  , /  x 3  2  .

Din acest motiv, iniţializarea populaţiei în programarea genetică este


bazată, de obicei, pe proceduri recursive care returneaza liste.
Metoda „full” selectează noduri din F dacă adâncimea lor nu
depăşeşte o valoare maximă si noduri din T în caz contrar. Această
tehnică duce la obţinerea unei populaţii iniţiale cu frunzele la acelaşi
nivel (adâncime).

* * * * *

+ + + + /

2 2 y 2 y

* *

+ /
+ /

y x 2 y x 3
2

Figura 9.5

Metoda „grow” selectează noduri din C dacă adâncimea lor


este mai mică dacat o valoare minimă şi din T în caz contrar. Cum C
conţine şi elemente terminale, această metodă produce arbori iniţiali
de diferite forme şi adâncimi, ca în figura 9.6
221

* * * * *

x x + x + x +

1 1 y

Figura 9.6

Metoda „ramped half and half” combină cele două metode


anterioare, pentru a da o mai mare diversitate populaţiei iniţiale. Ea
lucrează după următorul algoritm

for i  1 to max _ adâncime do


begin
%
 50 
generează   din populaţie folosind metoda
 max _ adâncime 1

„full” cu adâncimea maximă i


%
 50 
generează   din populaţie folosind metoda
 max _ adâncime 1

„grow” cu adâncimea maximă i


end

Metodele „full” şi „grow” pot fi implementate cu următoarea


procedură recursivă
222

Generează_expresie( F , T , max_adâncime, metodă)


begin
if max _ adâncime  0
then
begin
selectează t  T
inserează t în arbore
end
else
begin
if metoda = full
then selectează f  F
else selectează f  F  T
inserează f în arbore
if f  F
then
begin
n : aritatea lui f

for i  1 to n do
generează_expresie( F , T , max_adâncime-1, metodă)
end
end
end
223
La apelul

genereaz ă _ exp resie    / ,  x y 0 1 2 3 , 3 , full 

se poate genera expresia

   x 1 2 0  /  y 3  2 x   

care corespunde arborelui următor

+ /

* - + -

x 1 2 0 y 3 2 x

Figura 9.7

La apelul

genereaza _ exp resie    1,  x y 0 1 2 3 , 3 , grow 

se genereaza expresia

  3 x   2 / 1 y  

care corespunde arborelui


224

Figura 9.8

9.2.3 Operatori de evoluţie

Încrucişarea constă în selectarea aleatoare a punctului de


încrucişare (nod sau arc în arbore) în fiecare părinte şi schimbarea
subarborilor care încep de la punctul de încrucişare. Este indicat ca
punctele de încrucişare să nu fie selectate uniform aleator ci să fie
favorizate nodurile interioare; de exemplu, în 90% din cazuri se aleg
noduri neterminale. Este posibil ca numărul încrucişărilor să fie
controlat de probabilitatea de încrucişare. Un exemplu de încrucişare
este dat în figura următoare.
225

(a*b)*sin(c) a*sin(c)

* *

* sin a sin

a b c c

a*(b+c) (a*b)*(b+c)

* *

a + * +

b c a b b c

părinţi descendenţi

Figura 9.9

Deşi este un operator secundar, mutaţia permite modificarea


structurilor arborescente în moduri în care încrucişarea nu o poate
face. În funcţie de efectul pe care îl are asupra structurii, există trei
variante de mutaţie:
• mutaţia simplă: modifică eticheta unui nod selectat aleator
226

* *

* sin + sin

a b c a b c

Figura 9.10

• mutaţia de expandare: constă în înlocuirea unui nod terminal cu


un subarbore, construit după aceleaşi reguli dar care nu face parte
neapărat din populaţia curentă, aşa cum se întâmplă în cazul
încrucişării

Figura 9.11

• mutaţia de reducere: constă în înlocuirea unui subarbore cu un nod


terminal
227
(a*b)*sin(c) a*sin(c)

* *

* sin a sin

a b c

Figura 9.12

În general se poate alege un nod al arborelui şi subarborele


dominat de acel nod este înlocuit cu altul generat aleator. Astfel,
mutaţia poate fi văzută ca încrucişarea cu un arbore generat aleator.

9.2.4 Rularea programelor în Programarea


genetică

În programarea genetică programele sunt reprezentate prin


arbori sau liste; întotdeauna este posibil să transformăm o astfel de
structură într-un cod C, C++, Java, Lisp, etc. Dar, în majoritatea
cazurilor o asemenea operaţie este ineficientă deoarece
• iniţial, majoritatea programelor au un fitness foarte mic şi vor
supravieţui puţine generaţii
• multe programe (bune sau mai puţin bune) vor fi schimbate prin
încrucişare sau mutaţie şi vor trebui recompilate.
228
O abordare mai bună este de a interpreta programele, în loc de
a le compila. Unele limbaje de programare, cum este Lisp, au deja un
interpretor în mediul de dezvoltare a programelor. Pentru a-l utiliza
trebuie să ne asigurăm că sintaxa este corectă; de exemplu, să utilizăm
paranteze rotunde în locul celor pătrate. Pentru toate celelalte limbaje
de programare este necesară construcţia unui astfel de interpretor. A
interpreta un program înseamnă a-l parcurge în adâncime şi a evalua
toate nodurile începând cu frunzele. Parcurgerea în adâncime permite
evaluarea nodurilor numai după ce valorile argumentelor lor sunt
cunoscute. De exemplu,
5
-
-1
4 * *

2 -1
+ 2 -
- 1 -

0 1 x 2 x x

Figura 9.13

În urma interpretării, valoarea nodului rădăcină este valoarea


programului. Aplicaţiile posibile ale programării genetice sunt multe
şi variate, singura problemă fiind definirea unei funcţii fitness
corespunzătoare. Tehnicile de scalare şi penalizare folosite în
229
algoritmi genetici sunt aceleaşi, cu diferenţa că pentru a decide dacă
un program este bun sau nu, trebuie executat odată sau de mai multe
ori cu date de intrare diferite sau în diferite contexte.

O clasă de probleme în care programarea genetică s-a dovedit


foarte utilă este regresia simbolică. Aceasta este o tehnică folosită
foarte des în interpretarea datelor şi constă în găsirea coeficienţilor
unei funcţii – obţinute ca o combinaţie de funcţii elementare astfel
încât aceasta să aproximeze cât mai bine o funcţie cunoscută prin
valorile ei în puncte date. Termenul „simbolic” semnifică faptul că nu
suntem interesaţi în găsirea parametrilor optimi (numere) ci a
funcţiilor optime (expresii, reprezentări simbolice).
Pentru a folosi programarea genetică în rezolvarea problemelor
de regresie simbolică este necesar:
• să avem o mulţime de puncte date, unde fiecare punct reprezintă
valorile luate de anumite variabile la un anumit moment
• să selectăm variabilele pe care le considerăm dependente de altele
• să definim o funcţie fitness care să măsoare capacitatea fiecărui
program de a determina valorile variabilelor independente când sunt
date valorile celor dependente
• să selectăm mulţimile adecvate de funcţii şi terminale; terminalele
trebuie să includă toate variabilele dependente şi poate şi altele iar
funcţiile trebuie selectate pe baza cunoştintelor despre domeniu.

Ca exemplu să găsim expresia simbolică care aproximează cel


mai bine mulţimea de date
230
 x i , y i    1 . 0 , 0 . 0 ,   0 . 9 ,  0 . 1629 ,   0 . 8 ,  0 . 2624 ,  , 1 . 0 , 4 . 0 
care a fost generată folosind funcţia
y  f  x   x  x  x  x , x   1, 1 .
2 3 4

Parametrii programului genetic sunt


• dimensiunea populaţiei = 1000
• mulţimea funcţiilor =  ,  , *, log, exp, sin, cos, div 
• mulţimea terminalelor = x 
• adâncimea maximă = 4
• metoda de generare a populaţiei iniţiale = full
• număr de generaţii = 50
• probabilitatea de încrucişare = 0.7
• pobabilitatea de mutaţie = 0
• funcţia fitness =  y i  eval  prog , x i 
i

Câteva din programele optime obţinute la diverse generaţii sunt:


• la generatia 1
[+ [- [log [exp x] ] [+ [sin x] [- x x] ] ] [+ [exp [log x] ] [sin [log x]
]]]
cu fitnessul -8.2098
• la generaţia 2
[* [+ [+ [+ x x] [div x x] ] x ] [log [exp [* x x] ] ] ]
cu fitnessul -7.0476
• la generaţia 3
[* [log [- [sin x] [exp x] ] ] [+ [cos [* x x] ] [+ [+ x x] [cos x] ] ] ]
cu fitnessul -4.74338
231
• la generaţia 6
[* [+ [+ [+ x [exp [log x] ] ] [div x x] ] x ] [log [exp x] ] ]
cu fitnessul -2.6334
Se observă că fitnessul descreşte, ceea ce înseamnă că programul
tinde spre găsirea combinaţiei optime de funcţii; de exemplu, la
iteraţia 26 se obţine fitnessul -0.841868.

S-ar putea să vă placă și