Salta ai contenuti

108 — Softmax e sigmoid: dalla somma alla probabilità

Due funzioni che traducono numeri reali in probabilità, e tengono insieme classificatori, attention e sampling LLM.

Una rete neurale, alla fine del suo forward pass, produce numeri reali. Per chiamare “probabilità” ciò che esce, serve un mapping che porti R o R^K dentro [0,1] o sul simplesso (vettori non negativi che sommano a 1). Sigmoid e softmax sono le due funzioni canoniche per fare questa traduzione. Sono il ponte fra il mondo continuo dei logit e il mondo discreto delle decisioni.

Ma il loro ruolo non si ferma al layer di output di un classificatore. La stessa softmax compare dentro l’attention dei transformer (riga per riga sul prodotto Q K^T), nel sampling di un large language model (sui logit di vocabolario prima di scegliere il prossimo token), nel routing di un Mixture of Experts, nelle policy di reinforcement learning. La stessa sigmoid governa un singolo gate in un’LSTM, un classificatore binario, una porta in una rete ricorrente. Capire bene questa coppia significa capire un meccanismo che ricorre ovunque nell’AI moderna.

C’è poi una ragione tecnica più sottile. La derivata di sigmoid e softmax, combinata con cross-entropy, si semplifica in una formula pulita: gradiente = (probabilità predetta − label vera). È questa cancellazione algebrica che rende il backprop trattabile su classificatori a milioni di classi. Senza, l’addestramento moderno sarebbe più fragile.

Il grafo concettuale è denso. A monte, probabilità di base introduce distribuzioni e categoriche; entropia e cross-entropy definisce la loss che incontreremo qui in versione gradiente; gradienti a intuito e discesa del gradiente preparano il calcolo che useremo. A valle, attention e sampling LLM (in preparazione) consumano direttamente softmax come componente meccanica, non concettuale.

Le persone. John S. Bridle (ricercatore al Royal Signals and Radar Establishment, UK) introduce il nome “softmax” in un contributo del 1990 al volume Neurocomputing della NATO ASI Series, il paper Probabilistic Interpretation of Feedforward Classification Network Outputs: la stessa funzione esisteva da decenni in fisica statistica come distribuzione di Boltzmann (Ludwig Boltzmann, fisico austriaco, 1877), ma il nome “softmax” e l’uso nelle reti neurali sono di Bridle. Joseph Berkson (statistico americano) introduce il termine “logit” nel 1944 nel contesto della regressione logistica: è il logaritmo degli odds, l’inverso di sigmoid. Ashish Vaswani et al. (Google Brain, 2017) nel paper Attention Is All You Need (NeurIPS 2017) motivano esplicitamente la divisione per √d_k come rimedio alla saturazione di softmax in attention. Geoffrey Hinton et al. (2015) mostrano nel paper Distilling the Knowledge in a Neural Network (NeurIPS DL Workshop) come la temperature in softmax estragga “dark knowledge” dal teacher.

Si parte da due numeri da decodificare. Logit: numero reale che, applicato a sigmoid, produce una probabilità. È log(p/(1−p)), il logaritmo degli odds. Temperature (in questo contesto): scalare T > 0 che divide i logit prima della softmax, e che controlla quanto la distribuzione sia piccata (T piccolo) o piatta (T grande). Sono due variabili che useremo costantemente.

Un score reale x può vivere ovunque in R: può valere −7.3, 0, oppure 142. Se vogliamo interpretarlo come probabilità di un evento binario, ci serve una funzione che lo schiacci dentro (0,1) senza perdere l’ordine: x più grande deve implicare probabilità più grande, x più piccolo probabilità più piccola, e x = 0 deve corrispondere alla “incertezza massima” 0.5.

Sigmoid σ(x) = 1/(1+e^{-x}) è la scelta canonica: monotona crescente, simmetrica intorno a (0, 0.5), passa attraverso 0.5 esattamente in x=0, e satura dolcemente verso 1 e 0 ai due estremi. La sua forma a S è familiare a chiunque abbia visto una curva di “adozione di una tecnologia” o una funzione di crescita logistica. Non è un caso: la regressione logistica è esattamente sigmoid + label binarie + maximum likelihood.

L’intuizione vale anche per softmax in versione collettiva. K score reali (x_1, …, x_K) devono diventare K probabilità che sommino a 1, mantenendo il loro ordine relativo. La ricetta è: prendi l’esponenziale (lo rendi positivo, e amplifichi le differenze), poi normalizzi dividendo per la somma. È la versione vettoriale della stessa idea: schiacciare numeri reali dentro una distribuzione, in modo monotono e differenziabile.

