By Claudio Fin 16-8-2000


Ho voluto verificare la possibilità di realizzare reti neurali senza dover effettuare calcoli con numeri reali, ma usando solo interi. Questo per poter realizzare una rete in assembler senza il bisogno di coprocessori matematici o routines in floating point. Inoltre mi interessava verificare la possibilità di contenere i valori di attività in 8 bit (range 0..255) in modo da poter interfacciare direttamente la rete con comuni convertitori ADC a 8 bit in ingresso, o DAC in uscita.
L'obbiettivo era quindi quello di ottenere una funzione di attività del neurone integer perfettamente identica a quella di un comune neurone formale real, in modo da poter effettuare una semplice conversione matematica per passare dal modello real a quello integer.
 


Il neurone formale ha uno o più ingressi pesati (con valori di attività afferente compresi tra 0 e 1 e pesi qualsiasi), un'attivazione interna Ai e una funzione di trasferimento di uscita sigmoidale  Y=1/(1+exp(-X)). In più ogni neurone ha un bias (o polarizzazione) ottenuto aggiungendo un ingresso pesato idealmente collegato a un'unità fittizia sempre attiva al massimo. In questo modo, variando il peso di bias si può aumentare o diminuire il valore dell'attivazione interna.

Il neurone formale:

Il neurone formale "h" riceve l'attività "Ox" del neurone afferente "x" pesata (moltiplicata) per il valore del peso "Wxh". Inoltre riceve l'attività di bias, sempre 1, pesata per il peso di bias "Wb". Queste attività pesate vengono sommate e danno l'attivazione interna "Ai". Questa attivazione passa per la funzione di trasferimento a sigmoide e si ottiene l'attività di uscita "Oh".

Nel caso di più neuroni afferenti, chiamiamoli x1 x2 x3 ecc..., si sommano in "Ai" tutti i loro contributi. Se i pesi "W..." sono minori di 1 hanno una funzione attenuante sull' attività afferente, se sono maggiori di 1 hanno un effetto moltiplicativo. Inoltre se sono positivi aumentano l'attivazione "Ai", se sono negativi la diminuiscono (connessione inibitoria).

Andamento della funzione
di trasferimento sigmoidale

Sull'asse delle x abbiamo l'attivazione interna "Ai", su quello delle y  l'attività di uscita "Oh"


 

Andamento dell' attività Oh in funzione di dell'attività
afferente Ox con tre diverse configurazioni di pesi.


Wxh = 6.40
Wb  = -5.30
Wxh = -11.20
Wb  = 6.00
Wxh = -1.50
Wb  = 0.80

 

