Alcuni problemi di probabilità hanno una soluzione elegante su carta. Molti altri no: troppe variabili, dipendenze complicate, distribuzioni irregolari. Per questi esiste un metodo di una semplicità quasi disarmante: simulare il fenomeno migliaia di volte con numeri casuali e guardare cosa succede. È il metodo Monte Carlo, e con un linguaggio come Python bastano poche righe per usarlo.
L'idea
Se non sappiamo calcolare una probabilità o un valore atteso, possiamo:
- scrivere un programma che generi uno scenario possibile secondo le regole del problema;
- ripeterlo moltissime volte con numeri casuali diversi;
- contare quante volte accade l'evento che ci interessa, oppure calcolare la media del risultato.
Per la legge dei grandi numeri, la frequenza osservata si avvicina alla probabilità vera e la media osservata al valore atteso. Il metodo funziona per qualunque problema che si sappia descrivere come un processo casuale, anche quando nessuna formula è disponibile.
Primo esempio: stimare π
Un esempio classico, utile per capire il meccanismo, è la stima di π. Si generano punti casuali uniformi nel quadrato di lato 1. La frazione che cade dentro il quarto di cerchio di raggio 1 tende al rapporto tra le aree, cioè π/4.
import numpy as np
rng = np.random.default_rng(42)
n = 1_000_000
x, y = rng.random(n), rng.random(n)
stima = 4 * np.mean(x**2 + y**2 <= 1)
print(stima)
Con un milione di punti la stima è di solito corretta alla seconda o terza cifra decimale. Non è certo il modo migliore per calcolare π, ma mostra bene come una proporzione di eventi casuali possa stimare una grandezza deterministica.
Secondo esempio: la durata di un progetto
Ecco un caso più utile nel lavoro reale. Un progetto è composto da tre attività in sequenza, e per ciascuna abbiamo una stima con minimo, valore più probabile e massimo, in giorni:
| Attività | Minimo | Più probabile | Massimo |
|---|---|---|---|
| Analisi | 2 | 3 | 5 |
| Sviluppo | 4 | 6 | 10 |
| Collaudo | 1 | 2 | 4 |
Sommando i valori più probabili si ottengono 11 giorni, ed è facile promettere quella data. Simuliamo invece il progetto modellando ogni attività con una distribuzione triangolare:
n = 100_000
durata = (rng.triangular(2, 3, 5, n)
+ rng.triangular(4, 6, 10, n)
+ rng.triangular(1, 2, 4, n))
print(durata.mean()) # circa 12,3 giorni
print(np.mean(durata <= 11)) # circa 0,2
print(np.percentile(durata, [50, 90])) # circa 12,3 e 14,4
Il risultato è istruttivo: la probabilità di finire entro 11 giorni è solo di circa il 20%. Il motivo è che le stime sono asimmetriche: ogni attività può ritardare molto più di quanto possa anticipare, e i ritardi si sommano. Per avere il 90% di probabilità di rispettare la scadenza bisognerebbe indicare circa 14,4 giorni. Una simulazione di poche righe trasforma una stima puntuale ottimistica in una distribuzione di esiti realistica.
Quanto è precisa una simulazione
Il risultato di una simulazione Monte Carlo è a sua volta una stima, con un suo errore. Per una probabilità p stimata con n prove, l'errore standard è circa √(p(1 − p)/n). Per una media, è la deviazione standard del risultato divisa per √n.
La conseguenza è la stessa vista per il campionamento: per dimezzare l'errore servono quattro volte più simulazioni. Con 10.000 prove una probabilità intorno al 20% è stimata con un errore standard di circa 0,4 punti percentuali; con un milione di prove, di circa 0,04.
Conviene sempre riportare questo errore, o almeno ripetere la simulazione con semi diversi per vedere quanto oscillano i risultati.
Buone pratiche
Usare un generatore moderno e un seme
In NumPy il modo raccomandato è creare un generatore con np.random.default_rng(seme). Fissare il seme rende la simulazione riproducibile: chi rilancia il codice ottiene gli stessi numeri, cosa indispensabile per verificare e correggere un'analisi.
Vettorizzare
Generare tutti i numeri casuali in una volta con array NumPy è molto più veloce che usare cicli Python. Negli esempi sopra, centomila scenari si calcolano in una frazione di secondo.
Validare su un caso noto
Prima di fidarsi di una simulazione complessa, provala su una versione semplificata di cui conosci la soluzione esatta. Se la simulazione del lancio di due dadi non restituisce 7 come somma media, c'è un errore nel codice.
Gli errori tipici
- Ignorare le dipendenze: se due attività tendono a ritardare insieme, perché dipendono dalla stessa persona o dallo stesso fornitore, simularle come indipendenti sottostima il rischio.
- Scegliere distribuzioni a caso: il risultato non può essere migliore delle ipotesi in ingresso. Se le stime di minimo e massimo sono troppo ottimistiche, la simulazione sarà ottimista con grande precisione.
- Confondere precisione e accuratezza: un milione di simulazioni riduce l'errore casuale, non quello dovuto a un modello sbagliato.
- Usare generatori inadatti dove serve sicurezza: i generatori pseudocasuali per simulazione non sono adatti a creare chiavi o token; per quello servono generatori crittografici.
In sintesi
- La simulazione Monte Carlo stima probabilità e valori attesi ripetendo un processo casuale molte volte.
- Funziona anche quando nessuna formula è disponibile, purché si sappia descrivere il processo.
- Sommare i valori più probabili di stime asimmetriche porta a previsioni ottimistiche.
- L'errore della simulazione decresce con √n: dimezzarlo richiede quattro volte le prove.
- Semi fissati, codice vettorizzato e validazione su casi noti rendono la simulazione affidabile.