Algoritmi genetici e reti neurali

29
~ 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)

description

Introduzione agli algoritmi genetici e approfondimento su sistemi di apprendimento evolutivi per reti neurali

Transcript of Algoritmi genetici e reti neurali

Page 1: Algoritmi genetici e reti neurali

~ 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)

Page 2: Algoritmi genetici e reti neurali

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

Page 3: Algoritmi genetici e reti neurali

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

Page 4: Algoritmi genetici e reti neurali

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

Page 5: Algoritmi genetici e reti neurali

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.

Page 6: Algoritmi genetici e reti neurali

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>

Page 7: Algoritmi genetici e reti neurali

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‟:

Page 8: Algoritmi genetici e reti neurali

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.

Page 9: Algoritmi genetici e reti neurali

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)

{

Page 10: Algoritmi genetici e reti neurali

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.

Page 11: Algoritmi genetici e reti neurali

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

Page 12: Algoritmi genetici e reti neurali

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.

Page 13: Algoritmi genetici e reti neurali

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

Page 14: Algoritmi genetici e reti neurali

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.

Page 15: Algoritmi genetici e reti neurali

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.

Page 16: Algoritmi genetici e reti neurali

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

Page 17: Algoritmi genetici e reti neurali

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.

Page 18: Algoritmi genetici e reti neurali

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.

Page 19: Algoritmi genetici e reti neurali

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.

Page 20: Algoritmi genetici e reti neurali

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:

Page 21: Algoritmi genetici e reti neurali

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

Page 22: Algoritmi genetici e reti neurali

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>

Page 23: Algoritmi genetici e reti neurali

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

Page 24: Algoritmi genetici e reti neurali

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)

Page 25: Algoritmi genetici e reti neurali

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

Page 26: Algoritmi genetici e reti neurali

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.

Page 27: Algoritmi genetici e reti neurali

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.

Page 28: Algoritmi genetici e reti neurali

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

- ...

Page 29: Algoritmi genetici e reti neurali

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