C’è un altro modo di guardare softmax che illumina il ruolo della temperature. Pensa a K candidati che competono per attenzione: hanno punteggi diversi, e bisogna assegnargli una “quota” del totale. Se i punteggi sono molto diversi e amplifichiamo aggressivamente le differenze, il vincitore prende quasi tutto (regime “winner-take-all”). Se invece schiacciamo le differenze, tutti ricevono una quota simile (regime quasi-uniforme).

La temperature è la manopola che decide quanto amplificare. Softmax(x/T) con T piccolo (tipo 0.1) rende la distribuzione molto piccata: il candidato più alto domina. Con T grande (tipo 5) la distribuzione diventa quasi uniforme: tutti i candidati pesano uguale. T = 1 è il default “naturale”.

Questa è esattamente la stessa intuizione della distribuzione di Boltzmann in fisica statistica: in un sistema in equilibrio termico, la probabilità di un microstato dipende dalla sua energia e dalla temperatura. Bassa temperatura → il sistema sta quasi sempre nello stato a minima energia. Alta temperatura → il sistema si spalma su tutti gli stati. Equivalenza: softmax con energie −x_i e temperatura T è formalmente la distribuzione di Boltzmann. Non è un’analogia didattica, è la stessa formula con etichette diverse.

In pratica, in un large language model, T diventa il “regolatore di creatività” (analogia, non equivalenza, perché la creatività umana è un’altra cosa): T = 0 produce output deterministico ripetitivo, T = 0.7 produce output vivaci ma controllati, T > 1.5 produce nonsense. Lo stesso parametro che in fisica è la temperatura termodinamica, in un LLM regola la varietà del testo generato.

sigmoid and its derivative, with saturation regions highlighted

softmax at three different temperatures showing peakedness vs flatness

σ(x)=1/(1+ex)σ(x) = 1 / (1 + e^{-x})

Range: (0,1). σ(0) = 0.5. Simmetria: σ(−x) = 1 − σ(x).

La derivata ha forma chiusa elegante:

σ’(x) = σ(x) · (1 − σ(x))

Teorema (lo deriviamo riga per riga). Sia σ(x) = 1/(1+e^{-x}) = (1+e^{-x})^{-1}. Per la regola della catena:

σ’(x) = −1 · (1+e^{-x})^{-2} · (−e^{-x}) = e^{-x} / (1+e^{-x})^2

Riscriviamo: e^{-x} / (1+e^{-x})^2 = [1/(1+e^{-x})] · [e^{-x}/(1+e^{-x})] = σ(x) · (1 − σ(x)). □

Conseguenza pratica numero uno: la derivata si esprime in funzione del valore già calcolato di σ. In implementazione, calcoli σ una volta e ottieni gratis il gradiente.

Conseguenza pratica numero due: la derivata vale al massimo 0.25 (in x=0) e tende a 0 nelle code. È piccola dappertutto rispetto alle attivazioni non saturanti tipo ReLU. In una rete profonda, gradienti che si moltiplicano layer dopo layer attraverso sigmoid si schiacciano: è il famoso vanishing gradient. È una delle ragioni storiche per cui sigmoid è uscita dalle hidden layer ed è rimasta solo come gate (in LSTM) o output binario.

Se p = σ(x), allora invertendo:

x = log(p / (1 − p)) = logit(p)

Il logit è il logaritmo degli odds. Una probabilità di 0.5 ha logit 0; una probabilità di 0.9 ha logit ≈ 2.2; una probabilità di 0.99 ha logit ≈ 4.6. L’asse logit “stira” le probabilità vicino a 0 e a 1, dove piccole variazioni di p contano molto. Questa è la ragione per cui le reti emettono logit, non probabilità: i logit hanno scala “lineare-friendly” e gradienti gestibili; le probabilità si schiacciano contro 0 e 1.

Dato x = (x_1, …, x_K) ∈ R^K:

softmax(x)i=exp(xi)/Σj=1Kexp(xj)softmax(x)_i = exp(x_i) / Σ_{j=1}^{K} exp(x_j)

Output: vettore in R^K con componenti positive che sommano a 1. È una distribuzione categorica su K classi.

