Vai al contenuto principale

Reti neurali feed-forward spiegate: un tutorial completo

Le Feed-Forward Neural Networks (FFNN) sono le fondamenta del deep learning, usate nel riconoscimento di immagini, nei Transformer e nei sistemi di raccomandazione. Questo tutorial completo sulle FFNN ne spiega l’architettura, le differenze rispetto alle MLP, le attivazioni, il backpropagation, esempi reali e l’implementazione in PyTorch.
· 11 min leggi

Scopri con l'IA

Quando si parla di algoritmi di machine learning, ci sono pochissimi concetti fondamentali quanto le Feed-Forward Neural Network (FFNN). Se hai mai costruito la tua prima rete neurale, è molto probabile che fosse di tipo feed-forward. Le trovi quasi ovunque: dai semplici problemi di classificazione all’alimentare i layer di architetture profonde. 

Per prima cosa, ti mostro dove si collocano le Feed-Forward Neural Network nel quadro generale:

Il diagramma mostra dove si trovano le Feed-Forward Neural Network sotto l’ombrello dell’IA. Immagine dell’autore

In questo tutorial ti spiegherò cosa sono davvero le Feed-Forward Neural Network, come si sono evolute e perché sono ancora rilevanti oggi, oltre a esplorare esempi reali.

Che cos’è una Feed-Forward Neural Network?

Intuitivamente, il modo migliore per descrivere questa architettura è “I dati scorrono solo in avanti, niente cicli”.

Alla base, una FFNN elabora i dati in un’unica direzione, dall’input all’output. Non ci sono retroazioni, né ricorsione, né cicli (escludendo il backpropagation, di cui parleremo tra poco). 

Quando ho iniziato a studiare l’argomento, l’immagine mentale che mi ha aiutato di più è stata un nastro trasportatore in fabbrica. 

Perché? 

Perché ogni passaggio del processo (o ogni layer della rete) fa qualcosa di semplice all’input prima di passarla a quello successivo.

Ecco come funziona:

  1. Gli input entrano nel primo layer (l’input layer).
  2. Vengono trasformati da pesi e bias e passati attraverso una funzione di attivazione.
  3. Questo continua attraverso uno o più layer nascosti.
  4. Infine, il layer di output produce una previsione.

Prima di mostrarti come appare un’architettura del genere, voglio chiarire un’idea sbagliata piuttosto comune:

“Le Feed-Forward Neural Network sono le stesse delle MLP.”

Per chiarire questo equivoco, dobbiamo esplorare la storia iniziale del Deep Learning. 

La storia iniziale del deep learning

Perceptron: partiamo dal Perceptron, inventato negli anni ’50 da Frank Rosenblatt. Era un classificatore binario a singolo layer e, pur non potendo risolvere tutto (come i problemi XOR), ha posto le basi per le reti neurali.

In parole semplici, un perceptron funzionava così:

  1. Input: ogni input (ad esempio x_1, x_2, ecc.) ha un peso associato (w_1, w_2, ecc.).
  2. Somma pesata: gli input vengono moltiplicati per i loro pesi e sommati insieme, più un termine di bias: z = w1*x1 + w2*x2+ ... + b.
  3. Attivazione: il risultato passa attraverso una funzione a gradino (un’attivazione). Se il valore supera una soglia, il perceptron restituisce 1; altrimenti 0.

La figura mostra il funzionamento interno di un singolo Perceptron. Immagine dell’autore.

In sostanza, il perceptron è un classificatore lineare, cioè poteva tracciare solo frontiere di decisione rettilinee (o iperpiani). Da un lato, il Perceptron aveva i suoi meriti, essendo molto semplice ed elegante (poiché ispirato alla biologia) oltre che computazionalmente economico. Dall’altro, non poteva risolvere problemi non linearmente separabili, come l’XOR. 

Multi-Layer Perceptron (MLP): avanzando di qualche decennio, negli anni ’80 i ricercatori capirono che impilando più perceptron e aggiungendo funzioni di attivazione non lineari si potevano risolvere problemi più complessi. Questa struttura è diventata nota come Multi-Layer Perceptron.

Ecco come funzionava: 

  1. Input layer: riceveva le feature grezze dal dataset.
  2. Hidden layer: uno o più layer di perceptron che applicano trasformazioni. Ogni neurone eseguiva z = w*x + b e lo passava attraverso una funzione di attivazione non lineare come sigmoid, tanh, ecc. 
  3. Output layer: il layer finale produceva la previsione (es. probabilità di classe, valori di regressione).

L’aggiunta di queste attivazioni non lineari è stata cruciale. Senza di esse, impilare layer si ridurrebbe a una singola trasformazione lineare. Con esse, le MLP possono rappresentare funzioni altamente complesse e non lineari.

