19 Novembre 2006
Chi vuol essere un utente NNFW?
In questo breve tutorial, si vedrà come utilizzare il NNFW per creare e gestire una semplice rete neurale di tipo feed-forward.
L’architettura della rete che andremo a costruire è estremamente semplice. Essa è costituita da tre strati: in input vi sono 6 neuroni continui (suddivisi in un cluster da 4 e un altro da 2), che elaborano l’informazione e la passano ai 9 neuroni continui dello strato intermedio, i quali a loro volta la processano per poi inviarla ai 2 neuroni booleani di output.
Tutto il codice scritto verrà incluso all’interno di un singolo file. Iniziamo pertanto creando un nuovo file “main.cpp”.
Innanzitutto è necessario includere all’interno del file appena creato una direttiva di inclusione relativa alle componenti delle NNFW che andremo ad utilizzare. Nel nostro caso, oltre ai generici:
#include “nnfw.h”
#include “random.h”
using namespace nnfw;
includiamo anche:
#include “fakecluster.h”
#include “biasedcluster.h”
#include “matrixlinker.h”
Nel dettaglio, la prima di queste tre istruzioni serve per includere le direttive che definiscono la classe FakeCluster, ossia quella tipologia di clusters di neuroni privi di bias e di funzioni di attivazione (pertanto “fake”). La seconda istruzione è relativa alla classe BiasedCluster (i clusters “tradizionali” di neuroni, con bias e funzione di attivazione), mentre la terza si riferisce alla classe MatrixLinker (le matrici delle connessioni tra i vari strati di neuroni).
Sistemate tutte le questioni relative alle varie inclusioni, possiamo ora addentrarci nella stesura del codice che implementa la rete neurale sopra descritta.
All’interno del main del nostro programma possiamo iniziare subito a definire la rete che vogliamo costruire, partendo dai vari clusters di neuroni. Quelli di input, innanzitutto, vengono solitamente dichiarati come FakeCluster. Nel nostro caso, rispettiamo la “convenzione” e chiamiamo i due clusters di input, composti rispettivamente da 2 e da 4 neuroni, “inputCluster1” ed “inputCluster2”:
FakeCluster *inputCluster1, *inputCluster2;
inputCluster1 = new FakeCluster(2);
inputCluster2 = new FakeCluster(4);
Mancano all’appello l’hidden e l’output layer. In questo caso dobbiamo fare ricorso alla famiglia dei BiasedCluster, per creare “hiddenLayer” (composto da nove neuroni) ed “outputLayer” (formato da due soli neuroni):
BiasedCluster *hiddenLayer, *outputLayer;
hiddenLayer = new BiasedCluster(9);
outputLayer = new BiasedCluster(2);
A differenze dei FakeCluster, i BiasedCluster devono avere una propria funzione di attivazione. Nel nostro caso, impostiamo lo strato intermedio (quindi il cluster “hiddenLayer”) come dotato di funzione di trasferimento sigmoidale, con gli estremi fissati nei punti -1.0 e + 1.0:
hiddenLayer->setFunction(ScaledSigmoidFunction(1.0, -1.0, 1.0));
Siccome vogliamo che la nostra rete neurale abbia un output booleano, la funzione di trasferimento dell’output layer (cluster “outputLayer”) deve essere impostata in maniera adeguata. Nel nostro caso facciamo in modo che il valore di uscita possa essere 0.0 o 1.0, con il valore di soglia (la somma pesata degli input che arrivano al neurone/cluster) impostato a 0.0:
outputLayer->setFunction(StepFunction(0.0, 1.0, 0.0));
Definiti tutti i clusters che compongono la rete, è giunta l’ora di stabilire le connessioni che vi sono tra essi. La classe deputata a gestire le matrici delle connessioni tra i vari cluster è MatrixLinker. Nel nostro caso, avendo a che fare con una rete feed-forward molto semplice, abbiamo soltanto tre gruppi di connessioni: quella che va dal primo cluster di input allo strato intermedio, quella che connette il secondo cluster di input con lo strato intermedio ed infine quella che collega il cluster intermedio a quello di uscita. La definizione delle connessioni è molto intuitiva:
MatrixLinker *input1-hidden, *input2-hidden, *hidden-output;
input1-hidden = new MatrixLinker(inputCluster1, hiddenLayer);
input2-hidden = new MatrixLinker(inputCluster2, hiddenLayer);
hidden-output = new MatrixLinker(hiddenLayer, outputLayer);
Il più è fatto. Ora si tratta soltando di racchiudere tutte le componenti create finora (clusters e linkers) all’interno di un “contenitore”. Si tratta della BaseNeuralNet, che possiamo creare utilizzando la seguente sintassi:
BaseNeuralNet *net;
net = new BaseNeuralNet();
Una volta creata la rete, richiamiamo il suo metodo addCluster() per inserirvi all’interno i vari clusters. Si noti che, quando si aggiungo clusters di input, la funzione di inserimento deve essere chiamata con il passaggio di due parametri, il secondo dei quali impostato sul flag booleano “true”. Quando si aggiunge invece un cluster di output, i parametri da passare devono essere tre: il secondo impostato su “false”, il terzo su “true”.
net->addCluster(inputLayer1, true);
net->addCluster(inputLayer2, true);
net->addCluster(hiddenLayer);
net->addCluster(outputLayer, false, true);
In modo del tutto simile a quello che abbiamo appena visto, andiamo ad inserire all’interno della rete neurale anche le matrici delle connessioni:
net->addLinker(input1-hidden);
net->addLinker(input2-hidden);
net->addLinker(hidden-output);
Ora non rimane che “spiegare” alla rete neurale qual è il percorso che devono seguire le informazioni immesse in input. In sostanza, quale dev’essere la “direzione” delle connessioni. Per farlo si crea un vettore di tipo UpdatableVec e, tramite il metodo setOrder() di BaseNeuralNet, si setta al suo interno la sequenza. Nel nostro caso abbimo:
UpdatableVec updatables_order;
net->setOrder(updatables_order << input1-hidden << input2-hidden << hiddenLayer << hidden-output << outputLayer);
Si noti che i clusters di input non sono stati inclusi all’interno di questa sequenza. Questo perché i due gruppi di unità neurali in questione sono stati settati come FakeClusters e, pertanto, non è di alcuna utilità il fatto che essi vengano presi in considerazione durante lo “step” della rete. Ciò sarà da tenere a mente in seguito, in quanto gli input della rete neurale non dovranno essere settati come input dei clusters di input, ma come loro output.
La creazione della rete neurale è stata completata. Non ci resta che inizializzare il seme del generatore di numeri random con l’istruzione:
Random::setSeed(time(0));
e quindi inizializzare i pesi delle connessioni sinaptiche. Nel nostro caso, siccome vogliamo che i pesi assumano un valore casuale compreso tra -1.0 e + 1.0, utilizziamo l’istruzione:
net->randomize(-1.0,1.0);
La rete neurale è finalmente pronta per essere utilizzata all’interno del proprio progetto.
A tal fine è bene osservare alcuni esempi delle funzioni più utilizzate:
net->step(); // Effettua lo “step”, ossia “aggiorna la rete” sulla base dei valori settati in input
input1-hidden->getWeight(0, 2); // Restituisce il peso della connessione tra il primo neurone di inputLayer1 ed il terzo di hiddenLayer
hidden-output->setWeight(0, 2, -0.343); // Setta il peso della connessione tra il primo neurone di inputLayer1 ed il terzo di hiddenLayer, sul valore -0.343
outputLayer->getOutput(1); // Restituisce l’output del secondo neurone di outputLayer
hiddenLayer->setInput(4, -0.3); // Imposta l’input netto del quinto neurone dell’hidden layer sul valore 0.3
inputLayer1->setOutput(1, 0); // Imposta l’output del secondo neurone di inputLayer1 sul valore 0 (essendo un FakeCluster, è come se si trattasse dell’input).
Per consultare un elenco dettagliato delle altre numerose funzioni utilizzabili, si veda la documentazione di riferimento presente sul sito ufficiale del Neural Network Framework, all’indirizzo: http://www.nnfw.org.
Per il momento, buon divertimento!



