X
    Categories: blog

Le reti neurali

Con questo articolo iniziamo ad esplorare il mondo delle reti neurali. Come sempre cominciamo dalle cose semplici, dando un po’ di nozioni generali, per poi spingerci sempre più in dettaglio, coadiuvati anche da codice di esempio.

Il neurone biologico

Il neurone è l’unità cellulare che costituisce il tessuto nervoso, il quale fa parte del sistema nervoso.

Rispetto ad altre tipologie di cellule, ha proprietà fisiologiche e chimiche particolari che lo rendono in grado di ricevere, elaborare e trasmettere impulsi nervosi.

Ogni neurone è costituito, principalmente, da:

  • dendriti: fibre minori che raccolgono i segnali di input da altri neuroni
  • soma: il nucleo del neurone, in cui arrivano i segnali raccolti dai dendriti
  • assone: una fibra principale molto lunga che trasporta un segnale uscente dal soma e lo propaga verso altri neuroni

La sinapsi

Il collegamento tra l’assone di un neurone e i dendriti degli altri neuroni non è diretto, ma è mediato tramite una “interfaccia” elettrochimica chiamata sinapsi. Ogni singola sinapsi può modificare la propria risposta e variare, in questo modo, l’efficienza di trasporto dell’informazione.

Quando il soma riceve i segnali in ingresso dai dendriti, esegue una “elaborazione”. Se i segnali ricevuti superano una certa soglia, scaturisce un nuovo segnale di uscita sull’assone. Questo segnale si propagherà ad altri neuroni, anche molto distanti tra loro.

Il valore di questa soglia e l’efficienza di trasmissione elettrochimica delle sinapsi sono strettamente legate ai processi di apprendimento.

Il neurone artificiale

Nel 1943 venne definito il primo modello matematico di neurone, consistente in:

  • n ingressi (corrispondenti ai dendriti) ognuno di essi dotato di un coefficiente chiamato peso che simula l’efficacia della corrispondente sinapsi.
  • una funzione sommatoria (corrispondente al soma) che raccoglie i valori degli ingressi “pesati” e li somma, aggiungendo un ulteriore valore fisso interno chiamato bias. Questo è un offset che può servire a trovare il punto di lavoro ottimale del neurone).
  • una funzione di attivazione che, partendo dal valore della sommatoria precedente, produce un valore di uscita (il corrispondente dell’assone)

Funzione di attivazione

La funzione di attivazione simula il comportamento del neurone biologico di attivarsi, appunto, solo se i segnali in ingresso superano una certa soglia.



Mentre il neurone biologico, per motivi di “risparmio energetico“, genera impulsi in uscita, il neurone artificiale utilizza funzioni continue e derivabili.

Il motivo apparirà più chiaro tra poco.




Le funzioni di attivazione utilizzabili sono diverse a seconda dell’obiettivo a cui si vuole arrivare. Le più utilizzate sono le seguenti:

Funzione Sigmoidale

Si tratta di una funzione che fornisce un’uscita compresa tra 0 ed 1, centrata a 0,5 per valore X=0.

Per la sua natura è spesso utilizzata per ottenere probabilità statistiche, percentuali.

Ad esempio, come uscita di una rete neurale potrebbe andare bene per pilotare l’acceleratore di un’auto. Varrà 0 quando non si preme l’acceleratore, 1 quando l’acceleratore è premuto a fondo, tutti gli altri valori per le parzializzazioni.

Funzione Tangente Iperbolica

E’ una funzione simile alla sigmoidale, ma i suoi valori sono compresi tra -1 e +1, centrato in 0 per valore X=0.

Facendo riferimento all’esempio precedente dell’auto, questa funzione potrebbe essere utilizzata per pilotare il volante dell’auto stessa. Varrà 0 quando il volante è centrato, -1 quando il volante è sterzato tutto da una parte (ad esempio a sinistra), +1 quando il volante è sterzato tutto dalla parte opposta (ad esempio a destra). I valori intermedi per sterzate parziali.

Funzione Rectified Linear Units (ReLU)

E’ una funzione lineare che resta nulla per valori di X negativi.

E’ una funzione molto utilizzata nelle reti neurali in quanto è molto semplice anche la sua derivata.

Per sua natura “filtra” i valori negativi in ingresso e questo spesso consente di velocizzare sensibilmente la fase di addestramento della rete neurale.

Funzione Leakey ReLU

E’ una funzione molto simile alla ReLU, ma per valori negativi di X viene applicato un coefficiente (solitamente < 1) che consente di limitare l’azione di “filtraggio” dei negativi.

