Chiarezza sulle reti neurali

Chiarezza sulle reti neurali

Condividi con i tuoi amici...

Hai mai pensato al modo in cui il tuo cervello è in grado di elaborare informazioni, prendere decisioni e imparare dalle esperienze? Questo è possibile grazie ai miliardi di neuroni che lavorano insieme in una fitta rete di connessioni. Ma cosa accadrebbe se potessimo replicare questo processo in un sistema artificiale?

Le reti neurali sono una rappresentazione artificiale della rete di connessioni tra i neuroni nel cervello umano. Ma come funzionano esattamente questi sistemi artificiali? E cosa c’è dentro un elaboratore che assomiglia ai neuroni?

Il neurone umano è composto da un corpo cellulare, dendriti e un assone. Il suo compito è raccogliere segnali elettrici dai neuroni adiacenti e trasmettere questi segnali lungo l’assone ai neuroni successivi. Nella rete neurale artificiale, ogni neurone è rappresentato da un nodo matematico che svolge un compito simile a quello del neurone umano.

Ma cosa si intende per “rete”? In termini tecnici, una rete è un insieme di nodi connessi tra loro da archi. Nel caso delle reti neurali, questi nodi rappresentano i neuroni artificiali e gli archi rappresentano le connessioni tra di loro.

Allora, come imparano le reti neurali? Come riescono a prendere decisioni e elaborare informazioni come fa il nostro cervello? Scopriamolo insieme in questo articolo sulle reti neurali. Siamo pronti a immergerci in questo mondo affascinante di intelligenza artificiale e a capire come queste reti possono cambiare il mondo che ci circonda.

Una rete immateriale

Chiariamo subito che una rete neurale non è una struttura fisica come lo è il cervello umano, quindi i neuroni artificiali non esistono fisicamente come i neuroni umani. Si tratta di una struttura matematica che si realizza grazie ad un programma scritto in un certo linguaggio di programmazione o codice. Vedremo un esempio al termine di questo articolo. Prima scopriamo a cosa serve e qual è la logica di funzionamento di una rete neurale.

A cosa serve e come funziona una rete neurale

Una rete neurale è un modello di apprendimento basato su una struttura di rete di neuroni artificiali. I neuroni artificiali sono unità di calcolo che simulano il comportamento dei neuroni biologici nel cervello umano. Una rete neurale consiste in un insieme di queste unità di calcolo organizzate in una struttura a più livelli, che viene utilizzata per analizzare i dati e fare previsioni o prendere decisioni.

Le reti neurali possono essere utilizzate per risolvere una vasta gamma di problemi, come la classificazione di oggetti in categorie, la predizione delle tendenze future in base ai dati storici o il riconoscimento delle parole in un testo. Per funzionare correttamente, una rete neurale deve essere addestrata utilizzando un set di dati di esempio, che viene utilizzato per determinare i pesi dei collegamenti tra i neuroni. Una volta addestrata, la rete neurale può essere utilizzata per fare previsioni o prendere decisioni in base ai nuovi dati forniti.

Le reti neurali possono essere sia shallow (poco profonde) che deep (profonde). Le reti neurali shallow hanno solo uno o due livelli di neuroni, mentre le reti neurali deep hanno molti livelli di neuroni e sono spesso utilizzate per risolvere problemi di classificazione o regressione complessi.

Un esempio concreto: le previsioni del tempo

Supponiamo di avere una rete neurale che deve fare previsioni su un set di dati di esempio che contiene le temperature dell’aria durante il giorno in una città per una settimana. La rete neurale ha tre neuroni di input, che rappresentano la temperatura, l’umidità e il vento, e un neurone di output, che rappresenta la temperatura prevista per il giorno successivo.

Durante il processo di addestramento, l’algoritmo di retropropagazione del gradiente utilizza i valori di temperatura, umidità e vento per ogni giorno del set di dati di esempio per fare previsioni sulla temperatura per il giorno successivo. Se la previsione della rete neurale è inferiore al valore corretto, l’algoritmo aumenta i pesi dei collegamenti tra i neuroni di input e il neurone di output per fare previsioni più alte in futuro. Se la previsione della rete neurale è superiore al valore corretto, l’algoritmo diminuisce i pesi dei collegamenti per fare previsioni più basse in futuro.

Una volta che il processo di addestramento è completo, la rete neurale ha i pesi dei collegamenti ottimizzati per fare previsioni accurate sulla temperatura per il giorno successivo in base ai valori di temperatura, umidità e vento forniti.

