<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mind Unpacked &#187; Algoritmi genetici</title>
	<atom:link href="http://mindunpacked.com/tag/algoritmi-genetici/feed/" rel="self" type="application/rss+xml" />
	<link>http://mindunpacked.com</link>
	<description>informatica.elettronica.chimica.new stuff</description>
	<lastBuildDate>Tue, 22 Dec 2009 17:35:13 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<image>
<link>http://mindunpacked.com</link>
<url>http://mindunpacked.com/wp-content/plugins/maxblogpress-favicon/icons/favicon-2.ico</url>
<title>Mind Unpacked</title>
</image>
		<item>
		<title>Reti neurali attraverso algoritmi genetici in C++. Parte V</title>
		<link>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-v/</link>
		<comments>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-v/#comments</comments>
		<pubDate>Fri, 12 Dec 2008 13:07:07 +0000</pubDate>
		<dc:creator>Francesco</dc:creator>
				<category><![CDATA[Informatica]]></category>
		<category><![CDATA[Algoritmi genetici]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Intelligenza artificiale]]></category>
		<category><![CDATA[Reti neurali]]></category>

		<guid isPermaLink="false">http://mindunpacked.com/?p=252</guid>
		<description><![CDATA[In questa penultima parte della serie di articoli dedicati alle reti neurali, vedremo come creare una classe che riunisca tutto cio&#8217; che abbiamo programmato in precedenza e ne renda piu&#8217; facile l&#8217;utilizzo. Era gia&#8217; possibile utilizzare le classi cosi&#8217; com&#8217;erano, ma in questo modo ci rendiamo la vita piu&#8217; semplice. Inoltre c&#8217;e&#8217; da notare che [...]]]></description>
			<content:encoded><![CDATA[<p>In questa penultima parte della serie di articoli dedicati alle reti neurali, vedremo come creare una classe che riunisca tutto cio&#8217; che abbiamo programmato in precedenza e ne renda piu&#8217; facile l&#8217;utilizzo. Era gia&#8217; possibile utilizzare le classi cosi&#8217; com&#8217;erano, ma in questo modo ci rendiamo la vita piu&#8217; semplice. Inoltre c&#8217;e&#8217; da notare che quello che faremo oramai non ho molto a che fare con l&#8217;implementazione della rete, ma e&#8217; piu&#8217; una questione di ordine e OOP.</p>
<p><span id="more-252"></span></p>
<p align="center"><strong>Riunire il tutto.</strong></p>
<p align="justify">In questo piccolo paragrafo vedremo come fare comunicare tra loro le due classi attraverso una terza ed ultima classe che si occupa della gestione di tutte le operazioni. In realtà questa classe è superflua perchè è già possibile, con le sole classi Population e NeuralNet, creare una rete neurale funzionante ma essa ci rende molto più comoda la scritture del codice. Essa contiene per lo più funzioni ausiliarie come quella per caricare il training set (senza dover parsare manualmente il file), o per salvare e caricare i pesi della rete neurale e per questo non mi soffermerò molto sul codice. La funzione più importante è quella che esegue l&#8217;allenamento della rete neurale, ma vediamo prima il codice della classe:</p>
<pre class="brush: cpp;">
class NNController {
	public:
		NNController(int c_numInputNeurons, int c_numOutputNeurons,
		int c_numHiddenLayers, int c_numNeuronsPerLayer);
		void loadTrainingSet(string inputFile, string outputFile);
		void train();
		void saveWeights(string file);
		void loadWeights(string file);
		void test();
	private:
		vector&lt; vector &gt; inputData; // Training set
		vector&lt; vector &gt; outputData;
		NeuralNet* N;
		Population* P;
		Chromosome* C;
};
</pre>
<p align="justify">Il costruttore ha gli stessi parametri di quello della classe NeuralNet che servono ad inizializzare l&#8217;oggetto N all&#8217;interno di questa classe.</p>
<p align="justify">La funzione loadTrainingSet(string inputFile string outputFile) carica appunto il training set: essa riceve come argomenti i nomi di due file, il primo deve contenere gli input da passare alla rete neurale mentre il secondo gli output desiderati corrispondenti agli input. Per esempio, se stiamo addestrando la rete a fare la somma di due numeri, ed il file contenente l&#8217;input contenesse:</p>
<p align="justify">
<p align="justify">0 1</p>
<p align="justify">0 2</p>
<p align="justify">
<p align="justify">Quello contenente l&#8217;output dovrebbe contenere:</p>
<p align="justify">1</p>
<p align="justify">2</p>
<p align="justify">(Gli input devono essere separati tra di loro da uno spazio ed ogni riga deve contenere ovviamente sempre lo stesso numero di input che deve essere a sua volta uguale al numero dei neuroni di input specificato nel codice).</p>
<p align="justify">Le funzioni saveWeights(string file) e loadWeights(string file) servono rispettivamente a salvare i pesi della rete neurale (generalmente dopo che questa è allenata) e a poterli caricare in seguito senza dover ripetere l&#8217;addestramento.</p>
<p align="justify">La funzione test() serve invece a poter testare la rete con vari input dopo che questa è stata allenata. Ho conservato volutamente alla fine la funzione train() perchè è l&#8217;unica un po più complessa e serve ad effettuare l&#8217;allenamento della rete.</p>
<pre class="brush: cpp;">
void NNController::train() {
	double currentErr = 0; int i = 0; double bestFit = 99999999;&lt;
	while (bestFit &gt; MAX_ERROR) {
		if (i &gt;= MAX_EPOCHS) {
			break;
		}
		for (int j = 0; j &lt; POPULATION_SIZE; j++) {
			currentErr = 0;
			for (int k = 0; k &lt; inputData.size(); k++) {
				N-&gt;run(inputData[k], (*P)[j]);
				currentErr += N-&gt;globalError(outputData[k]) / inputData.size()
			}
			(*P)[j]-&gt;setFitness(currentErr);
		}
		bestFit = P-&gt;best()-&gt;getFitness();
		if (i % UPDATE == 0) {
			cout &lt;&lt; &quot;(&quot; &lt;&lt; i &lt;&lt; &quot;) - &quot;&lt;&lt; &quot;Best = &quot; &lt;&lt; bestFit
			     &lt;&lt; &quot;\t&quot; &lt;&lt; &quot;Worst = &quot; &lt;&lt; P-&gt;worst()-&gt;getFitness()
			     &lt;&lt; endl;
		}
		P-&gt;next();
		i++;
	}
}
</pre>
<p><center><script type="text/javascript">
heyos_ad_user = 11334;
heyos_ad_type = "G";
heyos_ad_format = "1";
heyos_color_border = "23292b";
heyos_color_bg = "23292b";
heyos_color_link = "FFFFFF";
heyos_color_text = "21b8ca";
heyos_color_url = "21b8ca";
</script>
<script type="text/javascript" src="http://admaster.heyos.com/core/bnr.js"></script></center></p>
<p align="justify">La funzione esegue un ciclo while che termina o quando il fitness raggiunge un livello accettabile (cioè l&#8217;errore raggiunto è minore o uguale all&#8217;errore massimo stabilito in precedenza e definito nella costante MAX_ERROR) oppure quando (vedete il break) il numero di epoche (cioè di cicli di riproduzione della popolazione) supera un certo limite. Dentro questo ciclo while c&#8217;è un ulteriore ciclo for che scorre tutti gli elementi della popolazione (che, come oramai dovrebbe essere chiaro, rappresenta una configurazione dei pesi della rete neurale) e per ognuno esegue tutto il training set in input. Il fitness di questo individuo sarà l&#8217;errore medio che il cromosoma ha sugli elementi del training set. Un errore pari a 0, che corrisponde alla perfezione assoluta, è praticamente impossibile da raggiungere e quindi, a seconda dei casi, ci si può accontentare di un errore più o meno alto come, per esempio, 0.001.</p>
<p align="justify">La funzione stampa anche un output ogni tanto per informare l&#8217;utente a che punto è arrivato l&#8217;allenamento, e dipende dal valore della costante UPDATE: se per esempio questa vale 50, ogni 50 generazioni sarà stampato un messaggio contente i fitness del migliore e del peggiore cromosoma della popolazione corrente.</p>
<p align="justify">Quando questa funzione termina la nostra rete neurale è allenata (con un errore massimo stabilito dalla costante MAX_ERROR) ed è possibile testarla tramite il metodo test() fornito dalla stessa classe.</p>
<p align="justify">Vediamo ora un codice di esempio di rete neurale realizzata con le nostre classi:</p>
<pre class="brush: cpp;">
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;sstream&gt;
#include &lt;vector&gt;
#include &lt;ctime&gt;
#include &lt;cmath&gt;

using namespace std;

/* Misc.hpp è un header che contiene le varie costanti come
MAX_POPULATION, ELITISM, MAX_TOUR, etc...
Gli altri header contengono le varie classi */
#include &quot;Misc.hpp&quot;
#include &quot;GenAlg.hpp&quot;
#include &quot;NeuralNet.hpp&quot;
#include &quot;NNController.hpp&quot;

int main() {
	/* Alla riga seguente creiamo una rete neurale con 2 neuroni
	di input, 1 neurone di output e 0 layer nascosti */
	NNController NC(2, 1, 0, 0);
	/* Carica il training set contenuto nei due file */
	NC.loadTrainingSet(&quot;input&quot;, &quot;output&quot;);
	NC.train();
	cout &lt;&lt; &quot;*** La rete e' allenata ***&quot; &lt;&lt; endl;
	NC.test();
}
</pre>
<p align="justify">Come vedete il codice è davvero basilare eppure una rete neurale come quella di questo esempio, sarebbe in grado di imparare a fare la somma di due numeri con soli tre neuroni!</p>
<p align="center"><strong>Come impostare una rete neurale.</strong></p>
<p align="justify">Abbiamo visto la creazione pratica di una rete neurale ma come avete notato il risultato ottenuto da una rete dipende da una miriade di fattori come per esempio il numero di livelli, il numero di neuroni presente nei livelli nascosti, la funzione di trasferimento, etc&#8230; di conseguenza vi starete probabilmente chiedendo quali sono le impostazioni migliori per una rete neurale. Chiaramente non esistono delle impostazioni migliori in assoluto, ma dipende tutto dal problema che dobbiamo analizzare, di conseguenza quelle che scriverò qui saranno solo delle brevi linee guida da tenere in considerazione ma non delle regole fisse.</p>
<p align="justify">Ho gia parlato in precedenza delle funzioni di attivazione e di quali sia più comodo usare a seconda dei casi, ora parleremo del numero dei livelli.</p>
<p align="justify">Quanti livelli nascosti vanno usati? Nell&#8217;esempio precedente abbiamo creato una rete capace di imparare a fare la somma dei due numeri senza utilizzare nessun livello nascosto e in molti casi può verificarsi questa eventualità. Generalmente, nei casi restanti, un solo livello nascosto è più che sufficiente per ottenere risultati corretti. Dal punto di vista matematico possiamo dire che una rete neurale con i soli livelli si input e outputè capace di risolvere problemi linearmente separabili, mentre per problemi non linearmente separabili sono richiesti uno o più livelli nascosti.</p>
<p align="justify">Il numero di neuroni per ogni livello nascosto può essere un&#8217;altra incognita nella creazione della rete neurale: esso dipende da più fattori come il numero dei neuroni di input e di output, la dimensione del training set, la funzione di attivazione, l&#8217;algoritmo di apprendimento, etc&#8230; Esistono diverse regole approssimative che cercano di fornire un modo per calcolare il numero di unità nascoste necessarie (per esempio: &#8220;il numero di unità nascoste deve essere sempre minore del doppio dei neuroni di input&#8221;) ma molto spesso esse sono inutili. La cosa migliore è fare alcune prove e trovare il numero di unità che fornisce un errore minimo.</p>
]]></content:encoded>
			<wfw:commentRss>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-v/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Reti neurali attraverso algoritmi genetici in C++. Parte IV</title>
		<link>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iv/</link>
		<comments>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iv/#comments</comments>
		<pubDate>Tue, 09 Dec 2008 12:04:57 +0000</pubDate>
		<dc:creator>Francesco</dc:creator>
				<category><![CDATA[Informatica]]></category>
		<category><![CDATA[Algoritmi genetici]]></category>
		<category><![CDATA[Intelligenza artificiale]]></category>
		<category><![CDATA[Reti neurali]]></category>

		<guid isPermaLink="false">http://mindunpacked.com/?p=200</guid>
		<description><![CDATA[Le classi Chromosome e Population.
Abbiamo finito l&#8217;implementazione della rete ed ora possiamo passare alla parte relativa all&#8217;addestramento della stessa, che come ho già detto, verrà realizzata tramite algoritmi genetici. Vediamo la classe Chromosome:


class Chromosome {
	public:
		Chromosome(int c_w);
		Chromosome(vector&#60;double&#62; weights); // overload
		void setWeight(int i, double val);
		double getWeight(int i) const;
		void setFitness(double fit);
		double getFitness() const;
		int getWeightsNum() const;
		void mutate();
	private:
		vector&#60;double&#62; DNA;
		double fitness;
};

Innanzitutto [...]]]></description>
			<content:encoded><![CDATA[<p align="center"><strong>Le classi Chromosome e Population.</strong></p>
<p align="justify">Abbiamo finito l&#8217;implementazione della rete ed ora possiamo passare alla parte relativa all&#8217;addestramento della stessa, che come ho già detto, verrà realizzata tramite algoritmi genetici. Vediamo la classe Chromosome:</p>
<p align="justify"><span id="more-200"></span></p>
<pre class="brush: cpp;">
class Chromosome {
	public:
		Chromosome(int c_w);
		Chromosome(vector&lt;double&gt; weights); // overload
		void setWeight(int i, double val);
		double getWeight(int i) const;
		void setFitness(double fit);
		double getFitness() const;
		int getWeightsNum() const;
		void mutate();
	private:
		vector&lt;double&gt; DNA;
		double fitness;
};
</pre>
<p align="justify">Innanzitutto essa possiede due costruttori. Il primo serve nel caso volessimo inizializzare un cromosoma con pesi random specificando solamente il numero dei pesi  (in pratica la lunghezza del DNA); il secondo serve quando vogliamo creare un cromosoma con dei pesi ben precisi passandogeli come argomento: quest&#8217;ultimo viene usato per implementare una funzione che permette di salvare i pesi dopo che la rete neurale è allenata e di poterli caricare per evitare di dover ripetere ogni volta l&#8217;allenamento.</p>
<p align="justify">Come potete vedere le funzioni getWieght(int) e setWeight(int, double) hanno dei compiti abbastanza evidenti così come setFitness(double) e getFitness().</p>
<p align="justify">La funzione mutate() applica il famoso operatore genetico delle mutazione al cromosoma, vediamone il codice:</p>
<pre class="brush: cpp;">
void Chromosome::mutate() {
	if (dRand() &lt;= MUTATION_RATE) {
		DNA[rand()%DNA.size()] += wRand()*MAX_PERTURBATION;
	}
}</pre>
<p align="justify">Viene generato un numero random compreso tra 0 e 1 e se è minore del valore di MUTATION_RATE la mutazione viene effettuata. Nella parte sugli algoritmi genetici avevamo visto che, quando il DNA è codificato in forma binaria la mutazione agisce semplicemente trasformando uno 0 in un 1 o viceversa. Nel nostro caso, invece, la mutazione non fa altro che aggiungere o sottrarre un piccolo valore compreso tra 0 e MAX_PERTURBATION ad un peso qualsiasi del DNA (wRand() genera infatti un numero compreso tra -1 e 1 quindi il valore di MAX_PERTURBATION può essere sottratto o aggiunto, a seconda del numero generato da wRand()).</p>
<p align="justify">La classe Population ingloba un array di oggetti Chromosome* cioè la popolazione vera e propria, più una serie di metodi come la riproduzione, il crossover, etc&#8230; ecco il codice:</p>
<pre class="brush: cpp;">
class Population {
	public:
		Population(int c_num, int c_numw);
		void crossover(Chromosome* a1, Chromosome* a2, Chromosome* b1, Chromosome* b2);
		Chromosome* getChromosomeFromTournament();
		void next();

		Chromosome* best();
		Chromosome* worst();
		Chromosome* operator[](int i);
	private:
		int length;
		vector&lt;Chromosome*&gt; popv;
};</pre>
<p><!--adsense--><br />
Il costruttore è abbastanza semplice: prende come argomenti il numero di individui di cui è formata la popolazione, ed il numero di pesi per ogni individui cioè la lunghezza del DNA (che chiaramente è uguale per tutti gli individui).</p>
<p align="justify">Il metodo del crossover incrocia il DNA dei primi due cromosomi che gli passiamo come argomento e lo mette negli ultimi due, sempre se il numero random generato sia minore della costante CROSSOVER_RATE: un valore di quest&#8217;ultima costante pari a 0.7, per esempio, vuol dire che nel 70% dei casi il crossover viene effettuato. Se ciò non avviene gli individui si riproducono così come sono.</p>
<pre class="brush: cpp;">
void Population::crossover(Chromosome* a1, Chromosome* a2, Chromosome* b1, Chromosome* b2) {
	if (dRand() &lt;= CROSSOVER_RATE) {
		int crosspoint = rand()%length;

		for (int i = 0; i &lt; crosspoint; i++) {
			b1-&gt;setWeight(i, a1-&gt;getWeight(i));
			b2-&gt;setWeight(i, a2-&gt;getWeight(i));

			b1-&gt;setWeight(length-(i+1),
			a1-&gt;getWeight(length-(i+1)));
			b2-&gt;setWeight(length-(i+1),
			a2-&gt;getWeight(length-(i+1)));
		}
	} else { // Se non fa il crossover riproduce gli stessi cromosomi.
		b1 = a1;
		b2 = a2;
	}
}</pre>
<p>La funzione getChromosomeFromTournament() svolge il cosiddetto &#8220;torneo&#8221; fra i cromosomi e seleziona quello con il miglior fitness. Essa non è altro che il nostro metodo di selezione:</p>
<pre class="brush: cpp;">
Chromosome* Population::getChromosomeFromTournament() {
	vector&lt;Chromosome*&gt; r;
	for (int i = 0; i &lt; NUM_TOUR; i++) {
		r.push_back(popv[rand()%popv.size()]);
	}
	sort(r.begin(), r.end(), compareChromosomes);
	return r[0];
}
</pre>
<p><!--adsense--></p>
<p align="justify">Per svolgere il &#8220;torneo&#8221; viene creato un vettore che conterrà i cromosomi scelti casualmente dalla popolazione: NUM_TOUR è un&#8217;altra costante che definisce quanti cromosomi saranno scelti. Più NUM_TOUR è alta più basse saranno le probabilità degli individui più deboli di sopravvivere e ricordate che questo può non essere sempre un vantaggio; nel caso estremo in cui NUM_TOUR sia uguale alla grandezza della popolazione verrebbe sempre e solo scelto il cromosoma migliore della prima generazione (che poi tanto buono non è visto che nella prima generazione il DNA è random) portando così a tutt&#8217;altro che la soluzione del problema.</p>
<p align="justify">L&#8217;array così ottenuto viene ordinato in base al fitness (la funzione compareChromosomes() non fa altro che restituire il cromosoma che ha il fitness minore tra i due che riceve come argomento) e così l&#8217;elemento 0 dell&#8217;array conterrà quello con il fitness più basso (ricordate che per noi fitness più basso equivale ad errore più basso e quindi a punteggio migliore) mentre l&#8217;ultimo quello con fitness più alto. Questa funzione, insieme a quella del crossover, viene utilizzata nel metodo next() che effettua la riproduzione dell&#8217;intera popolazione finchè non si ha una nuova popolazione con lo stesso numero di individui della precedente.</p>
<pre class="brush: cpp;">
void Population::next() {
	vector&lt;Chromosome*&gt; newPop;

	sort(popv.begin(), popv.end(), compareChromosomes);

	Chromosome* b1 = new Chromosome(length);
	Chromosome* b2 = new Chromosome(length);

	// Garantisce la sopravvivenza dell'inviduo con il fitness piu' alto
	// della popolazione precedente.
	for (int i = 0; i &lt; ELITISM; i++) {
		newPop.push_back(popv[0]);
	}
	while (newPop.size() &lt; popv.size()) {
		Chromosome* a1 = getChromosomeFromTournament();
		Chromosome* a2 = getChromosomeFromTournament();

		crossover(a1, a2, b1, b2);
		b1-&gt;mutate();
		b2-&gt;mutate();
		newPop.push_back(b1); newPop.push_back(b2);
	}

	popv = newPop;
}</pre>
<p align="justify">Viene creato inizialmente un nuovo vettore che conterrà i nuovi individui ed il vettore contenente la popolazione viene ordinato in ordine di fitness (come abbiamo fatto nel metodo per svolgere il &#8220;torneo&#8221;): questo serve per mettere in atto un procedimento particolare definito elitismo. L&#8217;elisitmo consiste nel fare sopravvivere sempre, nella generazione successiva, una o più copie dell&#8217;elemento che nella generazione precedente aveva totalizzato il punteggio migliore: questo ci garantisce che, quali che siano le ricombinazioni che vengono effettuate sui nuovi individui, non perderemo mai il risultato raggiunto, e inoltre,  queste copie possono fornire un aiuto decisivo al raggiungimento della soluzione. ELITISM è una costante che definisce quante copie del miglior cromosma vadano inserite nella nuova popolazione. Nel ciclo while vengono selezionati due cromosomi dal &#8220;torneo&#8221;, viene fatto su di loro il crossover e sui nuovi individui generati viene effettuata una mutazione (sempre con la percentuale che abbiamo stabilito nella costante MUTATION_RATE) e poi vengono inseriti nel nuovo vettore. Quando questo vettore raggiunge le dimensioni della vecchia popolazione, si esce dal ciclo ed esso viene copiato sulla popolazione originale.</p>
<p align="justify">Le altre funzioni della classe sono abbastanza ovvie: best() serve a selezionare il cromosoma con il fitness migliore, worst() il peggiore e l&#8217;overloading dell&#8217;operatore [] ci permette di accedere ai membri della popolazione come se fossero elementi di un array senza dover scrivere un apposita funzione del tipo getChromosome(int i) (come quella che abbiamo usato per i neuroni: volendo anche lì si può fare infatti l&#8217;overloading dell&#8217;operatore []).</p>
<p align="justify">Manca solo una classe per riunire tutto il codice che abbiamo fatto fino ad ora e la nostra rete sarà utilizzabile (anche se, in pratica, e&#8217; utilizzabile anche da adesso con qualche sforzo in più): la vedremo nella prossima parte, intanto vi do i link a tutte le parti precedenti:</p>
<ol>
<li><a title="Reti neurali e algoritmi genetici parte I" href="http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-i/" target="_self">Reti neurali e algoritmi genetici I</a></li>
<li><a title="Reti neurali e algoritmi genetici parte II" href="http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-ii/" target="_self">Reti neurali e algoritmi genetici II</a></li>
<li><a title="Reti neurali e algoritmi genetici parte III" href="http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iii/" target="_self">Reti neurali e algoritmi genetici III</a></li>
</ol>
<p>Alla prossima&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iv/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Reti neurali attraverso algoritmi genetici in C++. Parte III</title>
		<link>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iii/</link>
		<comments>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iii/#comments</comments>
		<pubDate>Fri, 28 Nov 2008 22:14:33 +0000</pubDate>
		<dc:creator>Francesco</dc:creator>
				<category><![CDATA[Informatica]]></category>
		<category><![CDATA[Algoritmi genetici]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Intelligenza artificiale]]></category>
		<category><![CDATA[Reti neurali]]></category>

		<guid isPermaLink="false">http://mindunpacked.com/?p=132</guid>
		<description><![CDATA[IMPLEMENTAZIONE
Ok, siamo arrivati alla parte più tecnica cioè all&#8217;implementazione vera e propria della rete. Ho scelto di utilizzare il C++ e di programmare tutto ad oggetti perchè, anche se questo approccio sacrifica forse un po&#8217; la velocità, rende, almeno per me, il tutto più comprensibile e riutilizzabile (dalle classi che qui espongo ho costruito poi [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><strong>IMPLEMENTAZIONE</strong></p>
<p align="justify">Ok, siamo arrivati alla parte più tecnica cioè all&#8217;implementazione vera e propria della rete. Ho scelto di utilizzare il C++ e di programmare tutto ad oggetti perchè, anche se questo approccio sacrifica forse un po&#8217; la velocità, rende, almeno per me, il tutto più comprensibile e riutilizzabile (dalle classi che qui espongo ho costruito poi una libreria per l&#8217;utilizzo delle reti neurali).</p>
<p><span id="more-132"></span></p>
<p align="justify">Cominciamo a partire dalle classi della rete neurale.</p>
<p align="center"><strong>Le classi Neuron, NeuronLayer e NeuralNet </strong></p>
<p align="justify">La prima classe che implementeremo è la classe <strong>Neuron</strong>, che rappresenta la struttura più piccola della rete neurale: il neurone.</p>
<p align="justify">Ogni neurone come abbiamo visto ha dei collegamenti pesati in entrata (tranne i neuroni di input) e un valore, che è contiene il valore del neurone.</p>
<p align="justify">Il codice della classe Neuron è il seguente:</p>
<pre class="brush: cpp;">
class Neuron {
	public:
		Neuron(int c_numWeights);
		void setValue(double v);
		double getValue() const;
		void setWeight(int i, double v);
		double getWeight(int i) const;
		int getWeightsNum(bool bias) const;
	private:
		double value;
		vector&lt;double&gt; weights;
};
</pre>
<p>I metodi di questa classe sono tutti molto facili da capire: riporto qui solamente il codice del costrutture e di getWeightsNum(bool):</p>
<pre class="brush: cpp;">
// Costruttore
Neuron::Neuron(int c_numWeights) {
	for (int i = 0; i &lt; c_numWeights+1; i++) {
		weights.push_back(wRand());
	}
}
int Neuron::getWeightsNum(bool bias) const {
	return (bias) ? weights.size() : weights.size() 1;
}
</pre>
<p align="justify">Il costruttore riceve come argomento il numero dei pesi in entrata per questo neurone e li inizializza a valori random (wRand() è una funzione definita in un file header che restituisce un numero compreso tra -1 e 1). Se osservate bene il ciclo va da 0 a c_numWeights+1 questo perchè viene creato un peso aggiuntivo chiamato bias, la cui funzione vi sarà chiara fra poco.</p>
<p align="justify">Nella classe Neuron avremmo potuto anche creare un puntatore a funzione che puntasse alla funzione di attivazione (o funzione di trasferimento) del neurone ed in questa maniera avremmo potuto impostare una funzione di attivazione diversa per ogni neurone (o per ogni livello), ma visto che la nostra funzione sarà uguale per tutti i neuroni, ho preferito non farlo per questioni di semplicità.</p>
<p align="justify">E&#8217; arrivato il momento di spendere due parole proprio sulle funzioni di attivazione. La <strong>funzione di attivazione</strong> può essere una qualunque funzione, come per esempio , ma alcune offrono risultati migliori in base al contesto nella quale sono utilizzate. Scegliere la funzione giusta può essere determinante per ottenere buoni risultati.</p>
<p align="justify">Vediamo alcune funzioni comunemente usate:</p>
<p align="justify">
<p align="justify"><strong>Funzione a gradino: y = 1 per x &gt;= 0, 0 per x &lt; 0</strong></p>
<p align="justify">La funzione a gradino (chiamata così per il suo grafico) è la funzione che più rispetta il comportamento del <strong>neurone biologico</strong>. Infatti essa restituisce 1, cioè propaga il segnale, se la somma degli input ricevuti e maggiore o uguale a un certo numero , che altro non è che la soglia di attivazione. E&#8217; utile utilizzare questa funzione quando vogliamo che la nostra rete abbia un risultato che o 1 o 0 e che quindi separi gli input che gli forniamo in due classi distinte.</p>
<p align="justify">
<p align="justify"><span style="color: #808080;"><strong>Funzione identità: y = x</strong></span></p>
<p align="justify">Non penso ci sia molto da dire su questa funzione, l&#8217;unica cosa da sapere è che si utilizza quando il nostro output non è limitato e può variare da meno infinito a più infinito.</p>
<p align="justify">
<p align="justify"><strong>Funzione sigmoidale: y = 1/(1+e^(-x)) </strong></p>
<p align="justify">
<p align="justify">La funzione sigmoidale è una delle funzioni più utilizzate. Essa passa sempre per il punto (0, 1/2) ed è compresa tra 0 e 1. Proprio per quest&#8217;ultimo fatto non può essere utilizzata se ci aspettiamo come output numeri numeri che non sono compresi in questo intervallo, per i quali è necessario utilizzare altre funzioni.</p>
<p align="justify">
<p align="justify">
<p align="justify"><strong>Tangente iperbolica: y = tanh(x)</strong></p>
<p align="justify">La tangente iperbolica è molto simile alla funzione sigmoidale solo che il suo output è compreso tra -1 e 1 e passa sempre per l&#8217;origine. Come al solito va utilizzata solo nei casi in cui l&#8217;output deve essere compreso tra questi valori e per esempio non andrebbe bene se volessimo allenare la rete a fare la somma di due numeri qualsiasi. In realtà le cose non stanno esattamente così, perchè, come vedremo in seguito, esiste un apposito valore (bias) per ogni neurone che serve a spostare il centro della funzione e quindi ad ottenere valori che non sono più compresi tra -1 e 1 ma tra meno infinito  e  più infinito.</p>
<p align="justify">In ogni caso è comunque preferibile una funzione di trasferimento lineare nei casi in cui il nostro output possa variare in maniera illimitata.</p>
<p align="justify">
<p><!--adsense--></p>
<p align="justify">Dopo questa parentesi a proposito delle <strong>funzioni di attivazion</strong>e torniamo alla nostra implementazione. Una volta che il neurone fa la sommatoria pesata di tutti gli input ricevuti passa questo valore alla funzione di attivazione che, a sua volta, restituisce un altro valore. Quest&#8217;ultimo sarà propagato ai neuroni successivi e così via.</p>
<p align="justify">La seconda classe di cui ci occuperemo è NeuronLayer, che rappresenta uno strato di neuroni:</p>
<p align="justify">
<pre class="brush: cpp;">
class NeuronLayer {
	public:
		NeuronLayer(int c_numNeurons, int c_weightsPerNeuron);
		Neuron* getNeuron(int i) const;
		int getNeuronsNum() const;
	private:
		vector&lt;Neuron*&gt; neurons;
};
</pre>
<p align="justify">
<p align="justify">Come è logico, la classe <strong>NeuronLayer</strong> dovrà contenere un insieme di neuroni e per questo usiamo la classe vector della STL per istanziare un vettore di oggetti Neuron*.</p>
<p align="justify">Anche qui i metodi sono abbastanza semplici: il costruttore riceve come parametri il numero dei neuroni facenti parte del layer ed il numero di collegamenti in entrata (e quindi di pesi) per ogni neurone. La funzione getNeuron(int) riceve come parametro un intero i e restituisce l&#8217;i-esimo neurone del livello; l&#8217;ultima funzione restituisce il numero totale dei neuroni facenti parti del livello.</p>
<p align="justify">
<p align="justify">Siamo giunti alla parte cruciale del nostro programma e cioè la classe NeuralNet.</p>
<p align="justify">Sarà lei che si occuperà di collegare i neuroni fra di loro, di propagare i segnali attraverso i neuroni e di darci l&#8217;output restituito dalla rete.</p>
<p align="justify">
<pre class="brush: cpp;">
class NeuralNet {
	public:
		NeuralNet(int c_numInputNeurons, int c_numOutputNeurons,
		int c_numHiddenLayers, int c_numNeuronsPerLayer);
		void createNet();
		void run(vector&lt;double&gt; input, Chromosome* d);
		int totalNumWeights(bool bias);
		void dump();
		double globalError(vector&lt;double&gt; des);
		void printOutput();
	private:
		vector&lt;NeuronLayer*&gt; layers;
		int numInputNeurons,
		numOutputNeurons,
		numHiddenLayers,
		numNeuronsPerLayer;
}
</pre>
<p align="justify">
<p align="justify">Il costruttore della classe <strong>NeuralNet</strong> riceve in input 4 parametri che sono rispettivamente il numero dei neuroni di input, il numero dei neuroni di output, il numero dei livelli nascosti e il numero di neuroni contenuti nei livelli nascosti.</p>
<p align="justify">Il costruttore non fa altro che prendere questi argomenti e salvarli nelle quattro variabili apposite. Il vettore layers contiene dei puntatori ad oggetti NeuronLayer che saranno appunto i vari livelli della rete neurale. Due sono i metodi più importanti di questa classe: createNet() e run(vector&lt;double&gt;, Chromosome*). Il primo inizializza l&#8217;array layers creando i livelli necessari ed i collegamenti tra i vari neuroni, mentre il secondo, esegue la rete neurale passandogli in input i valori contenuti nell&#8217;array input ed utilizzando come pesi della rete il DNA dell&#8217;oggeto Chromosome*. Vediamo il codice di queste due funzioni:</p>
<pre class="brush: cpp;">
void NeuralNet::createNet() {
	if (numHiddenLayers &gt; 0) {
		// Se ci sono layer nascosti li crea
		NeuronLayer* HL = new NeuronLayer(numNeuronsPerLayer,
							numInputNeurons);
		layers.push_back(HL);
		for (int i = 1; i &lt; numHiddenLayers; i++) {
			NeuronLayer* HL = new NeuronLayer(numNeuronsPerLayer,
								numNeuronsPerLayer);
			layers.push_back(HL);
		}
		NeuronLayer* OL = new NeuronLayer(numOutputNeurons,
							numNeuronsPerLayer);
		layers.push_back(OL);
	} else {
		NeuronLayer* OL = new NeuronLayer(numOutputNeurons,
							numInputNeurons);
		layers.push_back(OL);
	}
}
</pre>
<p align="justify">La prima cosa che la funzione fa è quella di verificare se deve creare dei <strong>layer nascosti</strong> o meno. Nel primo caso crea il primo layer nacosto in cui ogni neurone ha un numero di pesi pari al numero dei neuroni in input (infatti i neuroni del primo layer ricevono i dati proprio dai neuroni di input)  e crea i layer seguenti in cui ogni neurone ha un numero di pesi pari al numero di neuroni presenti negli strati precedenti a lui, che essendo tutti strati nascosti avranno un numero di neuroni pari al contenuto della variabile numNeuronsPerLayer; come ultima cosa viene creato il layer di output. Se non ci sono layer nascosto la prima parte viene saltata e viene creato un layer di output collegato direttamente a quello di input.</p>
<p><!--adsense--></p>
<p align="justify">La funzione run() è un po più complessa. Ho evitato di scriverla tutta qui per problemi di impagnazione, quindi vi conviene leggerla direttamente dal file sorgente. Comunque, la prima cosa che la funziona fa è controllare che il numero degli input ricevuti dalla funzione corrisponda effettivamente al numero di neuroni di input: se così non è l&#8217;esecuzione del programma termina dopo aver stampato un messaggio di errore.</p>
<p align="justify">Successivamente la funzione deve impostare i pesi della rete neurale con il DNA del <strong>cromosoma</strong> che gli passiamo come argomento, per fare questo si utilizzano tre cicli for annidati: il primo scorre tutti i livelli della rete neurale, il secondo tutti i neuroni di ogni livello ed il terzo tutti i pesi di ogni neurone, sosituendoli con quelli presenti nel cromosoma.</p>
<p align="justify">Dopo aver impostato i pesi non ci resta altro che dare alla rete l&#8217;input e propagarlo attraverso i neuroni. Si usano anche qui tre cicli for annidati per scorrere tutti i neuroni della rete neurale e per calcolare la somma degli input pesati. Notate che c&#8217;è un if che distingue due casi: se ci troviamo al primo livello nascosto l&#8217;input dovremo prenderlo dai neuroni di input (che altro non sono che gli elementi dell&#8217;array passato come argomento): questo lo facciamo alla linea:</p>
<p align="justify">
<pre class="brush: cpp;">
totInput += layers[i]-&gt;getNeuron(j)-&gt;getWeight(k) * input[k];
</pre>
<p align="justify">
<p align="justify">Come vedete, il k-esimo peso del neurone viene moltiplicato per il k-esimo input, che altro non è che un valore dall&#8217;array.</p>
<p align="justify">Quando non siamo più al primo livello, ma a quelli successivi l&#8217;input andrà preso da neuroni precedenti ed infatti:</p>
<p align="justify">
<pre class="brush: cpp;">
totInput += layers[i]-&gt;getNeuron(j)-&gt;getWeight(k) * layers[i-1]-&gt;getNeuron(k)&gt;getValue();
</pre>
<p align="left">
<p align="justify">Il k-esimo peso del neurone viene moltiplicato per il valore contenuto nel k-esimo neurone del livello precedente (layers[i-1]). La backslash dopo il * sta solamente ad indicare che la linea è spezzata per ragioni di formattazione, ma che il codice va su una sola riga. La variabile totInput conterrà alla fine del ciclo la sommatoria pesata degli input del neurone. Subito dopo il ciclo viene definita una variabile con il valore dell&#8217;ultimo peso del neurone (questo peso non è stato preso in considerazione quando facevamo la sommatoria, se notate, nel ciclo abbiamo passato il parametro false alla funzione getWeightsNum() facendoci così restituire il numero dei pesi meno 1). Questo sarà il bias che alla riga successiva viene sottratto al valore totale del  neurone dopo che quest&#8217;ultimo è stato passato attraverso la funzione sigmoid() cioè la funzione di trasferimento sigmoidale. Dal punto di vista grafico potete immaginare il bias semplicemente come una traslazione sull&#8217;asse y del grafico della funzione e questo comporta che la funzione non sia centrata nel punto per ogni neurone ma che ogni neurone abbia appunto una funzione centrata in un punto differente.</p>
<p align="justify">Il compito della funzione è finito qui, poiché dopo tutti questi cicli i neuroni dell&#8217;ultimo livello, cioè i neuroni di output conterrano il risultato ottenuto dalla rete.</p>
<p align="justify">L&#8217;ultima funzione degna di nota è globalError(): essa calcola l&#8217;errore della rete facendo la differenza tra ogni output ed il rispettivo output desiderato specificato nel training set. Ci servirà in seguito per assegnare il fitness ad i nostri cromosomi.</p>
<p align="justify">Nella prossima parte parleremo delle classi relative alla gestione dei cromosomi e metteremo in pratica quello che abbiamo visto teoricamente nella parte seconda quindi continuate a visitarci!</p>
<p align="justify">Link alle parti precedenti:</p>
<p align="justify"><a title="Reti neurali attraverso algoritmi genetici in C++. Parte I" href="http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-i/" target="_self">Parte I</a></p>
<p align="justify"><a title="Reti neurali attraverso algoritmi genetici in C++. Parte II" href="http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-ii/" target="_self">Parte II</a></p>
]]></content:encoded>
			<wfw:commentRss>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-iii/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Reti neurali attraverso algoritmi genetici in C++. Parte II</title>
		<link>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-ii/</link>
		<comments>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-ii/#comments</comments>
		<pubDate>Sun, 23 Nov 2008 18:00:53 +0000</pubDate>
		<dc:creator>Francesco</dc:creator>
				<category><![CDATA[Informatica]]></category>
		<category><![CDATA[Algoritmi genetici]]></category>

		<guid isPermaLink="false">http://mindunpacked.com/?p=88</guid>
		<description><![CDATA[Algoritmi genetici.
Gli algoritmi genetici sono algoritmi particolari che si ispirano all&#8217;evoluzione naturale delle specie descritta da Darwin. Essi sono molto utilizzati nel risolvere problemi nei quali lo spazio di ricerca delle soluzioni non è ben definito e garantiscono sempre un avvicinamento alla soluzione ideale del problema. In un algoritmo genetico ogni individuo (cromosoma) possiede un [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center"><strong>Algoritmi genetici.</strong></p>
<p style="text-align: justify;">Gli algoritmi genetici sono algoritmi particolari che si ispirano all&#8217;evoluzione naturale delle specie descritta da Darwin. Essi sono molto utilizzati nel risolvere problemi nei quali lo spazio di ricerca delle soluzioni non è ben definito e garantiscono sempre un avvicinamento alla soluzione ideale del problema. In un algoritmo genetico ogni individuo (cromosoma) possiede un <strong>DNA</strong> che rappresenta una potenziale soluzione del problema. Una popolazione di un certo numero di individui con DNA casuale viene creata e su di essa agiscono varie operazioni atte a simulare la selezione naturale per scegliere via via quelli che più si avvicinano alla soluzione desiderata. Il DNA di ogni individuo è in genere una soluzione del problema codificata in modo da facilitare le operazioni genetiche che bisogna fare su di essa (crossover, mutazioni genetiche, etc&#8230;, le vedremo fra poco).</p>
<p><span id="more-88"></span></p>
<p style="text-align: justify;">Per fare un esempio, potremmo scegliere di creare un algoritmo genetico che cerchi due numeri interi che diano come somma il numero (intero) passatogli in input. Un esempio di DNA per questo algoritmo potrebbe essere il seguente:</p>
<p style="text-align: left">00010100</p>
<p style="text-align: justify;">Dove le due quartine rappresentano i due numeri da noi cercati codificati in forma binaria. La forma binaria non è l&#8217;unica possibile per codificare il DNA, infatti ognuno potrebbe scegliere di usare un&#8217;altra codifica, ma nel caso appena descritto si rivela abbastanza comoda (anche perchè è di facile decodifica). Ora abbiamo visto come si codifica il DNA, ma come avviene l&#8217;evoluzione? Vediamolo.</p>
<p style="text-align: justify;">All&#8217;inizio viene creata una popolazione di individui con DNA casuale e ogni individuo di questa popolazione viene valutato, cioè gli viene dato un punteggio più o meno alto in base a quanto si è avvicinato alla soluzione che cerchiamo. Qui entra in gioco una delle parti più importanti nell&#8217;algoritmo genetico e cioè la <strong>funzione di fitness</strong>. Il fitness è proprio il punteggio ottenuto da ogni individuo e questa funzione serve a calcolarlo. Nel nostro caso è molto facile creare una funzione di fitness: per assegnare un punteggio basta semplicemente fare la differenza in valore assoluto tra il numero desiderato in output e il numero ottenuto tramite la somma dei due numeri codificati. Supponiamo di volere il numero 12 come output e di avere due cromosomi con il seguente DNA:</p>
<p style="text-align: left">1) 00010010</p>
<p style="text-align: left">2) 01000101</p>
<p style="text-align: justify;">Il primo codifica i numeri 1 e 2 in forma binaria (0001 == 1, 0010 == 2) mentre il secondo i numeri 4 e 5 (0100 == 4, 0101 == 5). Ilcromosoma 1) da come risultato 3 mentre il 2) da come risultato 9. Se calcoliamo il fitness semplicemente facendo la differenza il primo avra&#8217; un fitness di 121= 11, mentre il secondo 129= 3. Chiaramente nel nostro esempio il fitness più basso corrisponde al cromosoma migliore e un fitness pari a 0 corrisponde all&#8217;aver raggiunto la soluzione perfetta. La funzione di fitness in questo caso è molto semplice, ma spesso è un fattore determinante per la buona riuscita di un algoritmo genetico: scegliere una funzione di fitness al posto di un&#8217;altra puo&#8217; cambiare drasticamente i risultati!</p>
<p style="text-align: justify;">Dopo aver calcolato il fitness per tutti gli individui della popolazione, si passa alla riproduzione. La riproduzione avviene emulando la selezione naturale e cioè facendo sopravvivere gli individui più forti (quelli con il fitness migliore) ed eliminando quelli più deboli. Durante la riproduzione avvengono anche i fenomeni del crossover e della mutazione che servono a creare varietà  genetica (altrimenti i cromosomi resterebbero sempre gli stessi) e che vedremo fra un istante.</p>
<p style="text-align: justify;">Gli individui da far riprodurre non posso essere scelti casualmente, perchè altrimenti non si assisterebbe mai ad un miglioramento con il passare delle generazioni (ah, per generazione si intende  ogni ciclo completo di riproduzione), perciò esistono vari metodi di selezione e noi useremo quella che viene chiamata selezione a torneo (tournament selection). Giusto per la cronaca, un altro dei metodi di selezioni molto utlizzati è la “<strong>roulette  wheel selection</strong>” che assegna ad ogni cromosoma una probabilità di essere selezionato direttamente proporzionale al suo fitness. La selezione a torneo consiste nello scegliere casualmente N individui dalla popolazione di partenza e di confrontare il loro fitness: quello con il fitness migliore viene scelto per la riproduzione. La selezione a torneo permette di variare facilmente la pressione selettiva: più è alto il numero N degli individui partecipanti al torneo è più basse saranno le possibilità di riproduzione degli individui con fitness basso (con basso intendo semplicmente peggiore e non minore numericamente, perchè come abbiamo visto, nel nostro caso, un fitness minore equivale ad un punteggio migliore).</p>
<p><!--adsense--></p>
<p style="text-align: justify;">Gli individui con fitness basso, sono comunque tutt&#8217;altro che inutili perchè in seguito alla ricombinazione genetica, possono fornire materiale utile alla ricerca della soluzione (leggete più avanti per averne un esempio). In genere vengono scelti due cromosomi alla volta (e quindi vengono effettuati due &#8220;tornei&#8221;) e da loro nascono due nuovi individui. I figli, se così si possono chiamare, non avranno lo stesso patrimonio genetico dei genitori (altrimenti non assisteremmo mai ad un miglioramento e l&#8217;individuo migliore della prima generazione resterebbe sempre il migliore per tutte le generazioni seguenti) ma avranno un patrimonio che deriva dal loro in seguito ad alcune modifiche. Ad introdurre queste modifiche sono due operatori genetici: il crossover e la mutazione. Chi ha studiato un po&#8217; di biologia dovrebbe conoscere almeno in linea teorica il crossover: esso avviene quando due cromosomi si rompono in un determinato punto e si scambiano una parte di DNA. Supponiamo di avere due cromosomi:</p>
<p style="text-align: left">1) <span style="color: #ff0000;">00100</span> <span style="color: #ff0000;">100</span> ( 2 + 4 = 6 ) fitness = 6</p>
<p style="text-align: left">2) <span style="color: #ff0000;">00011 010</span> ( 1 + 10 = 11 ) fitness = 1</p>
<p style="text-align: left">
<p style="text-align: justify;">e supponiamo di scegliere come punto di rottura quello indicato dalla freccia. Effettuando il crossover nasceranno due figli con i seguenti DNA:</p>
<p style="text-align: left">1a) <span style="color: #ff0000;">00100</span><span style="color: #0000ff;">010 </span> ( 2 + 2 = 4 ) fitness = 8</p>
<p style="text-align: left">2b) <span style="color: #0000ff;">00011</span><span style="color: #ff0000;">100 </span> ( 1 + 12 = 13 )  fitness = 1</p>
<p style="text-align: justify;">che come vedete sono l&#8217;incrocio dei due DNA precedenti. Il crossover è fondamentale perchè puo&#8217; produrre nuovi cromosomi che si avvicinano molto di piu&#8217; alla soluzione: nell&#8217;esempio il cromosoma 2b) dà come risultato 13 che è quasi la soluzione che cercavamo. E&#8217; interessante notare come, in questo caso, un individuo con fitness basso (il cromosoma 1) con fitness 6) abbia contribuito in maniera fondamentale al raggiungimento della soluzione. Generalmente il crossover non avviene sempre, ma si stabilisce una probabilità (il 70% va bene) con cui farlo avvenire. Dopo aver creato i due nuovi individui su di essi agisce un altro operatore genetico, la mutazione, che effettua un cambiamento su un singolo gene del DNA. Se il DNA è  codificato in forma binaria la mutazione non fa altro che trasforamare uno 0 in un 1 o viceversa. Anch&#8217;essa non si verifica in ogni riproduzioni ma ha una probabilità che in genere è molto piu&#8217; bassa rispetto a quella del crossover. Questo procedimento di riproduzione si ripete fino a che la nuova popolazione non ha un numero di individui pari a quella vecchia e poi si ricomincia tutto da capo: si rivaluta il fitness dei nuovi individui (se tutto va bene troveremo qualcuno con un fitness migliore), si effettua una nuova riproduzione, etc&#8230; tutto questo si ripete fino a che non troviamo una soluzione accettabile al nostro problema.</p>
<p style="text-align: justify;">Probabilmente sarete un po&#8217; confusi, e ora vi starete chiedendo cosa c&#8217;entrino gli algoritmi genetici con le reti neurali. Fra un secondo vi sarà tutto più chiaro. Abbiamo detto che la parte fondamentale per ottenere risultati esatti con una rete neurale è trovare una configurazione ottimale dei pesi e sarà proprio per trovare i valori di tutti i pesi della rete che useremo gli algoritmi genetici. Il DNA dei nostri cromosomi rappresenterà i pesi della rete e via via cercheremo di individuare soluzioni migliori facendo riprodurre quei cromosomi il cui DNA fornisce risultati con un errore più basso quando viene eseguito nella rete. In pratica prendiamo un cromosoma e sostituiamo il suo DNA ai pesi della rete e verifichiamo quant&#8217;è l&#8217;errore prodotto: in questo caso la funzione di fitness corrisponde semplicemente all&#8217;errore, ed anche qui cromosomi con fitness più basso (e quindi con errore più basso) saranno migliori. C&#8217;e&#8217; da notare anche che il DNA non verra codificato ma sarà costituito da un semplice vettore di double che contiene tutti i pesi di tutti i neuroni della rete neurale.</p>
<p style="text-align: justify;">La parte sugli algoritmi genetici è terminata e spero che quello che ho scritto sia sufficiente a farvi capire quello che implementeremo. Consiglio ancora una volta a chi non ha ben chiari questi concetti di leggere alcuni dei tuorial sugli algoritmi genetici consigliati in fondo e soprattutto, di provare a realizzare una propria implementazione di un piccolo algoritmo genetico perchè può aiutare molto.</p>
]]></content:encoded>
			<wfw:commentRss>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-ii/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Reti neurali attraverso algoritmi genetici in C++. Parte I</title>
		<link>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-i/</link>
		<comments>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-i/#comments</comments>
		<pubDate>Wed, 19 Nov 2008 23:05:10 +0000</pubDate>
		<dc:creator>Francesco</dc:creator>
				<category><![CDATA[Informatica]]></category>
		<category><![CDATA[Algoritmi genetici]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Intelligenza artificiale]]></category>
		<category><![CDATA[Reti neurali]]></category>

		<guid isPermaLink="false">http://mindunpacked.com/?p=37</guid>
		<description><![CDATA[PREFAZIONE
Le reti neurali sono un argomento molto ostico per molti all&#8217;inizio.
Anche io per riuscire ad implementare la mia prima rete neurale funzionante ci ho impiegato molto tempo ma ho soprattutto dovuto provare e riprovare più volte passando per diversi fallimenti. Mi sono accorto che molti dei tutorial che si trovano in giro tralasciano alcuni aspetti [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center"><strong>PREFAZIONE</strong></p>
<p style="text-align: justify;">Le <strong>reti neurali</strong> sono un argomento molto ostico per molti all&#8217;inizio.<br />
Anche io per riuscire ad implementare la mia prima <strong>rete neurale</strong> funzionante ci ho impiegato molto tempo ma ho soprattutto dovuto provare e riprovare più volte passando per diversi fallimenti. Mi sono accorto che molti dei tutorial che si trovano in giro tralasciano alcuni aspetti o li spiegano in maniera poco chiara, e tra l&#8217;altro i tutorial in lingua italiana sull&#8217;argomento sono anche pochi.<br />
Con questo tutorial cercherò di creare un documento che comprenda almeno le basi per rendere chiunque lo legga, ed abbia alcuni prerequisiti elencati in seguito, capace di implementare una rete neurale.<br />
<span id="more-37"></span></p>
<p style="text-align: center"><strong>Nota sul codice.</strong></p>
<p style="text-align: justify;">Prima di iniziare va scritta una piccola nota sul codice: il codice che troverete nell&#8217;archivio che vi sarà dato alla fine di questa guida, non è esattamente lo stesso che trovate scritto in questa serie di tutorial. Questo perchè ho fatto in seguito delle modifiche (minori) che comunque non cambiano in sostanza la struttura del codice, ma aggiungono solo qualche funzionalità alla rete neurale. Non dovrebbe essere difficile seguirlo lo stesso.</p>
<p style="text-align: center"><strong>PREREQUISITI</strong></p>
<p style="text-align: justify;">Ho cercato di rendere questa guida comprensibile alla maggior parte delle persone (anche a quelle che sull&#8217;argomento reti neurali sono poco informate) ma restano sempre degli argomenti che vengono dati per scontati per la comprensione del testo.<br />
Sono:</p>
<ul>
<li>Buona conoscenza del C++ e della programmazione OO</li>
<li>Basi matematiche</li>
<li>Conoscenza degli <strong>algoritmi genetici</strong></li>
</ul>
<p style="text-align: justify;">L&#8217;ultimo punto non è strettamente necessario, visto che tenterò di spiegare almeno quello che serve sapere sugli algoritmi genetici per implementare la nostra rete, ma se avete gia&#8217; una conoscenza dell&#8217;argomento di certo avrete meno difficoltà a comprendere alcuni concetti. Alla fine del tutorial sono consigliati una serie di link utili la cui lettura e caldamente consigliata.</p>
<p style="text-align: center"><strong>PARTE GENERALE</strong></p>
<p style="text-align: center"><strong>Introduzione.</strong></p>
<p style="text-align: justify;">Questa Parte generale del tutorial contiene alcuni elementi di base come il funzionamento di un <strong>neurone</strong> biologico, la struttura di un neurone artificiale (cioè quello che noi implementeremo) e la struttura di una rete neurale artificiale. Ho inserito anche una piccola parte sugli <strong>algoritmi genetici</strong> che non tratta esaustivamente l&#8217;argomento ma dovrebbe dare almeno l&#8217;infarinatura necessaria a comprendere per quale motivo li useremo e come li useremo. Comunque, come ho scritto sopra, consiglio lo stesso la lettura di altri tutorial più approfonditi se avete difficoltà a comprendere quello che dico sugli algoritmi genetici. Avrei tralasciato volentieri questa parte, perchè penso che molti di voi già la conoscano se hanno letto almeno qualche altro documento sull&#8217;argomento reti neurali. Purtroppo mi tocca scriverla lo stesso visto che è un requisito fondamentale per la<br />
comprensione delle parti successive e questo tutorial mira ad essere il più chiaro possibile anche per chi è agli inizi. Comunque, se conoscete già questi argomenti potete tranquillamente saltare tutta la Parte generale del tutorial e passare a leggere direttamente il resto.<br />
<!--adsense--></p>
<p style="text-align: center;"><strong>Come funziona un neurone biologico.</strong></p>
<p style="text-align: justify;">I neuroni sono le cellule che costituiscono la nostra mente e che trasmettono i segnali nervosi (sia tra di loro che verso le altre parti del corpo). Ogni uomo posside circa 100 miliardi di neuroni (o almeno dovrebbe) variamente collegati tra di loro attraverso una struttura che prende il nome di sinapsi.<br />
Ogni neurone possiede un lungo prolungamento chiamato assone e diverse ramificazioni più piccole che prendono il nome di dendriti. Attraverso i dendriti (che sono collegati agli assoni di altri neuroni) la cellula riceve i segnali che gli vengono inviati, mentre attraverso l&#8217;assone viene mandato un nuovo segnale.<br />
Se non avete ben chiaro cio&#8217; che abbiamo detto fino ad ora, l&#8217;immagine seguente dovrebbe chiarvi le idee:</p>
<div class="wp-caption alignnone" style="width: 410px"><img title="Schema di un neurone biologico" src="http://mindunpacked.com/images/neuron.bmp" alt="Schema di un neurone biologico" width="400" height="216" /><p class="wp-caption-text">Schema di un neurone biologico</p></div>
<p style="text-align: justify;">(Nella figura sono presenti parti del neurone che non ho nominato, come per esempio la guaina mielinica, perche&#8217; non sono utili al nostro scopo).<br />
In generale, quando un neurone riceve un segnale, se esso supera una certa soglia (detta soglia di attivazione), propaga quel segnale attraverso il suo assone agli altri neuroni, altrimenti resta inattivo.</p>
<p style="text-align: center"><strong>Come funziona una rete neurale.</strong></p>
<p style="text-align: justify;">Un <strong>neurone artificiale</strong> tenta di emulare il comportamento di quello biologico, anche se in maniera molto semplificata.<br />
Ogni neurone (d&#8217;ora in poi con il termine neurone mi riferirò esclusivamente a quelli artificiali) ha una serie di collegamenti pesati in ingresso e  fornisce un output in base agli input ricevuti dai collegamenti. Per collegamento pesato si intende un collegamento al quale è anche associato un peso,<br />
che rappresenta, in poche parole, la forza del collegamento e quindi quanto esso potrà influenzare l&#8217;output del neurone.<br />
Il peso non è altro che un numero, generalmente compreso tra 0 e 1 (o tra 1e 1).<br />
L&#8217;informazione trasportata dal collegamento (parliamo sempre di un numero) viene moltiplicata per il peso e quindi varia al variare del peso (resta invariata quando esso è uguale a 1, diventa nulla quando è 0 e diventa l&#8217;opposto quando è 1).<br />
Come vedremo, sarà proprio il valore dei pesi a determinare l&#8217;esattezza dei risultati della nostra rete ed è appunto variando i pesi tramite speciali algoritmi che si tenta di raggiungere una configurazione ottimale. Ricordo che i pesi subito dopo l&#8217;inizializzazione della rete hanno valori casuali.<br />
Il neurone, dopo aver ricevuto gli input pesati da tutti i collegamenti in ingresso li somma. La sommatoria degli input pesati viene poi passata ad una funzione chiamata funzione di attivazione che fa propagare il segnale ai neuroni successivi.<br />
Spesso, e questo comportamento differisce da quello del neurone biologico, non viene stabilita una soglia minima di attivazione al di sotto della quale il neurone non invia nessuno stimolo, ma questo dipende dalla funzione di attivazione scelta e lo vedremo più tardi.<br />
Le reti che andremo ad implementare in questo tutorial sono definite <strong>reti feedforward</strong> perchè l&#8217;impulso si propaga sempre nella stessa direzione; esistono molti altri tipi di reti e ognuna è più adatta ad alcuni compiti particolari, ma non ne parleremo in questo tutorial.<br />
Una rete neurale è formata appunto da un certo numero di neuroni, chiaramente molto minore di quello del nostro cervello: in genere alcune decine di neuroni sono in grado di compiere compiti anche piuttosto complessi.<br />
I neuroni di una rete sono organizzati in diversi livelli, o strati, e ognuno di essi ha un compito preciso. Il primo strato è quello di input: contiene i neuroni che ricevono l&#8217;input e non hanno collegamenti pesati; non essendo infatti i dati che ricevono provenienti da nessun altro neurone essi immagazzinano semplicemente il dato così come è e lo trasmettono al livello successivo.<br />
Lo strato di input può essere collegato direttamente a quello di output oppure tra di essi si può trovare un certo numero di strati nascosti (<strong>hidden layers</strong>): per gli scopi di questo tutorial useremo una rete con un solo strato nascosto il quale è più che sufficiente per svolgere i compiti che ci interessano.</p>
<p style="text-align: justify;">Abbiamo visto la struttura di una rete neurale, ma come fa essa ad apprendere? Va detto che esistono due tipi di <strong>apprendimento</strong>: quello <strong>supervisionato</strong> (che tratteremo in questo tutorial) e quello non supervisionato.</p>
<p style="text-align: justify;">Usando il primo tipo di apprendimento la rete necessita di una prima fase di addestramento durante la quale le vengono forniti alcuni esempi comprendenti sia l&#8217;input che l&#8217;output desiderato. La rete viene eseguita con questi input di cui si conosce già in precedenza il risultato e tramite alcuni algoritmi vengono modificati i pesi della rete per fornire risultati sempre più accurati. Quando la fase di allenamento è finita (cioè si e&#8217; raggiunta una soglia minima di errore stabilita in precedenza) la rete è in grando non solo di dare un risultato esatto se gli forniamo gli stessi dati che gli<br />
abbiamo fornito nel <strong>training set</strong> (gli esempi) ma anche con dati in input che non ha mai visto. Questo avviene perchè la rete non associa un singolo input all&#8217;output corrispondente ma scopre la relazione che li lega. Potremmo per esempio allenare una <strong>rete neurale</strong> ad imparare a fare la somma di due numeri fornendo un training set del genere:</p>
<table style="text-align: center" border="0">
<tbody>
<tr>
<td>Primo input</td>
<td>Secondo input</td>
<td>Output desiderato</td>
</tr>
<tr>
<td>0.1</td>
<td>0.2</td>
<td>0.3</td>
</tr>
<tr>
<td>0.1</td>
<td>0.3</td>
<td>0.4</td>
</tr>
<tr>
<td>0.1</td>
<td>0.4</td>
<td>0.5</td>
</tr>
<tr>
<td>0.1</td>
<td>0.5</td>
<td>0.6</td>
</tr>
<tr>
<td>0.1</td>
<td>0.6</td>
<td>0.7</td>
</tr>
<tr>
<td>0.1</td>
<td>0.7</td>
<td>0.8</td>
</tr>
<tr>
<td>etc&#8230;</td>
<td>etc&#8230;</td>
<td>etc..</td>
</tr>
</tbody>
</table>
<p><!--adsense--></p>
<p style="text-align: justify;">Un training set deve essere il più omogeneo possibile, e deve descrivere una grande varietà di esempi. Un training set in cui il primo input è sempre 0.1 non è per niente buono per ottenere dei risultati soddisfacenti. Più è vasto il training set, inoltre, più sarà alta la <strong>capacità di generalizzazione</strong> della rete, cioè di dare risultati corretti anche quando i dati in input sono per lei sconosciuti, cioè non facevano parte del training set.<br />
Modificando in maniera corretta i pesi della rete essa riconosce la relazione tra gli input e l&#8217;output (e cioè, nel nostro caso, che l&#8217;output deve essere la somma degli input) e dà un risultato corretto. Ricordatevi comunque che l&#8217;applicazione delle reti neurali avviene in campi in cui è più importante la flessibilità e la capacità di generalizzazione della stessa, piuttosto che ottenere risultati precisissimi: quello che voglio dire è che una rete neurale è molto più utile per la sua flessibilità che per la sua precisione, e che per fare la somma di dui numeri è molto meglio usare una calcolatrice. Una rete<br />
addestrata a tale scopo, infatti, dà in genere, risultati approssimati come :<br />
0.5 + 0.5 = 0.99<br />
0.1 + 0.9 = 1.02<br />
o simili.<br />
Chiusa questa piccola parentesi torniamo ai tipi di apprendimento. Il secondo tipo di apprendimento (quello non supervisionato) serve a creare reti<br />
neurali che classificano i dati che gli forniamo in input in diverse categorie (senza fornigli nessun output desiderato, da qui il nome) in base ai loro elementi in comune, ma non verra&#8217; trattato in questo tutorial.<br />
Come avrete capito, la parte più importante per l&#8217;apprendimento della rete neurale è la modifica dei pesi fra le connessioni dei neuroni ed è proprio qui che agiscono gli <strong>algoritmi di apprendimento</strong>. Esistono diversi algoritmi: uno dei piu&#8217; famosi è quello di retro propagazione dell&#8217;errore (error back propagation) che mi limiterò a citare solamente visto che noi faremo uso di algoritmi genetici per fare evolvere la nostra rete.</p>
<p>La prima parte del tutorial finisce qui. A presto.</p>
]]></content:encoded>
			<wfw:commentRss>http://mindunpacked.com/2008/reti-neurali-attraverso-algoritmi-genetici-in-c-parte-i/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- www.000webhost.com Analytics Code -->
<script type="text/javascript" src="http://analytics.hosting24.com/count.php"></script>
<noscript><a href="http://www.hosting24.com/"><img src="http://analytics.hosting24.com/count.php" alt="web hosting" /></a></noscript>
<!-- End Of Analytics Code -->