Teorema: per ogni scalare c, softmax(x + c·1) = softmax(x), dove 1 è il vettore di tutti uno.

Dimostrazione: il numeratore exp(x_i + c) = exp(x_i) · exp(c). Il denominatore Σ_j exp(x_j + c) = exp(c) · Σ_j exp(x_j). Il fattore exp(c) si cancella. □

Conseguenza: si può sottrarre il massimo di x prima di esponenziare. Questo è il log-sum-exp trick:

log Σ_j exp(x_j) = M + log Σ_j exp(x_j − M), M = max_j x_j

Senza questa trasformazione, exp(700) overflow in float64, exp(89) overflow in float32. Con questa trasformazione, gli esponenti negativi sono limitati e tutto resta numericamente sano.

Conseguenza meno ovvia: softmax è invariante per traslazione, ma non è invariante per scaling. softmax(2x) ≠ softmax(x). Il fattore di scala (cioè 1/T) cambia la nitidezza della distribuzione. Per c → ∞, softmax(c·x) tende a one-hot sull’argmax. Per c → 0, tende a uniforme.

Sia p_i = softmax(x)_i. Allora:

∂p_i / ∂x_j = p_i · (δ_{ij} − p_j)

dove δ_{ij} è il delta di Kronecker (1 se i=j, 0 altrimenti). Equivalentemente, in forma matriciale: J = diag(p) − p p^T, una matrice K×K densa. Da sola sarebbe un peso significativo nel backprop. Ma nel caso d’uso più comune si combina con cross-entropy e si semplifica drasticamente.

Cross-entropy multi-classe: L = −Σ_i y_i log p_i, con y vettore one-hot della classe vera e p = softmax(x).

Calcoliamo ∂L/∂x_k:

∂L/∂x_k = −Σ_i y_i (1/p_i) · ∂p_i/∂x_k = −Σ_i y_i (1/p_i) · p_i (δ_{ik} − p_k) = −Σ_i y_i (δ_{ik} − p_k) = −y_k + p_k · Σ_i y_i = p_k − y_k

(usando Σ_i y_i = 1 perché y è one-hot)

Risultato:

∂L/∂x_k = p_k − y_k

Tutto il caos della Jacobiana si è cancellato contro il log della cross-entropy. Il gradiente è semplicemente “probabilità predetta meno label vera”, componente per componente. È la formula che fa funzionare il training di classificatori a milioni di classi senza overhead aggiuntivo.

Lo stesso pattern vale per sigmoid + binary cross-entropy: ∂L/∂x = σ(x) − y. Stessa forma, perché il caso binario è un caso particolare del multi-classe (vedi sotto).

Argomento di equivalenza. Con due classi e logit (x_1, x_2):

softmax(x1,x2)1=exp(x1)/(exp(x1)+exp(x2))softmax(x_1, x_2)_1 = exp(x_1) / (exp(x_1) + exp(x_2))

Dividi numeratore e denominatore per exp(x_2):

= 1 / (1 + exp(x_2 − x_1)) = σ(x_1 − x_2)

Quindi softmax binaria coincide con sigmoid sulla differenza dei due logit. Per l’invarianza per traslazione, uno dei due logit è ridondante: contano solo le differenze. Conseguenza pratica: in classificazione binaria si emette un solo logit (sigmoid + BCE), non due (softmax + CE). Stesso modello, parametri in meno, gradiente identico.

In pratica si lavora quasi sempre in spazio log per stabilità:

log_softmax(x)_i = x_i − M − log Σ_j exp(x_j − M), M = max(x)

F.cross_entropy(logits, target) in PyTorch è internamente NLL applicata a log_softmax dei logit. Mai fare softmax → log → loss a mano: si perde stabilità numerica.

import numpy as np
def softmax(x, T=1.0, axis=-1):
x = x / T
x = x - x.max(axis=axis, keepdims=True) # log-sum-exp trick
e = np.exp(x)
return e / e.sum(axis=axis, keepdims=True)
def log_softmax(x, axis=-1):
M = x.max(axis=axis, keepdims=True)
return x - M - np.log(np.exp(x - M).sum(axis=axis, keepdims=True))
def sigmoid(x):
# implementazione robusta su entrambe le code
return np.where(x >= 0,
1.0 / (1.0 + np.exp(-x)),
np.exp(x) / (1.0 + np.exp(x)))

Lo where su sigmoid evita overflow su x molto negativi (exp(x) tende a 0, controllato) e su x molto positivi (exp(-x) tende a 0, controllato).

