Il ruolo della casualità nei processi di apprendimento

La casualità degli eventi e lo stato d’incertezza presente in noi che ne consegue, spesso ci spaventa e fa si che ci formiamo un’idea negativa intorno al caso, ma in realtà, una giusta dose di casualità e quindi d’incertezza è di utilità fondamentale nei processi di apprendimento e non solo.
Già in ambito biologico è noto come le varietà all’interno di una specie sia determinata a monte da un processo che si chiama crossing-over.

Durante la divisione cellulare, i nostri cromosomi omologhi – uno ereditato dalla madre e uno dal padre – si avvicinano molto l’uno all’altro e si rompono in alcuni punti specifici. Quando questi punti si ricompongono, possono scambiare alcuni dei loro segmenti. Immaginate che ogni cromosoma sia una collana di perle, dove ogni perla rappresenta un gene. Durante il crossing-over, questi cromosomi si uniscono come in una danza intricata, facendo letteralmente scambiare le perle tra di loro. Questo processo di scambio delle informazioni genetiche genera una variazione unica, combinando diverse varianti di geni da entrambi i genitori in ogni cromosoma. La casualità è un aspetto importante nel meccanismo del crossing-over. Durante questo processo, i punti di rottura sul DNA sono selezionati in maniera casuale, e quindi non è possibile prevedere con certezza quali segmenti di DNA verranno scambiati. È come se il DNA lanciasse una moneta per decidere quali segmenti saranno scambiati tra i cromosomi. Chiaramente il processo nel quale si inserisce il crossing-over non è del tutto casuale ma rispetta delle regole.
Il caso delle sinapsi
Un altro esempio biologico in cui una certa dose di casualità e quindi d’incertezza entra in gioco è quello delle sinapsi del nostro cervello. Si tratta di un esempio che ci interessa particolarmente per via degli aspetti legati all’apprendimento naturale e artificiale di cui ci occupiamo.
Leggi questo articolo per approfondire l’aspetto delle somiglianze tra le reti neurali naturali e artificiali:
Le scienze cognitive e l’intelligenza artificiale
In breve, le sinapsi sono i punti di contatto in cui due neuroni comunicano tra loro o dove i neuroni trasmettono segnali ad altre cellule. Nelle sinapsi il segnale di natura elettrica proveniente dal neurone presinaptico si trasforma in un segnale di natura chimica perché vengono rilasciate nella fessura sinaptica delle sostanze chimiche dette neurotrasmettitori. Queste sostanze si legano ai recettori sul neurone postsinaptico, innescando “forse” un potenziale postsinaptico che può depolarizzare o iperpolarizzare il neurone postsinaptico, determinando se sarà attivato o meno e quindi se propagherà o meno il segnale elettrico giunto dal primo neurone.

