Upload
matteo-tosato
View
259
Download
15
Embed Size (px)
DESCRIPTION
Introduzione agli algoritmi genetici e approfondimento su sistemi di apprendimento evolutivi per reti neurali
Citation preview
~ Algoritmi genetici e reti neurali ~
Matteo Tosato 2011
<< La conservazione delle differenze e variazioni individuali favorevoli e la
distruzione di quelle nocive sono state da me chiamate "selezione naturale" o
"sopravvivenza del più adatto". Le variazioni che non sono né utili né nocive non
saranno influenzate dalla selezione naturale, e rimarranno allo stato di elementi
fluttuanti, come si può osservare in certe specie polimorfe, o infine, si fisseranno,
per cause dipendenti dalla natura dell'organismo e da quella delle condizioni >>
(Charles Darwin, L'origine delle specie, 1859, p. 147)
Algoritmi genetici e reti neurali – rev. 1.0 2
Copyright © 2011 Matteo Tosato
Indice
0x00] Prefazione
0x01] C. Darwin e l’origine delle specie
0x02] Implementazione algoritmica
0x03] Programmazione genetica
0x04] Ottimizzazione di reti neurali artificiali
0x05] Accenni di vita artificiale
0x06] Conclusione
0x07] Riferimenti
0x00] Prefazione
John H. Holland:
“Computer programs that "evolve" in ways that resemble natural selection can solve
complex problems even their creators do not fully understand”.
Nell‟iperuranio di ognuno, questo non è un concetto nuovo. Di frequente ci capita di
avere a che fare con sistemi di cui non riusciamo a carpirne la logica in modo
completo. E‟ qualcosa di eccezionale nonostante tutto.
In questo testo cerco di introdurre le basi e tenterò anche di addentrarmi in alcune
delle molte applicazioni pratiche ove gli algoritmi genetici (d‟ora in poi GA) vengono
impiegati. Partiremo parlando dell‟idea di Darwin riguardo l‟evoluzione, concetto sul
quale i GA si basano. Vedremo i principi di funzionamento base, i dettagli di
progettazione più comuni, alcuni esempi e applicazioni.
Holland è ritenuto il padre dei GA, in quanto fu il primo a proporre questo nuovo
metodo, ispirato direttamente dall‟evoluzione per selezione naturale.
Il paradigma base dei GA è l‟ottimizzazione. L‟ottimizzazione è sempre stato un
problema complesso eseguito con grande fatica, utilizzando molte conoscenze facente
parte di diversi settori.
In genere gli algoritmi utilizzati nelle discipline di Intelligenza artificiale operano
la ricerca di un massimo o di un minimo globale in uno spazio finito sulla base di
vincoli sullo spazio delle soluzioni.
Formalmente possiamo dire che un elemento appartenente ad uno spazio cartesiano D
(nel senso in cui n sia la cardinalità di D, allora tale elemento sarà un vettore), e
data una funzione , allora la ricerca dell‟ottimo globale è la ricerca di un
valore che massimizza la funzione, ovvero:
e .
Minimi della funzione e non linearità possono impedire o danneggiare le performance di
qualsiasi algoritmo di ricerca classico. I vantaggi che i GA offrono per problemi del
genere saranno molto chiari quando applicheremo i GA all‟intelligenza artificiale, o
meglio impiegheremo questi per fornire un metodo di addestramento spesso più efficiente
rispetto gli algoritmi standard.
Se pensiamo all‟ottimizzazione come ad un problema da risolvere, allora possiamo dire
che ogni organismo è un algoritmo più o meno efficiente per risolvere problemi sempre
differenti. Difatti questo sopravvivendo nel proprio ambiente generazione dopo
Algoritmi genetici e reti neurali – rev. 1.0 3
Copyright © 2011 Matteo Tosato
generazione, risolve il problema della sopravvivenza adattando se stesso.
Se indaghiamo con attenzione questo aspetto scopriamo qualcosa di inusuale per logica
tradizionale occidentale. I processi naturali interni di un organismo vivente sono dal
nostro punto di vista, stocastici. Mentre la soluzione che esso può ricavare
dall‟insieme di questi processi è molto precisa e ottimizzata. Come è possibile?
In particolar modo è stato verificato, che oltre a risolvere quei problemi di
ottimizzazione per i quali un approccio procedurale è impossibile o troppo complesso, i
GA possono produrre risultati competitivi anche per compiti normalmente affrontati nel
modo classico.
I GA, ma anche le reti neurali artificiali, sono in grado di svolgere questi compiti
„difficili‟ perché hanno una sorta di conoscenza interna accumulata durante il loro
ciclo di funzionamento.
Fin da subito seguiremo il modello originale proposto da Holland, che ancora oggi
risulta essere uno tra quelli più utilizzati a livello teorico, didattico e pratico.
Per comprendere questo testo è necessaria la conoscenza di almeno un linguaggio di
programmazione e preferibilmente, anche delle nozioni base sulle reti neurali
artificiali, in quanto il testo cerca di soffermarsi proprio sul problema
dell‟addestramento di una rete neurale con algoritmi evolutivi anziché gli algoritmi di
addestramento standard.
0x01] C. Darwin e l’origine delle specie
Prima di concentrare i nostri sforzi sul nocciolo della teoria di Holland è bene citare
anche a chi esso si è ispirato.
„L‟origine delle specie‟ di Charles Darwin è un documento pubblicato nel 1859.
Si tratta di una tra le opere cardini nella storia scientifica, ed indubbiamente una
delle più eminenti in biologia.
Pubblicata per la prima volta il 24 novembre 1859, in essa Darwin spiega con "una lunga
argomentazione" la sua teoria, secondo cui "gruppi" di organismi di una stessa specie
si evolvono gradualmente nel tempo attraverso il processo di selezione naturale, un
meccanismo che venne illustrato per la prima volta ad un pubblico generico proprio
grazie a questo libro. L'opera contiene dettagliate prove scientifiche che l'autore
ebbe il tempo di accumulare sia durante il viaggio del HMS Beagle nel 1830 che al suo
ritorno, preparando diligentemente la sua teoria e, contemporaneamente, rifiutando
quella più in voga fino a quel tempo, il creazionismo, che ritiene le specie, essendo
create da Dio, perfette ed immutabili.
Il libro risultò accessibile anche ai non specialisti, attraendo un grande interesse su
vasta scala. Sebbene una parte della teoria sia ora supportata da schiaccianti
dimostrazioni scientifiche, esistono ancora forti controversie, soprattutto tra i
Algoritmi genetici e reti neurali – rev. 1.0 4
Copyright © 2011 Matteo Tosato
sostenitori del creazionismo, i quali ritengono che questa teoria contraddica le
interpretazioni letterali di vari testi religiosi.
La teoria dell'evoluzione di Darwin si basa su 5 osservazioni-chiave e sulle
conclusioni che se ne traggono, come riassunto dal biologo Ernst Mayr:
- le specie sono dotate di una grande fertilità e producono numerosi discendenti che
possono raggiungere lo stadio adulto.
- Le popolazioni rimangono grosso modo delle stesse dimensioni, con modeste
fluttuazioni.
- Le risorse di cibo sono limitate, ma relativamente costanti per la maggior parte
del tempo. Da queste prime tre osservazioni è possibile dedurre che verosimilmente
in ogni ambiente ci sarà tra gli individui una lotta per la sopravvivenza.
- Con la riproduzione sessuale generalmente non vengono prodotti due individui
identici. La variazione è abbondante.
- Gran parte di questa variazione è ereditabile.
Per queste ragioni Darwin afferma che: in un mondo di popolazioni stabili, dove ogni
individuo deve lottare per sopravvivere, quelli con le "migliori" caratteristiche
avranno maggiori possibilità di sopravvivenza e così di trasmettere quei tratti
favorevoli ai loro discendenti. Col trascorrere delle generazioni, le caratteristiche
vantaggiose diverranno dominanti nella popolazione. Questa è la selezione naturale.
Il processo che abbiamo descritto produce inequivocabilmente risultati migliori di ciò
che può fare l‟ingegneria attuale. La sola differenza è il fattore temporale, assai più
lungo per l‟evoluzione.
I sistemi biologici possiedono molte caratteristiche di robustezza, come la auto-
organizzazione, adattamento e di efficienza che sono altamente desiderabili se
catturate ed integrate nei sistemi artificiali creati dall‟uomo. Nonostante questo
difficilmente i metodi convenzionali di costruzione dei sistemi artificiali riescono ad
eguagliare ciò che può fare un essere vivente, ad esempio molti compiti come la
navigazione, la ricerca del cibo, di un rifugio o la fuga da un predatore non sono
ancora fattibili completamente dai sistemi artificiali, anche se recentemente si stanno
facendo grossi passi, dunque la suddetta affermazione potrebbe anche divenire falsa in
breve tempo.
Il problema dei sistemi convenzionali si trova nei loro principi di funzionamento, di
come isolano il problema, definizione teorica, identificazione delle variabili e
derivazione formale di una soluzione specifica, questi principi sono completamente
differenti da quelli biologici.
Ciò nonostante, noi vedremo come, attraverso un linguaggio di programmazione e di un
calcolatore elettronico, possiamo simulare questa strategia biologica, facendogli
risolvere alcuni dei problemi che risultano essere difficili o onerosi per i normali
programmi in logica sequenziale.
0x02] Implementazione algoritmica
In biologia il „fenotipo‟ indica l‟effettiva manifestazione fisica di un organismo, in
opposizione al suo genotipo.
Il „genotipo‟ è invece un termine riferito all‟insieme di geni che compongono il DNA di
un organismo o di una popolazione. Ogni gene contribuisce in maniera diversa allo
sviluppo e alla fisiologia dell'organismo e l'interazione dei prodotti genici è
responsabile della sua formazione e di tutte le caratteristiche peculiari che lo
compongono, il fenotipo appunto.
Ogni caratteristica fenotipica non è codificata direttamente, questo significa che due
organismi con ugual genotipo non hanno necessariamente anche un fenotipo uguale,
probabilmente sarà soltanto simile. Per estensione, il termine fenotipo deve includere
Algoritmi genetici e reti neurali – rev. 1.0 5
Copyright © 2011 Matteo Tosato
caratteristiche che possono essere rese visibili attraverso qualche procedura tecnica.
Inoltre, estendendo ulteriormente questo concetto, vengono a far parte del fenotipo di
un organismo anche qualità ereditabili più complesse, come ad esempio il suo sviluppo o
il suo comportamento. In definitiva il ruolo giocato dai tre concetti cardine
dell'evoluzione (ambiente-genotipo-fenotipo) può essere ragionevolmente sintetizzato
nella seguente affermazione: il fenotipo è il frutto dell'interazione tra ambiente e
genotipo.
Dunque, dato un problema , noi parleremo del fenotipo riferendoci alle caratteristiche
salienti di una soluzione a prescindere che essa risolva o meno il problema.
Naturalmente ogni caso pratico dovrà essere attentamente analizzato per estrapolarne le
caratteristiche salienti. Attraverso la modifica di queste è possibile trovare la
soluzione ottimale. Il problema dei metodi classici è che non è possibile trovare in
modo analitico le combinazioni ottimali di queste caratteristiche. Dunque qui entra in
gioco la teoria di Darwin.
Un GA lavora su un insieme di genomi. I geni sono le codifiche dei fenotipi. Dunque una
volta individuate le caratteristiche salienti della soluzione, esse vengono codificate.
Questa è una operazione che può divenire piuttosto complicata, dipende dalla natura del
problema e quindi da come una soluzione è strutturata. Un esempio potrebbe essere la
soluzione al problema di ottimizzazione delle ore di lezione di un gruppo di professori
in modo da realizzare il miglior orario scolastico limitando al minimo le perdite di
tempo e distribuire in modo coerente le ore fra le classi. Di sicuro può essere ostica
trovare la corretta codificazione delle caratteristiche. L‟importante è non generare un
genoma troppo lungo, perché questo rende più lungo e meno efficiente l‟algoritmo
genetico. Di sicuro sarebbe più oneroso scrivere un programma sequenziale che faccia lo
stesso compito. La quantità di casi e variabili è troppo elevata, per questo è più
conveniente sfruttare le potenzialità della auto-organizzazione.
Dunque possiamo definire per punti i nodi importanti che un algoritmo genetico deve
implementare:
1. Generazione pseudo-casuale di una popolazione.
2. Valutazione della fitness di ogni gene.
3. In base alla fitness calcolata si sceglie, secondo una strategia, le coppie di
geni destinate all‟accoppiamento.
4. Si procede all‟incrocio delle sequenze genetiche dei genitori. Per ogni figlio
generato esiste una probabilità che il codice genetico muti.
5. I figli generati, sostituiscono o compensano quelli dei genitori a formare la
nuova popolazione. Si ripete l‟accoppiamento fino ad ottenere un gene con una
valore di fitness soddisfacente. (si ripete dal punto 2)
Per comprendere l‟algoritmo anche dal punto di vista pratico, ovvero di programmazione,
ricorriamo ad un semplicissimo esempio in cui partendo da N stringhe composte di
caratteri casuali, si arriva ad ottenere la frase voluta. (L‟esempio è realizzato in
linguaggio C#.)
Ad esempio, inserendo come target „Hello World!‟, l‟algoritmo, iniziando da N stringhe
casuali farà evolvere queste in funzione del suddetto target.
Affrontiamo per punti:
1] Deve essere posto a priori un numero N di genotipi. Questi sono inizializzati a
valori casuali.
Per l‟esempio, sarà necessario inizializzare questi geni utilizzando il range dei
caratteri stampabili. Di fatto i geni sono rappresentabili con degli array di elementi.
Il tipo di elementi si determina in base al tipo di compito con cui abbiamo a che fare.
Algoritmi genetici e reti neurali – rev. 1.0 6
Copyright © 2011 Matteo Tosato
Una classe „gene‟, nel suo metodo costruttore implementerà l‟inizializzazione del suo
valore. Nell‟esempio il membro privato „GeneValue‟ è un oggetto „string‟;
/// <summary>
/// Constructor with auto-initialization
/// </summary>
public Gene(int len, Random seed)
{
string val = "";
for (int i = 0; i < len; i++)
{
val += Convert.ToChar(seed.Next(32, 122));
}
GeneValue = val;
fitness = 0;
}
Il membro „fitness‟ è il relativo valore di fitness del gene. Questo verrà calcolato in
seguito, ora è stato posto a 0, in quanto stiamo solamente inizializzando l‟oggetto
gene.
Come detto la codifica del fenotipo deve essere pensata in funzione al problema, una
codifica di questo tipo può essere definita come „diretta‟ dato che non eseguiamo
nessuna trasformazione sul valore del fenotipo, che è la stessa stringa. In altri casi
la codifica può divenire molto importante per il funzionamento dell‟algoritmo. Il
compito essenziale che devono svolgere è quello di rappresentare le caratteristiche
salienti del fenotipo. Un metodo spesso utilizzato sono genomi costituiti da stringe
binarie, ogni bit rappresenta una caratteristica della soluzione, se è posto ad 1 essa
esiste nel fenotipo, altrimenti no.
A questo punto nel nostro algoritmo principale verrà iniziato un ciclo che continuerà
ad iterare le prossime azioni per un certo numero di volte, oppure fino a che un valore
di fitness minimo viene soddisfatto da uno dei geni. La scelta dipende dal tipo di
problema da risolvere. Nel nostro esempio sceglieremo la seconda opzione, dunque avremo
tante generazioni quante ne saranno necessarie per ottenere la stringa di caratteri
desiderata.
2] Il metodo per il calcolo della fitness dipende fortemente dal tipo di codifica che
abbiamo utilizzato. Non è possibile darne alcuna definizione generale. Solitamente il
gene deve essere decodificato e utilizzato per risolvere il problema, a seconda
dell‟errore o del risultato ottenuto si ha un valore detto di idoneità o anche bontà
della soluzione. Il valore di fitness rispecchierà questo risultato.
Nel nostro esempio, data la funzione di fitness ideata, ho fatto in modo che più il
valore di idoneità è basso più il gene è accettabile. Pertanto l‟algoritmo finisce le
sue iterazioni quando la fitness è 0.
Questa scelta è motivata dal fatto che la definizione della funzione di fitness è una
variante della distanza di Hamming (o anche di Levenshtein). In informatica viene detta
distanza di Hamming fra due stringhe binarie di ugual lunghezza il numero di posizioni
nelle quali i simboli sono diversi. In altre parole, tale distanza misura il numero di
sostituzioni necessarie per convertire una stringa nell‟altra. Ad esempio, la distanza
di Hamming tra 1011101 e 1001001 è 2.
Nel nostro caso, per dare peso anche a quanto un carattere è distante da quello
corretto, ho ridefinito questa regola facendo la sommatoria delle differenze di ciascun
carattere dal quello obiettivo, considerando il loro valore decimale (caratt. ASCII).
Ad esempio la stringa „case‟ dato il target „casa‟ ha un valore di fitness di 4 dato
che quattro sono il numero di caratteri che separano „a‟ da „e‟.
Dunque la parte importante della funzione di fitness è così implementata:
/// <summary>
Algoritmi genetici e reti neurali – rev. 1.0 7
Copyright © 2011 Matteo Tosato
/// Compute gene fitness
/// </summary>
/// <returns>true on fitness computation success, false on error</returns>
internal virtual bool ComputeFitness(string _target)
{
[...]
fitness = 0;
int t = 0;
// Similar to Hamming distance
foreach(char c in _str)
{
fitness += Math.Abs(Convert.ToInt32(c - _target[t++]));
}
return true;
}
Ciascuna stringa viene a turno valutata in modo che ad ogni gene vi sia associato un
valore di fitness.
Funzione di
fitness
ϴ(x)
12
29
32
2
10
5
7
9
1
. . . . . .
3] Nel terzo punto, secondo del nostro ciclo riproduttivo, dobbiamo adottare una
strategia per scegliere le coppie di geni destinate all‟accoppiamento. Anche per questo
compito possono essere impiegate diverse strategie. Vi sono molti modi per implementare
la riproduzione probabilistica, il metodo più diffuso adotta „la ruota della fortuna
truccata‟.
Il metodo è il seguente;
Consideriamo il caso comune dove la popolazione rimane di dimensione costante ad ogni
generazione e la popolazione viene ogni volta rimpiazzata completamente dai nuovi geni.
La ruota della fortuna avrà dunque tante caselle quanti sono i genomi, ma la dimensione
di ogni casella sarà proporzionale al valore di fitness di ogni gene. La riproduzione
selettiva consiste nel far girare la ruota tante volte quante sono gli individui da
generare e nel creare ogni volta una coppia della stringa corrispondente alla casella
in cui si ferma la pallina. Formalmente, ogni gene avrà il proprio valore di fitness
„f‟:
Algoritmi genetici e reti neurali – rev. 1.0 8
Copyright © 2011 Matteo Tosato
Dove è la funzione di valutazione.
Questo valore viene utilizzato per calcolare la probabilità del gene di essere
selezionato per la riproduzione. (Questa probabilità corrisponde all‟ampiezza della
casella della ruota della fortuna). Dunque la probabilità viene estratta da una
normalizzazione di tutti i valori:
∑
La ruota della fortuna teoricamente assume questa configurazione:
p=0,03
p=0,22
p=0,08
Secondo il metodo di Forrest (1985) il valore atteso di ogni individuo è funzione della
sua idoneità, dell‟idoneità della popolazione e della deviazione standard. Dunque è
definito come:
{
Dove è il valore atteso dell‟individuo i all‟istante t, con si intende
l‟idoneità di i, con l‟idoneità media della popolazione al tempo t,
rappresenta la deviazione standard della popolazione al tempo t. Secondo questo
criterio i genomi che ottengono un valore atteso che supera la media della deviazione
standard 1.5 discendenti attesi mentre ad un individuo il cui valore atteso è minore di
zero assegna forzatamente un valore atteso di 0.1 in modo da lasciare un piccola
probabilità che questi vengano scelti per la riproduzione.
Un‟altra tecnica, quella utilizzata nel nostro esempio, utilizza un approccio
elitistico. Questo significa che i genomi genitori vengono selezionati semplicemente
scegliendoli in base alla loro idoneità. Questo viene fatto prima ordinando i genomi
per il loro valore di fitness, poi scegliendone un certo numero. Solitamente si applica
un tasso. Ad esempio un tasso di elitismo pari a 0.1 selezionerà il 10% della
popolazione partendo dal più adatto. Praticamente su 100 genomi seleziona i primi 10
della classifica.
Questa procedura di selezione costituisce quello che in natura rappresenta la
sopravvivenza del più forte, o più adatto. Poi si può decidere di rimpiazzare
completamente la popolazione con i nuovi genotipi oppure generare la quantità di figli
necessaria per ritornare al numero di elementi originario. Oppure ancora, si può
prevedere una popolazione crescente.
Nell‟esempio scegliamo di generare tanti genotipi quanti sono necessari per rimpiazzare
quelli scartati perché inadatti dalla selezione elitistica.
Algoritmi genetici e reti neurali – rev. 1.0 9
Copyright © 2011 Matteo Tosato
4] L‟accoppiamento di due geni generalmente avviene con la pratica del “cross-ove”
detto incrocio. Questa operazione fa parte dell‟insieme degli operatori genetici.
I metodi più utilizzati sono proprio l‟incrocio e la mutazione, che avviene secondo una
certa probabilità definita a priori uguale per tutti i geni figlio.
Cominciando dal cross-over, questo può essere eseguito in due modi.
Il primo prevede un incrocio tra i due codici in un punto casuale delle stringhe. Su
questo punto i rispettivi contenuti del gene vengono invertiti,
Incrocio su un punto
A
B
B A
Soprattutto quando il codice genetico è lungo, può risultare più utile, perché aumenta
la variazione utilizzare invece un “double cross-over”. Il procedimento avviene come
per l‟incrocio singolo ma in questo caso vengono scelti due punti,
Incrocio su due punti
B
A
B A B
Quando il genoma è molto lungo l‟incrocio comincia a divenire contro producente.
Infatti questo tenderà ad interrompere gli schemi favorevoli, ad esempio sempre
utilizzando stringhe binarie, lo schema: 00110011#####111, dove „#‟ indica i bit non
corretti per la soluzione, può venire facilmente interrotto nei primi punti
dall‟operatore cross-over. Vedremo nel capitolo successivo i vari espedienti che i
ricercatori hanno inventato per ridurre il rischio che questo avvenga.
Nell‟esempio dell‟evoluzione della stringa, utilizziamo un doppio incrocio.
Naturalmente l‟immissione di una stringa molto lunga come obiettivo, fa fallire
l‟algoritmo che con tutta probabilità stazionerà dopo un certo numero di generazioni.
Il metodo di crossover è implementato nella classe popolazione.
/// <summary>
/// Genetic operations, two point crossover from two parents to two offsprings
/// </summary>
/// <param name="dest">Destination population array</param>
/// <param name="did">Destination array index</param>
/// <param name="a">First parents chosen idex</param>
/// <param name="b">Second parents chosen index</param>
public void double_cross_over(ref Gene[] dest, int did, int a, int b)
{
Algoritmi genetici e reti neurali – rev. 1.0 10
Copyright © 2011 Matteo Tosato
int SgPoint = MyRandUnit.Next(gcodeLen - 1);
int FgPoint = MyRandUnit.Next(SgPoint, gcodeLen);
string offspringA;
offspringA = GeneArray[a].GeneValue.Substring(0, SgPoint) +
GeneArray[b].GeneValue.Substring(SgPoint, FgPoint - SgPoint) +
GeneArray[a].GeneValue.Substring(
FgPoint, GeneArray[a].GeneValue.Count() – FgPoint
);
dest[did++] = new Gene(offspringA);
}
Alla funzione vengono passati l‟indice delle due coppie, la funzione creerà da queste
altri due figli che andranno a comporre la generazione successiva.
Come accennato prima, ogni nuovo codice genetico ottenuto dall‟incrocio ha una certa
probabilità di subire una mutazione genetica. Anche qui, a seconda dei casi il metodo
di mutazione può essere differente.
Nel nostro caso faccio variare un punto casuale della stringa allontanando o
avvicinando di 1 il valore del carattere all‟obbiettivo. In teoria si potrebbe
ricorrere ad una mutazione più invasiva, selezionando nuovamente il valore della
casella scelta dal range di caratteri stampabili e sostituendo questo a quello
esistente.
Il metodo mutazione è così implementato:
/// <summary>
/// Gene mutation
/// </summary>
/// <param name="seed">Random source</param>
internal virtual void Mutation(Random seed)
{
int Point = seed.Next(_str.Count());
char[] gene = GeneValue.ToCharArray();
// Pseudo-random mutation point
int rand = (seed.Next(0, 1) == 0)?(-1):(1);
rand += Convert.ToInt32(gene[Point]);
if
(rand > 122) gene[Point] = Convert.ToChar(32);
else if
(rand < 32) gene[Point] = Convert.ToChar(122);
else
gene[Point] = Convert.ToChar(rand);
// Mutation Done!
GeneValue = new string(gene);
}
In altri casi, la mutazione è realizzata in maniera differente, ma rimane invariato il
principio base.
Ad esempio se avessimo codificato il fenotipo in stringhe binarie, una pratica diffusa
è invertire i valori dei bit di due punti selezionati casualmente.
In altri casi si aggiunge o si sottrare una variazione del valore selezionato.
5] Ogni figlio generato comporrà la nuova generazione. Così facendo abbiamo variato la
popolazione iniziale perturbandone i codici genetici.
A questo punto, se nessun gene soddisfa i requisiti imposti, si ripete la procedura dal
punto 2.
Algoritmi genetici e reti neurali – rev. 1.0 11
Copyright © 2011 Matteo Tosato
In molti casi, per causa della deriva genetica, è possibile che in una data popolazione
la diversità venga meno, facendo così stazionare l‟algoritmo senza che questo riesca
più a produrre soluzioni migliori.
Per questo motivo questa casistica viene monitorata e al necessario, vengono introdotte
nuove combinazioni casuali. Nell‟esempio, introduco
della popolazione selezionata per
la riproduzione come nuova. Il codice di controllo viene inserito direttamente dentro
il ciclo principale e si preoccupa di stabilire quando ci si trova in un scenario di
deriva genetica che se lasciato proseguire, fa stazionare l‟algoritmo.
// Avoid genetic drift
CurrentBestFitness = population.GetFitnessOf(population.GetBest());
if (CurrentBestFitness == _pre_Best_ft_)
{
_gdrift++;
}
else
{
_gdrift = 0;
_pre_Best_ft_ = CurrentBestFitness;
}
// Mate pop
population.SortByFitness();
if (_gdrift >= 5)
{
population.ReInitPop(); // Genetic drift problem
_gdrift = 0;
}
population.Mate();
La funzione ReInitPop() esegue il compito specifico di inserire (fra i genotipi
selezionati tramite la tecnica dell‟elitismo) i nuovi geni.
/// <summary>
/// Add new elements in the population, 1/4 of elite
/// </summary>
internal void ReInitPop()
{
int qty = ElitismSelection / 4;
GeneArray.RemoveRange(0, qty);
for (int i = 0; i < qty; i++)
{
GeneArray.Add(new Gene(gcodeLen, MyRandUnit));
}
GeneArray.Reverse();
}
Questa pratica evita di dover utilizzare una popolazione eccessivamente numerosa nel
caso di un problema di ottimizzazione più complicato.
Il codice di esempio utilizzato è disponibile al seguente URL:
https://github.com/Matteo87/FirstGA
Un output di esempio è il seguente:
[+] Type Target String: This is a test program!
[+] Play GA algorithm...
Iteration: 0 Best: ,G&G$fAF/^gQ<>:s6c&S$S[ Fitness: 875
Algoritmi genetici e reti neurali – rev. 1.0 12
Copyright © 2011 Matteo Tosato
Iteration: 1 Best: `vap?;g0M-00iq`x^j_unw0 Fitness: 455
Iteration: 2 Best: `vap?;g0M-00iq)x^j_unw0 Fitness: 400
Iteration: 3 Best: XW`h8pF%_4ffSa:fsvuCIE, Fitness: 390
Iteration: 4 Best: T[\L5mm-['um]x:sKnaH?hM Fitness: 357
Iteration: 5 Best: W`tf-f^)`BdhnmKrrlBRup( Fitness: 294
Iteration: 6 Best: [xfHGfg*b9xHgx"lypZq\K$ Fitness: 278
Iteration: 7 Best: `vap(_p'T4p`iq)x^j_unw0 Fitness: 211
Iteration: 8 Best: ^clY7f^)`(nrnn$rWvgtWs" Fitness: 198
Iteration: 9 Best: ^oov"fy+^"olrX&nxqboS]3 Fitness: 166
Iteration: 10 Best: T_oy!lk c't\oK!qu`an_p% Fitness: 135
Iteration: 11 Best: XWov"fy+_"onrx!quo]r^W! Fitness: 115
Iteration: 12 Best: T_ov"fy+^"onqn liogw_p% Fitness: 94
Iteration: 13 Best: T^ov"fs%a,reqx!qvran_p% Fitness: 77
Iteration: 14 Best: T^ov"fs%a,reqx!qvran_p% Fitness: 77
Iteration: 15 Best: Sgrn kw'c nhnm!qupdn_p% Fitness: 74
Iteration: 16 Best: Whov"fs%a,rdqx!qwohvgm% Fitness: 65
Iteration: 17 Best: Whro ky"a!ncrx!nrudpam Fitness: 55
Iteration: 18 Best: Thro hs%^"oerx!lqxgpal! Fitness: 52
Iteration: 19 Best: Whro ky"a!ncrx!nrogpam Fitness: 46
Iteration: 20 Best: Thls"fs#c uerx!quodsbm% Fitness: 33
Iteration: 21 Best: Thls"fs#c uerx!quodsbm% Fitness: 33
Iteration: 22 Best: Whfs!gs"a xers!quniqbn% Fitness: 32
Iteration: 23 Best: Thls"fs"a!uetu!qmnhsbl% Fitness: 30
Iteration: 24 Best: Shhs!hs b ters#quojt_m# Fitness: 23
Iteration: 25 Best: Thgq hs _ udtx nrogp`m Fitness: 20
Iteration: 26 Best: Thhs!is#d xert"qrogsal! Fitness: 18
Iteration: 27 Best: Thio hs#a"tert qrogsal! Fitness: 14
Iteration: 28 Best: Thls"is a"rest psogral" Fitness: 12
Iteration: 29 Best: Thls"is a"rest psogral" Fitness: 12
Iteration: 30 Best: Thhr!ks"a ters program! Fitness: 9
Iteration: 31 Best: Thhs!is b tert qrogsam! Fitness: 6
Iteration: 32 Best: Thhs!is b tert qrogsam! Fitness: 6
Iteration: 33 Best: Thjs!js a test!program! Fitness: 4
Iteration: 34 Best: Thjs!js a test!program! Fitness: 4
Iteration: 35 Best: Thhs!is a tert program! Fitness: 3
Iteration: 36 Best: This is!a test progrbm! Fitness: 2
Iteration: 37 Best: This is!a test progrbm! Fitness: 2
Iteration: 38 Best: Thhs is a test program! Fitness: 1
Iteration: 39 Best: This is a test program! Fitness: 0
[+] GA done!
Come si vede dall‟output, la stringa viene generata da un insieme di stringhe
inizializzate con caratteri random. Il numero di iterazioni necessarie per questo
compito rimane molto basso. Nell‟esempio è stato utilizzato un tasso di elitismo del
10%, un tasso di mutazione del 25% e numero massimo di iterazioni di . La
popolazione è di 2048 individui.
Naturalmente questo esempio non rappresenta una soluzione ideale per risolvere problemi
di ottimizzazione, ma mira a far capire le linee base di un algoritmo genetico a
livello di programmazione.
Per concludere questo primo discorso sui fondamenti degli algoritmi genetici, finiamo
con il dire che il punto cruciale della costruzione di un GA funzionante non è
incentrato su uno dei punti visti o su un particolare operatore genetico, ma su come,
tutti questi componenti, sono in grado di lavorare assieme.
Algoritmi genetici e reti neurali – rev. 1.0 13
Copyright © 2011 Matteo Tosato
Vediamo di riassumere i punti critici di una GA:
Dimensione della popolazione,
Abbiamo detto essere di grande importanza il numero di geni che compongono la
popolazione. Quanto è minore, tanto maggiore è il rischio di non riuscire a replicare i
dati oppure di non riuscire a ottenere una soluzione soddisfacente. Spesso si
utilizzano più popolazioni, che replicandosi e rinnovandosi in modo indipendente,
assicurano la stabilità della soluzione.
Diversità,
è fondamentale che i geni conservino una certa diversità fra loro per evitare che gli
incroci non generino sufficienti variazioni. La mancanza di questa diversità
corrisponde ad una stagnazione delle caratteristiche. Inizialmente la diversità è
assicurata dall‟inizializzazione casuale, ma deve essere tenuta alta dall‟operatore di
mutazione assistito da una buona percentuale di probabilità. Anche perché nel corso
delle generazioni il processo di riproduzione tende a ridurre la diversità della
popolazione e, anche se il processo di selezione fosse completamente casuale, dopo
qualche generazione tutti i cromosomi si assomiglierebbero (deriva genetica). Anche
l‟operatore cross-over tende a contrastare questo effetto, creando nuove strutture
dalla combinazione di parti delle stringhe esistenti, ma esso non è sufficiente per
tutti i casi. Un metodo ulteriore per mantenere alto il livello di diversità è
introdurre nuovi genomi inizializzati casualmente ad ogni generazione, come abbiamo
visto in precedenza nell‟esempio.
Gli operatori genetici,
Sappiamo già bene come funzionano gli operatori, ma vanno dette ancora alcune cose
sull‟operatore incrocio. E‟ stato detto che lo scambio di materiale genetico può essere
fatto su un singolo punto o su due, ma esiste anche la variante „multi-crossover‟ dove
ci sono più punti di scambio. Una buona pratica è utilizzare tutte e tre queste
varianti, in modo che anche la modalità con la quale la riproduzione avviene, possa
variare. Invece l‟operatore „inversione‟ ancora non introdotto, può essere utilizzato
anch‟esso con lo stesso scopo. Questo operatore inverte semplicemente l‟ordine dei
componenti del genoma.
Un operatore di incrocio „parametrico‟ sviluppato da De Jong nel 1991, prevede un
incrocio in qualsiasi punto del genoma secondo una probabilità compresa fra 0.5 e 0.7.
Quest‟ultimo assieme a quello a due punti sono quelli più diffusi a livello
applicativo.
Sempre De Jong nel 1975, sviluppò il sistema di „sfollamento‟ che consiste nella
sostituzione, da parte di un individuo appena formato, di quello all‟interno della
popolazione, da parte di un individuo appena formato, di quello dell‟interno della
popolazione più simile al nuovo arrivato.
Ci sono poi molte altre varianti per ogni tecnica che abbiamo descritto, In particolare
per l‟operatore cross-over ne esistono una infinità. Nulla poi vieta di implementarne
una nuova. Si deve considerare che effettuando dei test e cambiando parametri e
operatori genetici si può ricercare la miglior combinazione per il proprio problema.
Parametri,
In ultimo, ci sono diversi parametri da stabilire a seconda del tipo di implementazione
scelta. I principali sono il coefficiente di mutazione e di incrocio. Non vi è molto da
dire se non che quelli attualmente utilizzati per la maggiore sono quelli di De Jong
definiti a metà degli anni „70. Egli prevede un numero di individui iniziale fra 50 e
100, un coefficiente di mutazione per ciascuna coppia di genitori pari a 0.6 e un
coefficiente di mutazione di 0.001.
Come è stato possibile constatare, gli AG possono avere un‟architettura assai varia,
non esistono metodi ben definiti per decidere quale architettura utilizzare, dipende
Algoritmi genetici e reti neurali – rev. 1.0 14
Copyright © 2011 Matteo Tosato
sempre dal compito sottoposto. Ricercatori anno anche pensato di utilizzare GA per
trovare i migliori parametri per i GA.
Cercando di essere esaurienti, vale la pena dedicare del tempo anche ad una trattazione
puramente teorica e storica.
Uno dei fondamenti matematici importanti degli algoritmi genetici è il teorema degli
schemi, formulato da Holland nel 1975. E‟ da questa che poi si sviluppa tutto il metodo
algoritmico che abbiamo descritto fino ad ora.
Uno schema è una sequenza ricorrente di bit in una stringa binaria. Ad esempio: H =
0#######1 identifica tutte le stringhe di 8 bit che cominciano con 0 e finiscono con 1.
H ha solo due bit definiti e risulta quindi essere di ordine 2 mentre la sua lunghezza
di definizione è 7 (distanza).
Il teorema dimostra come la probabilità di sopravvivere alla mutazione sia maggiore
negli schemi brevi e di ordine basso e aventi idoneità media superiore alla media
generale. In ogni generazione le istanze aumentano di un fattore, per questo gli schemi
sono rappresentati da un numero di istanze valutate crescenti esponenzialmente. Gli
schemi sono adottati per effettuare analisi e valutazioni sui GA.
(
)
(Mitchell M. 1998)
dove:
- „H‟ uno schema con almeno un‟istanza nella popolazione all‟istante t.
- m(H,t) il numero di istante di H all‟istante t.
- u(H,t) l‟idoneità media di H rilevata all‟istante t.
- f(t) l‟idoneità media della popolazione all‟istante t.
- la probabilità di applicare alla stringa un incrocio a un punto singolo.
Questa formula viene utilizzata per trovare il numero di istanze di H all‟stante t+1.
La sottostante formula sempre di Mitchell esprime la probabilità che fra i discendenti
di H ne esista almeno uno che è istanza di H.
Dove d(H) è la lunghezza di definizione di H e l la lunghezza delle stringhe di bit.
Quindi la probabilità che lo schema H sopravviva mutando un‟istanza di H:
In cui:
o(H) è il numero di bit, l‟ordine di H.
è la probabilità che un certo bit sia mutato.
0x03] Programmazione genetica
Una delle più importanti applicazioni degli algoritmi genetici è quello della
programmazione genetica o evolutiva. In sostanza si tratta di utilizzare un AG su una
popolazione formata da programmi.
I programmi che compongono la popolazione non vengono codificati in stringhe, ma come
strutture ad alberi sintattici che includono nodi e connessioni. Il ruolo degli
operatori genetici consiste nel far evolvere queste strutture di albero. Ogni nodo
dell‟albero sintattico indica una operazione e le connessioni (terminali) rappresentano
gli argomenti per ogni istruzione.
Algoritmi genetici e reti neurali – rev. 1.0 15
Copyright © 2011 Matteo Tosato
Gli alberi „PG‟ possono essere rappresentati in notazione prefissa (prefix notation),
come quella usata dal linguaggio LISP (S-expression), per fare un esempio, il programma
(funzione) ( )( )( ) può essere rappresentato nel modo seguente:
f
* + +
2 *
x x
x *
10 y
4 *
x y
Albero sintattico
Ma anche la funzione:
+
ΔWt
*
α *
- X
YYD
Naturalmente è necessario all‟inizio un insieme di terminali, ovvero argomenti
costanti, variabili indipendenti, etc) per ogni ramo del programma in evoluzione e un
insieme di funzioni primitive per ogni ramo del programma in evoluzione. Da questi
insiemi vengono estratti i valori casuali iniziali per i terminali degli alberi. Una
misura di fitness, i parametri soliti dei GA e un criterio con il quale far terminare
la ricerca.
Vediamo però in che cosa consistono in termini concreti questi schemi, un esempio è la
„regressione simbolica‟.
Ad esempio, trovare una espressione matematica per la funzione , usando
come insieme di funzioni {+,-,*,/} e come insiemi di terminali {x,[0…9]}.
I passi principali della programmazione genetica sono riassumibili con i seguenti
punti:
- Generare una popolazione iniziale aleatoria di programmi individuali, ognuno di
questi programmi, è composto da funzioni e terminali.
- Eseguire ogni programma della popolazione e valutare il suo fitness.
- Scegliere casualmente due programmi dalla popolazione valutando il loro valore di
idoneità, per la generazione della nuova popolazione.
- Creare i nuovi programmi della nuova generazione applicando gli operatori genetici.
- Applicare operatori e operazioni avanzati ai programmi scelti.
Algoritmi genetici e reti neurali – rev. 1.0 16
Copyright © 2011 Matteo Tosato
- Valutare il criterio di terminazione. Se il criterio risulta soddisfatto scegliere
il miglior programma della popolazione come soluzione al problema.
La selezione può avvenire in modo simile a quella vista in precedenza, sempre in
funzione all‟idoneità del programma.
Gli operatori genetici, anche in questo caso possono essere implementati in vari modi.
L‟operatore di incrocio genera nuovi programmi figli per la nuova popolazione
ricambiando in modo casuale le codifiche dei programmi, in un modo molto simile a
quello visto prima. I nodi vengono scelti e invertiti, portando dietro tutte le loro
connessioni terminali, ad esempio:
+
* +
x x * *
yyxy
+
+
* *
x
x x
Nel caso dell‟operatore di mutazione, ci sono due strategie adottabili. La prima è
scegliere un nuovo nodo dell‟albero in modo aleatorio e rimpiazzarlo con un altro nodo
scelto dal rispettivo insieme in modo casuale. Oppure si può far mutare un intero
sotto-albero sempre generandolo in modo casuale.
Poi in aggiunta a questi metodi standard, vi sono altri operatori degni di nota:
L‟operatore di permutazione, che è in grado di far permutare gli „m‟ argomenti di una
funzione facente parte di una S-expression.
L‟operatore di punizione, il quale rimuove alcuni programmi sulla base della loro
idoneità.
Operazione di modifiche dell‟architettura, questo genera un nuovo programma applicando
ad un programma esistente un‟operazione di modifica dell‟architettura scelta in modo
casuale.
0x04] Ottimizzazione di reti neurali artificiali
L‟applicazione dei GA che intendo affrontare in modo approfondito, è rivolta alle reti
neurali, in particolar modo vedremo come utilizzare la capacità di ottimizzazione dei
GA per il problema dell‟addestramento. Sia sostituendoci completamente agli algoritmi
standard, sia facendo dei GA solo uno strumento di supporto.
Sia i GA che le reti neurali, costituiscono una simulazione di sistemi biologici. I
primi si ispirano alla legge dell‟evoluzione, i secondo ai neuroni del cervello.
Entrambi sono utilizzati per risolvere classi di problemi troppo complessi per essere
affrontati in logica sequenziale. Negli anni i ricercatori hanno tentato di unire
questi sistemi nel tentativo di migliorarne ancor di più il rispettivo funzionamento.
Questa interazione fra apprendimento ed evoluzione ha dato dei risultati molto
interessanti e si è rivelata utile non solo in informatica ed elettronica, ma anche
nelle scienze cognitive, psicologia, medicina e vita artificiale. In quest‟ultima reti
neurali e GA sono in grado di costituire il comportamento, evoluzione e adattamento di
piccoli organismi completamente artificiali. Questi prendono coscienza del mondo
esterno prendendo delle decisioni in base a ciò che li circonda per raggiungere degli
obiettivi stabiliti.
Ma tornando a noi, prendiamo coscienza di quelli che sono i problemi risolvibili
attraverso l‟evoluzione dei pesi sinaptici di una rete neurale anziché il loro
Algoritmi genetici e reti neurali – rev. 1.0 17
Copyright © 2011 Matteo Tosato
addestramento seguendo per esempio la tecnica della discesa del gradiente.
Con l‟addestramento tradizionale sappiamo che ci sono diverse problematiche nelle
quali possiamo incappare, ad esempio i minimi locali e quindi il problema della
corretta impostazione dei coefficienti di apprendimento. Normalmente può capitare che
un algoritmo di addestramento come back-propagation rimanga bloccato su un minimo,
allungando enormemente i tempi di apprendimento della rete. Utilizzando parametri
adattativi che modificano i delta di aggiornamento per le sinapsi, sono stati
raggiunti si buoni risultati, ma spesso succede che per uscire da un minimo locale, il
rate di apprendimento cresce eccessivamente facendo diminuire la precisione finale
della rete.
Un‟altra occasione dove gli AG sono ben accolti, è quando la funzione da approssimare
risulta non continua. In questo caso l‟apprendimento basato sugli AG è l‟unica
soluzione possibile.
Una rete neurale viene codificata in geni attraverso una codifica diretta di tutte le
sue connessioni sinaptiche. Dunque ogni gene è rappresentato con un array di valori
decimali positivi e negativi.
x1
x2
x3
x4
x5
X6
X7
X8
X9
x0
h3
h2
h1
h0
Y0
... gene
Codifica
diretta
Inizialmente vengono allocate N stringhe, dove N è un numero che esprime la dimensione
iniziale della popolazione. C‟è subito un vantaggio evidente: un algoritmo genetico ha
modo di esplorare una spazio di soluzioni possibili molto più vasto, dato che inizia
la sua esplorazione da più punti nello spazio. Mentre un algoritmo standard può
partire solo da una combinazione e convergere da quella, al risultato desiderato.
Algoritmi genetici e reti neurali – rev. 1.0 18
Copyright © 2011 Matteo Tosato
1 - Soluzione inizializzata
aleatoriamente
2 - Soluzione scoperta
dall’algoritmo
1 - Soluzioni inizializzate
aleatoriamente
2 – Soluzione migliore
Esiste quindi una possibilità che la ricerca effettuata dall‟algoritmo genetico dia
effettivamente dei risultati migliori di quella standard. Il limite dei GA per questo
compito è la dimensione della rete. Con una rete di grosse dimensioni, avremo geni
troppo lunghi, gli schemi genetici vantaggiosi andrebbero continuamente distrutti e
ricreati rendendo la ricerca della miglior soluzione non efficace.
La funzione di fitness in questo caso è data da un ciclo di esecuzione della rete su
un pattern set completo. Quindi ogni gene, ovvero ogni configurazione sinaptica della
rete, risulterà idonea sulla base dell‟errore accumulato durante questo ciclo. Dunque
data l‟equazione dell‟errore quadratico medio utilizzata anche dagli algoritmi
standard, possiamo definire il valore di idoneità della rete come l‟inverso della
somma dei quadrati degli errori sull‟intera lista di pattern:
∑ ∑ ( )
Dove Sono i valori di uscita dei neuroni di output, i valori attesi. Dunque gli
errori della rete si accumulano durante il ciclo su „k‟ esempi.
A questo punto abbiamo tutto ciò che serve per poter far evolvere queste
configurazioni secondo i criteri già visti. In particolare adatteremo tutti gli
operatori genetici visti prima al tipo di codifica utilizzata. L‟incrocio sarà
esattamente uguale, per quanto riguarda l‟operatore di mutazione è opportuno variarlo,
meglio non sostituire completamente il valore di una connessione, ma estrarre
casualmente una valore delta compreso tra -1.0 e 1.0 da sommare.
Algoritmi genetici e reti neurali – rev. 1.0 19
Copyright © 2011 Matteo Tosato
Nonostante gli algoritmi genetici offrano una soluzione per l‟addestramento già di per
se completa, molte volte è preferibile integrarli con le soluzioni di addestramento
standard.
Questa soluzione prende il nome di “Apprendimento per rinforzo evolutivo” e risulta
essere quella che offre la migliore performance nella maggioranza dei casi. Prima si
fanno evolvere le varie configurazioni iniziali, poi sulla migliore configurazione, si
procede con un algoritmo di Error-back-propagation o una delle sue varianti.
Di seguito presento i risultati ottenuti da una rete neurale feed-forward, addestrata
per risolvere il problema (linearmente non separabile) XOR. Per l‟esempio ho
utilizzato il mio framework per l‟AI e algoritmi genetici. Riporterò solo il cuore
principale del codice di esempio di seguito.
Questi grafici indicano rispettivamente l‟output e l‟errore della rete neurale durante
l‟addestramento eseguito con algoritmi genetici:
Dall‟epoca 53 la condizione di precisione, impostata ad un errore di 0.01, viene
soddisfatta. Tutte le generazioni successive aumentano ulteriormente la precisione
della rete, difatti dal grafico precedente è ben visibile come l‟output si va pian
piano a sovrapporre all‟uscita target impostata dall‟algoritmo di addestramento.
Algoritmi genetici e reti neurali – rev. 1.0 20
Copyright © 2011 Matteo Tosato
Questo rivela l‟enorme potenzialità dell‟addestramento per mezzo dell‟evoluzione dei
pesi sinaptici per problemi di questo tipo.
I seguenti risultati sono invece ottenuti tramite un algoritmo Error-back-propagation
con un learning rate adattativo (D. Shiffman):
Sebbene l‟algoritmo EBP necessità di più iterazioni, una di queste è sicuramente meno
dispendiosa di una iterazione genetica, la quale richiede l‟esecuzione di molte più
operazioni semplici.
Si nota anche che l‟addestramento evolutivo ha portato ad una maggiore precisione la
rete neurale, e di come il processo di apprendimento risulti più lineare rispetto EBP,
il quale esegue sia micro-oscillazioni dovute al decremento o incremento dei delta di
aggiornamento che danno quella caratteristica linea, sia macro-oscillazioni dovute ai
minimi locali.
E‟ mezzo di questi due metodi che è possibile definire la tecnica che fa uso di
entrambi, citata in precedenza. L‟apprendimento per rinforzo evolutivo produce i
risultati seguenti:
Algoritmi genetici e reti neurali – rev. 1.0 21
Copyright © 2011 Matteo Tosato
Per il test sono state eseguite 40 iterazioni, quindi 40 generazioni, con gli
algoritmi genetici. Successivamente, la migliore configurazione emergente dalla
selezione genetica viene caricata nella rete, la quale viene poi finalizzata con un
algoritmo standard di retro-propagazione dell‟errore.
L‟output della rete oscilla molto, poi prende la giusta tendenza verso gli output
desiderati. L‟errore quadratico medio rispecchia lo scenario appena descritto:
Per il problema XOR, quest‟ultima combinazione, non ha apportato molti benefici, anche
se alla fine, abbiamo raggiunto la precisione desiderata in un numero di epoche minore
rispetto lo standard EBP.
In questo caso la miglior soluzione rimane quella dell‟addestramento evolutivo.
Di seguito la classe principale dell‟esempio. Permette di capire come utilizzare le
chiamate al framework „aneuro32‟.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Using My AI framework
using aneuro32.Structure; // Neural network structures
using aneuro32.Training.Propagation; // Standard back-propagation training algorithms
Algoritmi genetici e reti neurali – rev. 1.0 22
Copyright © 2011 Matteo Tosato
using aneuro32.Ga.EvolutionaryReinforcementLearning; // Evolutionary reinforcement learning algorithm
namespace ERLTest
{
// IA framework manager
internal class IAManCore
{
/// <summary>
/// Neural network
/// </summary>
internal IFFNeuralNetwork MyNeuralNetwork;
/// <summary>
/// Error-back-propagation ['adaptive' based on Shiffman theory]
/// </summary>
internal ShiffmanVariantBackpropagation MyEbpTrainingAlgorithm;
/// <summary>
/// Evolutionary reinforcement learning with EBP and genetic algorithms
/// </summary>
internal ERLearning MyERLearningAlgorithm;
/// <summary>
/// Show form
/// </summary>
private Form_perfrmon ShowForm;
/// <summary>
/// Status bar UI
/// </summary>
private bar status;
/// <summary>
/// learning mode definitions
/// </summary>
public enum mode_t {
ebp_only, erl, ga_only
};
/// <summary>
/// Arrays for logger...
/// </summary>
public double[][] DebugOutputArray;
public double DebugError;
/// <summary>
/// Error target
/// </summary>
private double error_target;
/// <summary>
/// Population size
/// </summary>
private int PopulationSize;
/// <summary>
/// Maximum number of generations
/// </summary>
private int nGenerations;
/// <summary>
/// Log results
/// </summary>
Logger ResLog;
/// <summary>
/// Constructor
/// </summary>
/// <param name="input_ne">Number of input neurons</param>
/// <param name="hidden_ne">Number of hidden neurons</param>
/// <param name="hidden_layer">Number of hidden layer</param>
/// <param name="output_ne">Number of output neurons</param>
/// <param name="error_target">Error target</param>
public IAManCore(
int input_ne,
int hidden_ne,
int hidden_layer,
int output_ne,
double error_target
)
{
// Initialize new ann
MyNeuralNetwork = new ffnetwork();
// Add layers
MyNeuralNetwork.AddNewLayer(input_ne);
for (int a = 0; a < hidden_layer; a++)
MyNeuralNetwork.AddNewLayer(hidden_ne);
MyNeuralNetwork.AddNewLayer(output_ne);
// Add bias
MyNeuralNetwork.AddBiasWeight();
// Finalize network
MyNeuralNetwork.Build();
// Allocate Output matrix (for logger)
DebugOutputArray = new double[xor_idOutput.Count()][];
for(int i = 0 ; i<DebugOutputArray.Count();i++)
DebugOutputArray[i] = new double[xor_idOutput[0].Count()];
this.error_target = error_target;
}
/// <summary>
/// Passing working parameters
/// </summary>
Algoritmi genetici e reti neurali – rev. 1.0 23
Copyright © 2011 Matteo Tosato
/// <param name="pop_n">Population maximum size</param>
/// <param name="max_generations">Maximum number of generations</param>
/// <param name="status">Status bar</param>
/// <param name="UIobj">Graphs form</param>
public void Parameters(
int pop_n,
int max_generations,
ref bar status,
ref Form_perfrmon UIobj
)
{
PopulationSize = pop_n;
nGenerations = max_generations;
// Status bar
this.status = status;
// Graphs form
ShowForm = UIobj;
}
/// <summary>
/// Run learning algorithms
/// </summary>
/// <param name="mode">Learning algorithm type</param>
/// <returns>Return true on success, false on error</returns>
public bool PlayTest(mode_t mode)
{
status.InitializeBar(100);
status.Show();
ResLog = new Logger("error.csv", "output.csv", "Error", "Output#Target");
// Clear previous synapses configuration
MyNeuralNetwork.clear();
switch (mode)
{
// Use EBP learning only
case mode_t.ebp_only:
{
// Builds new ebp training algorithm
MyEbpTrainingAlgorithm = new ShiffmanVariantBackpropagation(
ref MyNeuralNetwork,
xor_input,
xor_idOutput,
aneuro32.Functions.activation_mode.sigmoid,
0.35,
error_target,
0.2,
10000
);
if(ShowForm != null) ShowForm.SetFormTitle("Training with error-back-propagation");
do
{
if (MyEbpTrainingAlgorithm.iteration() == false)
throw (new Exception(MyNeuralNetwork.GetLastError()));
// Retrieve output
DebugOutputArray = MyEbpTrainingAlgorithm.GetPatternSetOutput();
// Get last error
DebugError = MyEbpTrainingAlgorithm.CurrentMeanSquareError;
// Update graphs
if (ShowForm != null) ShowForm.UpdateErrorGraph(DebugError);
if (ShowForm != null) ShowForm.UpdateOutputGraph(DebugOutputArray, xor_idOutput);
// Log
ResLog.ToErrorLog(DebugError);
ResLog.ToOutputLog(DebugOutputArray, xor_idOutput);
status.UpdateBar(Convert.ToInt32(((100 - DebugError * 100) > 100) ? 1 : (100 - DebugError *
100)));
} while (MyEbpTrainingAlgorithm.isTrained == false);
if (MyEbpTrainingAlgorithm.CurrentMeanSquareError > error_target)
{
ResLog.CloseAll();
return false; // Not sufficient
}
ResLog.CloseAll();
// The internal error target of EBP object may be lower then error target defined by the user
return true; // Trained
}
// Use evolutionary reinforcement learning
case mode_t.erl:
{
// Builds new ebp training algorithm
MyERLearningAlgorithm = new ERLearning(
ref MyNeuralNetwork,
PopulationSize,
xor_input,
xor_idOutput,
aneuro32.Functions.activation_mode.sigmoid,
error_target
Algoritmi genetici e reti neurali – rev. 1.0 24
Copyright © 2011 Matteo Tosato
);
if (ShowForm != null) ShowForm.SetFormTitle("Training with evolutionary reinforcement learning");
// Genes evolution
for (int i = 0; i < nGenerations; i++)
{
MyERLearningAlgorithm.WEvolutionSingleStep();
// Retrieve output
DebugOutputArray = MyERLearningAlgorithm.GetPatternSetOutput();
// Get last error
DebugError = MyERLearningAlgorithm.GetLastMeanSquareError;
// Update graphs
if (ShowForm != null) ShowForm.UpdateErrorGraph(DebugError);
if (ShowForm != null) ShowForm.UpdateOutputGraph(DebugOutputArray, xor_idOutput);
// Log
ResLog.ToErrorLog(DebugError);
ResLog.ToOutputLog(DebugOutputArray, xor_idOutput);
status.UpdateBar(Convert.ToInt32(((100 - DebugError * 100) > 100) ? 1 : (100 - DebugError *
100)));
}
// Load best solution
MyERLearningAlgorithm.LoadSolution(MyERLearningAlgorithm.GetBestId);
// Finalize with EBP
do
{
if (MyERLearningAlgorithm.ExecEBP() != true)
break;
// Retrieve output
DebugOutputArray = MyERLearningAlgorithm.GetPatternSetOutput();
// Get last error
DebugError = MyERLearningAlgorithm.GetLastMeanSquareError;
// Update graphs
if (ShowForm != null) ShowForm.UpdateErrorGraph(DebugError);
if (ShowForm != null) ShowForm.UpdateOutputGraph(DebugOutputArray, xor_idOutput);
// Log
ResLog.ToErrorLog(DebugError);
ResLog.ToOutputLog(DebugOutputArray, xor_idOutput);
status.UpdateBar(Convert.ToInt32(((100 - DebugError * 100) > 100) ? 1 : (100 - DebugError *
100)));
} while (MyERLearningAlgorithm.EBP.Trained() == false);
if (MyERLearningAlgorithm.GetLastMeanSquareError > error_target)
{
ResLog.CloseAll();
return false;
}
ResLog.CloseAll();
return true;
}
// Use genetic-algorithm only
case mode_t.ga_only:
{
// Builds new evolutionary training algorithm
MyERLearningAlgorithm = new ERLearning(
ref MyNeuralNetwork,
PopulationSize,
xor_input,
xor_idOutput,
aneuro32.Functions.activation_mode.sigmoid,
error_target
);
if (ShowForm != null) ShowForm.SetFormTitle("Training with genetic algorithms");
// Genes evolution
for (int i = 0; i < nGenerations; i++)
{
MyERLearningAlgorithm.WEvolutionSingleStep();
// Retrieve output
DebugOutputArray = MyERLearningAlgorithm.GetPatternSetOutput();
// Get last error
DebugError = MyERLearningAlgorithm.GetLastMeanSquareError;
// Update graphs
if (ShowForm != null) ShowForm.UpdateErrorGraph(DebugError);
if (ShowForm != null) ShowForm.UpdateOutputGraph(DebugOutputArray, xor_idOutput);
// Log
ResLog.ToErrorLog(DebugError);
ResLog.ToOutputLog(DebugOutputArray, xor_idOutput);
status.UpdateBar(Convert.ToInt32(((100 - DebugError * 100) > 100) ? 1 : (100 - DebugError *
100)));
}
// Load best solution
MyERLearningAlgorithm.LoadSolution(MyERLearningAlgorithm.GetBestId);
if (MyERLearningAlgorithm.GetLastMeanSquareError > error_target)
Algoritmi genetici e reti neurali – rev. 1.0 25
Copyright © 2011 Matteo Tosato
{
ResLog.CloseAll();
return false;
}
ResLog.CloseAll();
return true;
}
default:
return false;
}
}
// Data for test
// XOR TEST
private double[][] xor_input =
{
new double[] {0.0,0.0},
new double[] {1.0,0.0},
new double[] {0.0,1.0},
new double[] {1.0,1.0}
};
private double[][] xor_idOutput =
{
new double[] {0.0},
new double[] {1.0},
new double[] {1.0},
new double[] {0.0}
};
}
}
Come abbiamo potuto osservare nell‟esempio precedente, l‟utilizzo degli algoritmi
genetici nell‟addestramento di una rete neurale può presentare alcuni vantaggi. Questa
affermazione è vera fino a che la rete neurale rimane entro certe dimensioni e un
certo ordine di complessità, al crescere del numero di neuroni e sinapsi
l‟addestramento basato sui GA perde la sua forza diventando piuttosto inefficiente,
dato che, come avevamo detto, l‟operatore incrocio tenderebbe a distruggere gli schemi
corretti.
Finora abbiamo parlato di come ottimizzare solamente il valore delle connessioni delle
sinapsi che collegano i vari neuroni della rete. Esistono però soluzioni che fanno
qualcosa di più. Ovvero codificare alcuni parametri importanti per l‟architettura,
come ad esempio il numero di nodi, il tipo di connettività, le funzioni di
attivazione, etc … Questo tipo di codifica è ovviamente di tipo indiretto.
L‟evoluzione si occupa di ottimizzare questi parametri strutturali, poi una seconda
fase durante la vita, si occupa di ottimizzare i valori sinaptici.
Ma questa non è ovviamente la sola soluzione, altre varianti prevedono una struttura
genetica suddivisa in due parti separate da dei marcatori genetici. La prima contiene
la codifica dell‟architettura della rete, la seconda i valori sinaptici. I marcatori
genetici hanno il compito di evitare che l‟operatore di cross-over crei combinazioni
senza senso.
Nel 1990 „Kitano‟ utilizzò invece uno schema di sviluppo con cui il genotipo definisce
una serie di regole di sviluppo per l‟architettura. Una regola di sviluppo è
solitamente una equazione ricorsiva che viene applicata ad una matrice contenente i
valori delle sinapsi inizializzate casualmente. L‟equazione consente lo sviluppo di
una rete mediante la duplicazione delle sue parti. Concettualmente questo, schema
richiama fortemente la morfogenesi di alcune piante, nonché i frattali in matematica.
Altre tipologie prevedono in aggiunta anche un processo di „maturazione‟. Ovvero la
rete si sviluppa su un genotipo che codifica le regole per la crescita
dell‟architettura, non l‟architettura in se stessa. Il metodo prevede due fasi di
sviluppo; una nella quale la rete rimane plastica, ovvero essa potrà adattarsi
all‟ambiente in cui si trova, un‟altra fase ne prevede il consolidamento. Questo
rispecchia perfettamente quello che avviene anche negli organismi viventi. In questo
caso il genotipo è rappresentato in blocchi, ogni porzione contiene alcune proprietà
della rete, come i parametri di crescita, le dinamiche di maturazione e la
connettività. Questo tipo di rete è concepita come una struttura bidimensionale che si
estende su una superfice. Dunque ogni gene che descrive un nodo della struttura
neurale ne codifica le coordinate cartesiane x e y. La variazione di questi valori
Algoritmi genetici e reti neurali – rev. 1.0 26
Copyright © 2011 Matteo Tosato
influisce sull‟angolo dell‟assone del neurone modificandone ad esempio il
comportamento. Questo vale ovviamente quando si considera, oltre il valore delle
connessioni, anche la disposizione geometrica dei neuroni della rete. Caratteristica
che fin da subito era stata tralasciata nella rappresentazione artificiale delle reti
neurali biologiche.
E‟ ovvio che l‟evoluzione dell‟architettura di una rete neurale non può rientrare in
un campo di applicazione dove la velocità di ricerca o di ottimizzazione è un elemento
critico. Un algoritmo che evolve una rete neurale non potrà quasi mai essere di veloce
esecuzione, dato che i processi da compiere per ogni generazione sono molti.
Per ogni iterazione genetica dovremo infatti inizializzare topologicamente la rete,
inizializzare le sue connessioni sinaptiche, addestrare la rete per uno o più compiti
utilizzando pattern set generici e valutare i risultati ottenuti convogliandoli in un
valore di fitness che costituisce il feedback verso l‟algoritmo genetico sull‟idoneità
del genotipo.
Per questo motivo, il tipo di metodologia è più indicato nel campo di ricerca di vita
artificiale che nel campo tecnologico.
Oltre l‟evoluzione dell‟architettura neurale, gli algoritmi genetici sono utilizzabili
anche per l‟evoluzione delle regole di apprendimento. Formalizzando la definizione di
regola di apprendimento, possiamo dire che la variazione è una combinazione di
altre 3 variabili, l‟attività presinaptica , l‟attività postsinaptica , e il valore
corrente della sinapsi .
Dove è il learning rate.
Per poter rappresentare e di conseguenza far evolvere questa combinazione per mezzo di
un algoritmo genetico che lavori sui geni, è consuetudine rappresentare l‟equazione
per mezzo di serie delle combinazioni aggiungendo per ogni termine un coefficiente :
( ) ( ) ( ) ( ) ( )
Naturalmente solo i coefficienti vengono codificati. Essi possono assumere valori
tra 0 e 1 o anche tra -1 e 1.
La funzione di fitness decodifica la regola di apprendimento e la utilizza per
addestrare la rete neurale per uno o più problemi. La fitness è data dalle prestazioni
della rete neurale alla fine dell‟apprendimento su un insieme di pattern di
generalizzazione.
0x05] Accenni di vita artificiale
Come detto, gli algoritmi genetici trovano applicazione anche nell‟ambito delle
simulazioni di vita artificiale.
I primi a eseguire queste simulazioni al computer, furono Alan Turing e a John Von
Neumann.
Algoritmi genetici e reti neurali – rev. 1.0 27
Copyright © 2011 Matteo Tosato
Gli organismi che vengono simulati, sono degli automi, che vivono in base a semplici
regole. Questi sono comunque molto più semplici dei batteri reali che vivono sulla
terra, sono dotati di azioni banali come mangiare, riprodursi, spostarsi alla ricerca
di cibo e difendersi dagli altri automi.
La replicazione degli automi, non è sistematica, ma è soggetta a delle mutazioni che
garantiscono la continua evoluzione dell‟organismo.
In questo modo un sistema in cui vivono degli automi cellulari simula il corso di un
processo evolutivo per selezione naturale, con il vantaggio che i tempi che regolano
l‟andamento del processo possono essere controllati dai ricercatori, a differenza di
quanto avviene negli esperimenti effettuati con organismi reali.
Il più semplice esempio di simulazione è quello ideato dal matematico John Horton
Conway nel 1960. L‟ambiente del gioco è bidimensionale, con un territorio a
scacchiera. Gli organismi sono rappresentati da uno o più quadretti colorati di nero,
quelle bianche sono considerate morte. Le regole sono molto semplici:
- ogni cella con nessuna o una sola cella adiacenti muore (questa regola simula la morte
per isolamento).
- ogni cella con quattro celle adiacenti piene muore (questa regola simula la morte per
sovraffollamento).
- ogni cella morta con tre celle adiacenti piene torna in vita alla generazione
successiva (questa regola simula la nascita).
Seguendo queste regole il sistema evolve da solo e questa evoluzione genera forme di
vita sempre più complesse.
Ma in simulazioni più complicate, esiste la possibilità di simulare il comportamento di
sistemi ben più ricchi di caratteristiche. Esistono molte varianti di software di
simulazione, molti utilizzano gli algoritmi genetici per definire il comportamento
degli automi con possibilità di evoluzione secondo l‟ambiente circostante.
Algoritmi genetici e reti neurali – rev. 1.0 28
Copyright © 2011 Matteo Tosato
in certi casi l‟organismo è anche dotato di una sorta di intelligenza artificiale, ad
esempio adibita alla visione. Si tratta per lo più delle volte di esperimenti complessi
in cui si studiano le possibilità di emulare i sistemi biologici in un ambiente
artificiale, molte volte anche virtuale.
0x06] Conclusione
Dopo questa introduzione agli algoritmi genetici e viste alcune delle sue potenzialità
nelle applicazioni, è evidente che questo nuovo approccio che si ispira all‟ingegneria
della natura, permette innanzi tutto di capire il potere dell‟auto organizzazione che
possiamo osservare in natura e secondo, rende possibile lo studio di metodi ed
algoritmi per compiti di ottimizzazione altrimenti impossibili con i metodi standard.
Fin dalla logica Aristotelica, siamo abituati a pensare al Caos come qualcosa di non
ordinato, all‟interno del quale non si può trovare nessun significato.
In realtà noi dobbiamo fare i conti con i nostri limiti, noi riconosciamo e diamo
significato alle cose, soltanto se queste ci paiono organizzate secondo un criterio che
potrebbe essere quello dettato da un progettista umano. Quando non riconosciamo alcun
disegno, tiriamo le conclusioni dicendo “è disordinato”, “non c‟è criterio” oppure
diciamo “non vuol dir nulla”.
In realtà c‟è solo una realtà “osservatore-dipendente” la fuori. Non ci sono ordini o
disordini, o descrizioni assolute. Dunque non possiamo non considerare i sistemi con
elevata entropia pari a quelli che riteniamo ordinati.
Da processi stocastici possono derivare soluzioni altrimenti non raggiungibili con i
mezzi tradizionali.
Gli AG sono utilizzati oggi in una numerosa lista di settori, lista destinata a
crescere. Alcuni esempi:
- Programmazione Evolutiva
- Strategie Evolutive
- Sistemi Classificatori
- Programmazione Genetica
- Progettazione di circuiti elettronici digitali
- Data mining
- Logica fuzzy
- Biologia e bioinformatica
- Modelli scientifici
- Modelli statistici
- ...
Algoritmi genetici e reti neurali – rev. 1.0 29
Copyright © 2011 Matteo Tosato
0x07] Riferimenti
- http://it.wikipedia.org/wiki/Algoritmo_genetico
- http://www2.econ.iastate.edu/tesfatsi/holland.GAIntro.htm
- http://www.obitko.com/tutorials/genetic-algorithms/index.php
- Book - An introduction to genetic algorithms [Mitchell Melanie]
- Book - Algoritmi genetici [A.Boccalatte, E.Montaldo]
- Book - Global optimization algorithms [Thomas Weise]
- http://mnemstudio.org
- http://mathworld.wolfram.com/CellularAutomaton.html
- http://wiki.darwinbots.com/w/Main_Page
- http://www.semeion.it
- https://github.com/Matteo87/Aneuro32