Logitmoderatix=[2.0,1.0,0.0]:Logit moderati x = [2.0, 1.0, 0.0]: exp[7.39,2.72,1.0],somma11.11softmax[0.665,0.245,0.090]exp ≈ [7.39, 2.72, 1.0], somma ≈ 11.11 softmax ≈ [0.665, 0.245, 0.090]

Distribuzione “morbida”: il vincitore prende il 66%, gli altri ricevono massa significativa.

Stessi rapporti relativi ma scala 10× — logit x = [20.0, 10.0, 0.0]:

exp[4.85e8,22026,1],somma4.85e8softmax[0.99995,4.5e5,2.1e9]exp ≈ [4.85e8, 22026, 1], somma ≈ 4.85e8 softmax ≈ [0.99995, 4.5e-5, 2.1e-9]

Distribuzione quasi one-hot: il vincitore prende praticamente tutto. La Jacobiana p_i(δ_{ij} − p_j) si schiaccia a zero quasi ovunque, e il gradiente svanisce. Questo è esattamente il problema che √d_k risolve in attention: prevenire l’esplosione della scala dei logit.

Aggiungere una costante (test di invarianza). Logit x = [2.0, 1.0, 0.0] + 100 = [102, 101, 100]:

Con il log-sum-exp trick si sottrae max = 102 prima di esponenziare, e si torna a esattamente [0.665, 0.245, 0.090]. Senza il trick, exp(102) overflow in float32. Stessa distribuzione, calcolo numericamente sano solo nel secondo caso.

import torch
import torch.nn.functional as F
# logit con outlier estremi
logits = torch.tensor([1000.0, 999.0, 998.0, 1.0])
# naive: rompe
naive = torch.exp(logits) / torch.exp(logits).sum()
# tensor([nan, nan, nan, nan]) -- overflow
# stabile con log-sum-exp
M = logits.max()
stable = torch.exp(logits - M) / torch.exp(logits - M).sum()
# tensor([6.65e-1, 2.45e-1, 9.00e-2, 0.0])
# PyTorch builtin: identico, già stabile
torch_sm = F.softmax(logits, dim=-1)
# tensor([6.65e-1, 2.45e-1, 9.00e-2, 0.0])
# log-softmax: ancora più stabile per la loss
log_sm = F.log_softmax(logits, dim=-1)
# tensor([-4.07e-1, -1.41e+0, -2.41e+0, -9.99e+2])

L’ultimo elemento di stable è 0.0 per underflow di exp(998 − 1000) = exp(−2) — qui sopravvive — ma di exp(1 − 1000) = exp(−999) → 0 in float32. Il log-softmax mantiene il valore −999 senza problemi: lavorare in spazio log preserva precisione per probabilità minuscole.

Esempio 3: scenario reale, sampling di un LLM con temperature e top-p

Sezione intitolata “Esempio 3: scenario reale, sampling di un LLM con temperature e top-p”

Un modello produce, per il prossimo token, logit (semplificati a 5 candidati):

candidate logit
"Roma" 8.2
"Milano" 7.8
"Napoli" 6.5
"Torino" 5.9
"banana" -2.1

Greedy (T → 0): sceglie sempre “Roma”. Output deterministico, ripetitivo su prompt simili.

T = 0.7:

logit/T = [11.71, 11.14, 9.29, 8.43, −3.0] softmax ≈ [0.55, 0.31, 0.09, 0.04, 6e-7]

“Roma” ancora favorito ma “Milano” ha 31% di chance, “Napoli” 9%. “banana” praticamente esclusa.

T = 1.5:

logit/T = [5.47, 5.20, 4.33, 3.93, −1.4] softmax ≈ [0.41, 0.31, 0.13, 0.09, 0.0008]

Distribuzione più piatta, più diversità ma anche più rumore.

Top-p 0.9, ordinata decrescente, sopra T = 0.7: cumulata 0.55 + 0.31 = 0.86 < 0.9; aggiungo “Napoli” (0.09) → 0.95 ≥ 0.9. Mantengo {Roma, Milano, Napoli}, scarto il resto, rinormalizzo:

[0.55/0.95,0.31/0.95,0.09/0.95][0.58,0.33,0.09][0.55/0.95, 0.31/0.95, 0.09/0.95] ≈ [0.58, 0.33, 0.09]