Ovviamente quando tale coefficiente vale 0, si ottiene una semplice ReLU.



Addestramento di un neurone

In cosa consiste la fase di addestramento di un neurone?

Considerando che gli ingressi vengono forniti dall’esterno e le funzioni di attivazione sono già definite, le uniche variabili in gioco sono i pesi di ogni ingresso e l’eventuale bias.

Quindi addestrare un neurone equivale a trovare i pesi ed il bias per cui, applicando un ingresso al neurone, la rispettiva uscita generata è uguale o comunque si discosta poco dal valore di uscita che ci aspetteremmo.

Con la frase “si discosta poco” ovviamente stiamo parlando di un margine di errore, che sarà pressoché impossibile da eliminare.
Il nostro obiettivo sarà quindi quello di minimizzare questo errore il più possibile.

Tipologie di reti neurali

Le reti neurali si differenziano principalmente per numero di livelli e per come si propagano i segnali in uscita dai vari neuroni.

Classificazione per numero di livelli

Perceptrone



Il perceptrone è la rete neurale più semplice. In pratica è formato da un singolo neurone che accetta n ingressi e fornisce una uscita.



Single Layer Perceptron

Raggruppando più perceptroni su un singolo livello (layer) otteniamo una rete single layer perceptron.

Nella figura a fianco i perceptroni sono i due cerchi a destra. I tre cerchi a sinistra non sono conteggiati come neuroni in quanto sono considerati i punti di ingresso (nella figura precedente erano indicati con x1x2 e x3) e fanno parte del cosiddetto input layer.

Per loro natura, queste reti neurali possono apprendere solo mapping lineari e quindi classificare solo pattern linearmente separabili. Pertanto il numero di applicazioni possibili risulta essere piuttosto limitato: ad esempio, un layer del genere non può apprendere la funzione XOR.

Multi Layer Perceptron

Inserendo nuovi layer intermedi di neuroni, otteniamo in generale una rete multi layer perceptron.

I layer intermedi sono chiamati hidden layer in quanto restano “invisibili” dall’esterno della rete, la quale si interfaccia all’ambiente solo tramite il layer di ingresso e quello di uscita.

Gli hidden layer consentono di superare le limitazioni viste per il single layer, permettendo l’apprendimento di mapping estremamente complessi.
Di contro, ogni strato e neurone in più aumenta sensibilmente il numero di connessioni e quindi la complessità dei calcoli necessari sia alla risoluzione della rete che, soprattutto, al suo addestramento.

Deep Neural Network


Quando il numero di layer nascosti è >2 la rete neurale è genericamente definita deep neural network (rete neurale profonda)





Classificazione per propagazione dei segnali

Reti Feedforward



In queste reti ogni neurone di un layer riceve gli ingressi dal layer precedente e fornisce l’uscita al layer successivo.



Reti Ricorrenti (o Recurrent)

In queste reti esistono connessioni di feedback che connettono l’uscita dei neuroni di un layer agli ingressi dei neuroni dello stesso layer o dei layer precedenti.

Questa retroazione crea una sorta di memoria di quanto accaduto in passato e rende quindi l’uscita attuale non solo dipendente dall’ingresso attuale, ma anche da tutti gli ingressi precedenti.

Per questo motivo queste reti sono utilizzate per la gestione di sequenze di dati, come ad esempio i testi (che sono sequenze di lettere e/o parole), i video (sequenze di immagini), gli audio (sequenze di suoni), eccetera.

Esistono altre tipologie di reti, come le reti associative, le reti stocastiche, eccetera, ma quelle di maggior utilizzo e più semplici da implementare sono sicuramente le feedforward e le ricorrenti.

Universal Approximation Theorem

Il teorema noto come Universal Approximation Theorem asserisce che ogni funzione continua che mappa intervalli di numeri reali su un intervallo di numeri reali può essere approssimata da una rete dual layer.

L’esistenza del teorema non significa però che sia facile metterlo in pratica. Con solo due layer a disposizione, per la mappatura di classificazioni complesse il layer nascosto dovrebbe contenere un numero di neuroni estremamente alto, aumentando vertiginosamente il numero di connessioni.

Per questo motivo risulta in generale più agevole usare reti profonde.
In questo caso però il calcolo dei pesi (fase di addestramento) di una rete formata da decine o centinaia di livelli risulta essere estremamente complesso, perchè i pesi di ogni livello variano anche in funzione dei pesi dei livelli precedenti.

