Immagine dell'articolo

Introduzione alla Teoria della Probabilità

In questi appunti riassumo la teoria che è alla base dei classificatori bayesiani e di una varietà di altri modelli probabilistici. Le nozioni sono utili per la comprensione degli appunti di Fondamenti relativi alle trasformazioni con riduzione di dimensionalità basate su modelli probabilistici generativi (e.g. LDA, pLSI).

La teoria della probabilità fornisce il framework matematico per modellare l'incertezza e ragionare in presenza di informazioni incomplete. Nel machine learning, questo framework è essenziale per sviluppare algoritmi robusti che possano fare predizioni affidabili anche quando i dati sono rumorosi o incompleti.

Concetti Fondamentali di Probabilità

Spazio Campionario e Eventi

Lo spazio campionario Ω rappresenta l'insieme di tutti i possibili risultati di un esperimento aleatorio. Formalmente, ogni elemento ω ∈ Ω rappresenta un possibile esito dell'esperimento.

Esempi pratici:

  • Lancio di una moneta: Ω = {Testa, Croce}
  • Lancio di un dado: Ω = {1, 2, 3, 4, 5, 6}
  • Classificazione di email: Ω = {spam, non-spam}
  • Riconoscimento di cifre: Ω = {0, 1, 2, ..., 9}

Un evento A è un sottoinsieme dello spazio campionario (A ⊆ Ω). Gli eventi possono essere:

  • Elementari: contengono un solo esito
  • Composti: contengono più esiti
  • Certi: A = Ω (probabilità = 1)
  • Impossibili: A = ∅ (probabilità = 0)

Assiomi della Probabilità

La probabilità è una funzione P: 2^Ω → [0,1] che soddisfa tre assiomi fondamentali di Kolmogorov:

  1. Non-negatività: P(A) ≥ 0 per ogni evento A
  2. Normalizzazione: P(Ω) = 1
  3. Additività numerabile: Per eventi disgiunti A₁, A₂, ...: P(⋃ᵢ Aᵢ) = Σᵢ P(Aᵢ)