Campiono da questi tre. È esattamente il flusso che gira dentro qualunque chat moderna: temperature scaling per controllare la varietà, top-p per eliminare la coda lunga improbabile, softmax finale per rinormalizzare prima di campionare.

flowchart LR
    A[Hidden state h] --> B[Unembedding W_U]
    B --> C[Logit sul vocabolario]
    C --> D[Divisione per temperatura T]
    D --> E[Filtro top-k o top-p]
    E --> F[Softmax]
    F --> G[Sample del prossimo token]
    G --> H[Append al contesto]
    H --> A

Figura 5 — catena di sampling in un LLM dal hidden state al token successivo — unembedding W_U, divisione per temperatura T, filtro top-k/top-p, softmax, sample, append al contesto

Output layer di classificatori multi-classe. ImageNet (1000 classi), language modeling (vocabolario di decine o centinaia di migliaia di token), classificazione di intent in NLU. Sempre softmax + cross-entropy. Quando le classi sono mutuamente esclusive.

Sampling in language model. Il decoder produce logit sul vocabolario; temperature scaling, top-k, top-p o min-p filtrano; softmax finale rinormalizza; si campiona. T = 0 (greedy), T = 0.7 (chat), T = 1.0 (modello “in voce naturale”). L’approccio è universale fra OpenAI, Anthropic, Google, Mistral.

Attention in transformer. attn(Q, K, V) = softmax(QK^T / √d_k) V. La softmax è applicata per riga (ogni token query distribuisce attenzione sui key). Il /√d_k è motivato dalla saturation: senza scaling, la softmax tende a one-hot all’aumentare di d_k, e il gradiente svanisce (Vaswani 2017 lo dice esplicitamente).

Mixture of Experts routing. Router con softmax sui logit per-expert sceglie i top-k esperti. La softmax dà i pesi della miscela. Switch Transformer, GShard, DeepSeekMoE: tutti varianti dello stesso schema.

Policy in reinforcement learning. In policy gradient con policy categorica, π(a|s) = softmax(f_θ(s, ·)). La temperature controlla l’esplorazione; spesso si applica un annealing: T grande all’inizio per esplorare, T piccolo verso la fine per sfruttare.

Distillation. Hinton-Vinyals-Dean 2015. Il teacher produce una distribuzione soft con T > 1 (tipico T = 4 o T = 10); lo studente impara a riprodurla. Le hard label dicono solo “classe 3”; le soft label dicono “classe 3 con 0.7, classe 7 con 0.2, classe 1 con 0.05” — la “dark knowledge”, informazione molto più ricca.

Calibration post-hoc. Guo 2017 ha mostrato che le reti deep moderne sono sovra-confidenti. Soluzione: imparare un T scalare sul validation set tale che softmax(logit/T) abbia errore di calibrazione minimo. Operazione cheap, post-hoc, non cambia gli argmax (quindi non cambia l’accuracy), solo le confidence.

Saturation di sigmoid e vanishing gradient. La derivata di sigmoid vale al massimo 0.25, e tende a zero nelle code. In una rete profonda con sigmoid in ogni hidden layer, i gradienti passano attraverso una catena di moltiplicazioni per numeri ≤ 0.25 e si schiacciano. È una delle ragioni storiche per cui ReLU (e successive: GELU in BERT, SwiGLU in LLaMA e PaLM) si è imposta nelle hidden layer. Sigmoid sopravvive solo come gate (LSTM, GRU) o come output binario.

Softmax non è calibrata di default. Guo et al. (ICML 2017) hanno mostrato che reti deep moderne sono sistematicamente sovra-confidenti: un classifier che dice “92% di sicurezza” potrebbe avere accuracy 75%. Conseguenza pratica: se la confidence è un input downstream (astensione, routing, escalation a un umano), bisogna ricalibrare. Una probabilità softmax cruda non è una probabilità affidabile in senso bayesiano.

Attention satura senza scaling. Se Q e K hanno componenti con varianza 1, il prodotto scalare q·k ha varianza d_k. Senza dividere per √d_k, all’aumentare di d_k la softmax sui logit di attention tende a one-hot, il gradiente sui parametri di Q e K svanisce, il training fallisce. Vaswani 2017 motiva esplicitamente lo scaling /√d_k come rimedio. Se vedi attention che “non impara”, controlla lo scaling prima di tutto.

