Recurrent Neural Network (RNN)
Una rete neurale ricorrente (RNN) legge una sequenza un passaggio alla volta, mantenendo uno stato nascosto. Include ricorrenza, addestramento, varianti e codice.
Una rete neurale ricorrente (RNN) è una rete neurale che elabora una sequenza un elemento alla volta, mantenendo uno stato nascosto da un passaggio al successivo; così, l'output di ogni passaggio può dipendere da tutto ciò che la rete ha letto fino a quel momento. Gli stessi pesi vengono applicati a ogni passaggio, consentendo a una rete di gestire sequenze di qualsiasi lunghezza, da una frase a ore di letture dei sensori. Le RNN sono state l'architettura principale per l'elaborazione del linguaggio naturale (NLP), il riconoscimento vocale e l'analisi delle serie temporali fino a quando i Transformer le hanno sostituite nella maggior parte delle attività linguistiche, verso la fine degli anni 2010. Le loro varianti con gate sono ancora comuni nei modelli in streaming e nei modelli eseguiti sul dispositivo; inoltre, la ricorrenza è tornata nei modelli a spazio di stato (SSMs), come Mamba.
Come funziona una RNN#
Una RNN semplice, detta anche rete di Elman dal nome dell'articolo di Jeffrey Elman del 1990, aggiorna lo stato nascosto secondo hₜ = tanh(Wxₜ + Uhₜ₋₁ + b) e può generare un output yₜ = Vhₜ + c a ogni passaggio. Lo stato nascosto è un vettore di dimensione fissa che riassume la sequenza elaborata finora; tanh mantiene i suoi valori tra −1 e 1 mentre viene reinserito nella rete. Rappresentato con una copia della rete per ogni passaggio, il ciclo si srotola in una rete feedforward profonda i cui strati condividono gli stessi pesi:
Lo snippet qui sotto applica PyTorch's nn.RNN a una sequenza e ripete la stessa ricorrenza con un semplice ciclo:
import torch
from torch import nn
torch.manual_seed(0)
rnn = nn.RNN(input_size=3, hidden_size=4, batch_first=True)
x = torch.randn(1, 6, 3) # one sequence: 6 time steps, 3 features each
# PyTorch runs the whole sequence; output holds the hidden state after every step
output, h_n = rnn(x)
# The same recurrence by hand, reusing one set of weights at every step
h = torch.zeros(1, 4)
for t in range(x.shape[1]):
h = torch.tanh(x[:, t] @ rnn.weight_ih_l0.T + rnn.bias_ih_l0 + h @ rnn.weight_hh_l0.T + rnn.bias_hh_l0)
print(f"Output shape: {tuple(output.shape)}, final state shape: {tuple(h_n.shape)}")
print(f"Hand-written loop matches nn.RNN: {torch.allclose(h, h_n[0], atol=1e-6)}")Stampa Output shape: (1, 6, 4), final state shape: (1, 1, 4) e Hand-written loop matches nn.RNN: True. L'output contiene uno stato nascosto a 4 dimensioni per ogni passaggio temporale e il ciclo riproduce lo stato finale usando una sola matrice di pesi per l'input e una per lo stato precedente.
Attività sulle sequenze#
La stessa ricorrenza si adatta ad attività con lunghezze diverse di input e output:
- Molti a uno: legge un'intera sequenza e la classifica in base allo stato finale, come nell'analisi del sentiment o nell'assegnazione di etichette a una finestra di dati dei sensori.
- Molti a molti: genera un output a ogni passaggio, ad esempio assegnando un'etichetta a ogni parola di una frase o a ogni fotogramma audio.
- Da sequenza a sequenza: una RNN encoder legge l'input e una RNN decoder genera un output di lunghezza diversa; è l'architettura dei primi modelli di traduzione automatica neurale e di altri modelli da sequenza a sequenza.
- Uno a molti: genera una sequenza da un singolo input, ad esempio una didascalia a partire dal vettore di caratteristiche di un'immagine nei primi modelli di generazione di didascalie per immagini.
Le RNN bidirezionali aggiungono una seconda RNN che legge la sequenza al contrario e combinano i due stati a ogni passaggio, così ogni output tiene conto del contesto passato e futuro. Le RNN impilate alimentano gli stati nascosti di uno strato allo strato successivo come sequenza di input.
Addestramento con backpropagation through time#
Le RNN vengono addestrate con la backpropagation through time (BPTT): la rete viene srotolata lungo la sequenza, la loss viene sommata sui passaggi e la backpropagation attraversa il grafo srotolato. Il gradiente che arriva al passaggio t dal passaggio T attraversa T − t copie dello strato ricorrente e viene moltiplicato per U e per la derivata di tanh a ogni passaggio. Se questi fattori sono perlopiù inferiori a 1, il gradiente si riduce geometricamente: è il problema del gradiente evanescente, analizzato nel 1994 da Yoshua Bengio, Patrice Simard e Paolo Frasconi. Se sono maggiori di 1, cresce geometricamente: è il problema del gradiente esplosivo. Lo snippet qui sotto misura quanto ciascun input di una sequenza di 50 passaggi influisce sullo stato nascosto finale:
import torch
from torch import nn
torch.manual_seed(0)
rnn = nn.RNN(input_size=8, hidden_size=32, batch_first=True)
x = torch.randn(1, 50, 8, requires_grad=True) # one 50-step sequence
# Backpropagate from the final hidden state to the input at every step
output, h_n = rnn(x)
h_n.sum().backward()
grad_norms = x.grad[0].norm(dim=1)
for distance in [0, 10, 20, 30, 40]:
print(f"Input {distance:>2} steps before the end: gradient {grad_norms[-1 - distance]:.1e}")Stampa i gradienti di 1.6e+00, 3.4e-03, 6.2e-06, 1.2e-08 e 1.1e-11 per gli input a 0, 10, 20, 30 e 40 passi dalla fine. Ogni dieci passi il costo aumenta di circa tre ordini di grandezza, quindi durante l'addestramento i pesi riescono a malapena ad adattarsi per usare un input distante qualche decina di passi. Il gradient clipping, analizzato per le RNN da Razvan Pascanu e colleghi nel 2013, limita i gradienti esplosivi, mentre la BPTT troncata limita la retropropagazione a una finestra fissa per contenere l'uso di memoria. Per risolvere il problema dei gradienti evanescenti serve una modifica dell'architettura, fornita dai gate di LSTM e GRU.
RNN e architetture correlate#
- LSTM e GRU: RNN con gate e aggiornamento additivo dello stato, che consentono a informazioni e gradienti di passare attraverso molti passi. In pratica, "RNN" spesso indica anche una di queste architetture, oltre alla RNN semplice.
- Transformer: presta attenzione direttamente a ogni posizione precedente invece di comprimere la cronologia in un unico stato, e si addestra in parallelo su tutte le posizioni. Il costo dell'attenzione aumenta con la lunghezza della sequenza, mentre una RNN usa la stessa quantità di calcolo e memoria per ogni nuovo passo.
- Rete neurale convoluzionale (CNN): combina posizioni vicine con filtri di dimensione fissa, nello spazio per le immagini o nel tempo con convoluzioni 1D; il suo campo ricettivo è determinato dalla profondità.
- Modelli a spazio di stato: ricorrenze lineari come Mamba si addestrano in parallelo con una scansione e, in inferenza, vengono eseguite passo dopo passo come una RNN. Le reti neurali liquide sono RNN a tempo continuo i cui neuroni seguono equazioni differenziali.
Applicazioni#
- Previsione da tastiera: il modello di Gboard per la parola successiva era una LSTM con gate di input e di dimenticanza accoppiati, una variante più leggera della LSTM, addestrata con apprendimento federato sui telefoni degli utenti, come i ricercatori di Google hanno descritto nel 2018.
- Previsione della domanda: DeepAR di Amazon addestra una RNN autoregressiva su molte serie temporali correlate e restituisce una distribuzione di probabilità per ogni valore futuro.
- Audio in streaming: i rilevatori di parole di attivazione e i soppressori di rumore elaborano l'audio fotogramma per fotogramma con piccoli strati ricorrenti, mantenendo costanti memoria e latenza su telefoni e auricolari.
- Tracciamento degli oggetti: un tracker mantiene uno stato per ogni oggetto da un fotogramma all'altro, come fa lo stato nascosto di una RNN. Ogni tracker integrato nella modalità di tracciamento di YOLO26, da ByteTrack e BoT-SORT al tracker predefinito TrackTrack, aggiorna lo stato con un filtro di Kalman, una ricorrenza lineare con equazioni della velocità costante impostate manualmente al posto dei pesi appresi.