Quindi, per molti anni, furono utilizzate solo reti neurali semplici. Queste potevano gestire solo casi d’uso limitati e non complessi. Questo fece sì che la comunità scientifica le considerasse poco interessanti: in pratica quei casi d’uso si potevano gestire in maniera più semplice con gli algoritmi di classificazione e regressione classici già visti negli altri articoli.

La rinascita delle reti neurali profonde è stata aiutata principalmente da alcuni fattori:

  1. l’aumento della potenza di calcolo disponibile a prezzi ragionevoli
  2. l’aumento di dati da elaborare
  3. la definizione dell’algoritmo di backpropagation, che semplificò estremamente i calcoli necessari a trovare i pesi nella fase di addestramento

Algoritmo di backpropagation

Come accennato prima, l’algoritmo di backpropagation consente una estrema semplificazione nella fase di addestramento della rete, quando devono essere determinati i pesi di ogni connessione tra i neuroni.

In questo articolo non parleremo della teoria matematica che sta dietro a questo algoritmo, anche perchè in rete, googlando un po’, troverete decine di scritti che parlano in modo approfondito di come funziona.

Diremo solo che tramite la backpropagation viene valutato l’errore in uscita e in base a questo vengono aggiornati i valori dei pesi partendo dall’ultimo strato risalendo fino al primo (da cui il nome back propagation).

Per i calcoli sono utilizzate le derivate delle funzioni di attivazione di ogni neurone, ecco perchè queste funzioni devono essere scelte in modo oculato e soprattutto devono essere funzioni continue e derivabili. Nella fattispecie, la funzione ReLU, essendo lineare, ha una derivata semplicissima. Per questo motivo (ma anche perchè filtra i valori negativi) è spesso utilizzata nelle reti neurali complesse.

Pseudocodice

In linea di principio, l’addestramento supervisionato di una rete neurale usando l’algoritmo di backpropagation, avviene in questo modo (pseudo codice):

initialize_weights_at_random()
repeat
  for each example in the_training_set
    output = compute_output(example)
    compute_quadratic_error(output, ground_truth(example))
    for i = levels_number down to 1
      compute_update_for_weights_at_level(i)
    end 
    update_all_weights()
  end 
until (all_examples_correctly_classified() or max_iterations_reached())

Si parte quindi con l’inizializzazione dei pesi con valori random. Solitamente sono utilizzate specifiche funzioni pseudo-casuali con particolari distribuzioni di probabilità, in modo da minimizzare eventuali problemi che potrebbero verificarsi in fase di addestramento: l’algoritmo potrebbe arenarsi in un errore minimo locale, o comunque avvicinarsi all’obiettivo in modo più lento del previsto. In generale, comunque, è possibile utilizzare qualsiasi funzione pseudo-casuale per la generazione dei valori random.
Se si inizializzassero tutti i pesi a 0, risulterebbe impossibile aggiornarne i valori (lo capiremo meglio tra poche righe) e la rete neurale non potrebbe essere addestrata.

Si procede applicando un pattern in ingresso alla rete e si ottiene una uscita, che è poi confrontata con il rispettivo valore di ground_truth (cioè il valore che ci aspettiamo) per calcolare l’errore della rete.

Il calcolo dei valori di aggiornamento dei pesi di ogni livello (dall’ultimo al primo) avviene sfruttando i pesi stessi come coefficienti di propagazione (questa volta all’indietro), l’errore e le derivate delle funzioni di attivazione. Ecco perchè, se i pesi fossero tutti inizializzati a 0, l’addestramento della rete non progredirebbe.

Una volta calcolati tutti i nuovi valori, questi sono applicati al modello e si riparte con il prossimo pattern. Quando tutti i pattern del training set sono stati applicati alla rete, allora si dice che è stata elaborata una epoca. A questo punto si ricomincia dal primo pattern del dataset, per continuare ad aggiornare i pesi e completare un’ulteriore epoca.

Ci si ferma quando la classificazione è perfetta o, più realisticamente, quando l’errore medio del training set scende sotto una certa soglia, oppure se si raggiunge un certo numero predeterminato di epoche.

See you soon!

Nel prossimo articolo inizieremo a vedere qualche semplice rete neurale e a testarla in codice Python.

A presto!

Cristiano Casadei: Lavoro in Maggioli dal ’96 e ho contribuito a diversi prodotti dell’Azienda: da Concilia a TradeWin, ai prodotti per i Demografici. Dal 2016 entro a far parte a tempo pieno del team dei Progetti Speciali, ora R&D. In questo team ho contribuito allo sviluppo di Revisal, Scacco2 e ora mi occupo di studiare e sperimentare soluzioni che fanno uso di Intelligenza Artificiale. Come si può intuire dalla foto, amo la montagna.
Related Post