R E T I     N E U R A L I    C O N    P I C
I N T R O D U Z I O N E

(avvertenze)

[Indice principale]


 

Come realizzare reti neurali artificiali "low resources"
Non sto qui ad introdurre la teoria delle NN (neural networks) in quanto esistono numerosi siti che presentano la materia in modo esteso (si veda  ad esempio il seguente link: http://www.ulisse.it/~marchese/book/neurbook.html) e mi rifaccio ad uno studio del giugno 2000 in cui volevo verificare la possibilita' di lavorare solo con numeri interi su hardware "poveri" (http://members.xoom.virgilio.it/stor/informatica/neurint/neurint.htm).


Come parziale introduzione metto comunque qui una risposta che ho lasciato tempo fa nella mailing list roboteck:

Dunque, premetto che di NN non ne ho realizzata neppure una e che pure io preferirei evitare la matematica, ma in realta' quello che c'e' di matematico nei neuroni e' il comportamento complessivo nel tempo, che non e' un semplice ricevi impulso ---> ritrasmetti impulso.

Intanto c'e' un funzionamento fisiologico basato sull'emissione di uno spike di circa 1mS quando la sommatoria di tutte le eccitazioni sinaptiche (pesate, o se vogliamo attenuate dall' impedenza dendritica) raggiunge un certo livello di soglia. Segue un periodo refrattario di parecchi mS e poi il N e' pronto a generare un nuovo impulso.

Gia' da questo si vede che c'e' un primo livello di matematica, in quanto l'attivazione interna del neurone deriva dalla sommatoria pesata di molte piccole attivazioni raccolte dai dendriti, ed e' la loro stessa fisiologia biochimica ad effettuare questa sommatoria.

Poi c'e' un secondo livello che e' il firing-rate, la frequenza di scarica del neurone, questa frequenza (detta attivita' di uscita) sembra rappresentare il vero segnale utile (e dico sembra perche' e' solo una semplificazione su cui non tutti sono completamente daccordo), e la sua funzione di uscita, in relazione all'attivazione interna, non e' lineare ma bensi' approssimabile ad una sigmoide.

In pratica, mentre un neurone fisiologico emette spikes di ampiezza costante (che poi vengono integrati pesati e sommati nei dendriti dei neuroni successivi) modulati in frequenza (da 0 a circa 100..200Hz) in un neurone artificiale si assume che il valore dell'attivita' di uscita rappresenti gia' questa frequenza... questa semplificazione/astrazione porta alle reti neurali artificiali realizzabil anche con comuni operazionali.

Tempo fa ho scritto una pagina per verificare la possibilita' di usare la matematica integer al posto della floating point, la prima parte del discorso sul neurone artificiale formale puo' tornare utile: http://members.xoom.it/stor/informatica/neurint/neurint.htm

Come si vede dai tre grafici di esempio dell'attivita' di uscita in funzione dei pesi e dell'attivita' in ingresso, l'attivita' di uscita ha una forma che puo' essere di volta in volta differente, e da questo nasce la possibilita' di una NN di calcolare (come se fosse un potente sistema di interpolazione) una funzione di complessita' arbitraria.

Calcolare una funzione puo' poi essere gia' il risultato voluto (per esempio una rete che dati i due ingressi distanza e tempo di volo calcola due uscite velocita' e angolo di lancio).

Oppure puo' essere il riconoscimento di una zona nello spazio dei possibili valori di ingresso. Se prendiamo per esempio il grafico intermedio possiamo dire che il neurone riconosce (e' molto attivo) quando l'ingresso Ox e' compreso tra 0 e 0,3.

Da questo punto di vista addestrare una rete significa creare o modellare opportunamente la forma della funzione di uscita agendo sui pesi, e intuitivamente lavorare un piano metallico in modo che abbia la stessa forma e poi spostarlo a seconda del valore degli ingressi, e leggerlo da sopra con una camma, e' la stessa cosa ;)

Per avere funzioni di uscita abbastanza complesse da diventare utili di solito e' necessario ricorrere a reti con diversi ingressi e composte da almeno uno strato intermedio oltre gli ingressi e le uscite, in questo modo e' possibile o far calcolare funzioni arbitrariamente complesse, o far riconoscere spazi del problema 
sempre piu' strutturati, per esempio con un semplice neurone come quello dell'esempio possiamo al massimo riconoscere se siamo nella parte sinistra o destra del piano cartesiano, ma lavorando con piu' strati possiamo formare molte "dune" separate piu' o meno estese e/o elevate.

L'esempio della pagina tra l'altro e' il caso piu' semplice in quanto il problema e' unidimensionale (un solo ingresso). Se avessimo due ingressi l'uscita la potremmo rappresentare come l'elevazione Z su un piano XY di base, con 3 o piu' ingressi la rappresentazione grafica diventa praticamente impossibile, e si dice che il neurone, o la rete, lavorano su uno spazio N-dimensionale.