Softmax non è invariante per scaling. softmax(c·x) ≠ softmax(x). T conta. Questo significa che la scala dei logit è una variabile semantica, non solo numerica. Reti che producono logit con scala drasticamente diversa fra i layer (es. mancanza di LayerNorm/RMSNorm) avranno softmax instabili: a volte one-hot, a volte uniformi, in modo dipendente dall’input.

Softmax è “soft argmax”, non “soft max”. Il nome è storicamente fuorviante. Softmax non approssima il valore massimo di x: approssima l’indice dell’argmax, restituendo una distribuzione concentrata su quell’indice. La vera versione “soft” del massimo è log-sum-exp (LSE), che soddisfa max(x) ≤ LSE(x) ≤ max(x) + log K. La gente dice “softmax” intendendo soft argmax e di solito non è un problema, ma è un trabocchetto concettuale ricorrente.

Float32 e log-sum-exp trick obbligatorio. exp(89) overflow in float32. Logit di 100 sono comuni in attention prima dello scaling, in classifier su vocabolari grandi prima di una buona normalizzazione, in residual stream lunghi. Senza log-sum-exp trick, exp produce inf, la divisione produce nan, l’intero forward pass è da buttare. Tutti i framework seri lo applicano internamente; se implementi softmax a mano, non saltare il trick.

Mixed precision (fp16/bf16). La softmax in fp16 può perdere precisione drasticamente, soprattutto in attention con sequence length grande. Pattern comune: si fa il prodotto QK^T in fp16, si converte in fp32 per la softmax, si torna in fp16 per il prodotto con V. FlashAttention e librerie simili gestiscono questo internamente.

Categorical sampling non è differenziabile. Sample(softmax(x)) non ha gradiente rispetto a x. Per RL e policy gradient si usa lo score function estimator (REINFORCE). Per setup che richiedono rilassamenti differenziabili (es. discrete VAE) si usa il Gumbel-softmax (Jang-Gu-Poole 2017, ICLR; in parallelo Maddison-Mnih-Teh, “The Concrete Distribution”): si aggiunge rumore di Gumbel ai logit e si applica softmax con T → 0 schedulato. Il risultato è un sample differenziabile che converge a categorical hard durante il training.

Multi-label NON è multi-class. Errore frequente: usare softmax dove le label sono indipendenti e possibilmente compresenti. Esempio: “questa immagine contiene cane E gatto”. Con softmax, somma a 1 → il modello è costretto a “scegliere” e non può alzare entrambe le probabilità sopra 0.5. Soluzione: K sigmoid indipendenti + somma di K binary cross-entropy. È un bug di design, non un bug numerico, e produce modelli silenziosamente sbagliati.

Softmax bottleneck. Yang et al. (2018) hanno mostrato che la softmax con un singolo set di logit ha rango limitato sulle distribuzioni rappresentabili. In language modeling, questo limita la capacità del modello di rappresentare distribuzioni multi-modali sul vocabolario. Soluzioni proposte (mixture of softmaxes) sono interessanti ma non si sono imposte; il problema è in larga parte mitigato da architetture e scala più grandi.

flowchart LR
    A[Input x] --> B[Forward del network]
    B --> C[Logit z in R^K]
    C --> D{K classi?}
    D -->|K=2, mutuamente esclusive| E[Sigmoid su z1-z2]
    D -->|K>2, mutuamente esclusive| F[Softmax]
    D -->|K label, indipendenti| G[K sigmoid indipendenti]
    E --> H[Probabilità p]
    F --> I[Distribuzione p1..pK]
    G --> J[K probabilità]

Figura 4 — saturation in attention without √d_k scaling vs with scaling, max softmax probability vs d_k

Esempio 4: numerico, derivata di sigmoid in tre punti

Sezione intitolata “Esempio 4: numerico, derivata di sigmoid in tre punti”

Per fissare l’intuizione del vanishing gradient di sigmoid, calcoliamo σ’(x) in tre punti:

  • x = 0: σ(0) = 0.5, σ’(0) = 0.5 · 0.5 = 0.25 (massimo possibile).
  • x = 3: σ(3) ≈ 0.9526, σ’(3) ≈ 0.9526 · 0.0474 ≈ 0.0452.
  • x = 6: σ(6) ≈ 0.9975, σ’(6) ≈ 0.9975 · 0.0025 ≈ 0.0025.