Avendo come punto di partenza il vincolo di realizzare una rete "eseguibile" su un hardware "povero" sono da fare alcune considerazioni:
  • Non abbiamo la possibilità di effettuare calcoli floating point, la funzione di trasferimento sigmoidale deve perciò essere precalcolata e inserita in una tabella.
  • Dobbiamo interfacciare la rete con circuiti reali a 8 bit, l'attività delle unità (neuroni) deve perciò essere ristretta a 8 bit.
  • Idealmente anche i pesi dovrebbero essere contenuti in 8 bit, ma questo non consente un adeguato range e si ottiene una attività di uscita fortemente a gradini assolutamente non accettabile. Usando 16 bit possiamo rappresentare il peso in 15 di essi e il segno nel rimanente, abbiamo così 32768 punti per rappresentare il range dei pesi.
  • L'attività di moltiplicazione delle attività integer con i pesi integer genera numeri molto grandi (calcolabili in 32 bit). L'attivazione Ai dovrà perciò essere divisa alla fine per un adeguato coefficiente potenza di 2 in modo da essere "proiettabile" all'interno della tabella della funzione di trasferimento sigmoidale.
  • La funzione di trasferimento deve essere adeguatamente "espansa" all'interno degli elementi della tabella in modo da far coincidere la rappresentazione real con quella integer con una minima conversione matematica.
  • I due punti precedenti sono mutuamente vincolati, nel senso che esistono più possibilità per ottenere lo stesso risultato, quello che cambia è la dimensione della tabella. Visto che questa deve essere contenuta in memoria su un hardware "povero" la sua dimensione non deve eccedere gli 8K.

  • Detto questo empiricamente si è dimostrata valida una tabella di 8K contenente la funzione sigmoidale espansa su circa 6000 elementi e centrata sull'elemento 4095 (avendo come indice un range da 0 a 8190). Come indicato nella procedura seguente il coefficiente 407 "allarga" la sigmoide e anch'esso è stato ricavato empiricamente. Il 255 rappresenta il valore massimo che può assumere l'attività di uscita del neurone.

    Procedura di calcolo punti sigmoide

    var
      h:integer;
      x,y:real;
      ft:array[0..8190]of byte;

    begin
      for h:=0 to 8190 do
      begin
        x:=(h-4095)/407
        y:= 1/(1+exp(-x))*255
        ft[h]:=round(y);
      end;
    end;

    La pesatura degli ingressi si effettua con una semplice moltiplicazione integer e il valore ottenuto va sommato ad Ai. Quindi, nel caso del neurone rappresentato all'inizio (con un solo afferente) possiamo dire che Ai vale:


    Ai = Ox * Wxh   +  255 * Wb

    (ricordare che sono calcoli con interi!)

    A questo punto dobbiamo dividere Ai per un secondo coefficiente di "regolazione", che empiricamente è risultato essere 1024, ed infine dobbiamo applicare la funzione di trasferimento. In pratica possiamo ottenere l'indice della tabella con 4095+Ai e leggere il valore di attività contenuto. Nasce un problema se Ai è minore di -4095 o maggiore di 4095, perchè in questo caso si andrebbe a puntare a elementi inesistenti (prima dell'inizio o dopo la fine della tabella). Si risolve usando la tabella solo se Ai è nel range ammesso. Se è minore l'attività è sicuramente 0, se è maggiore è sicuramente 255.

     
    Procedura di applicazione funzione di trasferimento

    function ftrasf(Ai:integer):byte;
    begin
      if Ai < -4095 then
        result:=0
      else
        if Ai > 4095 then
          result:=255
        else
          result:=ft[4095+Ai];
    end;

    Nota:Ai deve essere un intero a 32 bit e non a 16

    Fatte queste premesse di seguito sono riportati i grafici dell'attività di uscita del neurone integer impostando le stesse condizioni viste nel caso del neurone real. Come si può vedere l'andamento dell'attività è identico, solo che il valore della sua attività varia da 0 a 255 invece che da 0.000 a 1.000, e i pesi sono la controparte intera moltiplicata per 1638.35 dei pesi reali. Si può così progettare o studiare una rete real e passarla in integer semplicemente moltiplicando i pesi per 1638.35 e convertendo il range 0..1 in 0..255!
    Wxh = 10485
    Wb  = -8683
    Wxh = -18350
    Wb  = 9830
    Wxh = -2458
    Wb  = 1311

     
     

    Questo oggetto Delphi 2.0 realizza in modo semplice il comportamento del neurone integer, e incapsula tutti i coefficienti e la funzione di trasferimento. Si usa nel seguente modo: si istanzia l'oggetto, ad esempio usando una variabile N di tipo Tneurone, lo si inizializza chiamando il metodo N.init.

    Poi si impostano peso e attività afferente (N.p:=....  N.i:=....) e si chiama il metodo N.calc, si ripetono questi tre passi per tutte le connessioni afferenti e alla fine si imposta il bias con N.b:=... e si chiama la funzione N.o che fornisce l'attività di uscita del neurone. Prima di effettuare un altro calcolo si chiama N.reset che azzera bias e attività interna.

    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..8190]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=407;  //coefficiente di espansione della sigmoide
      coeffic2=1024; //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 < -4095 then result:=0
      else if ai > 4095 then result:=mass
      else result:=ft[4095+ai];
    end;

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

    procedure Tneurone.Init;
    var h:integer; x,y:real;
    begin
      for h:=0 to 8190 do begin
        x:=(h-4095)/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.


    Pagina e disegni realizzati da Claudio Fin
    Ultimo aggiornamento 22-9-2000