Questo ha portato a uno dei risultati più famosi nella teoria delle reti neurali: il Teorema di approssimazione universale. 

Frontiera lineare vs non lineare. Immagine dell’autore.

Questo teorema afferma che una rete neurale anche con un solo layer nascosto — purché abbia una funzione di attivazione non lineare e un numero sufficiente di neuroni — può approssimare qualsiasi funzione continua su un dominio limitato. 

Era del Deep Learning: arrivando agli anni 2010, entriamo nell’era del deep learning. Con GPU e big data, le FFNN si sono evolute in architetture più profonde e potenti, diventando la base di CNN, RNN e Transformer, cioè dove ci troviamo sostanzialmente oggi.

Tornando all’idea sbagliata iniziale, ora sappiamo che:

  • Feed-Forward Neural Network (FFNN): categoria generale di qualsiasi rete neurale in cui i dati scorrono in un’unica direzione (input → hidden → output), senza cicli.
  • Multi-Layer Perceptron (MLP): un tipo di FFN che ha specificamente almeno un layer nascosto di perceptron (neuroni con pesi, bias e attivazione).

In altre parole, tutte le MLP sono FFNN, ma non tutte le FFNN sono MLP. Questo è un punto molto importante da ricordare. Un altro equivoco comune riguarda il perceptron a singolo layer (cioè solo input → output).

Un perceptron a singolo layer è una FFNN, ma non una MLP! Diventa una MLP solo quando aggiungiamo layer nascosti.

Questa distinzione è importante perché le MLP sono in grado di approssimazione universale delle funzioni (con abbastanza unità nascoste e funzioni di attivazione non lineari), mentre i semplici perceptron a singolo layer sono limitati in ciò che possono rappresentare.

Architettura di una Feed-Forward Neural Network

Dalla sezione precedente abbiamo imparato molto sulle FFNN, ma in sintesi si possono pensare come una pila di trasformazioni semplici. Ci sono molti componenti che compongono una FFNN, quindi esploriamoli più in dettaglio, in particolare la struttura MLP. 

In una MLP ci sono tre tipi di layer: Input, Hidden e Output layer, che abbiamo menzionato brevemente prima. 

Architettura di una Feed-Forward Neural Network completamente connessa (MLP). Immagine dell’autore.

Dai un’occhiata al diagramma sopra mentre esploriamo questi layer più nel dettaglio. 

Input layer

Questo è il punto d’ingresso della rete.  Ogni neurone qui rappresenta una feature del dataset. Importante: l’input layer non esegue alcun calcolo — si limita a inoltrare i numeri grezzi. Nel nostro esempio, poiché ci sono solo due neuroni nell’Input layer, significa che il nostro dataset contiene due feature (o che stiamo considerando solo due feature dal nostro dataset). 

Hidden layer

Questi sono dove avviene il vero calcolo. Abbiamo due Hidden layer, ciascuno con quattro neuroni. Ogni neurone in un hidden layer fa tre cose:

  1. Somma pesata: ogni input è moltiplicato per un peso e poi tutti vengono sommati. 
  2. Bias: si aggiunge un bias costante per dare flessibilità nello spostare la funzione.
  3. Funzione di attivazione: si applica una funzione non lineare come ReLU, tanh o sigmoid. Questo passaggio permette alla rete di apprendere pattern complessi e non lineari.

Output layer

L’output layer produce la previsione finale e la sua progettazione dipende dal problema che stiamo cercando di risolvere:

  • Regressione come prevedere i prezzi delle case. In questo caso, l’output layer conterrà un neurone senza attivazione (lineare). Perché lineare? Perché non vogliamo “schiacciare” i valori: i prezzi delle case possono essere qualsiasi numero reale.
  • Classificazione binaria (decisioni sì/no), come prevedere se un’email è spam o no. Qui l’output layer avrà un neurone con attivazione sigmoid. Perché sigmoid? Perché vogliamo che la rete restituisca valori tra 0 e 1 (dato che stiamo prevedendo probabilità). 
  • Classificazione multiclasse (3 o più classi), ad esempio classificare un’immagine tra diversi animali. L’output layer avrà un neurone per classe, con attivazione softmax. Perché softmax? Perché trasforma i punteggi grezzi in probabilità che sommano a 1 (es. 70% gatto, 20% cane, 10% cavallo).

Per riferimento, ecco un diagramma con nove forme comuni di funzioni di attivazione usate nelle FFNN e nel Deep Learning in generale. 

Visualizzazione delle funzioni di attivazione comuni. Immagine dell’autore.

Prima di proseguire, è importante notare che ogni neurone in un layer si collega a ogni neurone del layer successivo (ecco perché si parla di completamente connessa). 