Schematicamente…

Ecco un esempio di come potrebbero essere assegnati i pesi dei collegamenti tra i neuroni in una rete neurale utilizzando l’algoritmo di retropropagazione del gradiente:

1- Inizializza i pesi dei collegamenti in modo casuale.
2- Itera attraverso il set di dati di esempio e fa le previsioni della rete neurale per ogni esempio.
3- Calcola l’errore totale della rete neurale utilizzando una funzione di errore.
4- Calcola gli aggiustamenti dei pesi dei collegamenti necessari per ridurre l’errore totale utilizzando l’algoritmo di retropropagazione del gradiente.
5- Aggiorna i pesi dei collegamenti della rete neurale utilizzando gli aggiustamenti calcolati.
Ripete i passaggi 2-5 finché l’errore totale della rete neurale non raggiunge un livello accettabile.

Aumentiamo la complessità: reti a più livelli

In una rete neurale, un layer o livello rappresenta un insieme di neuroni che lavorano insieme per effettuare una determinata attività di elaborazione del segnale. Una rete neurale è composta da più strati o layer, ognuno dei quali ha un compito specifico nell’elaborazione dei dati di input.

I layer di una rete neurale sono interconnessi tra loro tramite dei pesi, che rappresentano la forza della connessione tra due neuroni. Questi pesi, come abbiamo già anticipato, sono i parametri che vengono addestrati durante il processo di apprendimento automatico, ovvero vengono ottimizzati per rendere la rete in grado di effettuare correttamente una specifica attività di elaborazione.

Esistono diversi tipi di layer che possono essere utilizzati all’interno di una rete neurale, ad esempio i layer densi o fully connected, i layer a convolution, i layer recurrenti e i layer di pooling. Ogni tipo di layer ha un’architettura specifica e un compito specifico nella elaborazione del segnale.

Ad esempio, un layer denso o fully connected è un layer in cui ogni neurone è connesso a tutti gli altri neuroni del layer precedente, mentre un layer a convolution è un layer utilizzato per la elaborazione del segnale in immagini e video, in cui i neuroni effettuano operazioni di filtraggio su porzioni locali dell’input. Un layer recurrente è un layer in cui i neuroni sono connessi a se stessi e ai neuroni del layer precedente, ed è utilizzato per elaborare segnali sequenziali come ad esempio il linguaggio naturale. Infine, un layer di pooling è un layer che effettua una riduzione della dimensionalità dell’input, ad esempio sottoponendolo a una operazione di massimo o media.

Livelli nascosti

Una rete neurale a più livelli per fare previsioni su un set di dati di esempio che contiene le temperature dell’aria durante il giorno in una città per una settimana potrebbe essere formata da questi livelli:

Livello di input: questo livello include tre neuroni di input, che rappresentano la temperatura, l’umidità e il vento.

Livello nascosto: questo livello include quattro neuroni, che ricevono input dai neuroni di input e elaborano ulteriormente i dati.

Livello di output: questo livello include un neurone di output, che rappresenta la temperatura prevista per il giorno successivo. Il neurone di output riceve input dai neuroni del livello nascosto e utilizza i pesi dei collegamenti ottimizzati durante il processo di addestramento per fare la previsione.

Durante il processo di addestramento, i dati di input vengono inviati attraverso i neuroni di input al livello nascosto, dove vengono elaborate ulteriormente. Le informazioni elaborate vengono quindi inviate al neurone di output, dove vengono utilizzate per fare la previsione sulla temperatura per il giorno successivo. L’errore totale della rete neurale viene quindi calcolato utilizzando una funzione di errore e utilizzato per aggiornare i pesi dei collegamenti tra i neuroni in modo da ridurre l’errore totale. Questo processo viene ripetuto finché l’errore totale della rete neurale non raggiunge un livello accettabile.

A cosa servono i livelli nascosti?

Considera una rete neurale che viene addestrata per riconoscere le immagini di animali. Il livello nascosto potrebbe includere neuroni che rappresentano caratteristiche dell’immagine, come gli occhi, il muso o le orecchie. Una volta che la rete ha imparato a riconoscere queste caratteristiche, può utilizzarle per fare previsioni sull’intera immagine senza dover esaminare ogni singolo pixel. Ciò rende il processo di riconoscimento più veloce e efficiente.