Quando entra in gioco la casualità?
Queste sinapsi possono presentare un certo grado di variabilità o incertezza nella trasmissione del segnale. Ciò significa che, anche se un segnale viene trasmesso attraverso una sinapsi, questo può non essere completamente efficace nel depolarizzare o attivare il neurone postsinaptico. L’incertezza nella propagazione del segnale attraverso le sinapsi può essere influenzata da diversi fattori, come l’affidabilità della sinapsi stessa o le fluttuazioni del rumore di fondo.
Questa incertezza nella propagazione del segnale nel cervello contribuisce a diversi aspetti del funzionamento neurale, come l’adattamento alle variazioni nell’ambiente, la capacità di gestire l’informazione incompleta o rumorosa e la rappresentazione delle informazioni incerte.
Nel sistema visivo umano, l’introduzione di casualità è legata al meccanismo della ricettività dei neuroni nella retina. Le cellule retiniche hanno campi di ricettività che rispondono in modo casuale a stimoli visivi. Questo rende il sistema visivo più robusto a variazioni e rumore nel mondo visivo, consentendo una migliore discriminazione degli oggetti in diverse condizioni ambientali.
Anche nell’ascolto e nella comprensione del parlato, l’introduzione di incertezza è legata alla natura stocastica dei segnali acustici. Il cervello umano è in grado di riconoscere e comprendere il parlato nonostante variazioni tra i parlanti, l’accento, il rumore di fondo e altre sorgenti di incertezza. Questa capacità di gestire l’incertezza è importante per comprendere il parlato nei contesti del mondo reale.
Nel riconoscimento di oggetti, sia nel contesto visivo che tattile, l’introduzione di incertezza può giocare un ruolo fondamentale nel facilitare la generalizzazione. Il cervello umano è in grado di riconoscere oggetti anche quando sono parzialmente oscurati, distorti o nelle diverse posizioni. Ciò è possibile grazie alla capacità delle reti neurali naturali di integrare informazioni da diverse caratteristiche e di adattarsi a variazioni nelle condizioni di visualizzazione.
In sintesi, vediamo che una certa dose di casualità risulta fondamentale per le attività del nostro cervello oltre che per lo sviluppo biologico degli esseri viventi.
e l’intelligenza artificiale?
Nello studiare le reti neurali artificiali ci chiediamo sotto quali aspetti esse introducono un simile meccanismo e se lo fanno, per quale ragione.
Può esserti utile leggere anche i seguenti articoli:
Reti neurali con Tensorflow: i principi su cui si basa l’apprendimento automatico
L’overfitting
Hai mai indossato un abito che sembrava perfetto nel negozio, ma una volta indossato a casa ti rendi conto che non ti si adatta come pensavi? Questo potrebbe accadere perché l’abito era “fatto su misura”, progettato esclusivamente per te, ma non tenendo conto della tua postura o delle piccole differenze nel tuo corpo rispetto a quando sei in piedi dritto. Questa situazione nella scienza dell’apprendimento automatico è conosciuta come overfitting.
Quando si tratta di addestrare sistemi di apprendimento automatico per riconoscere determinati modelli in un insieme di dati, la sfida è trovare un equilibrio tra l’apprendimento dei particolari del proprio set di dati e la capacità di generalizzare e applicare correttamente queste conoscenze ai nuovi dati. L’overfitting si verifica quando un modello di apprendimento automatico si adatta così bene al set di dati di addestramento che diventa eccessivamente specializzato e non riesce a generalizzare correttamente i nuovi dati.
Un esempio classico di overfitting può essere considerato quando si costruisce un modello per prevedere il rendimento finanziario delle azioni. Potresti addestrare il modello utilizzando dati storici, ma se il modello si adatta troppo strettamente a quei dati specifici, potrebbe essere incapace di prevedere correttamente il rendimento futuro delle azioni. Questo perché potrebbero essere presenti fattori imprevisti che influenzano i mercati finanziari e che il modello non è stato in grado di imparare dal set di dati di addestramento.
Le tecniche di regolarizzazione
La teoria prevalente per evitare l’overfitting e ottenere una buona capacità di generalizzazione è quella dell’utilizzo di tecniche di regolarizzazione e di validazione incrociata durante il processo di addestramento del modello. La regolarizzazione consiste nell’introdurre una penalità per la complessità del modello, impedendo al modello di adattarsi eccessivamente ai dati di addestramento. La validazione incrociata consente di valutare le prestazioni del modello su dati non visti in fase di addestramento, garantendo che il modello sia in grado di generalizzare correttamente.
Le tecniche di regolarizzazione mirano a trovare un equilibrio tra la capacità del modello di adattarsi ai dati di addestramento e la sua capacità di generalizzare su nuovi dati. Le seguenti sono alcune delle tecniche di regolarizzazione comuni:
- La tecnica del dropout consiste nel disattivare casualmente un numero di unità nascoste (neuroni) durante la fase di addestramento delle reti neurali. Ciò impone al modello di fare affidamento su diverse combinazioni di nodi e rende la rete più robusta alla co-dipendenza dei neuroni. Il dropout aiuta a ridurre l’overfitting e promuove una migliore generalizzazione.
- Regolarizzazione L1 e L2: Queste tecniche di regolarizzazione aggiungono termini di penalità ai pesi durante la fase di addestramento delle reti neurali. La regolarizzazione L1 introduce un termine di penalità proporzionale alla somma degli valori assoluti dei pesi, mentre la regolarizzazione L2 introduce un termine di penalità proporzionale alla somma dei quadrati dei pesi. Questi termini di penalità incoraggiano il modello a preferire pesi più piccoli e contribuiscono a limitare la complessità del modello.
- Early stopping: Questa tecnica di regolarizzazione si basa sull’arresto dell’addestramento del modello quando le prestazioni sui dati di validazione raggiungono un picco e iniziano a diminuire. In altre parole, l’addestramento viene interrotto prima che l’overfitting si verifichi. Questo può evitare che il modello memorizzi eccessivamente i dati di addestramento e aiuta a ottenere migliori prestazioni su nuovi dati.
- Data Augmentation: Questa tecnica consiste nel creare nuovi dati di addestramento artificiale mediante la manipolazione dei dati originali. Ad esempio, si potrebbe applicare una trasformazione geometrica, come lo spostamento, la rotazione o l’inversione, alle immagini per aumentarne la varietà. La data augmentation può aiutare a espandere il set di dati di addestramento, ridurre l’overfitting e migliorare le prestazioni del modello.
- Dropout probabilistico: Questa tecnica estende il concetto di dropout introducendo un’incertezza probabilistica durante la fase di inferenza del modello. Invece di disattivare casualmente i neuroni, il dropout probabilistico assegna ad ogni neurone un valore di output ponderato nelle inferenze diverse, producendo una distribuzione probabilistica di output. Questo aiuta a gestire l’incertezza nei dati e può essere utile in applicazioni come il riconoscimento del parlato o la classificazione degli oggetti.
Dal punto di vista matematico, usare tecniche di dropout e di regolarizzazione L1 ed L2 equivale ad introdurre del “rumore additivo” negli input. La dimostrazione rigorosa la puoi trovare consultando il testo di Haykin: Neural Networks and Learning Machines
Il rumore additivo viene solitamente generato da una distribuzione casuale, come ad esempio la distribuzione gaussiana o normale. Questa distribuzione è caratterizzata da una media e una deviazione standard. Il rumore viene estratto da tale distribuzione e sommato a ciascun dato di addestramento.
Matematicamente, l’aggiunta di rumore additivo ai dati di addestramento può essere rappresentata come:
xwith_noise = x + vnoise
dove x è il dato di input senza rumore