Tornando al nostro esempio, abbiamo:

Parametri = (2⋅4+4)+(4⋅4+4)+(4⋅1+1)=12+20+5=37

Ogni parentesi mostra il numero di parametri tra layer consecutivi, dove si moltiplicano il numero di neuroni del layer precedente e di quello attuale, e poi si aggiunge il numero di bias (cioè il numero di neuroni nel layer attuale). Prova a fare questi calcoli anche su altri esempi di reti neurali per esercizio!

Addestrare una rete neurale feed-forward

Nel Deep Learning, l’addestramento si divide in due fasi: forward pass/propagation e backpropagation. In parole semplici, la forward propagation ci dà le previsioni, mentre il backpropagation è il modo in cui impariamo dagli errori. 

Forward propagation 

Tutto ciò che abbiamo trattato finora rientra nella forward propagation. In sintesi, quando i dati attraversano una MLP, ogni layer esegue gli stessi due passaggi, che possiamo scrivere matematicamente con queste equazioni:

Qui z è l’output dopo aver moltiplicato l’output del layer precedente per i pesi del layer corrente e sommato il bias, anche noto come passaggio lineare. 

Naturalmente, se il layer precedente è l’input layer, allora sarebbe x invece di al-1. Il passaggio successivo è il passaggio di attivazione. Per essere più rigorosi matematicamente, possiamo scrivere pesi e bias così:

Backpropagation

Quando otteniamo un output, come facciamo a dire alla rete se è corretto o no? O meglio ancora, come facciamo a migliorare la rete nel compito dato? Qui entra in gioco il backpropagation. Possiamo vederlo come il sistema di feedback della rete: dice al modello quanto era lontano e come aggiustarsi. Possiamo scomporlo così:

  1. Confronta le previsioni: osserva cosa ha previsto il modello (ŷ) e qual era la risposta reale (y).
  2. Calcola l’errore: usiamo una funzione di perdita per misurare quanto la previsione fosse sbagliata, come la MSE.
  3. Backpropagate (via regola della catena): si calcolano i gradienti rispetto a tutti i bias e ai pesi. ∂L/∂w,∂L/∂b .
  4. Aggiornamento con Gradient Descent: i relativi parametri vengono aggiornati per ridurre la loss usando la regola seguente:

Qui η è il learning rate, un parametro che può essere modificato per determinare di quanto devono cambiare i parametri a ogni aggiornamento. 

Visualizzazione del Gradient Descent - mostra come la funzione di perdita diminuisce dopo ogni epoca. Immagine dell’autore.

Dopo molti cicli di questo processo, la rete impara gradualmente e migliora nel compito.

Per chiarire ulteriormente, percorriamo insieme un piccolo esempio. Supponiamo di avere:

  • Input: x = 2
  • Output target: y=4
  • Previsione: ŷ = 3

Ora seguiamo i passaggi. 

  1. Confronto della previsione: ŷ =3, y = 4
  2. Errore: per questo esempio, useremo la MSE: L=(y−y^​)2=(4−3)2=1
  3. Backpropagation: la rete capisce quanto ciascun peso e bias ha contribuito a questo errore usando le derivate e poi aggiorniamo ciascun parametro con lo step di aggiornamento del gradient descent. 

Codificare una Feed Forward Neural Network in PyTorch

Abbiamo coperto la matematica necessaria per costruire una FFNN (in particolare una MLP). Per proseguire, la coderemo in PyTorch. 

Multilayer perceptron

# Imports we will be needing
import torch
import torch.nn as nn
import torch.optim as optim

X,y = dataset # Here will be our dataset 

# We have created a MLP here using nn.Sequential()
model = nn.Sequential(
    nn.Linear(2, 4),  # input layer → hidden layer (2 → 4)
    nn.ReLU(),        # relu activation function
    nn.Linear(4, 4), # hidden layer 1 → hidden layer 2 (4 → 4)
    nn.ReLU(), 
    nn.Linear(4, 1)   # hidden layer 2 → output (4 → 1)
)


criterion = nn.MSELoss()         # regression loss, i.e our loss function
optimizer = optim.SGD(model.parameters(), lr=0.1) # Optimizer using the Stochastic Gradient Descent

# Training Loop
EPOCHS = 200 # Number of epochs we will be training for
for epoch in range(EPOCHS):
    # Forward pass
    outputs = model(X)
    loss = criterion(outputs, y)

    # Backward pass
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

A questo punto dovresti riuscire a collegare il codice PyTorch ai passaggi matematici visti prima. Tuttavia, potresti avere una domanda sulle epoche.