In generale, il livello nascosto in una rete neurale a più livelli svolge un ruolo importante nel processo di apprendimento della rete e può aiutare la rete a elaborare i dati in modo più efficiente e fare previsioni accurate.

Funziona così anche la mente umana

Il processo di riconoscimento delle immagini da parte di una rete neurale a più livelli può essere paragonato al modo in cui l’uomo riconosce le immagini. L’uomo può utilizzare pochi tratti chiave di un’immagine per riconoscere l’intera figura, proprio come la rete neurale può utilizzare le caratteristiche importanti dell’immagine per fare previsioni accurate.

A volte possiamo essere ingannati da immagini che contengono alcuni dei tratti chiave di un’immagine che abbiamo già visto, ma che in realtà sono diverse da quelle che abbiamo di fronte. Questo può essere paragonato al modo in cui una rete neurale a più livelli può essere ingannata da immagini che contengono caratteristiche simili a quelle che ha imparato durante il processo di addestramento, ma che in realtà rappresentano qualcosa di completamente diverso.

In entrambi i casi, è importante notare che il riconoscimento delle immagini può essere influenzato da molti fattori, come la qualità dei dati di addestramento, la struttura della rete neurale e il livello di errore accettabile. Tutti questi fattori possono influire sulla capacità della rete di fare previsioni accurate e di evitare di essere ingannata da immagini ingannevoli.

La programmazione di una rete neurale

Sei un programmatore o ti piacerebbe diventarlo?

Bene, allora ti può interessare capire come si traduce in pratica tutta la teoria esposta.

Anche se non sei un programmatore, grazie alle informazioni che seguono puoi capire la logica che regge la programmazione di una rete neurale a partire da un singolo neurone artificiale.

Abbiamo detto che una rete neurale non é altro che un programma.

Che cosa sono i neuroni artificiali?

Dal punto di vista del codice di programmazione, un neurone in una rete neurale artificiale è rappresentato come una funzione matematica che elabora i dati di input.

In generale, un neurone riceve un certo numero di input (che possono essere chiamati “segnali“), li combina con i propri pesi e li passa attraverso una funzione di attivazione, che produce un output. Questo output viene quindi inviato ai neuroni successivi nella rete.

In termini di codice, un neurone potrebbe essere implementato come una funzione che accetta un vettore di input e un vettore di pesi, e che restituisce un output calcolato come:

output = activation_function(dot(inputs, weights) + bias)

dove dot è una funzione che effettua il prodotto scalare tra i vettori inputs e weights, bias è un termine di offset che viene aggiunto al risultato del prodotto scalare, e activation_function è la funzione di attivazione.

Semplicissimo esempio in Python

Una volta installato Python e un editor come IDLE, installa mediante prompt Win il modulo
numpy mediante questa istruzione:

pip install numpy

Il seguente codice mostra la logica che sta dietro ad un neurone artificiale:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def neuron(inputs, weights, bias):
    dot_product = np.dot(inputs, weights)
    z = dot_product + bias
    output = sigmoid(z)
    return output

inputs = np.array([1, 2, 3])
weights = np.array([0.2, 0.8, -0.5])
bias = 2

neuron_output = neuron(inputs, weights, bias)
print(neuron_output)

la funzione neuron accetta tre parametri: inputs, weights e bias. Il prodotto scalare tra inputs e weights viene calcolato utilizzando la funzione np.dot, e viene sommato a bias. Il risultato di questa operazione viene quindi passato attraverso la funzione sigmoid, che rappresenta la funzione di attivazione, e il valore di output viene restituito.

Nel codice sopra riportato, il neurone ha tre input (inputs = [1, 2, 3]), tre pesi (weights = [0.2, 0.8, -0.5]) e un bias (bias = 2). Il neurone elabora questi dati di input e produce un output (neuron_output) che viene stampato a schermo.

…e come si fa a ottimizzarlo mediante l’adeguamento dei pesi?

Il codice che si utilizza per adeguare i pesi in una rete neurale artificiale è noto come “algoritmo di ottimizzazione”. L’obiettivo di questo algoritmo è di modificare i pesi in modo da minimizzare la differenza tra la previsione della rete e i valori desiderati.

Ci sono molte tecniche di ottimizzazione che possono essere utilizzate in una rete neurale, tra cui l’algoritmo di gradiente descent (SGD), l’algoritmo di gradient descent con momentum, l’algoritmo di Adagrad, l’algoritmo di Adadelta, l’algoritmo di Adam e così via.