Bene, se fin qui e' tutto ben confuso, possiamo passare al livello successivo;) Le reti ricorsive sono quelle in cui le uscite dei neuroni dell'ultimo strato ritornano in ingresso al primo (o comunque tornano indietro in un qualche punto intermedio). In questo caso una rete acquista una nuova capacita' che e' quella di contenere potenzialmente molte configurazioni stabili di neuroni attivi, e altre invece non permesse. Questa caratteristica fa si che nella rete si formino i cosiddetti "attrattori", ogni dato di input viene cioe' riportato alla configurazione stabile piu' vicina ad esso (sempre nello spazio N-dimensionale del problema, come una pallina lasciata cadere sul piano metallico di prima che seguendo le pendenze naturali rotola fino alla buca piu' vicina), e questo genera la capacita' di ricostruire informazioni corrotte o incomplete, o riconoscere "cose" in ambiente molto rumoroso e variabile. Due esempi tipici di questo tipo di rete sono la memoria associativa (2 strati con connessioni bidirezionali) e l'autoassociatore (uno strato interamente interconnesso).

Ci sono molti esperimenti che indicano come gli attrattori giochino un ruolo fondamentale nelle capacita' del cervello, basta pensare a quelle volte in cui di sfuggita sembra di vedere qualcosa e ci vuole un po' di tempo per accorgersi che e' qualcos'altro, per esempio un tronco che spunta a lato della strada che all'inizio magari ci sembra un cane... il cervello comincia a ricevere input e si attivano i riconoscimenti "piu' probabili", poi con l'aumentare delle informazioni si sposta su una configurazione sempre piu' probabile.... ah, mi ero dimenticato... in campo neurale la certezza, intesa anche come risultato 1 o 0, non esiste mai, esiste solo l'avvicinarsi ad una probabilita' cosi' alta da poter essere trattata ai fini pratici come certezza ;)

Sono interessanti anche altri esperimenti in cui si e' simulata la conoscenza di parole e concetti in una rete dotata di microfono e sintetizzatore vocale, e si e' visto che danneggiando alcuni pesi o nella sezione di riconoscimento o in quella della "conoscenza ad attrattori" la rete presentava gli stessi sintomi di afasia dei pazienti umani, in un caso la rete pronunciava parole con significato diverso da quelle dette al microfono, ma sonorita' simile, nell'altro parole diverse ma con significato simile, e questo in un certo senso taglia la testa al toro a tutte le ricerche convenzionali che cercano di simulare le capacita' cognitive attraverso schemi a blocchi funzionali... tutti questi blocchi "emergono" in modo molto naturale e sfumato (fuzzy) nelle reti di neuroni.

E ora la brutta notizia... per realizzare anche piccole cose, occorrono moltissimi neuroni e risorse elaborative (oltre alle difficolta' dell'addestramento)... per quanto ne so un moscerino di quelli che si tuffano nella minestra ha almeno 10mila neuroni;)

Nel nostro caso l'interesse principale nell'utilizzo delle NN artificiali nasce dalla frase che ho evidenziato nel testo precedente, e cioe' che e' possibile realizzare un complesso calcolatore di funzioni matematiche (sto pensando per esempio alle equazioni della cinematica inversa) usando solo somme e moltiplicazioni integer!

Lo studio del giugno 2000 prevedeva di usare una tabella da 8Kbytes per contenere i punti della funzione di trasferimento sigmoidale, e questo non sarebbe un problema usando per esempio uno Z80 con 30 o piu'K di memoria, diventa pero' difficile da realizzare volendo usare un PIC che dispone solitamente di pochi Kbytes.

Una possibilta' e' quella di usare una memoria esterna, che risulta pero' essere lenta se si usa una mem.seriale I2C da 8K come la 26LC64, oppure puo' richiedere troppi fili di ingresso/uscita se di tipo parallelo come una comune EPROM 27C256.

Una terza possibilita' e' ridurre le dimensioni della tabella ad un solo Kbytes, cosa che la rende inseribile anche su un PIC 16F628. Per evitare di sprecare spazio ho pensato di memorizzare solo mezza sigmoide (per valori di X positivi), in quanto l'altra parte (per valori di X negativi) e' speculare e basta sottrarla dal valore massimo di uscita (255).

Come nel caso dell'altro studio mi interessa ottenere una perfetta corrispondenza tra la rappresentazione real e quella integer, in modo che si possa passare rapidamente dall'una all'altra con una semplice moltiplicazione/divisione. Naturalmente la rappresentazione integer (assieme alla tabella precalcolata per la funzione di trasferimento) crea una quantizzazione dei valori che introduce un certo errore rispetto al puro calcolo real.


