Informatica Applicata
Transcript
Informatica Applicata
Informatica Applicata Algoritmi Evolutivi Laurea In Informatica Applicata Università degli studi di Pisa A.A. 2003/2004 Laboratorio di Algoritmi Evolutivi Autori: Dario Maggiari & Giorgio Montali Introduzione Il progetto di implementazione e sviluppo di un Algoritmo Genetico, pone come obiettivo principale lo studio di problemi di massimizzazione e minimizzazione di funzioni matematiche. Lo scopo principale del funzionamento dell’algoritmo evolutivo, non è esclusivamente quello di ottenere una soluzione ottima, ma piuttosto di studiare l’implementazione attraverso un linguaggio orientato ad oggetti, quale Java, e approcciare in modo sistematico gli strumenti di ricerca nel campo degli algoritmi evolutivi. Il documento presentato è suddiviso nelle seguenti sezioni: Descrizione del funzionamento e dell’implementazione degli operatori genetici. Presentazione delle funzioni analizzate. Risultati sperimentali: prove ed analisi. Conclusioni. 1. Descrizione del funzionamento e dell’implementazione degli operatori genetici 1.1 Presentazione delle classi Java implementate Le classi implementate sono le seguenti: 1. Classe Chromosome. La classe Chromosome implementa l’oggetto cromosoma contenente le informazioni genetiche necessarie al funzionamento dell’algoritmo, codificate in stringhe binarie di bit. In questa classe sono implementati i metodi necessari a costruire casualmente i cromosomi più adatti in relazione alle funzioni matematiche, nonché un metodo di decodifica della stringa di bit in un numero reale. La classe presenta inoltre metodi utili a reperire informazioni sul cromosoma stesso. 2. Classe Individual La classe Individual mantiene tutte le informazioni relative ad un individuo di una popolazione. Contiene il Cromosoma, il valore decodificato dello stesso, il valore di fitness e tiene traccia delle informazioni evolutive relative all’individuo nelle varie generazioni. 3. Classe Population La classe Population implementa tutto ciò che riguarda l’effettivo funzionamento dell’algoritmo di ricerca. Qui sono definiti i metodi di inizializzazione della popolazione, di selezione, di crossover e di mutazione. Inoltre sono stati implementati alcuni metodi di utilità relativi al calcolo delle statistiche delle generazioni nonché i metodi di stampa su file che mostrano il procedere dell’algoritmo. 1.2 Funzionamento globale del GAS Gli algoritmi evolutivi, come noto, utilizzano tre operatori principali: • Operatore di selezione. • Operatore di mutazione. • Operatore di crossover. Come primo passo la popolazione viene creata in modo (pseudo)casuale con la generazione di stringhe adatte alla funzione da ottimizzare. Successivamente è il primo operatore che si occupa di analizzare e selezionare gli individui più adatti alla sopravvivenza. Gli operatori di selezione che sono stati implementati sono i seguenti: ✔ Operatore di selezione basato sul metodo della Roulette. Questo operatore effettua una ricerca globale sull’intera popolazione e assegna ad ogni individuo la probabilità di sopravvivenza calcolata come: fitness_individuo / fitness_totale Dopodiché si seleziona con probabilità precedentemente assegnata un individuo della popolazione che verrà poi utilizzato per il crossover. In questo modo saranno selezionati gli individui con probabilità più alta che andranno a combinarsi con altri individui presumibilmente con buona fitness. VANTAGGI: • Semplicità di programmazione. • Semplicità computazionale. • Adattabilità ad ogni tipo di problema. SVANTAGGI: • ✔ Convergenza non assicurata (convergenza prematura e stagnazione). Operatore di selezione con Torneo. Questo operatore seleziona un certo numero di individui della popolazione e li confronta tra loro restituendo il migliore tra essi. Permette una vastissima gamma di scelte implementative in relazione al tipo di problema da ottimizzare. Su una popolazione di 30 individui, ad esempio, se si selezionano 8 individui da raffrontare, con un semplice calcolo probabilistico si noterà che in questo modo si avrà una probabilità di circa 84% di selezionare almeno un individuo tra i migliori 6 dell’intera popolazione. A seconda del problema che si intende risolvere/ottimizzare occorrerà decidere se aumentare o diminuire il numero di individui da raffrontare che conseguentemente aumenterà o diminuirà la probabilità di ottenere individui con fitness migliore. VANTAGGI: • Scalabilità. • Buona funzionalità in termini di selezione degli individui migliori. • Bassa probabilità di stagnazione. SVANTAGGI: • Convergenza non assicurata (convergenza prematura). ✔ Operatore di selezione d’Elite. L’operatore di selezione d’Elite è un operatore che viene applicato sulla nuova generazione dopo che gli altri operatori (selezione con Roulette o Torneo, Crossover e Mutazione) hanno già lavorato. La selezione d’Elite si preoccupa di analizzare le fitness degli individui appena creati, in modo tale da sostituire l’individuo peggiore con una copia del migliore della popolazione precedente. VANTAGGI: • Sempre applicabile. • Convergenza assicurata. SVANTAGGI: • In particolari casi soffre di stagnazione su valori prossimi ad una buona soluzione. Dopo l’operatore di selezione, l’algoritmo evolutivo procede con l’operatore di crossover, il quale richiama al suo interno l’operatore di mutazione. ✔ Operatore di mutazione. L’operatore di mutazione muta ciascun allele di ogni cromosoma in modo indipendente con probabilità assegnata. ✔ Operatore di crossover. L’operatore di crossover combina con probabilità assegnata due individui precedentemente selezionati. Quindi genera un taglio casuale sul cromosoma e combina i due cromosomi incrociandone gli alleli, ottenendo nuovi individui (crossover one- point). A seconda delle funzioni studiate sono stati implementati diversi operatori di crossover per risolvere problemi di dimensione nella rappresentazione delle variabili (parte intera e parte decimale) delle funzioni e per risolvere problemi di decisione del segno algebrico. 2. Presentazione delle funzioni analizzate Per testare il funzionamento dell’algoritmo sono state studiate 5 diverse funzioni matematiche che sono state implementate nella classe ObjectiveFunction. Due di queste funzioni necessitano di essere massimizzate, mentre le altre tre presentano un problema di minimizzazione. 2.1 Funzione presentata da Goldberg: f(x) = (x/c)10 (Problema di Massimizzazione) Per lo studio di tale funzione la variabile x assume valori interi tra 0 e 230, mentre la costante di normalizzazione c assume valore 1.073.741.823. La massimizzazione di questa funzione non presenta né problemi di rappresentazione numerica né particolari problemi di ricerca del massimo valore. La costante di normalizzazione è inserita per verificare sperimentalmente il raggiungimento dell’ottimo che assume valore 1. 2.2 Funzione f(x) = x2 (Problema di Massimizzazione) Per lo studio di tale funzione la variabile x assume valori interi tra 0 e 1.000.000. Questa funzione è stata utilizzata per effettuare test su problematiche relative alla rappresentazione binaria dei cromosomi e per verificare il corretto funzionamento dei vari operatori di selezione e dell’operatore di crossover. L'ottimo di questa funzione, come logico, è posto in 1.000.000. 2.3 Prima Funzione di De Jong: f(xi) = ∑3i = 1 x2i (Problema di Minimizzazione) Per lo studio di tale funzione la variabile x assume valori reali tra –5,12 e 5,12. La rappresentazione binaria di tali valori è stata implementata codificando separatamente (…ma nel solito cromosoma…) la parte intera e la parte decimale. Per la parte intera sono stati utilizzati 3 bits, mentre per la parte decimale ne sono stati utilizzati 7. La decodifica della stringa a valore reale è stata effettuata decodificando la parte intera alla quale è stata sommata la centesima parte della decodifica della parte decimale. A differenza delle precedenti funzioni si è dovuto tenere conto del segno algebrico nel calcolo dell’ottimo (assunto in –15,36). Per questo motivo la decodifica e i controlli dei valori delle stringhe binarie sono molteplici. Uno di questi è l’assegnamento del segno ad un nuovo individuo creato dal Crossover (la prima popolazione presenta individui con segno casuale). Dato che la funzione è composta di 3 variabili indipendenti accorre verificare su quale variabile cade il taglio del crossover. Quindi occorre verificare, per ogni individuo partecipante al crossover, il segno della variabile interessata dal taglio e assegnare il segno ai nuovi individui creati secondo una logica dipendente dalla tipologia del problema. In questo caso poiché si tratta di determinare un minimo a segno negativo, la regola di assegnamento del segno è visualizzata in TAB1: TAB1 SEGNO VAR 1° INDIVIDUO SEGNO VAR. 2° INDIVIDUO Positivo Positivo Positivo Negativo Negativo Positivo Negativo Negativo SEGNO VAR. FIGLI Negativo Positivo Positivo Negativo 2.4 Funzione di Rosembrok: f(x, y) = (1 – x)2 + 105(y – x2)2 (Problema di Minimizzazione) Per lo studio di tale funzione la variabile x assume valori reali tra –5,42 e 5,42. La rappresentazione binaria di tali valori è stata implementata codificando separatamente (…ma nel solito cromosoma…) la parte intera e la parte decimale. Per la parte intera sono stati utilizzati 3 bits, mentre per la parte decimale ne sono stati utilizzati 7. La decodifica della stringa avviene come nella funzione precedente. L’ottimo di questa funzione si ha nel punto (1,1). Per la decisione dei segni vedere TAB2: TAB2 SEGNO VAR 1° INDIVIDUO SEGNO VAR. 2° INDIVIDUO Positivo Positivo Positivo Negativo Negativo Positivo Negativo Negativo SEGNO VAR. FIGLI Positivo Negativo Negativo Positivo 2.5 Seconda Funzione di De Jong: f(x, y) = 100(x2 – y)2 + (1 –x)2 (Problema di minimo) Per lo studio di tale funzione la variabile x assume valori reali tra –2,048 e 2,048. La rappresentazione binaria di tali valori è stata implementata codificando separatamente (…ma nel solito cromosoma…) la parte intera e la parte decimale. Per la parte intera sono stati utilizzati 2 bits, mentre per la parte decimale ne sono stati utilizzati 10. La decodifica della stringa avviene come nella funzione precedente a differenza che contiene tripla precisione decimale. L’ottimo di questa funzione, come nella funzione di Rosembrok, si trova nel punto (1,1). Per la decisione dei segni vedere TAB2. 3. Risultati sperimentali: prove ed analisi L’analisi del funzionamento dell’algoritmo evolutivo è stata effettuata attraverso numerosissime prove eseguite su tutte le funzioni e modificando più volte gli operatori che intervengono nell’algoritmo genetico fino ad ottenere un’implementazione molto buona in termini di convergenza. Per ogni funzione sono stati implementati diversi tipi di crossover in relazione alle caratteristiche della funzione stessa. Fra gli operatori di selezione si è deciso di utilizzare la “Selezione con torneo”, anziché la “Selezione con Roulette”, dato che i numerosi test hanno evidenziato un miglior comportamento in termini di ricerca degli individui migliori. L’operatore che implementa la “Selezione d’Elite” è utilizzabile, come già detto, indipendentemente dal tipo selezione effettuata, perché lavora sulla popolazione appena generata sostituendo l’individuo peggiore con una copia del migliore individuo. Si è notato che con l’utilizzo dell’Elite si limitano le oscillazioni della fitness e si velocizza il processo di ottimizzazione, soprattutto per la funzioni di Rosembrok e la seconda di DeJong. Presentiamo, di seguito, una breve analisi dei risultati dell’algoritmo applicato alle varie funzioni. Per ogni funzione studiata sono state effettuate 10 prove indipendenti su un campione di 30 individui con Probabilità di crossover posta a 0.6 e Probabilità di mutazione posta a 0.0333 usando l’operatore di selezione con Torneo. Funzione presentata da Goldberg: f(x) = (x/c)10 (Problema di Massimizzazione) Sulle 10 prove effettuate, utilizzando Selezione con Torneo e 30 generazioni, si raggiunge SEMPRE il valore ottimo della funzione (pari a 1) a partire all’incirca dalla ventesima generazione (si nota che già a partire dalla venticinquesima generazione si raggiunge comunque l’ottimo). Utilizzando la Selezione d’Elite si raggiunge il valore ottimo già dalla quindicesima generazione. Funzione f(x) = x2 (Problema di Massimizzazione) I risultati presentati per questa funzione si basano anch’essi su 10 prove con 30 individui e 10 generazioni, utilizzando però la Selezione d’Elite. La funzione non è così semplice da calcolare come quella precedente ed infatti fornisce risultati che non sono sempre soddisfacenti. Sotto queste ipotesi, ad una prima analisi, si ha circa il 30-40% dei casi che stagna su una soluzione prossima all'ottimo (dal punto di vista della codifica binaria); il 2030% che si avvicina molto a questa soluzione e il 30-40% che stagna sempre sul valore 9.66365675521 * 1011 contro il 9.98846332929 * 1011 dell'altro valore. Analizzando in dettaglio la conformazione dei cromosomi che formano i due risultati si riesce a capire meglio il comportamento quantomeno anomalo della funzione. I due valori di stagnazione sono: 9.98846332929 * 1011 rappresentato dalla stringa 11110011111111111111 contro 9.66365675521 * 1011 rappresentato dalla stringa 11101111111111111111 Come si può notare le due stringhe binarie sono molto simili. Per quanto riguarda il valore più basso, il funzionamento dell’algoritmo è tale che, qualora l’algoritmo generi una stringa della forma 1110[…] e fornisca essa stessa il valore massimo di fitness in un'attuale generazione, difficilmente si riuscirà a smembrare questa conformazione. Non ci si può infatti affidare molto sul crossover, in quanto il taglio effettuato è del tutto casuale e quindi la probabilità che vada a cadere sul 3° allele, è sì equamente distribuita in relazione agli altri possibili tagli, ma diventa molto bassa se si considera che si deve tagliare in posizione 3 l’individuo giusto (gli individui sono 30, quindi 1/30 = 0,033% di probabilità di selezionare l'individuo in questione + la probabilità di tagliare in posizione 3). Non possiamo inoltre affidarci molto neanche sulla mutazione, sempre per lo stesso motivo: dovremmo infatti sperare che venga mutato il 4° allele del cromosoma. Inoltre, la mutazione non ha addirittura alcuna possibilità di riuscita nel caso in cui si raggiunga uno dei valori di stagnazione. Infatti, La loro codifica è tale che necessiterebbe di almeno due mutazioni dello stesso cromosoma: una in posizione 4 e l'altra in una posizione superiore. Infatti, qualunque cromosoma di questo tipo: 1111*111111111111111 o 11111*11111111111111 viene scartato in fase di controllo perché oltrepassa il range di rappresentazione. Quindi, la mutazione di questi individui genera un individuo da scartare e la mutazione a “singolo allele” diventa inutile. Effettuando prove in cui si aumenta la probabilità di mutazione, si può cercare di risolvere il problema della stagnazione. Portando la percentuale di mutazione al 23%, si elimina definitivamente la stagnazione su quei valori, ma non si riesce comunque a raggiungere l'ottimo che dovrebbe essere così codificato: 11110100001001000000 * 1012 Si sono effettuate quindi, 200 prove indipendenti iterando per ognuna fino a 100 generazioni successive alla prima. Si è visto convergere l'algoritmo solo in 5 occasioni (le generazioni totali sono 20.000...). Nella sola implementazione di selezione con torneo, i valori variano veramente di poco, anche perché, lavorando su numeri naturali non si hanno oscillazioni intorno al valore massimo di fitness che tende sempre e comunque a salire. L'unico dato rilevante è che su 100 prove non si è osservata alcuna convergenza finale, ma solamente tre risultati ottimi in generazioni intermedie (convergenza locale), fatto dovuto principalmente al caso (percentuale di mutazione abbastanza elevata). Prima Funzione di De Jong: f(xi) = ∑3i = 1 x2i (Problema di Minimizzazione) Il primo problema di minimizzazione presentato, offre, sotto determinate ipotesi, risultati molto soddisfacenti in relazione alla codifica effettuata su valori decimali e con segno (si veda la sezione relativa alla descrizione delle funzioni). I risultati presentati, non si riferiscono al raggiungimento dell'ottimo, che si attesta sul valore -15,36, ma si basa sul raggiungimento di valori inferiori a -15,30. Questo è stato fatto perché la decodifica dei valori decimali, fatta con sette bit, nonostante sia poco significativa nel risultato finale, ha molta influenza sia sul crossover che sulla mutazione (intuitivamente, il crossover e la mutazione “influiscono poco” sul raggiungimento dell'ottimo dato che i bit che rappresentano la parte decimale sono molto maggiori rispetto alla parte intera e quindi il miglioramento è “lento”). Per questo motivo, risulta quantomeno più difficile raggiungere precisamente il valore ottimo della funzione. Effettuando svariati test sia con Selezione con Torneo che con selezione d'Elite, con percentuale di mutazione pari a 0.033, si è notato come su 20 prove indipendenti, effettuate con 30, 50, e 100 generazioni successive alla prima, non si riescano a raggiungere valori inferiori a -15,30. Portando la percentuale di mutazione a 0.13, si ha un netto miglioramento: • Con Selezione di Elite su 30 generazioni, si ha circa il 50% di convergenza. • Su 50 generazioni, si ha circa il 90% di convergenza. • Su 100 generazioni, si ha il 100% di convergenza. Effettuando i test con sola Selezione con Torneo, si ha solamente un leggerissimo calo dei valori, ma le percentuali rimangono le stesse. Funzione di Rosembrok: f(x, y) = (1 – x)2 + 105(y – x2)2 (Problema di Minimizzazione) La funzione di Rosembrok è forse la funzione più difficile di questa serie da calcolare. Mette insieme, infatti, i problemi di decodifica di valori decimali, i problemi di assegnamento del segno e gli svariati problemi di ricerca della soluzione che possono interessare gli algoritmi genetici. Mentre nelle funzioni precedenti, il valore ottimo della fitness corrispondeva anche a ricercare quale fosse il valore minimo o massimo dei cromosomi corrispondenti alle variabili, in questa funzione, ciò non accade. Infatti, il valore minimo di questa funzione si ha nel punto (1,1), vale a dire una soluzione che sta all'incirca a metà del range di ricerca dell'algoritmo che tenta di trovare una soluzione “pescando” i valori delle variabili tra -5.42 e 5.42. Chiaramente i valori decimali, per la risoluzione di questa funzione, hanno ben poca influenza, ma si è scelto di inserirli per aumentare lo spazio di ricerca e per simulare il funzionamento dell'algoritmo nell'ambito dei reali. Per questo si è deciso di considerare pari all'ottimo tutti quei valori di fitness che pur non raggiungendo lo zero, si attestano nell'intervallo compreso tra 0.0 e 0.09. Effettuando i soliti test, con Selezione d'Elite e probabilità di mutazione pari a 0.033, si sono ottenuti i seguenti risultati: • Su 20 prove di 30 generazioni si hanno circa 6 prove convergenti. • Su 20 prove di 50 e 100 generazioni si hanno circa 7 prove convergenti. Dati gli scarsi risultati, si è aumentata la probabilità di mutazione portandola al 13% e effettuando molteplici test, si sono ottenute le seguenti statistiche: • Su 30 generazioni, circa il 55% delle prove converge. • Su 50 generazioni, circa 70 – 80%. • Su 100 generazioni non si raggiunge il 100% , ma comunque un buon 90% . In tutti i casi di insuccesso, non si riscontrano comunque particolari casi di stagnazione su determinati valori, ma, salvo in alcuni rari casi, il valore finale di attesta intorno allo 0.1, quindi molto vicino comunque allo zero. Nell'implementazione delle prove con sola Selezione con Torneo, si ha un calo indicativamente del 10% nel raggiungimento dell'ottimo. Ciò è dovuto principalmente al fatto che l'Elite, per questa funzione è piuttosto importante perché evita le oscillazioni intorno a valori facilmente creabili dal crossover, ma poco efficienti per quanto riguarda il calcolo della fitness. Una bassa percentuale di mutazione e una poco attenta implementazione dell'algoritmo nel calcolo di funzioni quadratiche può portare a stagnazione sul punto (0,0). Ciò accade qualora non si riesca a scendere a valori di fitness sotto lo zero in tempi abbastanza rapidi. Un bassa percentuale di mutazione, infatti, andrà ad incidere maggiormente sulla parte decimale della codifica del cromosoma, modificando così valori poco significativi nella ricerca del punto (1,1). Seconda Funzione di De Jong: f(x, y) = 100(x2 – y)2 + (1 –x)2 (Problema di minimo) La seconda funzione di De Jong è del tutto simile alla funzione di Rosembrok. Poteva anche non essere inserita nello studio del funzionamento dell'algoritmo, ma è stata ugualmente implementata e testata per verificare la correttezza e la modularità e adattabilità dell'implementazione scelta. Questa funzione è stata calcolata prevedendo codifica a 3 cifre decimali e una intera. Nelle stesse condizioni di ricerca della funzione di Rosembrok, i risultati ottenuti sono del tutto analoghi alla stessa. 4. Conclusioni 4.1 In relazione al funzionamento dell'algoritmo I buoni risultati ottenuti dai test evidenziano alcune caratteristiche riguardo al funzionamento dell'algoritmo di ricerca evolutivo, algoritmo regolato dalla probabilità e dalla capacità di autogestirsi in base, però, a linee guida fornite da operatori esterni, quali l'uomo. In effetti, nonostante vi sia una indiscussa buona capacità di ricerca, l'algoritmo in sé, può fare ben poco senza le informazioni base necessarie al suo funzionamento e in particolare, non sa riconoscere input sbagliati che lo potrebbero portare a ricercare in spazi a volte molto lontani da quello che dovrebbe essere lo spazio di ricerca ottimale. Ciò che più si è messo in evidenza è la criticità dell'operatore di selezione. Come si sarà notato, l’operatore di Selezione con Roulette non è mai stato utilizzato nei test di verifica. Questo è dovuto alla scarsa potenziale denotata in fase primo sviluppo, quando già dai primi test di funzionamento si è notata la scarsa efficienza dei questo operatore, che, su popolazioni di già di trenta individui, fatica a trovare gli individui più forti. Molto probabilmente questo operatore è in grado di lavorare bene solo su piccole popolazioni, in cui è possibile che la fitness di un individuo sia molto maggiore della fitness degli altri. In questo modo la probabilità ad esso associata sarà molto più alta e quindi influirà positivamente sulla scelta di quell'individuo. Su popolazioni di 30 o oltre individui, lo spazio ampio di ricerca, influisce negativamente sulla scelta degli individui migliori. Molto più efficiente, sotto tutti i punti di vista si è rivelata la Selezione con Torneo. In primo luogo perché non è condizionata dal numero di individui che compongono la popolazione; in secondo luogo perché è “regolabile” proprio in dipendenza di questo numero. Questo tipo di selezione, è basata anch'essa sulla probabilità, ma è del tutto slegata dalla bontà di ogni individuo. Alcuni individui vengono infatti selezionati e confrontati tra loro fino a restituire il migliore di questa selezione. E' proprio il numero di questi individui confrontabili tra loro che fornisce diverse probabilità di riuscita (vedere la sezione relativa alla descrizione e funzionamento degli operatori genetici). A supporto di questo tipo di selezione interviene la Selezione d'Elite. Il suo compito principale è quello di evitare oscillazioni intorno ai valori prossimi all'ottimo, nonché di perdere proprio il cromosoma ottimo. Rimpiazzando di generazione in generazione l'elemento peggiore con una copia del migliore della generazione precedente (a meno che un individuo ancora più “forte” non si già presente...), previene in una certa misura il pericolo di stagnazione su valori anomali di fitness, velocizza il processo di convergenza e aumenta la capacità di trovare l'ottimo. L'altro elemento su cui si sono orientati i nostri studi è stata la probabilità di mutazione. Come si sa una bassa probabilità di mutazione può portare ad una bassa capacità di ricerca, mentre un'alta percentuale rischia di far fare all'algoritmo una ricerca pressoché casuale. Come evidenziato dai valori dei test, si ha un'accettabile compromesso ponendo la percentuale al 13%, il che vuol dire, che ogni singolo allele ha questa probabilità di essere mutato indipendente dagli altri: in totale, su trenta alleli (dimensione di un individuo), ne mutano circa 4. Non sono stati invece effettuati studi sulla percentuale di crossover, perché, per come è stato implementato l'operatore, ha poca influenza. Infatti, quello che accade, è che per ogni coppia di individui selezionati, si ha il 60% di probabilità che questi vengano “crossati”. In caso contrario, si ripete tutto il processo di selezione. Se questa è implementata con Torneo, allora molto probabilmente, saranno individui molto simili che parteciperanno al nuovo crossover, senza pregiudicare più di tanto la ricerca. 4.2 In relazione all'implementazione. Quello che ancora è degno di nota, riguardo all'implementazione dell'algoritmo, è una breve descrizione delle potenzialità e limitazioni del codice presentato. Partendo dalle potenzialità, ciò che più è rilevante è la discreta modularità delle funzioni e l'adattabilità e modularità sia delle classi che dei metodi implementati. Le particolari condizioni dei test effettuati, sono solo un esempio dei possibili scenari di circa che offre l'algoritmo. Il calcolo e l'implementazione delle funzioni è totalmente staccato dai valori limite di ricerca imposti. E' possibile infatti, con pochissimi settaggi di valori, ampliare, diminuire, modificare e potenziare gli spazi di ricerca su cui lavorare e questo per ogni funzione. E' possibile passare abbastanza facilmente da un problema di massimo ad uno minimo, è possibile aumentare la precisione in termini di valori decimali o di valori interi, è possibile passare da valori con segno a naturali e viceversa. Con un po' più di difficoltà si può ampliare il numero di funzioni calcolabili, in quanto per ognuna occorrerà copiare e modificare opportunamente la funzione di crossover, in relazione soprattutto al numero di variabili della funzione. E' possibile controllare direttamente i progressi dell'algoritmo evolutivo al termine della ricerca, in quanto tutti i cromosomi, i valori codificati, l'individuo massimo e minimo di una popolazione, sono scritti su file e sono salvati su disco. Inoltre, attraverso particolari parametri, è possibile far calcolare dall'algoritmo se si è avuta o meno convergenza e a quale generazione è avvenuta. L'unico difetto di questa parte è che la formattazione del testo non è troppo precisa, ma ugualmente molto leggibile. I limiti di questo tipo di implementazione sono legati perlopiù alla decisione di codifica in stringa binaria della parte decimale dei valori. Vediamo un esempio numerico: Nel nostro caso, si sono dovuti implementare numeri come 5,34 o 2,015. Nel primo esempio, è sempre possibile scrivere 5,34 come 5 + (34/100) e questo è quello che è stato fatto. Una codifica binari di questo tipo si ottiene con una stringa di 10 bit: • 3 per la parte intera. • 7 per la parte decimale. ricordando che 5 è codificato come 101 e 99 come 1100011. Quindi, nel procedimento da binari a reali, si sono decodificati i primi 3 bit e li si è sommati alla decodifica dei secondi 7 bit divisi per 100 (cento). Ciò che non si è riusciti a gestire troppo bene, anche perché java non supporta funzioni adatte a questo tipo di lavoro, è il fatto che 7 bit sono “ troppi” per codificare valori tra 0 e 99. Con 7 bit si oltrepassa il centinaio, e questo è stato controllato solo nell'inizializzazione della prima popolazione, mentre non si è potuto far niente né in fase di crossover, né tantomeno in fase di decodifica (che avviene subito dopo il crossover, ma in una sezione di programma indipendente). Per questo, si è cercato di limitare i problemi dividendo per 1000 (mille) anziché per 100 (cento) tutti quei valori superiori, appunto, a 100 (cento), per evitare che la parte dedicata ai decimali generasse anche valori interi. Naturalmente sono stati potenziati i controlli in fase di crossover per garantire di non generare individui che oltrepassassero i range di valori assumibili dalle variabili inizialmente stabiliti. Informatica Applicata Autori: Dario Maggiari & Giorgio Montali