L’algoritmo di gradient descent è uno dei più semplici e comuni algoritmi di ottimizzazione utilizzati in reti neurali. Il principio fondamentale dell’algoritmo è quello di utilizzare la derivata della funzione di errore per calcolare la direzione di massima discesa e modificare i pesi nella direzione opposta.

Ecco un esempio di codice che implementa l’algoritmo di gradient descent in Python:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def neuron(inputs, weights, bias):
    dot_product = np.dot(inputs, weights)
    z = dot_product + bias
    output = sigmoid(z)
    return output

def error_function(prediction, target):
    return 0.5 * (prediction - target) ** 2

def derivative_of_error_function(prediction, target):
    return prediction - target

def update_weights(inputs, weights, bias, prediction, target, learning_rate):
    derivative = derivative_of_error_function(prediction, target)
    d_weights = derivative * inputs
    d_bias = derivative
    weights = weights - learning_rate * d_weights
    bias = bias - learning_rate * d_bias
    return weights, bias

inputs = np.array([1, 2, 3])
weights = np.array([0.2, 0.8, -0.5])
bias = 2
target = 0.5
learning_rate = 0.1

for i in range(100):
    prediction = neuron(inputs, weights, bias)
    error = error_function(prediction, target)
    weights, bias = update_weights(inputs, weights, bias, prediction, target, learning_rate)
    if i % 10 == 0:
        print("Step:", i, "Prediction:", prediction, "Error:", error)

Questo codice implementa una semplice rete neurale artificiale. La rete neurale è composta da un singolo neurone che utilizza una funzione sigmoidea come attivazione.

L’import di numpy viene impiegato per utilizzare le funzioni matematiche offerte da questa libreria.

La funzione sigmoid accetta un input x e restituisce il valore della funzione sigmoidea.

La funzione neuron accetta tre input: inputs, weights e bias. Questi tre input sono utilizzati per calcolare la dot product tra inputs e weights che viene poi sommato al bias. Il risultato viene quindi passato alla funzione sigmoidea come input per calcolare l’output del neurone.

Ricordiamo che la dot product, o prodotto scalare, tra due vettori è un’operazione matematica che restituisce un valore scalare. Nel caso specifico della rete neurale, la dot product tra inputs e weights rappresenta un modo per combinare i valori degli input con i pesi associati.

Il prodotto scalare di due vettori A e B di lunghezza n è definito come la somma del prodotto di ciascuno dei loro elementi:

A = [a1, a2, ..., an]
B = [b1, b2, ..., bn]

A dot B = a1 * b1 + a2 * b2 + ... + an * bn

Nel codice, la dot product tra inputs e weights viene calcolata utilizzando la funzione np.dot di numpy. Il risultato di questa operazione rappresenta l’input per la funzione sigmoidea, che calcola l’output del neurone.

La funzione error_function accetta due input: prediction e target e calcola l’errore come la metà della differenza tra prediction e target elevata al quadrato:

0.5 * (p – t) ^2 dove p indica prediction e t sta per target

La funzione derivative_of_error_function accetta due input: prediction e target e calcola la derivata parziale della funzione d’errore:

d/dp (0.5 * (p – t) ^2) = p-t

La derivata fornisce informazioni sul tasso di variazione della funzione rispetto a una certa variabile. In questo caso, la variabile è l’output del neurone (prediction), e la derivata della funzione d’errore fornisce informazioni sul tasso di variazione dell’errore rispetto all’output del neurone. Queste informazioni vengono utilizzate per aggiornare i pesi e il bias in modo da ridurre l’errore.

La funzione update_weights accetta sei input: inputs, weights, bias, prediction, target e learning_rate. Questa funzione calcola la derivata della funzione d’errore, calcola la variazione dei pesi e del bias e li aggiorna utilizzando la formula weights = weights - learning_rate * d_weights e bias = bias - learning_rate * d_bias.

Il codice principale crea alcune variabili come gli input, i pesi, il bias e la velocità di apprendimento. Quindi, all’interno di un ciclo for, viene eseguita la previsione del neurone, il calcolo dell’errore, l’aggiornamento dei pesi e il bias e l’output viene stampato ogni dieci passi.

Per approfondire l’argomento delle reti neurali leggi l’articolo:

Reti neurali con Tensorflow: i principi su cui si basa l’apprendimento automatico