Come punto di partenza prendo di nuovo la sigmoide real. Questa funzione raggiunge il valore 1 o 0 per Ai che vale rispettivamente +6,907 e -6,907 (in realta' raggiunge lo 0,999 o lo 0,001). Se voglio memorizzare in un array di 1020 punti la parte destra del grafico, devo fare in modo che al mio punto 1019 corrisponda il valore real 6,907. La procedura di calcolo in pascal e' la seguente:

 const
   coeffic1=1019/6.907; //espansione sigmoide
   mass=255             //max. attività
 var
  h:integer;
  x,y:real;
  ft:array[0..1019]of byte
 begin
   for h:=0 to 1019 do begin
     x:=h/coeffic1;
     y:= 1/(1+exp(-x))*mass;
     ft[h]:=round(y);
   end
 end;

Ora rimane da vedere a quali valori real corrispondono i valori integer dei pesi, per esempio gli estremi -32767 + 32767. Questo dipende da come trattiamo l'attivazione interna Ai del neurone dopo la sommatoria di tutti i contributi in ingresso. Nello studio precedente prima di applicare la funzione di trasferimento si divideva Ai per 1024 (un semplice shift a destra di 10 bit) e gli estremi integer corrispondevano all'incirca a un range di pesi real esteso da -20 a +20. Ora che la dimensione della tabella si e' ridotta notevolmente dobbiamo dividere per un valore piu' grande, per esempio 2048 (shift a destra di 11 bit).

Possiamo paragonare il caso real (ingresso 1 con peso 6,097 che produce una Ai di 6,907 e una uscita pari a 1) al caso integer (ingresso 255 con peso X che produce una Ai 1019 e un'uscita 255):

255 * X / 2048 = 1019,  quindi X=8183,968.

Il peso 6,907 e' rappresentato dal valore integer 8184, questo vuol dire che ogni unita' integer rappresenta il valore real 0,00084386  e quindi gli estremi +/-32767 rappresentano i valori real +/-27,651.

Rispetto allo studio precedente il range integer abbraccia quindi un campo piu' ampio di valori real, e questo significa che ogni variazione su un valore integer produce un effetto un po' piu' amplificato sul valore di uscita.

RIASSUMENDO:

  • Attivita' real    = Attivita' integer / 255
  • Attivita' integer = Attivita' real * 255
  • Peso real         = peso integer * 0,00084386
  • Peso integer      = peso real / 0,00084386
  • Ed ecco il nuovo oggetto pascal Tneurone con cui si puo' simulare il funzionamento di una rete integer implementabile successivamente su PIC:

    unit neurone;

              interface

              type
                Tneurone=object
                  Ai:integer;               //attività interna 32 bit
                  P:integer;                //peso
                  I:byte;                   //attività in ingresso
                  B:integer;                //bias
                  Ft:array[0..1019]of byte; //tabella dati sigmoide
                  procedure Reset;          //predispone per nuovo calcolo
                  procedure Calc;           //calcola nuova ai con ultimi dati
                  function  O:byte;         //calcola il bias e la f di trasf.
                  procedure Init;           //crea la tabella dati sigmoide
                end;

              implementation

              const
                coeffic1=1019/6.907;  //coefficiente di espansione della sigmoide
                coeffic2=2048;        //coefficiente di divisione di Ai
                mass=255;             //massimo valore dell'attività

              //---------------------------------------------------------------

              function Tneurone.O:byte;
              begin
                ai := ai + mass*b;  // calcola l'influsso del bias
                ai := ai div coeffic2;
                if ai < 0 then
                  if ai < -1019 then  result:=0
                                else  result:=255-Ft[abs(ai)]
                else
                  if ai > 1019  then  result:=mass
                                else  result:=Ft[ai];
              end;

              //---------------------------------------------------------------

              procedure Tneurone.Init;
              var h:integer; x,y:real;
              begin
                for h:=0 to 1019 do begin
                  x:=h/coeffic1;
                  y:= 1/(1+exp(-x))*mass;
                  Ft[h]:=round(y);
                end;
                ai:=0; b:=0;              //azzera bias e attivazione
              end;

              //---------------------------------------------------------------

              procedure Tneurone.Reset;
              begin
                ai:=0; b:=0;
              end;

              //---------------------------------------------------------------

              procedure Tneurone.Calc;
              begin
                ai := ai + p*i;
              end;

              end.


    ... ed un programma per toccare con mano il comportamento di un singolo neurone con una sola connessione.
    E' possibile modificare il peso dell'ingresso e il bias in un range +/- 20


    percint.zip  (97K)
     

    [Indice principale]


    Sito by Claudio Fin (2001-2002)  -  Ultimo aggiornamento 22-9-2002