Da questi assiomi derivano proprietà importanti come:

  • P(Aᶜ) = 1 - P(A) (probabilità del complemento)
  • P(∅) = 0 (probabilità dell'evento impossibile)
  • Se A ⊆ B, allora P(A) ≤ P(B) (monotonia)

Variabili Aleatorie e Distribuzioni

Una variabile aleatoria X è una funzione X: Ω → ℝ che associa a ogni esito dell'esperimento un numero reale. Le variabili aleatorie si dividono in:

Variabili Aleatorie Discrete

Assumono valori in un insieme numerabile. Esempi: numero di email spam ricevute in un giorno, numero di errori in un dataset.

Variabili Aleatorie Continue

Assumono valori in un intervallo continuo. Esempi: peso di una persona, tempo di risposta di un algoritmo.

Funzioni di Probabilità

Funzione di Massa di Probabilità (PMF)

Per variabili aleatorie discrete, la PMF è definita come:

PX(x)=P(X=x)P_X(x) = P(X = x)

Proprietà della PMF:

  • P_X(x) ≥ 0 per ogni x
  • Σₓ P_X(x) = 1 (somma su tutti i valori possibili)
  • P(X ∈ A) = Σₓ∈A P_X(x) per qualsiasi insieme A

Esempio: Lancio di un dado equo

PX(1)=PX(2)==PX(6)=16P_X(1) = P_X(2) = \ldots = P_X(6) = \frac{1}{6}
PX(x)=0 per x{1,2,3,4,5,6}P_X(x) = 0 \text{ per } x \notin \{1,2,3,4,5,6\}

Funzione di Densità di Probabilità (PDF)

Per variabili aleatorie continue, la PDF f_X(x) ha le proprietà:

  • f_X(x) ≥ 0 per ogni x
  • ∫₋∞^∞ f_X(x) dx = 1
  • P(a ≤ X ≤ b) = ∫ₐᵇ f_X(x) dx

Nota importante: Per variabili continue, P(X = x) = 0 per qualsiasi valore specifico x. La probabilità è definita solo su intervalli.

Funzione di Ripartizione (CDF)

La CDF F_X(x) = P(X ≤ x) è definita sia per variabili discrete che continue e ha le proprietà:

  • È monotona non-decrescente
  • limₓ→₋∞ F_X(x) = 0 e limₓ→∞ F_X(x) = 1
  • È continua a destra

Per variabili discrete:

FX(x)=txPX(t)F_X(x) = \sum_{t \leq x} P_X(t)

Per variabili continue:

FX(x)=xfX(t)dtF_X(x) = \int_{-\infty}^x f_X(t) dt

Probabilità Condizionale

La probabilità condizionale di A dato B (con P(B) > 0) è:

P(AB)=P(AB)P(B)P(A|B) = \frac{P(A \cap B)}{P(B)}

Interpretazione: P(A|B) rappresenta quanto siamo certi che A sia vero sapendo che B è vero.

Esempio pratico: In un dataset di email

  • A = {email è spam}
  • B = {email contiene la parola "offerta"}
  • P(A|B) = probabilità che un'email sia spam dato che contiene "offerta"

Indipendenza

Due eventi A e B sono indipendenti se:

P(AB)=P(A)P(B)P(A \cap B) = P(A) \cdot P(B)

Equivalentemente: P(A|B) = P(A) (se P(B) > 0)

Per variabili aleatorie, X e Y sono indipendenti se:

P(X=x,Y=y)=P(X=x)P(Y=y)P(X = x, Y = y) = P(X = x) \cdot P(Y = y)

per tutti i valori x, y.

Probabilità Composta e Totale

Probabilità Composta (Regola della Catena)

Per eventi A₁, A₂, ..., Aₙ:

P(A1A2An)=P(A1)P(A2A1)P(A3A1A2)P(AnA1An1)P(A_1 \cap A_2 \cap \ldots \cap A_n) = P(A_1) \cdot P(A_2|A_1) \cdot P(A_3|A_1 \cap A_2) \cdot \ldots \cdot P(A_n|A_1 \cap \ldots \cap A_{n-1})

Applicazione nel ML: Modellare sequenze di osservazioni dove ogni osservazione dipende dalle precedenti.

Probabilità Totale

Se {B₁, B₂, ..., Bₙ} è una partizione dello spazio campionario (eventi disgiunti la cui unione è Ω):

P(A)=i=1nP(ABi)P(Bi)P(A) = \sum_{i=1}^n P(A|B_i) \cdot P(B_i)

Interpretazione: La probabilità totale di A si ottiene "pesando" le probabilità condizionali di A dato ogni Bᵢ con le probabilità a priori dei Bᵢ.

Inferenza Bayesiana

Teorema di Bayes: Fondamento dell'Inferenza

Il teorema di Bayes è il pilastro dell'inferenza probabilistica:

P(HE)=P(EH)P(H)P(E)P(H|E) = \frac{P(E|H) \cdot P(H)}{P(E)}

Componenti del teorema:

  • P(H|E): probabilità a posteriori - quanto siamo certi dell'ipotesi H dopo aver osservato l'evidenza E
  • P(E|H): verosimiglianza - quanto è probabile osservare E se H fosse vera
  • P(H): probabilità a priori - quanto siamo certi di H prima di osservare E
  • P(E): evidenza marginale - probabilità totale di osservare E

Interpretazione Filosofica

Il teorema di Bayes formalizza il processo di aggiornamento delle credenze:

  1. Prior: Iniziamo con una credenza iniziale P(H)
  2. Likelihood: Osserviamo dati che ci dicono quanto sono compatibili con H
  3. Posterior: Aggiorniamo la nostra credenza ottenendo P(H|E)

Esempio medico:

  • H = {paziente ha la malattia}
  • E = {test risulta positivo}
  • P(H) = prevalenza della malattia nella popolazione
  • P(E|H) = sensibilità del test
  • P(H|E) = probabilità che il paziente abbia la malattia dato il test positivo

Calcolo dell'Evidenza Marginale

L'evidenza marginale si calcola usando la probabilità totale:

P(E)=iP(EHi)P(Hi)P(E) = \sum_i P(E|H_i) \cdot P(H_i)

dove {H₁, H₂, ..., Hₙ} rappresenta tutte le possibili ipotesi.

Ruolo di normalizzazione: P(E) assicura che le probabilità a posteriori sommino a 1.

Classificatori Bayesiani

Naive Bayes: Assunzione di Indipendenza

Il classificatore Naive Bayes assume che le features siano condizionalmente indipendenti data la classe:

P(Cx1,x2,,xn)=P(C)i=1nP(xiC)P(x1,x2,,xn)P(C|x_1, x_2, \ldots, x_n) = \frac{P(C) \prod_{i=1}^n P(x_i|C)}{P(x_1, x_2, \ldots, x_n)}

Perché "Naive"? L'assunzione di indipendenza condizionale è spesso irrealistica ma rende il modello:

  • Computazionalmente efficiente
  • Robusto con pochi dati
  • Sorprendentemente efficace in pratica

Classificazione: Dato un nuovo esempio x, si sceglie la classe che massimizza P(C|x):

C^=argmaxCP(C)i=1nP(xiC)\hat{C} = \arg\max_C P(C) \prod_{i=1}^n P(x_i|C)

Varianti di Naive Bayes:

  • Gaussiano: per features continue con distribuzione normale
  • Multinomiale: per dati di conteggio (es. frequenza parole)
  • Bernoulli: for features binarie

Analisi Discriminante

L'analisi discriminante cerca funzioni che separino ottimalmente le classi nello spazio delle features.

Linear Discriminant Analysis (LDA):

  • Assume che le classi abbiano la stessa matrice di covarianza
  • Trova direzioni che massimizzano la separazione tra classi
  • Produce confini di decisione lineari

Quadratic Discriminant Analysis (QDA):

  • Permette matrici di covarianza diverse per classe
  • Produce confini di decisione quadratici
  • Più flessibile ma richiede più dati

Funzione discriminante: gᵢ(x) = log P(Cᵢ|x) + costante La classificazione sceglie la classe con gᵢ(x) massimo.

Distribuzioni Probabilistiche

Distribuzione Binomiale

Modella il numero di successi in n prove indipendenti con probabilità p di successo:

P(X=k)=(nk)pk(1p)nkP(X = k) = \binom{n}{k} p^k (1-p)^{n-k}

Parametri:

  • n: numero di prove
  • p: probabilità di successo in ogni prova
  • k ∈ {0, 1, 2, ..., n}

Proprietà:

  • Media: E[X] = np
  • Varianza: Var(X) = np(1-p)
  • Per n grande e p non troppo vicino a 0 o 1: X ≈ N(np, np(1-p))

Applicazioni nel ML:

  • Modellare click-through rates
  • Validazione incrociata (successi/fallimenti)
  • A/B testing

Esempio: Numero di email spam in 100 email, se ogni email ha probabilità 0.3 di essere spam.

Distribuzione Multinomiale

Generalizzazione della binomiale per k > 2 esiti possibili:

P(X1=n1,,Xk=nk)=n!n1!nk!i=1kpiniP(X_1 = n_1, \ldots, X_k = n_k) = \frac{n!}{n_1! \cdots n_k!} \prod_{i=1}^k p_i^{n_i}

dove Σᵢ₌₁ᵏ nᵢ = n e Σᵢ₌₁ᵏ pᵢ = 1.

Proprietà:

  • Media: E[Xᵢ] = npᵢ
  • Varianza: Var(Xᵢ) = npᵢ(1-pᵢ)
  • Covarianza: Cov(Xᵢ, Xⱼ) = -npᵢpⱼ per i ≠ j

Applicazioni nel ML:

  • Modellare frequenze di parole in documenti
  • Classificazione multi-classe
  • Topic modeling

Distribuzione Beta

Distribuzione continua su [0,1], spesso usata come prior per parametri di probabilità:

f(x;α,β)=xα1(1x)β1B(α,β)f(x; \alpha, \beta) = \frac{x^{\alpha-1} (1-x)^{\beta-1}}{B(\alpha,\beta)}

dove B(α,β) è la funzione Beta di Eulero:

B(α,β)=01tα1(1t)β1dt=Γ(α)Γ(β)Γ(α+β)B(\alpha,\beta) = \int_0^1 t^{\alpha-1} (1-t)^{\beta-1} dt = \frac{\Gamma(\alpha)\Gamma(\beta)}{\Gamma(\alpha+\beta)}

Parametri e loro effetti:

  • α, β > 0: parametri di forma
  • α > 1, β > 1: distribuzione unimodale
  • α = β = 1: distribuzione uniforme su [0,1]
  • α < 1 o β < 1: distribuzione U-shaped

Proprietà:

  • Media: E[X] = α/(α+β)
  • Varianza: Var(X) = αβ/[(α+β)²(α+β+1)]
  • Moda (se α,β > 1): (α-1)/(α+β-2)

Coniugazione con Binomiale: Se X|θ ~ Binomial(n,θ) e θ ~ Beta(α,β), allora: θ|X ~ Beta(α + X, β + n - X)

Applicazioni:

  • Prior per probabilità di successo
  • Modellare proporzioni
  • Bayesian A/B testing

Distribuzione di Dirichlet

Generalizzazione multivariata della Beta, definita sul simplesso (k-1)-dimensionale:

f(x1,,xk;α1,,αk)=Γ(i=1kαi)i=1kΓ(αi)i=1kxiαi1f(x_1,\ldots,x_k; \alpha_1,\ldots,\alpha_k) = \frac{\Gamma(\sum_{i=1}^k \alpha_i)}{\prod_{i=1}^k \Gamma(\alpha_i)} \prod_{i=1}^k x_i^{\alpha_i-1}

soggetta ai vincoli: xᵢ ≥ 0 per tutti i, e Σᵢ₌₁ᵏ xᵢ = 1.

Proprietà:

  • Media: E[Xᵢ] = αᵢ/α₀ dove α₀ = Σⱼ αⱼ
  • Varianza: Var(Xᵢ) = αᵢ(α₀-αᵢ)/[α₀²(α₀+1)]
  • Covarianza: Cov(Xᵢ,Xⱼ) = -αᵢαⱼ/[α₀²(α₀+1)] per i≠j

Coniugazione con Multinomiale: Se X|θ ~ Multinomial(n,θ) e θ ~ Dirichlet(α), allora: θ|X ~ Dirichlet(α + X)

Applicazioni fondamentali:

  • Prior per distribuzioni di probabilità discrete
  • Topic modeling (LDA)
  • Modellare proporzioni multi-categoriali

Il Simplesso: Spazio delle Probabilità

Definizione Geometrica

Il simplesso (k-1)-dimensionale è lo spazio di tutti i vettori di probabilità k-dimensionali:

Sk1={xRk:xi0 per tutti i,i=1kxi=1}S^{k-1} = \left\{x \in \mathbb{R}^k : x_i \geq 0 \text{ per tutti i}, \sum_{i=1}^k x_i = 1\right\}

Esempi:

  • k=2: Simplesso 1D = segmento [0,1] (dopo normalizzazione)
  • k=3: Simplesso 2D = triangolo equilatero
  • k=4: Simplesso 3D = tetraedro

Proprietà Geometriche

Vertici: I k vertici del simplesso sono i vettori unitari eᵢ = (0,...,0,1,0,...,0).

Centro: Il centro del simplesso è (1/k, 1/k, ..., 1/k).

Distanze: La distanza euclidea tra due punti x, y sul simplesso è:

d(x,y)=i=1k(xiyi)2d(x,y) = \sqrt{\sum_{i=1}^k (x_i - y_i)^2}

Importanza nel Machine Learning

Il simplesso è fondamentale perché:

  1. Distribuzioni discrete vivono naturalmente sul simplesso
  2. Output di softmax sono punti sul simplesso
  3. Mixture models hanno pesi che vivono sul simplesso
  4. Topic models rappresentano distribuzioni di topic sul simplesso

Operazioni sul Simplesso

Proiezione su simplesso: Data un vettore v ∈ ℝᵏ, la proiezione sul simplesso si ottiene risolvendo:

minxxv2 soggetto a xSk1\min_x \|x - v\|^2 \text{ soggetto a } x \in S^{k-1}

Distanza di Bregman: Spesso si usano divergenze invece della distanza euclidea:

  • Divergenza KL: D_KL(x||y) = Σᵢ xᵢ log(xᵢ/yᵢ)
  • Divergenza di Hellinger: D_H(x,y) = Σᵢ (√xᵢ - √yᵢ)²

Applicazioni nei Modelli Generativi

Latent Dirichlet Allocation (LDA)

LDA è un modello probabilistico generativo per collezioni di documenti che assume:

Processo generativo:

  1. Per ogni documento d:
    • Campiona una distribuzione di topic θ_d ~ Dirichlet(α)
  2. Per ogni topic k:
    • Campiona una distribuzione di parole φ_k ~ Dirichlet(β)
  3. Per ogni parola w in documento d:
    • Campiona un topic z ~ Multinomial(θ_d)
    • Campiona una parola w ~ Multinomial(φ_z)

Inferenza: L'obiettivo è stimare θ e φ dati i documenti osservati.

Algoritmi di inferenza:

  • Variational Bayes: Approssima la posterior con una distribuzione più semplice
  • Gibbs Sampling: Campiona dalla posterior usando MCMC
  • Collapsed Gibbs: Integra analyticamente alcuni parametri

Applicazioni:

  • Topic modeling
  • Raccomandazione di contenuti
  • Analisi di sentiment
  • Dimensionality reduction

Probabilistic Latent Semantic Indexing (pLSI)

pLSI è un predecessore di LDA che modella:

P(wd)=z=1KP(wz)P(zd)P(w|d) = \sum_{z=1}^K P(w|z) P(z|d)

Differenze con LDA:

  • Non ha prior sui parametri (non è completamente Bayesiano)
  • Più semplice ma meno robusto all'overfitting
  • Non può generare nuovi documenti facilmente

Hidden Markov Models (HMM)

Gli HMM sono utili per sequenze temporali:

Componenti:

  • Stati nascosti: S = {s₁, s₂, ..., sₙ}
  • Osservazioni: O = {o₁, o₂, ..., oₘ}
  • Matrice di transizione: A = {aᵢⱼ} dove aᵢⱼ = P(sⱼ at t+1 | sᵢ at t)
  • Matrice di emissione: B = {bᵢₖ} dove bᵢₖ = P(oₖ | sᵢ)
  • Distribuzione iniziale: π = {πᵢ} dove πᵢ = P(s₁ = sᵢ)

Problemi fondamentali:

  1. Evaluation: P(O|λ) - probabilità della sequenza osservata
  2. Decoding: Sequenza di stati più probabile data l'osservazione
  3. Learning: Stimare i parametri λ = (A, B, π)

Metodi di Stima dei Parametri

Maximum Likelihood Estimation (MLE)

L'MLE trova i parametri che massimizzano la verosimiglianza dei dati osservati:

θ^MLE=argmaxθL(θ)=argmaxθi=1nP(xiθ)\hat{\theta}_{MLE} = \arg\max_\theta L(\theta) = \arg\max_\theta \prod_{i=1}^n P(x_i|\theta)

Spesso si lavora con la log-verosimiglianza:

θ^MLE=argmaxθlogL(θ)=argmaxθi=1nlogP(xiθ)\hat{\theta}_{MLE} = \arg\max_\theta \log L(\theta) = \arg\max_\theta \sum_{i=1}^n \log P(x_i|\theta)

Proprietà dell'MLE:

  • Consistenza: Converge al valore vero per n → ∞
  • Invarianza: Se θ̂ è MLE di θ, allora g(θ̂) è MLE di g(θ)
  • Efficienza asintotica: Ha la minima varianza asintotica

Esempi:

  • Binomiale: θ̂ = Σxᵢ/n (frequenza relativa)
  • Normale: μ̂ = Σxᵢ/n, σ̂² = Σ(xᵢ-μ̂)²/n
  • Multinomiale: θ̂ⱼ = Σᵢ xᵢⱼ/(n·k)

Maximum A Posteriori (MAP)

Il MAP trova i parametri che massimizzano la probabilità a posteriori:

θ^MAP=argmaxθP(θX)=argmaxθP(Xθ)P(θ)\hat{\theta}_{MAP} = \arg\max_\theta P(\theta|X) = \arg\max_\theta P(X|\theta) P(\theta)

Relazione con MLE: MAP = MLE + regolarizzazione (dal prior)

Vantaggi del MAP:

  • Incorpora conoscenza a priori
  • Può prevenire overfitting
  • Funziona meglio con dati limitati

Esempi:

  • Con prior Beta per Binomiale: Aggiunge "pseudo-count" α-1 e β-1
  • Con prior Gaussiano per regressione: Equivale a ridge regression
  • Con prior Laplace: Equivale a LASSO

Stima Bayesiana Completa

Invece di stimare un singolo valore, si considera l'intera distribuzione a posteriori:

P(θX)=P(Xθ)P(θ)P(X)P(\theta|X) = \frac{P(X|\theta) P(\theta)}{P(X)}

Predizione: Per un nuovo dato x*:

P(xX)=P(xθ)P(θX)dθP(x^*|X) = \int P(x^*|\theta) P(\theta|X) d\theta

Vantaggi:

  • Quantifica completamente l'incertezza
  • Predizioni più robuste (averaging)
  • Principled way per model selection

Sfide computazionali:

  • Calcolo dell'integrale spesso intrattabile
  • Necessità di metodi approssimati (MCMC, Variational Inference)

Problemi e Soluzioni Pratiche

Overfitting nei Modelli Probabilistici

Cause:

  • Troppi parametri rispetto ai dati
  • Modello troppo complesso
  • Lack of regularization

Soluzioni Bayesiane:

  • Prior informativi: Incorporano conoscenza del dominio
  • Hierarchical models: Prior che dipendono da iperparametri
  • Model averaging: Media su diversi modelli possibili

Curse of Dimensionality

Problema: In alta dimensionalità, i dati diventano sparsi e la stima diventa difficile.

Effetti specifici:

  • Naive Bayes: Molte features possono violare l'assunzione di indipendenza
  • Density estimation: Servono esponenzialmente più dati
  • Nearest neighbors: Tutti i punti diventano equidistanti

Soluzioni:

  • Feature selection: Scegliere subset rilevanti di features
  • Dimensionality reduction: PCA, t-SNE, autoencoders
  • Regularization: L1, L2, elastic net
  • Bayesian methods: Prior che favoriscono sparsità

Problemi di Smoothing

Problema delle probabilità zero: P(xᵢ|C) = 0 può causare problemi in Naive Bayes.

Tecniche di smoothing:

Laplace Smoothing (Add-one):

P(xiC)=count(xi,C)+1count(C)+VP(x_i|C) = \frac{\text{count}(x_i, C) + 1}{\text{count}(C) + V}

dove V è la dimensione del vocabolario.

Add-k Smoothing:

P(xiC)=count(xi,C)+kcount(C)+kVP(x_i|C) = \frac{\text{count}(x_i, C) + k}{\text{count}(C) + kV}

Good-Turing Smoothing: Redistribuisce massa di probabilità basandosi su frequenze di frequenze.

Kneser-Ney Smoothing: Usa contesto per smoothing più sofisticato.

Computational Issues

Underflow numerico: Prodotti di molte probabilità piccole possono causare underflow. Soluzione: Lavorare in log-space: log(ab) = log(a) + log(b)

Normalizzazione instabile: In softmax: exp(xᵢ) può essere molto grande. Soluzione: Log-sum-exp trick: log(Σᵢ exp(xᵢ)) = max(x) + log(Σᵢ exp(xᵢ - max(x)))

Applicazioni Avanzate

Bayesian Networks

Definizione: Modelli grafici che rappresentano dipendenze condizionali tra variabili.

Componenti:

  • DAG (Directed Acyclic Graph): Nodi = variabili, archi = dipendenze
  • CPT (Conditional Probability Tables): P(Xᵢ|Parents(Xᵢ))

Factorization: La joint distribution si fattorizza come:

P(X1,,Xn)=i=1nP(XiParents(Xi))P(X_1,\ldots,X_n) = \prod_{i=1}^n P(X_i|\text{Parents}(X_i))

Inferenza: Calcolare P(Query|Evidence) usando:

  • Variable elimination
  • Message passing
  • Sampling methods

Variational Inference

Idea: Approssimare distribuzioni intrattabili con distribuzioni più semplici.

Mean Field Approximation: Assumere che le variabili siano indipendenti:

q(z1,,zn)=i=1nqi(zi)q(z_1,\ldots,z_n) = \prod_{i=1}^n q_i(z_i)

ELBO (Evidence Lower BOund):

logP(x)Eq[logP(x,z)]Eq[logq(z)]=ELBO\log P(x) \geq \mathbb{E}_q[\log P(x,z)] - \mathbb{E}_q[\log q(z)] = \text{ELBO}

Algoritmo: Massimizzare ELBO alternando tra aggiornamenti di qᵢ.

Markov Chain Monte Carlo (MCMC)

Obiettivo: Campionare da distribuzioni complesse quando il campionamento diretto è impossibile.

Metropolis-Hastings:

  1. Proponi nuovo stato x' da distribuzione q(x'|x)
  2. Calcola acceptance probability: α = min(1, P(x')q(x|x')/[P(x)q(x'|x)])
  3. Accetta con probabilità α

Gibbs Sampling: Caso speciale dove si campiona da distribuzioni condizionali complete.

Hamiltonian Monte Carlo: Usa gradienti per proposte più efficienti.

Esercizi e Applicazioni Pratiche

Esercizi Teorici

Esercizio 1 - Teorema di Bayes Un test medico per una malattia rara ha sensibilità 99% (P(T+|M+) = 0.99) e specificità 95% (P(T-|M-) = 0.95). La prevalenza della malattia è 0.1% (P(M+) = 0.001). Se una persona risulta positiva al test, qual è la probabilità che abbia effettivamente la malattia?

Soluzione:

P(M+T+)=P(T+M+)×P(M+)P(T+)P(M+|T+) = \frac{P(T+|M+) \times P(M+)}{P(T+)}
P(T+)=P(T+M+)×P(M+)+P(T+M)×P(M)P(T+) = P(T+|M+) \times P(M+) + P(T+|M-) \times P(M-)
P(T+)=0.99×0.001+0.05×0.999=0.00099+0.04995=0.05094P(T+) = 0.99 \times 0.001 + 0.05 \times 0.999 = 0.00099 + 0.04995 = 0.05094
P(M+T+)=0.99×0.0010.050940.01941.94%P(M+|T+) = \frac{0.99 \times 0.001}{0.05094} \approx 0.0194 \approx 1.94\%

Nonostante l'alta accuratezza del test, la probabilità è solo del 1.94% a causa della bassa prevalenza.

Esercizio 2 - Naive Bayes Dato un dataset di email con features:

  • Contiene "gratis": P(gratis|spam) = 0.8, P(gratis|ham) = 0.1
  • Contiene "!": P(!|spam) = 0.7, P(!|ham) = 0.2
  • P(spam) = 0.3, P(ham) = 0.7

Classificare un'email che contiene sia "gratis" che "!".

Soluzione:

P(spamgratis,!)P(spam)×P(gratisspam)×P(!spam)P(\text{spam}|\text{gratis},!) \propto P(\text{spam}) \times P(\text{gratis}|\text{spam}) \times P(!|\text{spam})
P(spamgratis,!)0.3×0.8×0.7=0.168P(\text{spam}|\text{gratis},!) \propto 0.3 \times 0.8 \times 0.7 = 0.168
P(hamgratis,!)P(ham)×P(gratisham)×P(!ham)P(\text{ham}|\text{gratis},!) \propto P(\text{ham}) \times P(\text{gratis}|\text{ham}) \times P(!|\text{ham})
P(hamgratis,!)0.7×0.1×0.2=0.014P(\text{ham}|\text{gratis},!) \propto 0.7 \times 0.1 \times 0.2 = 0.014

Classificazione: spam (0.168 > 0.014)

Implementazioni Python

Implementazione Naive Bayes da Zero:

import numpy as np
from collections import defaultdict

class NaiveBayes:
    def __init__(self, smoothing=1.0):
        self.smoothing = smoothing
        self.class_priors = {}
        self.feature_probs = defaultdict(dict)
        self.classes = []
        
    def fit(self, X, y):
        self.classes = np.unique(y)
        n_samples = len(y)
        
        # Calcola probabilità a priori delle classi
        for c in self.classes:
            self.class_priors[c] = np.sum(y == c) / n_samples
            
        # Calcola probabilità condizionali delle features
        for feature_idx in range(X.shape[1]):
            feature_values = np.unique(X[:, feature_idx])
            
            for c in self.classes:
                class_mask = (y == c)
                class_feature_counts = X[class_mask, feature_idx]
                
                for value in feature_values:
                    count = np.sum(class_feature_counts == value)
                    total = np.sum(class_mask)
                    
                    # Laplace smoothing
                    prob = (count + self.smoothing) / (total + self.smoothing * len(feature_values))
                    self.feature_probs[feature_idx][(c, value)] = prob
    
    def predict_proba(self, X):
        predictions = []
        
        for sample in X:
            class_scores = {}
            
            for c in self.classes:
                score = np.log(self.class_priors[c])
                
                for feature_idx, value in enumerate(sample):
                    if (c, value) in self.feature_probs[feature_idx]:
                        score += np.log(self.feature_probs[feature_idx][(c, value)])
                    else:
                        # Smoothing per valori non visti
                        score += np.log(self.smoothing / (np.sum([y == c]) + self.smoothing * 10))
                
                class_scores[c] = score
            
            # Normalizzazione
            max_score = max(class_scores.values())
            exp_scores = {c: np.exp(score - max_score) for c, score in class_scores.items()}
            total = sum(exp_scores.values())
            probabilities = {c: score/total for c, score in exp_scores.items()}
            
            predictions.append(probabilities)
        
        return predictions
    
    def predict(self, X):
        probas = self.predict_proba(X)
        return [max(proba.keys(), key=lambda k: proba[k]) for proba in probas]

Implementazione Distribuzione Dirichlet:

import numpy as np
from scipy.special import gammaln

class DirichletDistribution:
    def __init__(self, alpha):
        self.alpha = np.array(alpha)
        self.k = len(alpha)
        
    def pdf(self, x):
        """Calcola la densità di probabilità"""
        x = np.array(x)
        
        # Verifica vincoli del simplesso
        if not (np.all(x >= 0) and np.abs(np.sum(x) - 1) < 1e-10):
            return 0.0
        
        # Log-densità per stabilità numerica
        log_pdf = (gammaln(np.sum(self.alpha)) - 
                   np.sum(gammaln(self.alpha)) + 
                   np.sum((self.alpha - 1) * np.log(x + 1e-10)))
        
        return np.exp(log_pdf)
    
    def sample(self, n_samples=1):
        """Campiona dalla distribuzione"""
        samples = np.random.gamma(self.alpha, 1, (n_samples, self.k))
        return samples / np.sum(samples, axis=1, keepdims=True)
    
    def mean(self):
        """Media della distribuzione"""
        return self.alpha / np.sum(self.alpha)
    
    def variance(self):
        """Varianza della distribuzione"""
        alpha0 = np.sum(self.alpha)
        return (self.alpha * (alpha0 - self.alpha)) / (alpha0**2 * (alpha0 + 1))
    
    def concentration(self):
        """Parametro di concentrazione"""
        return np.sum(self.alpha)

# Esempio d'uso
if __name__ == "__main__":
    # Distribuzione simmetrica
    dir_sym = DirichletDistribution([1, 1, 1])
    samples_sym = dir_sym.sample(1000)
    
    # Distribuzione concentrata
    dir_conc = DirichletDistribution([10, 2, 1])
    samples_conc = dir_conc.sample(1000)
    
    print("Media distribuzione simmetrica:", dir_sym.mean())
    print("Media distribuzione concentrata:", dir_conc.mean())

Caso di Studio: Sistema di Raccomandazione Probabilistico

Problema: Sviluppare un sistema di raccomandazione per articoli di news che incorpori incertezza nelle preferenze utente.

Approccio Bayesiano:

class BayesianRecommendationSystem:
    def __init__(self, n_topics=10, alpha=0.1, beta=0.01):
        self.n_topics = n_topics
        self.alpha = alpha  # Prior per distribuzione topic-utente
        self.beta = beta    # Prior per distribuzione parola-topic
        
        # Parametri del modello
        self.user_topic_dist = {}  # θ: distribuzione topic per utente
        self.topic_word_dist = {}  # φ: distribuzione parole per topic
        
    def fit(self, user_item_matrix, content_features):
        """
        Addestra il modello usando Variational Bayes
        
        Args:
            user_item_matrix: matrice utenti x articoli (ratings)
            content_features: features di contenuto degli articoli
        """
        n_users, n_items = user_item_matrix.shape
        n_words = content_features.shape[1]
        
        # Inizializza parametri variational
        gamma = np.random.gamma(1, 1, (n_users, self.n_topics))  # q(θ)
        lambda_param = np.random.gamma(1, 1, (self.n_topics, n_words))  # q(φ)
        
        # EM algorithm con Variational Bayes
        for iteration in range(100):
            # E-step: aggiorna distribuzioni a posteriori approssimate
            
            # Aggiorna γ (parametri per θ)
            for u in range(n_users):
                for k in range(self.n_topics):
                    gamma[u, k] = self.alpha + np.sum([
                        self._compute_topic_assignment_prob(u, i, k, gamma, lambda_param) 
                        * user_item_matrix[u, i]
                        for i in range(n_items) if user_item_matrix[u, i] > 0
                    ])
            
            # Aggiorna λ (parametri per φ)
            for k in range(self.n_topics):
                for w in range(n_words):
                    lambda_param[k, w] = self.beta + np.sum([
                        self._compute_word_topic_prob(i, w, k, gamma, lambda_param) 
                        * content_features[i, w]
                        for i in range(n_items)
                    ])
            
            # Verifica convergenza
            if iteration > 0 and self._check_convergence(gamma, lambda_param):
                break
        
        # Salva parametri finali
        self._save_parameters(gamma, lambda_param)
    
    def _compute_topic_assignment_prob(self, user, item, topic, gamma, lambda_param):
        """Calcola probabilità di assegnamento topic"""
        # Implementazione semplificata
        user_topic_score = gamma[user, topic] / np.sum(gamma[user, :])
        item_topic_score = np.sum(lambda_param[topic, :] * self.content_features[item, :])
        return user_topic_score * item_topic_score
    
    def predict_rating(self, user_id, item_id, n_samples=100):
        """
        Predice rating con quantificazione dell'incertezza
        
        Returns:
            mean_rating: rating medio predetto
            std_rating: deviazione standard della predizione
        """
        if user_id not in self.user_topic_dist:
            return 0.0, 1.0  # Utente nuovo: alta incertezza
        
        # Campiona dalle distribuzioni a posteriori
        ratings = []
        
        for _ in range(n_samples):
            # Campiona distribuzione topic per utente
            theta = np.random.dirichlet(self.user_topic_dist[user_id])
            
            # Calcola rating basato su similarity topic-based
            item_topics = self._get_item_topic_distribution(item_id)
            predicted_rating = np.dot(theta, item_topics) * 5  # Scala 1-5
            
            ratings.append(predicted_rating)
        
        return np.mean(ratings), np.std(ratings)
    
    def recommend_items(self, user_id, n_recommendations=10, uncertainty_threshold=0.5):
        """
        Raccomanda items considerando sia rating che incertezza
        """
        candidate_items = self._get_candidate_items(user_id)
        recommendations = []
        
        for item_id in candidate_items:
            mean_rating, std_rating = self.predict_rating(user_id, item_id)
            
            # Scarta items con alta incertezza
            if std_rating > uncertainty_threshold:
                continue
            
            # UCB-style ranking: bilancia exploitation vs exploration
            ucb_score = mean_rating + 0.1 * std_rating
            
            recommendations.append({
                'item_id': item_id,
                'predicted_rating': mean_rating,
                'uncertainty': std_rating,
                'ucb_score': ucb_score
            })
        
        # Ordina per UCB score
        recommendations.sort(key=lambda x: x['ucb_score'], reverse=True)
        
        return recommendations[:n_recommendations]

Frontiere della Ricerca

Probabilistic Programming

I linguaggi di programmazione probabilistica permettono di specificare modelli complessi in modo dichiarativo:

Esempio in PyMC3:

import pymc3 as pm
import theano.tensor as tt

def hierarchical_regression_model(X, y, groups):
    """
    Modello di regressione gerarchica Bayesiana
    """
    with pm.Model() as model:
        # Hyperpriors
        mu_alpha = pm.Normal('mu_alpha', mu=0, sd=1)
        sigma_alpha = pm.HalfNormal('sigma_alpha', sd=1)
        mu_beta = pm.Normal('mu_beta', mu=0, sd=1)
        sigma_beta = pm.HalfNormal('sigma_beta', sd=1)
        
        # Priors per group-level parameters
        alpha = pm.Normal('alpha', mu=mu_alpha, sd=sigma_alpha, shape=len(np.unique(groups)))
        beta = pm.Normal('beta', mu=mu_beta, sd=sigma_beta, shape=len(np.unique(groups)))
        
        # Likelihood
        mu = alpha[groups] + beta[groups] * X
        sigma = pm.HalfNormal('sigma', sd=1)
        y_obs = pm.Normal('y_obs', mu=mu, sd=sigma, observed=y)
        
        # Sampling
        trace = pm.sample(2000, tune=1000, cores=4)
        
    return model, trace

# Inferenza predittiva
def posterior_predictive_check(model, trace, X_new, groups_new):
    with model:
        # Predizioni out-of-sample
        pm.set_data({'X': X_new, 'groups': groups_new})
        posterior_pred = pm.sample_posterior_predictive(trace, samples=1000)
        
    return posterior_pred

Causal Inference

Andare oltre le correlazioni per scoprire relazioni causali:

Pearl's Causal Hierarchy:

  1. Association (P(Y|X)): correlazioni statistiche
  2. Intervention (P(Y|do(X))): effetti di interventi
  3. Counterfactuals (P(Y_x|X',Y')): reasoning controfattuale

Esempio - Causal Effect Estimation:

class CausalInferenceFramework:
    def __init__(self, confounders, treatment, outcome):
        self.confounders = confounders
        self.treatment = treatment
        self.outcome = outcome
        
    def estimate_ate(self, data, method='ipw'):
        """
        Stima Average Treatment Effect usando diversi metodi
        
        Args:
            data: dataframe con confounders, treatment, outcome
            method: 'ipw' (Inverse Propensity Weighting), 'doubly_robust', etc.
        """
        if method == 'ipw':
            return self._inverse_propensity_weighting(data)
        elif method == 'doubly_robust':
            return self._doubly_robust_estimation(data)
        else:
            raise ValueError(f"Unknown method: {method}")
    
    def _inverse_propensity_weighting(self, data):
        """Inverse Propensity Weighting"""
        # Stima propensity scores
        propensity_model = LogisticRegression()
        propensity_model.fit(data[self.confounders], data[self.treatment])
        propensity_scores = propensity_model.predict_proba(data[self.confounders])[:, 1]
        
        # Calcola pesi IPW
        weights = np.where(data[self.treatment] == 1, 
                          1/propensity_scores, 
                          1/(1-propensity_scores))
        
        # Stima ATE
        treated_outcome = np.average(data[data[self.treatment] == 1][self.outcome], 
                                   weights=weights[data[self.treatment] == 1])
        control_outcome = np.average(data[data[self.treatment] == 0][self.outcome], 
                                   weights=weights[data[self.treatment] == 0])
        
        ate = treated_outcome - control_outcome
        
        # Bootstrap per uncertainty quantification
        bootstrap_ates = []
        for _ in range(1000):
            boot_indices = np.random.choice(len(data), len(data), replace=True)
            boot_data = data.iloc[boot_indices]
            boot_ate = self._inverse_propensity_weighting(boot_data)
            bootstrap_ates.append(boot_ate)
        
        std_error = np.std(bootstrap_ates)
        
        return {
            'ate': ate,
            'std_error': std_error,
            'confidence_interval': np.percentile(bootstrap_ates, [2.5, 97.5])
        }

Scalable Bayesian Methods

Stochastic Variational Inference:

class StochasticVariationalInference:
    def __init__(self, model, batch_size=100, learning_rate=0.01):
        self.model = model
        self.batch_size = batch_size
        self.learning_rate = learning_rate
        
    def fit(self, data, n_epochs=100):
        """
        Addestra usando mini-batch gradient ascent su ELBO
        """
        n_samples = len(data)
        variational_params = self._initialize_variational_params()
        
        for epoch in range(n_epochs):
            # Shuffle data
            shuffled_data = data.sample(frac=1).reset_index(drop=True)
            
            epoch_elbo = 0
            for i in range(0, n_samples, self.batch_size):
                batch = shuffled_data[i:i+self.batch_size]
                
                # Calcola stochastic gradient dell'ELBO
                elbo, grad = self._compute_stochastic_elbo_gradient(
                    batch, variational_params, n_samples
                )
                
                # Update variational parameters
                variational_params = self._update_parameters(
                    variational_params, grad, self.learning_rate
                )
                
                epoch_elbo += elbo
            
            if epoch % 10 == 0:
                print(f"Epoch {epoch}, ELBO: {epoch_elbo/n_samples:.4f}")
        
        return variational_params
    
    def _compute_stochastic_elbo_gradient(self, batch, var_params, total_samples):
        """
        Calcola gradiente stocastico dell'ELBO
        """
        batch_size = len(batch)
        scaling_factor = total_samples / batch_size
        
        # Monte Carlo estimate del gradiente
        mc_samples = 10
        grad_estimates = []
        
        for _ in range(mc_samples):
            # Campiona da distribuzione variational
            z_sample = self._sample_from_variational(var_params)
            
            # Calcola gradiente per questo sample
            log_likelihood = self.model.log_likelihood(batch, z_sample)
            log_prior = self.model.log_prior(z_sample)
            log_variational = self._log_variational_density(z_sample, var_params)
            
            # ELBO sample
            elbo_sample = scaling_factor * log_likelihood + log_prior - log_variational
            
            # Gradient usando score function estimator
            grad = self._score_function_gradient(z_sample, var_params, elbo_sample)
            grad_estimates.append(grad)
        
        # Media dei gradienti
        final_grad = np.mean(grad_estimates, axis=0)
        final_elbo = np.mean([est.elbo for est in grad_estimates])
        
        return final_elbo, final_grad

Considerazioni Etiche e Filosofiche

Interpretabilità vs Prestazioni

I modelli probabilistici offrono spesso maggiore interpretabilità rispetto ai metodi black-box, ma questo può venire a scapito delle prestazioni:

Vantaggi dell'interpretabilità:

  • Fiducia nelle decisioni automatiche
  • Debugging e miglioramento del modello
  • Compliance con regulazioni (GDPR, etc.)
  • Scoperta di bias nascosti

Trade-offs:

  • Modelli più semplici potrebbero avere prestazioni inferiori
  • L'interpretabilità ha costi computazionali
  • Diverse stakeholder richiedono diversi tipi di spiegazioni

Bias e Fairness

I modelli probabilistici possono perpetuare o amplificare bias presenti nei dati:

Tipi di bias:

  • Selection bias: campioni non rappresentativi
  • Confirmation bias: prior informativi incorretti
  • Algorithmic bias: assunzioni del modello inappropriate

Mitigazione:

  • Auditing regolare dei modelli
  • Fairness constraints nell'ottimizzazione
  • Diversità nei team di sviluppo
  • Testing su popolazioni diverse

Privacy e Sicurezza

Differential Privacy: Garantire che le predizioni non rivelino informazioni su individui specifici:

def add_laplace_noise(value, sensitivity, epsilon):
    """
    Aggiunge rumore Laplaciano per differential privacy
    
    Args:
        value: valore originale
        sensitivity: sensibilità della query
        epsilon: privacy budget
    """
    scale = sensitivity / epsilon
    noise = np.random.laplace(0, scale)
    return value + noise

class PrivacyPreservingBayesianModel:
    def __init__(self, epsilon=1.0):
        self.epsilon = epsilon  # Privacy budget
        
    def private_posterior_sampling(self, data, n_samples=1000):
        """
        Campiona dalla posterior aggiungendo rumore per privacy
        """
        # Calcola sufficient statistics
        sufficient_stats = self._compute_sufficient_statistics(data)
        
        # Aggiungi rumore differentially private
        noisy_stats = [
            add_laplace_noise(stat, self._compute_sensitivity(stat), self.epsilon)
            for stat in sufficient_stats
        ]
        
        # Campiona da posterior usando noisy statistics
        posterior_samples = self._sample_posterior(noisy_stats, n_samples)
        
        return posterior_samples

Riferimenti e Approfondimenti

Testi Fondamentali

  1. "Pattern Recognition and Machine Learning" - Christopher Bishop

    • Trattazione completa di metodi probabilistici nel ML
    • Excellent balance tra teoria e pratica
  2. "The Elements of Statistical Learning" - Hastie, Tibshirani, Friedman

    • Fondamenti statistici del machine learning
    • Approfondimenti su bias-variance trade-off
  3. "Probabilistic Graphical Models" - Daphne Koller & Nir Friedman

    • Guida definitiva ai modelli grafici
    • Teoria e algoritmi per inference
  4. "Bayesian Data Analysis" - Gelman, Carlin, Stern, Rubin

    • Approccio pratico all'analisi Bayesiana
    • Molti esempi reali e case studies

Risorse Online

Software e Librerie

Python:

  • PyMC3/PyMC4: Probabilistic programming
  • Stan (PyStan): Bayesian inference platform
  • Scikit-learn: Implementazioni standard
  • TensorFlow Probability: Deep probabilistic models

R:

  • BUGS/JAGS: Bayesian inference usando Gibbs sampling
  • rstanarm: Interface R per Stan
  • MCMCpack: MCMC algorithms

Specialized:

  • Church/WebPPL: Probabilistic programming languages
  • Edward/TensorFlow Probability: Deep probabilistic models
  • Pyro: Deep universal probabilistic programming

Conclusioni Finali

La teoria della probabilità rappresenta il linguaggio matematico fondamentale per modellare l'incertezza nel machine learning e nell'intelligenza artificiale. I concetti esplorati in questi appunti - dall'inferenza Bayesiana ai modelli generativi, dalle distribuzioni classiche alle applicazioni moderne - forniscono gli strumenti concettuali necessari per sviluppare sistemi intelligenti robusti e affidabili.

Punti chiave da ricordare:

  1. Principi Fondamentali: Gli assiomi della probabilità e il teorema di Bayes sono alla base di tutto il reasoning probabilistico

  2. Modeling: La scelta della distribuzione e dei prior è cruciale per il successo del modello

  3. Computation: L'implementazione efficiente richiede attenzione ai problemi numerici e alla scalabilità

  4. Validation: La valutazione deve includere non solo accuracy ma anche calibration e robustezza

  5. Ethics: Considerazioni su bias, fairness e privacy sono essenziali per applicazioni responsabili

Il futuro del machine learning probabilistico è ricco di opportunità, dall'integrazione con deep learning alla scalabilità per big data, dalla causal inference alle applicazioni in domini critici come medicina e finanza. La padronanza di questi concetti fondamentali è essenziale per contribuire a questo campo in rapida evoluzione e per sviluppare la prossima generazione di sistemi intelligenti.