In una rete con dieci layer di sigmoid e attivazioni medie attorno a |x| = 3, il gradiente che arriva al primo layer è moltiplicato per ≈ 0.0452^10 ≈ 3 · 10^{-14}. È spento. Con ReLU, il fattore in regime attivo è 1, e dieci layer non degradano nulla. Questo è il senso operativo del vanishing gradient di sigmoid.

import torch
import torch.nn.functional as F
# Multi-class: una sola classe vera (digit 0-9)
logits = torch.tensor([[2.0, 1.0, 0.5, 0.0, -1.0, 0.2, 0.0, 0.0, 0.0, 0.0]])
target = torch.tensor([0]) # classe 0
loss_mc = F.cross_entropy(logits, target)
# usa softmax + NLL internamente
# Multi-label: più tag indipendenti su un'immagine
logits = torch.tensor([[1.5, -0.5, 2.0, -2.0]]) # 4 tag indipendenti
target = torch.tensor([[1.0, 0.0, 1.0, 0.0]]) # tag 0 e 2 attivi
loss_ml = F.binary_cross_entropy_with_logits(logits, target)
# usa sigmoid + BCE per ciascun tag, somma

Il bug tipico è invertire i due: usare crossentropycross_entropy su multi-label produce un modello che non riesce mai a dire “due tag attivi insieme” oltre 0.5, perché la softmax somma a 1 fra i tag.

flowchart LR
    A[Input x] --> B[Forward del network]
    B --> C[Logit z in R^K]
    C --> D{K classi?}
    D -->|K=2, mutuamente esclusive| E[Sigmoid su z1-z2]
    D -->|K>2, mutuamente esclusive| F[Softmax]
    D -->|K label, indipendenti| G[K sigmoid indipendenti]
    E --> H[Probabilità p]
    F --> I[Distribuzione p1..pK]
    G --> J[K probabilità]

Figura 4 — pipeline output di un classificatore — decision tree per scegliere fra sigmoid (K=2 mutuamente esclusive), softmax (K>2 mutuamente esclusive) e K sigmoid indipendenti (multi-label)

Per separare i ruoli:

  • Sigmoid: hidden activation storica e gate (LSTM/GRU). Output binario. Range (0,1).
  • Tanh = 2σ(2x) − 1. Output centrato a zero, range (−1, 1). Storicamente preferita a sigmoid nelle hidden, ora rara.
  • ReLU = max(0, x). Hidden activation di default dal 2012. Non satura per x > 0. “Dies” per x < 0 (gradiente esattamente zero, neuroni “morti”).
  • GELU = x · Φ(x), Φ CDF della normale standard. Versione smooth di ReLU. Default in BERT, GPT-2.
  • SwiGLU = (Wx) · σ(Vx) (con W, V due proiezioni separate, σ qui spesso indica swish/SiLU). Hidden gate moderno, default in LLaMA, PaLM, Mistral.

Sigmoid e softmax restano tipicamente solo all’output o come gate. Le hidden layer moderne sono ReLU/GELU/SwiGLU. Il punto è che sigmoid e softmax hanno una interpretazione probabilistica che le altre non hanno: sono i mapping naturali verso distribuzioni Bernoulli e categoriche. ReLU non è una probabilità di nulla.

La distribuzione di Boltzmann e la temperatura come parametro fisico

Sezione intitolata “La distribuzione di Boltzmann e la temperatura come parametro fisico”

Una nota di equivalenza che vale la pena fare esplicita. La distribuzione di Boltzmann (1877) descrive la probabilità che un sistema fisico in equilibrio termico si trovi in uno stato i con energia E_i:

p_i = exp(−E_i / kT) / Z, Z = Σ_j exp(−E_j / kT)

dove k è la costante di Boltzmann e T la temperatura assoluta. Sostituendo −E_i / kT con x_i si ottiene la softmax. Z è la funzione di partizione, l’analogo del denominatore di softmax.

Equivalenza, non analogia: è la stessa formula. Il fatto che il parametro T in softmax(x/T)softmax(x/T) per LLM sampling si chiami “temperature” non è un nome pittoresco — è la temperatura termodinamica del sistema “scelta del prossimo token”. T → 0 corrisponde a “raffreddare il sistema” finché si concentra tutto sullo stato a minima energia (massimo logit). T → ∞ corrisponde a portare il sistema a temperatura infinita, dove tutti gli stati sono ugualmente probabili.

Quando in distillation Hinton dice “alziamo la temperatura del teacher”, intende esattamente quello: spalmiamo la distribuzione, esponiamo le probabilità relative dei non-vincitori, estraiamo informazione termodinamica.