Un’epoca è un passaggio completo di tutto il dataset di addestramento attraverso la rete neurale. Ad esempio, supponiamo di avere 1.000 esempi di training e batch size = 100, allora dopo che la rete ha visto tutte le 1.000 immagini (cioè 10 batch) è passata 1 epoca. L’addestramento richiede di solito molte epoche così che il modello possa continuare a migliorare i propri pesi.

Convolutional neural network

Voglio menzionare brevemente anche altre FFNN, una molto famosa è la Convolutional Neural Network (CNN). 

Diagramma che mostra una Convolutional Neural Network. Immagine tratta da Mathworks - https://www.mathworks.com/discovery/convolutional-neural-network.html

Sebbene una CNN contenga anche una MLP alla fine, all’inizio include layer speciali chiamati Convolutional layer e Pooling layer. Parlando dei Convolutional layer, sono essenziali perché osservano solo piccole regioni locali dell’input alla volta usando un filtro (o kernel).

Questo è davvero utile nella classificazione di immagini perché:

  • I layer densi tratterebbero ogni pixel come separato, ignorando la struttura spaziale.
  • I Convolutional layer, invece, possono rilevare bordi, texture e forme in piccole porzioni — per poi combinarle in pattern più complessi man mano che si va in profondità.

Insieme ai convolutional layer, le CNN usano anche i pooling layer.

È importante notare che i Pooling layer non apprendono parametri, ma riducono (downsample) le mappe di feature per renderle più piccole e gestibili.

  • Il più comune è il Max Pooling, che prende il valore massimo da ogni piccola regione (es. una finestra 2×2).
  • Il pooling rende la rete più efficiente e conferisce anche invarianza alla traslazione (alla rete interessa più che una feature esista, non esattamente dove sia).

Possiamo codificare una CNN in PyTorch in questo modo:

class SimpleCNN(nn.Module): # Define our model as a class
    def __init__(self):
        super().__init__()
        self.conv = nn.Conv2d(1, 8, 3)     # 1→8 channels, 3x3 kernel
        self.pool = nn.MaxPool2d(2, 2)
        self.fc   = nn.Linear(8*13*13, 10) # flatten → 10 classes

    def forward(self, x): # Forward propagation function 
        x = self.pool(torch.relu(self.conv(x)))
        x = x.view(x.size(0), -1)          # flatten
        return self.fc(x)

Nel codice sopra abbiamo creato una CNN molto semplice, costituita da un singolo layer di convoluzione, un singolo layer di pooling e un singolo layer lineare. 

Altri esempi di reti neurali feed-forward

Abbiamo trattato due esempi molto famosi e importanti di FFNN, ma ce ne sono molti altri che sono stati rivoluzionari nei rispettivi domini, come:

  1. Transformer: ogni blocco contiene una parte MLP/FFN (dopo il self-attention, il "feed-forward layer" espande + riduce le dimensioni).
  2. Vision Transformer (ViT): applicano blocchi Transformer (con FFNN al loro interno) a patch di immagine.
  3. Autoencoder (di base): encoder + decoder costruiti entrambi con FFNN.
  4. Sistemi di raccomandazione (basati su MLP): es. modelli Wide & Deep per ranking e previsioni.

Architettura del Transformer. Immagine tratta dall’articolo originale di ricerca - Attention is all you need.

Considerazioni finali

Spero che tu abbia capito quanto siano importanti le FFNN nel campo dell’IA. Senza di esse, il panorama moderno del deep learning non esisterebbe.

Per proseguire ti consiglierei caldamente di padroneggiare il backpropagation e approfondire le funzioni di attivazione. Dato che questa è la base del Deep Learning, ti consiglierei anche di studiare Deep Learning con PyTorch.

Domande frequenti sulle Feed-Forward Neural Network

Le FFNN e le MLP sono la stessa cosa?

No, è un grosso equivoco! Una FFNN significa semplicemente che i dati scorrono in avanti. Una MLP è un tipo di FFNN con almeno un layer nascosto.

Perché usiamo le funzioni di attivazione?

Aggiungono non linearità. Senza di esse, più layer si comportano come un unico grande layer lineare e la rete non può imparare pattern complessi.

Dove sono usate le FFNN nella vita reale?

Ovunque: sistemi di raccomandazione, classificatori di immagini (CNN), Transformer e autoencoder si basano tutti su di esse.

Come posso codificare una FFNN in PyTorch?

Possiamo impilare layer nn.Linear, aggiungere attivazioni come nn.ReLU, scegliere una funzione di loss e un ottimizzatore e addestrare con passaggi forward + backward.

Che cos’è il backpropagation?

In una frase: è il modo in cui una rete impara — confrontando previsioni e target, misurando l’errore e aggiornando i pesi con il gradient descent.

Vaibhav Mehra

Autore

Vaibhav Mehra

Argomenti

Correlato