ReLU (Rectified Linear Unit)
ReLU (unità lineare rettificata) restituisce max(0, x), lasciando passare gli input positivi e azzerando quelli negativi. Include gradiente, ReLU morente, varianti e codice PyTorch.
L'unità lineare rettificata (ReLU) è la funzione di attivazione f(x) = max(0, x): lascia invariati gli input positivi e restituisce zero per tutti gli altri. Questo semplice confronto fornisce a una rete neurale (NN) la non linearità necessaria per apprendere schemi curvi, con un costo computazionale pressoché nullo. ReLU è diventata la funzione di attivazione predefinita per gli strati nascosti nel deep learning, dopo il suo utilizzo da parte di Vinod Nair e Geoffrey Hinton nel 2010 e la vittoria, nella sfida ImageNet del 2012, di AlexNet, una rete neurale convoluzionale (CNN) basata su ReLU. Resta il riferimento con cui si confrontano funzioni di attivazione più recenti come SiLU e GELU.
Come funziona ReLU#
La derivata di ReLU è 1 per gli input positivi e 0 per quelli negativi. Esattamente a zero la derivata non è definita e framework come PyTorch usano 0. Questi due valori spiegano le proprietà principali di ReLU:
- Calcolo economico: il passaggio forward è un confronto con zero e quello backward è una maschera, senza esponenziali come in sigmoid o tanh.
- Nessuna saturazione per gli input positivi: un gradiente esattamente pari a 1 attraversa inalterato ogni unità attiva, quindi i segnali di errore non si riducono mentre risalgono molti strati; ciò contrasta il problema del gradiente evanescente causato dalle funzioni saturanti.
- Attivazioni sparse: ogni input negativo diventa esattamente zero, perciò in un dato momento è attiva solo una parte dello strato. Con pesi inizializzati casualmente, circa metà delle unità di uno strato restituisce zero per un dato input.
- Lineare a tratti: una rete costruita con ReLU calcola una funzione lineare a tratti dell'input, con molte regioni lineari che si uniscono nei punti in cui le unità si attivano o si disattivano.
L'esempio PyTorch qui sotto applica nn.ReLU e usa autograd per calcolare il gradiente negli stessi punti:
import torch
from torch import nn
relu = nn.ReLU()
# Inputs on both sides of zero, tracked so PyTorch can compute gradients
x = torch.tensor([-2.0, -0.5, 0.0, 0.5, 2.0], requires_grad=True)
y = relu(x)
# Backpropagate a gradient of 1 from each output to its input
y.sum().backward()
print(f"Output: {y.tolist()}")
print(f"Gradient: {x.grad.tolist()}")Stampa i risultati [0.0, 0.0, 0.0, 0.5, 2.0] e i gradienti [0.0, 0.0, 0.0, 1.0, 1.0]. Gli input positivi passano senza alterare il gradiente; gli input negativi e lo zero non ricevono alcun gradiente, origine sia della sparsità di ReLU sia del suo principale modo di fallimento.
Il problema delle ReLU inattive#
Un'unità ReLU il cui input è negativo per ogni esempio nei dati di addestramento restituisce zero e riceve un gradiente nullo; i suoi pesi non cambiano più e l'unità è di fatto morta. In genere le unità muoiono quando un aggiornamento ampio dei pesi, spesso dovuto a un tasso di apprendimento elevato, spinge il bias molto al di sotto dello zero. Poche unità morte hanno un impatto ridotto, mentre un addestramento mal configurato può uccidere una parte consistente di uno strato. L'inizializzazione He, che scala i pesi iniziali per gli strati ReLU, insieme a tassi di apprendimento moderati e alla normalizzazione dei batch, mantiene attive la maggior parte delle unità; le varianti leaky eliminano il problema impedendo al gradiente di azzerarsi.
Varianti di ReLU#
- Leaky ReLU: moltiplica gli input negativi per una piccola pendenza fissa, in genere 0,01, così anche le unità inattive ricevono un gradiente.
- PReLU: apprende la pendenza negativa durante l'addestramento. Kaiming He e i colleghi la introdussero nel 2015, nello stesso articolo in cui proposero l'inizializzazione He.
- ReLU6: limita l'output a 6, min(max(0, x), 6), mantenendo le attivazioni in un intervallo rappresentato bene dall'aritmetica a precisione ridotta. La usano MobileNetV1 e MobileNetV2.
- ELU: sostituisce il lato negativo con una curva esponenziale regolare che satura a −1, avvicinando a zero la media delle attivazioni.
- SiLU e GELU: curve regolari, x · σ(x) e x · Φ(x), che si comportano come ReLU per input grandi, ma lasciano passare valori negativi piccoli e non presentano discontinuità angolari a zero. Hanno un costo maggiore per elemento; l'articolo su GELU ha riportato miglioramenti rispetto a ReLU in tutte le attività di visione, linguaggio e riconoscimento vocale valutate.
ReLU, sigmoid e tanh a confronto#
Prima di ReLU, gli strati nascosti usavano sigmoid o tanh. Entrambe saturano: per input molto positivi o negativi le loro curve si appiattiscono e le derivate si avvicinano a zero; inoltre, la derivata di sigmoid non supera mai 0,25, nemmeno nel punto di massima pendenza. Moltiplicare queste derivate attraverso molti strati priva gli strati iniziali del gradiente. Nell'articolo su AlexNet, una CNN a quattro strati con ReLU ha raggiunto un errore di addestramento del 25% su CIFAR-10 sei volte più rapidamente della stessa rete con unità tanh. Sigmoid e tanh restano utili quando servono i loro intervalli limitati: sigmoid negli strati di output per le probabilità e nei gate delle cellule ricorrenti, tanh negli stati nascosti ricorrenti.
ReLU nei modelli moderni#
ResNet e la maggior parte delle CNN della sua epoca usano ReLU in tutti gli strati; il Transformer originale del 2017 la usava negli strati feedforward. Molti modelli più recenti sono passati a funzioni più regolari: GELU in BERT e nei transformer per la visione, e SwiGLU in molti modelli linguistici di grandi dimensioni attuali; ReLU resta diffusa dove velocità e supporto hardware sono prioritari. YOLO26 usa SiLU per impostazione predefinita nei suoi blocchi Conv; una riga activation: nn.ReLU() nel file YAML del modello sostituisce ReLU a SiLU in ogni blocco Conv prima dell'addestramento, soluzione adatta ad acceleratori come la AMD Zynq UltraScale+ DPU, che esegue ReLU nativamente ma non SiLU. In PyTorch, nn.ReLU(inplace=True) sovrascrive il tensore di input con il risultato, risparmiando la memoria necessaria per un tensore di output separato.