Cenno breve, perché serve il vocabolario per il capitolo dedicato. Categorical sampling — z ~ Categorical(softmax(x)) — non è differenziabile rispetto a x. Per setup che richiedono backprop attraverso il sample (discrete VAE, sparse expert routing differenziabile), si usa il Gumbel-softmax (Jang-Gu-Poole 2017, ICLR; Maddison-Mnih-Teh, “The Concrete Distribution”, 2017):

  1. Si campionano g_i ∼ Gumbel(0, 1) i.i.d. (g_i = −log(−log u_i), u_i ∼ Uniform(0,1)).
  2. Si calcola y_i = softmax((x_i + g_i) / τ).
  3. y è un sample “soft” che converge a one-hot per τ → 0.

Filiazione: deriva dal Gumbel-max trick — argmax(x + g) ∼ Categorical(softmax(x)) è una identità classica. Il rilassamento softmax con τ → 0 è la versione differenziabile.

Riassumendo per chi implementa:

  • Classificazione binaria: un logit, sigmoid + BCE. F.binary_cross_entropy_with_logits.
  • Classificazione multi-classe (mutuamente esclusiva): K logit, softmax + CE. F.cross_entropy direttamente sui logit.
  • Multi-label (label indipendenti): K logit, K sigmoid indipendenti + somma di BCE. F.binary_cross_entropy_with_logits.
  • Sampling LLM: logit / T, top-k o top-p, softmax, sample.
  • Distillation: teacher con T = 4-10, KL fra distribuzioni soft.
  • Calibration: temperature scaling post-hoc su validation set.
  • Attention: softmax(QK^T / √d_k), preferibilmente in fp32 anche se il resto è in fp16/bf16.

Mai dimenticare il log-sum-exp trick. Mai applicare log su softmaxsoftmax a mano: usa logsoftmaxlog_softmax. Mai usare softmax in multi-label.

  • Probabilità di base — la categorica e la Bernoulli sono le distribuzioni che softmax e sigmoid parametrizzano direttamente.
  • Entropia, cross-entropy, KL divergence — la loss naturale per softmax e sigmoid è cross-entropy; insieme producono il gradiente pulito (p − y).
  • Gradienti a intuito — il calcolo della derivata di sigmoid e della Jacobiana di softmax è un esercizio canonico di chain rule.
  • Discesa del gradiente — la combo softmax+CE è ciò che SGD aggiorna in un classificatore standard.
  • attention-intuizione (in preparazione) — softmax(QK^T/√d_k) è l’operazione centrale di un transformer block, e la motivazione dello scaling è esattamente la saturation discussa qui.
  • output-logits (in preparazione) — temperature, top-k, top-p, min-p sono tutte trasformazioni che agiscono sulla softmax dei logit di vocabolario.
  • distillation (in preparazione) — temperature alta nel teacher per estrarre soft target.
  • calibration-abstention (in preparazione) — temperature scaling come metodo di calibration post-hoc dopo Guo 2017.
  • regressione-logistica (in preparazione) — è esattamente un layer lineare + sigmoid + binary cross-entropy.
  • mlp-backprop (in preparazione) — sigmoid e tanh sono le hidden activation storiche, sostituite da ReLU per il vanishing gradient discusso qui.
  • Bridle, J. S. (1990). Probabilistic Interpretation of Feedforward Classification Network Outputs, with Relationships to Statistical Pattern Recognition. In Neurocomputing: Algorithms, Architectures and Applications (NATO ASI Series F68, Springer). Il paper che dà il nome “softmax” e ne motiva l’uso in classificatori neurali.

  • Vaswani, A. et al. (2017). Attention Is All You Need. NeurIPS 2017. Sezione 3.2.1 motiva esplicitamente la divisione per √d_k come rimedio alla saturazione di softmax.

  • Hinton, G.; Vinyals, O.; Dean, J. (2015). Distilling the Knowledge in a Neural Network. NeurIPS Deep Learning Workshop. Introduce la temperature alta per il knowledge distillation.

  • Guo, C.; Pleiss, G.; Sun, Y.; Weinberger, K. Q. (2017). On Calibration of Modern Neural Networks. ICML 2017. Mostra che softmax di reti deep moderne non è calibrata e propone temperature scaling.

  • Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. Cap. 4.2-4.3: derivazione di sigmoid e softmax dalla famiglia esponenziale, link a logistic regression.