È importante notare che l’aggiunta di rumore additivo non è sempre necessaria o utile. Dipende dal particolare problema e dalle caratteristiche dei dati di addestramento. È consigliabile eseguire esperimenti per valutare l’impatto del rumore aggiuntivo e determinare se sia appropriato per il proprio caso di studio.
In conclusione si è visto che la controparte artificiale della componente d’incertezza presente a livello sinaptico in relazione all’attivazione o meno del neurone, è rappresentata dalle tecniche di regolarizzazione che matematicamente si traducono nell’introdurre un termine aggiuntivo nella funzione di perdita o equivalentemente nell’introduzione di rumore additivo al livello dell’input. Questo rumore è per sua natura casuale ed è definito mediante la nota distribuzione normale.
Questo aspetto è molto interessante ed istruttivo perché ci convince del fatto che la casualità ha un ruolo importante in natura e ancor di più nell’ambito dei processi di apprendimento in cui è necessario generalizzare. In altre parole, possiamo dire che è necessaria un po’ di “fantasia” nell’apprendimento affinché funzioni davvero.
Esempio di addestramento di una rete neurale con e senza rumore additivo
Consideriamo un insieme di dati di addestramento (tabella) come questa

Segue il codice Python d’esempio che può essere usato per valutare se sia utile o meno applicare il rumore additivo a certi dati di addestramento. Infatti questo algoritmo addestra due volte la stessa rete neurale, prima con input senza rumore additivo, poi con input con rumore additivo. In entrambi i casi effettua un test e valuta il modello usando come funzione di perdita (loss) l’errore quadratico medio (mean squared error) come metodo di valutazione.
Se Loss con rumore risulta inferiore a Loss senza rumore allora l’aver introdotto il ruomore addittivo ha reso più efficiente il modello.
import numpy as np
import tensorflow as tf
from sklearn.model_selection import train_test_split
import pandas as pd
# Leggi i dati da un file CSV
data = pd.read_csv('dati.csv')
y = data['diabete']
X_without_noise = data.drop('diabete', axis=1)
# Creazione del set di dati di addestramento e test
X_train, X_test, y_train, y_test = train_test_split(X_without_noise, y, test_size=0.2, random_state=42)
# Definizione e addestramento di una semplice rete neurale senza rumore con batching
model_without_noise = tf.keras.Sequential([
tf.keras.layers.Dense(16, activation='relu', input_shape=(X_train.shape[1],)), # Utilizza il numero di colonne di X_train come input_shape
tf.keras.layers.Dense(8, activation='relu'),
tf.keras.layers.Dense(1)
])
model_without_noise.compile(optimizer='adam', loss='mean_squared_error')
batch_size = 32
num_batches = len(X_train) // batch_size
for epoch in range(10):
for batch in range(num_batches):
start = batch * batch_size
end = (batch + 1) * batch_size
X_batch = X_train[start:end]
y_batch = y_train[start:end]
model_without_noise.train_on_batch(X_batch, y_batch)
# Valutazione delle previsioni senza rumore
loss_without_noise = model_without_noise.evaluate(X_test, y_test)
print("Loss senza rumore:", loss_without_noise)
# Aggiunta di rumore ai dati di input
X_with_noise = X_without_noise + np.random.normal(0, 0.1, X_without_noise.shape)
# Creazione del set di dati di addestramento e test con rumore
X_train_noise, X_test_noise, _, _ = train_test_split(X_with_noise, y, test_size=0.2, random_state=42)
# Definizione e addestramento della stessa rete neurale con rumore in input
model_with_noise = tf.keras.Sequential([
tf.keras.layers.Dense(16, activation='relu', input_shape=(X_train.shape[1],)), # Utilizza il numero di colonne di X_train come input_shape
tf.keras.layers.Dense(8, activation='relu'),
tf.keras.layers.Dense(1)
])
model_with_noise.compile(optimizer='adam', loss='mean_squared_error')
num_batches = len(X_train_noise) // batch_size
for epoch in range(10):
for batch in range(num_batches):
start = batch * batch_size
end = (batch + 1) * batch_size
X_batch = X_train_noise[start:end]
y_batch = y_train[start:end]
model_with_noise.train_on_batch(X_batch, y_batch)
# Valutazione delle previsioni con rumore
loss_with_noise = model_with_noise.evaluate(X_test_noise, y_test)
print("Loss con rumore:", loss_with_noise)
Il codice in questione effettua le seguenti operazioni:
- Importa i moduli necessari:
numpy
comenp
per operazioni matematichetensorflow
cometf
per la creazione e addestramento di reti neuralitrain_test_split
dal modulomodel_selection
disklearn
per suddividere i dati in set di addestramento e testpandas
comepd
per la gestione dei dati tabulari
- Legge i dati da un file CSV chiamato “dati.csv” utilizzando il modulo
pd
e assegna i valori della colonna “diabete” alla variabiley
e il resto dei dati alla variabileX_without_noise
. - Crea i set di dati di addestramento e test utilizzando la funzione
train_test_split
disklearn
sulla variabileX_without_noise
e la variabiley
. Imposta la dimensione del test set al 20% e fissa il seed casuale a 42 per riproducibilità. I set di addestramento e test vengono assegnati alle variabiliX_train
,X_test
,y_train
ey_test
rispettivamente. - Definisce e addestra un modello di rete neurale senza rumore usando
tensorflow
. Il modello è composto da tre strati (due strati densi con funzione di attivazione ReLU e uno strato di output senza funzione di attivazione) definiti tramiteSequential
. L’input_shape del primo strato è impostato sul numero di colonne dei dati di addestramento (X_train.shape[1]
). Il modello viene compilato con l’ottimizzatore Adam e la funzione di loss “mean_squared_error”. Successivamente, il modello viene addestrato utilizzando il batch gradient descent suddividendo i dati di addestramento in mini-batch. Vengono eseguite 10 epoche di addestramento sui mini-batch. - Valuta il modello senza rumore sui dati di test utilizzando il metodo
evaluate
sul modellomodel_without_noise
. La funzione di loss viene assegnata alla variabileloss_without_noise
e viene stampata a schermo. - Aggiunge rumore ai dati di input calcolando la somma fra
X_without_noise
e un array di numeri casuali generati dalla distribuzione normale con media 0 e deviazione standard 0.1 utilizzandonp.random.normal
. Il risultato viene assegnato alla variabileX_with_noise
. - Crea i nuovi set di dati di addestramento e test utilizzando
train_test_split
sulla variabileX_with_noise
e la variabiley
, senza interessarsi dei set di addestramento e test generati, quindi si utilizzano due variabili nulle_
al posto di assegnarle. - Definisce e addestra un nuovo modello di rete neurale con rumore in input utilizzando la stessa architettura e procedura di addestramento del modello senza rumore.
- Valuta il modello con rumore sui dati di test con rumore utilizzando il metodo
evaluate
sul modellomodel_with_noise
. La funzione di loss viene assegnata alla variabileloss_with_noise
e viene stampata a schermo.