Vai al contenuto
coden

Modelli di Markov nascosti: l’algoritmo di Viterbi e il casinò disonesto

Modello di Markov nascosto, i tre problemi secondo Rabiner, algoritmo forward, algoritmo di Viterbi, decodifica a posteriori e algoritmo di Baum-Welch — su un casinò che di tanto in tanto sostituisce di nascosto il dado con uno truccato.

14 minuti Matěj Žalmánek 2026-10-15

Serie Basi di IA Parte 39 di 52
  1. Mappa dell’intelligenza artificiale: che cos’è ciascuna cosa e come si incastra tutto
  2. Breve storia dell’intelligenza artificiale: da Turing ai modelli linguistici
  3. Dati, modello, addestramento, inferenza: i concetti di base su un unico esempio
  4. Come leggere la notazione matematica: simboli, indici, sommatorie e quantificatori
  5. Insiemi e relazioni: i mattoncini con cui si costruiscono le definizioni nell’IA
  6. Funzioni e applicazioni: perché ogni modello di IA è una funzione
  7. Logica e dimostrazioni: le regole con cui si ragiona nell’IA
  8. Grafi e alberi: la struttura su cui poggiano la ricerca e le reti neurali
  9. Algebra lineare per l’apprendimento automatico: vettori, matrici e perché l’IA soprattutto moltiplica
  10. Derivate, gradiente e regola della catena: come un modello capisce in quale direzione imparare
  11. Discesa del gradiente: come impara un modello
  12. Che cos’è la probabilità: dal lancio di un dado agli assiomi di Kolmogorov
  13. Probabilità condizionata e teorema di Bayes: come cambiare una stima quando scopri qualcosa di nuovo
  14. Variabile aleatoria e funzione di ripartizione: una sola funzione che dice tutto sulla distribuzione
  15. Funzione di densità e funzione di massa: perché una variabile continua ha probabilità zero in un punto
  16. Valore atteso, varianza e covarianza: dove si trova una distribuzione, quanto è larga e che cosa è legato a che cosa
  17. Panoramica delle distribuzioni di probabilità: Bernoulli, binomiale, Poisson, uniforme, normale ed esponenziale
  18. Legge dei grandi numeri e teorema del limite centrale: perché le medie si comportano in modo così prevedibile
  19. Statistica e stima dei parametri: massima verosimiglianza, MAP e intervalli di confidenza
  20. Teoria dell’informazione: entropia, entropia incrociata e divergenza KL — da dove vengono le funzioni di perdita
  21. Ricerca nello spazio degli stati: BFS, DFS, UCS e A* con animazioni
  22. Giochi e avversari: minimax, potatura alfa-beta e Monte Carlo tree search
  23. Soddisfacimento di vincoli: backtracking, propagazione dei vincoli e AC-3 su un sudoku e una mappa delle regioni ceche
  24. Logica e sistemi basati sulla conoscenza: concatenazione in avanti e all’indietro, risoluzione e sistemi esperti
  25. Pianificazione: STRIPS, il mondo dei blocchi, euristiche dal rilassamento e pianificazione negli agenti IA
  26. Ricerca locale e ricottura simulata: un commesso viaggiatore tra i capoluoghi di regione cechi
  27. Algoritmi genetici: popolazione, incrocio, mutazione e selezione su uno zaino per l’escursione
  28. Strategie evolutive e programmazione genetica: dalla regola di un quinto alla legge di Keplero
  29. Ottimizzazione con colonie di formiche: come una traccia di feromone trova un percorso breve
  30. Ottimizzazione a sciame di particelle: inerzia, esperienza propria ed esperienza dello sciame
  31. Neuroevoluzione: evoluzione dei pesi e della forma delle reti neurali e l’algoritmo NEAT
  32. Insiemi fuzzy e funzioni di appartenenza: come scrivere «un po’ caldo»
  33. Logica fuzzy e probabilità: perché «un po’ caldo» non significa «probabilmente caldo»
  34. Logica fuzzy: t-norme, implicazioni e regole SE–ALLORA
  35. Controllo fuzzy: Mamdani e Sugeno, dal ventilatore al pendolo inverso su un carrello
  36. Clustering fuzzy: l’algoritmo fuzzy c-means e i punti tra i cluster
  37. Sistemi neuro-fuzzy (ANFIS): regole fuzzy che imparano dai dati
  38. Catene di Markov: matrice di transizione, distribuzione stazionaria e PageRank
  39. Modelli di Markov nascosti: l’algoritmo di Viterbi e il casinò disonesto
  40. Reti bayesiane: perché l’erba è bagnata e che cosa significa per l’irrigatore
  41. Filtro di Kalman: come stimare da misure imprecise dove si trova un’auto e a che velocità va
  42. Tipi di apprendimento automatico: supervisionato, non supervisionato, semi-supervisionato e altri
  43. Prompt, contesto e RAG: come dare a un modello le informazioni giuste
  44. Autoencoder variazionali (VAE): immagini nuove da uno spazio latente continuo
  45. Reti generative avversarie (GAN): il falsario contro il detective
  46. Modelli di diffusione: come il rumore puro diventa un’immagine
  47. Processi decisionali di Markov: stati, azioni, ricompense ed equazioni di Bellman
  48. Q-learning e DQN: imparare il valore delle azioni, dalla tabella alla rete neurale
  49. Policy gradient e PPO: imparare direttamente la politica
  50. Come funziona un agente IA: un modello linguistico in un ciclo con strumenti
  51. Programmare con agenti IA: incarico, test e controllo
  52. Valutazione e sicurezza dei sistemi di IA: come misurare e come sorvegliare

Programma completo della serie 52 di 83 argomenti pubblicati

Cosa porterai a casa
  • Che cos’è un modello di Markov nascosto e come si calcola la probabilità delle osservazioni senza passare in rassegna tutti i cammini.
  • Come l’algoritmo di Viterbi trova gli stati nascosti più probabili e dove sbaglia.
  • Come il modello apprende i parametri dalle sole osservazioni e quanti dati gli servono.

Utile sapere prima: Catene di Markov: matrice di transizione, distribuzione stazionaria e PageRank

Nella parte precedente vedevamo lo stato della catena: oggi c’è il sole, domani piove. Spesso però ne vediamo solo gli effetti. Un microfono capta un’onda sonora, non la parola che l’ha prodotta. Un sequenziatore legge le lettere del DNA, non il punto in cui comincia un gene. E al casinò vediamo che numero è uscito, non con quale dado sta lanciando il croupier. I modelli di Markov nascosti sono catene i cui stati non vediamo — insieme agli algoritmi che permettono comunque di capire che cosa succede all’interno. Per decenni il riconoscimento vocale si è basato su di loro.

Questa parte prosegue l’articolo Catene di Markov.

1. Il casinò disonesto

Un classico esempio da manuale (Durbin et al., 1998): il croupier lancia un dado onesto oppure uno truccato, sul quale il sei esce con probabilità 1/2 e ciascuno degli altri numeri con 1/10. Dopo ogni lancio, con probabilità 0,05 sostituisce il dado onesto con quello truccato e con probabilità 0,1 rimette quello onesto al posto del truccato. Lo scambio non lo vediamo. Vediamo solo una serie di numeri — nel nostro esperimento 300 lanci, che cominciano così:

163665521623524236522511663566613666222461626262162666626516…

Ci chiediamo: qui si gioca onestamente? E se no, quando esattamente era in gioco il dado truccato?

2. Modello di Markov nascosto

Definizione — modello di Markov nascosto

Un modello di Markov nascosto (HMM) λ = (π, A, B) ha stati nascosti 1, …, N e simboli osservabili 1, …, M:

  • gli stati nascosti X1, X2, … formano una catena di Markov con distribuzione iniziale π e matrice di transizione A, aij = P(Xt+1 = j | Xt = i);
  • in ogni istante lo stato Xt = i genera un’osservazione Ot secondo le probabilità di emissione bi(o) = P(Ot = o | Xt = i), indipendentemente da tutto il resto.
In parole semplici

Dietro il sipario gira una normale catena di Markov — il dado cambia secondo la matrice A. Tu vedi solo ciò che «esce» da ciascuno stato: un lancio secondo le probabilità B. Sul dado truccato il sei è cinque volte più probabile di qualsiasi altro numero, ma può uscire anche su quello onesto. Un singolo lancio quindi non rivela nulla — a tradire il trucco è solo lo schema nell’intera serie.

nascosti osservati 0,05 0,1 0,95 onesto 1 2 3 4 5 6 numero uscito (punti) P(lancio) (0–1) 1/6 0,9 truccato 1 2 3 4 5 6 numero uscito (punti) P(lancio) (0–1) 6: 0,5
Il modello del casinò: stati nascosti in alto, probabilità dei lanci in basso

Dalla definizione segue quanto è probabile una particolare «storia»: una sequenza di dadi x = (x1, …, xT) insieme ai lanci O = (O1, …, OT).

P(x, O | λ) = πx1 bx1(O1) · ∏t=2T axt−1 xt bxt(Ot)

πx1
il primo dado non ha un predecessore da cui ci si sia passati — la sua probabilità la dà la distribuzione iniziale
axt−1 xt
ogni dado successivo dipende solo da quello precedente (proprietà di Markov), quindi per ogni passo basta un solo numero della matrice A
bxt(Ot)
il lancio dipende solo dal dado con cui si sta lanciando in quel momento — prendiamo la probabilità del numero effettivamente uscito
∏t=2T
in generale si moltiplicherebbero probabilità condizionate a tutto il passato (regola della catena); le due ipotesi di indipendenza della definizione le riducono a questi numeri locali, ed è per questo che il tutto è un semplice prodotto
Ogni passo aggiunge due fattori: se il dado è cambiato (o no) e che cosa è uscito.
Esempio — due storie dei primi tre lanci

La nostra serie comincia con i lanci 1, 6, 3. Indichiamo con O il dado onesto e con T quello truccato. La storia «tre volte truccato»:

P(TTT, 1, 6, 3) = 0,5 · 0,1 · 0,9 · 0,5 · 0,9 · 0,1 = 0,002025,

la storia «tre volte onesto»:

P(OOO, 1, 6, 3) = 0,5 · 1/6 · 0,95 · 1/6 · 0,95 · 1/6 = 0,002089.

La storia onesta risulta di un soffio più probabile: il sei sul dado truccato ottiene sì 0,5 invece di 1/6, ma l’uno e il tre solo 0,1 invece di 1/6. (Nella simulazione, in realtà, tutti e tre i lanci sono stati fatti con il dado truccato — da tre lanci non lo si può capire.)

Lawrence Rabiner, nella sua celebre rassegna del 1989, ha riassunto ciò che si può fare con un modello del genere in tre problemi: valutazione (quanto è probabile la serie osservata?), decodifica (quali stati nascosti c’erano più probabilmente dietro?) e apprendimento (quali parametri ha il modello, se conosciamo solo le osservazioni?). Per ciascuno esiste un algoritmo elegante.

3. Quanto è probabile la serie: l’algoritmo forward

La probabilità della serie osservata è una somma su tutte le possibili sequenze di stati nascosti. Per 300 lanci e due dadi sono 2300 ≈ 2 · 1090 — più degli atomi nell’universo. Sommarle direttamente non si può. La proprietà di Markov però permette di calcolare un passo alla volta.

Esempio — la probabilità di tre lanci come somma su tutte le storie

La probabilità delle osservazioni è la somma della probabilità congiunta su tutte le storie possibili: P(O | λ) = ∑x P(x, O | λ). Per i lanci 1, 6, 3 sono 23 = 8:

StoriaP(x, O)
OOO0,002089
OOT0,000066
OTO0,000035
OTT0,000188
TOO0,000132
TOT0,000004
TTO0,000375
TTT0,002025
somma0,004913

Ogni storia costa 2T − 1 = 5 moltiplicazioni, in tutto 8 · 5 = 40. Per T lanci sono (2T − 1) · 2T — con 300 lanci un numero inimmaginabile.

Algoritmo — algoritmo forward

Sia αt(j) = P(O1, …, Ot, Xt = j). Allora

α1(j) = πj bj(O1),   αt+1(j) = [∑i αt(i) aij] bj(Ot+1),   P(O | λ) = ∑j αT(j).

Il calcolo richiede O(N2 T) invece di O(NT). Perché i numeri non vadano in underflow, αt si normalizza a ogni passo e si sommano i logaritmi delle costanti di normalizzazione.

In parole semplici

Non devi seguire ogni possibile storia separatamente. Dopo ogni lancio ti basta ricordare due numeri: «quanto è probabile tutto ciò che ho visto finora, e che adesso sia in gioco il dado onesto» — e lo stesso per quello truccato. Da questi calcoli i numeri per il lancio successivo. Tutte le storie che finiscono nello stesso stato si fondono in un unico numero.

αt+1(j) = ∑i αt(i) aij · bj(Ot+1)

αt(i)
tutto ciò che sappiamo del passato: la probabilità dei lanci finora e del fatto che adesso sia in gioco il dado i; in questo solo numero sono fuse tutte le storie che finiscono in i
aij
un passo della catena da i a j; grazie alla proprietà di Markov non importa come siamo arrivati in i
∑i
non sappiamo con quale dado si è lanciato la volta prima, e a j si può arrivare da uno qualsiasi — per questo sommiamo su tutte le possibilità (le storie sono eventi mutuamente esclusivi)
bj(Ot+1)
il nuovo lancio deve generarlo davvero il dado j; non dipende da i, e quindi si può portare fuori dalla somma
La somma su 2T storie si scompone in T piccole somme di N termini.
Esempio — l’algoritmo forward sui lanci 1, 6, 3

Inizio: α1(O) = 0,5 · 1/6 = 0,0833, α1(T) = 0,5 · 0,1 = 0,0500. Il secondo lancio è un sei:

α2(O) = (0,0833 · 0,95 + 0,0500 · 0,1) · 1/6 = 0,0140,
α2(T) = (0,0833 · 0,05 + 0,0500 · 0,9) · 0,5 = 0,0246.

tlancioαt(O)αt(T)somma = P(O1, …, Ot)ct
110,08330,05000,1333330,1333
260,01400,02460,0386110,2896
330,00260,00230,0049130,1273

La somma nell’ultima riga è esattamente la somma delle otto storie dell’esempio precedente, solo che invece di 40 moltiplicazioni ne sono bastate 2 + 2 · 6 = 14. L’ultima colonna ct = P(Ot | O1, …, Ot−1) è la costante di normalizzazione del riscalamento: quanto era probabile il lancio t dati quelli precedenti. Il loro prodotto è P(O | λ), quindi log P(O | λ) = ∑t log ct.

Perché i logaritmi? Con 3 000 lanci P(O | λ) risulta intorno a 10−2 278, e il più piccolo numero positivo che un computer può memorizzare nella normale doppia precisione (IEEE 754) è circa 4,9 · 10−324. Senza riscalamento α andrebbe in underflow fino a zero.

def dopredu(hody, pi, A, B):
    alfa = pi * B[:, hody[0]]
    logP = 0.0
    for t in range(len(hody)):
        if t > 0:
            alfa = (alfa @ A) * B[:, hody[t]]       # somma su i, poi emissione
        c = alfa.sum()                              # c_t = P(O_t | O_1, …, O_t−1)
        alfa = alfa / c                             # riscalamento: ora α_t somma a 1
        logP += np.log(c)
    return logP                                     # log P(O | λ)

Per i nostri 300 lanci risulta P(lanci | modello del casinò) ≈ 10−230,03. Se per tutto il tempo si fosse giocato onestamente, la probabilità sarebbe (1/6)300 ≈ 10−233,45. Il modello con un dado truccato usato di tanto in tanto spiega i lanci osservati circa 2 600 volte meglio — una prova forte che qui si bara, anche se nessun singolo lancio rivela l’imbroglio.

Definizione — rapporto di verosimiglianza (fattore di Bayes)

Per due modelli M1, M2 e dati O, il fattore di Bayes è K = P(O | M1) / P(O | M2). Dal teorema di Bayes (scritto per entrambi i modelli e diviso) segue:

P(M1 | O) / P(M2 | O) = P(O | M1) / P(O | M2) · P(M1) / P(M2)

P(M1 | O) / P(M2 | O)
il rapporto di probabilità a posteriori — ciò che ci interessa: quanto più crediamo all’imbroglio che al gioco onesto, dopo aver visto i lanci
P(O | M1) / P(O | M2)
il fattore di Bayes — l’unico punto da cui i dati entrano nella formula; entrambe le verosimiglianze le calcola l’algoritmo forward. Il denominatore P(O) del teorema di Bayes si semplifica nella divisione, ed è per questo che non serve conoscerlo
P(M1) / P(M2)
il rapporto di probabilità a priori — chi si fida del casinò in partenza ha bisogno di una prova più forte; i dati lo moltiplicano soltanto

Calcolo. log10 K = −230,03 − (−233,45) = 3,42 (dai valori non arrotondati 3,413), quindi K ≈ 103,413 ≈ 2 590, arrotondato 2 600. Se in partenza consideravi l’imbroglio e il gioco onesto ugualmente probabili, dopo 300 lanci le probabilità sono circa 2 600 : 1 a favore dell’imbroglio. Secondo la scala di Kass e Raftery (1995), di uso comune, un fattore di Bayes superiore a 150 è una prova «molto forte».

4. Quando si barava: l’algoritmo di Viterbi

Algoritmo — algoritmo di Viterbi (1967)

Cerchiamo la sequenza di stati più probabile argmaxx P(x, O | λ). Sia δt(j) la probabilità del cammino migliore che all’istante t termina nello stato j:

δ1(j) = πj bj(O1),   δt+1(j) = maxi [δt(i) aij] · bj(Ot+1),

e per ogni δt+1(j) ricorda da quale i è arrivato. Alla fine scegli lo stato migliore e torna indietro seguendo i riferimenti memorizzati. Si calcola nei logaritmi; il tempo è di nuovo O(N2 T).

In parole semplici

La stessa idea dell’algoritmo forward, solo che invece della somma su tutti i cammini si prende il migliore. In ogni stato e in ogni istante arriva un solo cammino «vincente» — se il cammino complessivo migliore passasse di lì, arriverebbe a quel punto comunque lungo di esso. È programmazione dinamica, parente della ricerca del cammino minimo della parte sulla ricerca nello spazio degli stati.

δt+1(j) = maxi δt(i) aij · bj(Ot+1),   ψt+1(j) = argmaxi δt(i) aij

δt(i)
la probabilità della storia migliore che all’istante t termina con il dado i; le storie peggiori verso i non ci servono più — se il cammino completo migliore passasse per i, proseguirebbe da quella migliore
aij
il costo della transizione da i a j; il piccolo aOT = 0,05 è il motivo per cui Viterbi non ha voglia di cambiare per un solo sei
maxi, ψt+1(j)
a differenza dell’algoritmo forward non sommiamo, ma scegliamo il predecessore migliore — vogliamo un solo cammino, non la probabilità di tutti; ψ ricorda chi ha vinto (nel traliccio le frecce color ambra), così che alla fine il cammino si possa ricostruire all’indietro
bj(Ot+1)
quanto bene il dado j spiega il nuovo lancio — come per α
La stessa formula di α, solo che la somma ∑ è sostituita da un massimo.
onesto truccato lancio 5 −1,1 −1,3 2 −1,9 −2,3 2 −2,7 −3,4 5 −3,5 −4,4 1 −4,3 −5,5 1 −5,1 −6,5 6 −5,9 −6,7 6 −6,7 −7,0 3 −7,5 −8,1 5 −8,3 −9,1 6 −9,1 −9,5 6 −9,9 −9,8
Traliccio di Viterbi per i lanci 19–30: i numeri sono il log₁₀ della probabilità del cammino migliore

Su dodici lanci si vede come decide Viterbi. I primi sei lanci (5, 2, 2, 5, 1, 1) sembrano onesti. Poi escono due sei di fila e altri due poco più in là — e il cammino migliore passa al dado truccato. In realtà il croupier ha cambiato dado solo quattro lanci dopo; due sei di fila escono anche con un dado onesto.

Esempio — Viterbi passo per passo sui lanci 1, 6, 3

L’inizio è lo stesso di α: δ1(O) = 0,0833, δ1(T) = 0,0500. Per il sei del secondo lancio:

δ2(O) = max(0,0833 · 0,95; 0,0500 · 0,1) · 1/6 = 0,0792 · 1/6 = 0,0132   (vince O),
δ2(T) = max(0,0833 · 0,05; 0,0500 · 0,9) · 0,5 = 0,0450 · 0,5 = 0,0225   (vince T).

tlancioδt(O)ψt(O)δt(T)ψt(T)
110,083333—0,050000—
260,013194O0,022500T
330,002089O0,002025T

Alla fine vince O (0,002089 > 0,002025) e tornando indietro lungo i riferimenti ψ si ottiene il cammino OOO — esattamente la storia del primo esempio, che era la più probabile di tutte e otto. L’algoritmo forward però per il terzo lancio dà P(T | O1, O2, O3) = 0,002283 / 0,004913 = 0,465. Il cammino migliore e lo stato più probabile qui coincidono (O), ma la «vittoria» è di misura e tre lanci non bastano per esserne certi. In generale non devono coincidere — ci torneremo con la decodifica a posteriori.

import numpy as np

def viterbi(hody, pi, A, B):
    T, N = len(hody), len(pi)
    delta = np.log(pi) + np.log(B[:, hody[0]])
    odkud = np.zeros((T, N), dtype=int)
    for t in range(1, T):
        kandidati = delta[:, None] + np.log(A)       # da ogni i a ogni j
        odkud[t] = kandidati.argmax(axis=0)
        delta = kandidati.max(axis=0) + np.log(B[:, hody[t]])
    cesta = [int(delta.argmax())]
    for t in range(T - 1, 0, -1):                     # indietro lungo i riferimenti vincenti
        cesta.append(odkud[t][cesta[-1]])
    return cesta[::-1]

5. Tutti i 300 lanci

realtàlanci (punti)ViterbiP(truccato) (0–1) 01 0100200300 lancio (ordine nella simulazione)
Realtà, lanci, cammino di Viterbi e probabilità del dado truccato

Il dado truccato è stato in gioco 88 volte, in nove tratti. Viterbi ha segnato come truccati 62 lanci, di cui 56 correttamente, e in totale ha individuato il dado giusto nell’87,3 % dei lanci. Ha trovato i lunghi tratti di imbroglio, ma quelli brevi li ha mancati o fusi insieme — invece di nove tratti ne vede due. In un tratto breve semplicemente non escono abbastanza sei da giustificare un «costoso» cambio di dado (la probabilità di cambio è solo 0,05).

Definizione — probabilità a posteriori di uno stato

L’algoritmo forward-backward calcola in più βt(i) = P(Ot+1, …, OT | Xt = i) a partire dalla fine e da entrambi ottiene γt(i) = P(Xt = i | O) ∝ αt(i) βt(i).

In parole semplici

Viterbi dà una sola risposta: «era questo dado». La probabilità a posteriori ti dice quanto ne sei sicuro — per ogni lancio separatamente, tenendo conto di tutto ciò che è uscito prima e dopo. La curva in basso nella figura: dove sale sopra la metà, è più probabile che si stia barando.

γt(i) = αt(i) · βt(i) / P(O | λ),   βt(i) = ∑j aij bj(Ot+1) βt+1(j),   βT(i) = 1

γt(i)
la probabilità che all’istante t fosse in gioco il dado i, conoscendo tutti i lanci — per il dado truccato è la curva rossa nella figura
αt(i)
gli indizi dal passato: i lanci da 1 a t e lo stato i all’istante t
βt(i)
gli indizi dal futuro: quanto bene lo stato i spiega i lanci da t + 1 a T. Si calcola a partire dalla fine con lo stesso trucco di α — un passo della catena, l’emissione, il resto del futuro. Moltiplicare α e β è lecito perché, noto lo stato Xt, i lanci passati e futuri sono indipendenti
P(O | λ)
il prodotto α·β è la probabilità congiunta P(Xt = i, O); dividendo per la probabilità di tutti i lanci la rendiamo condizionata. È lo stesso numero dato dall’algoritmo forward, e vale ∑i αt(i) βt(i) = P(O | λ) per ogni t
Esempio — il secondo lancio: che cosa aggiunge il futuro

Restiamo sui lanci 1, 6, 3 e chiediamoci del secondo lancio (il sei). Il passo all’indietro da β3 = 1 con il terzo lancio (il tre):

β2(O) = 0,95 · 1/6 + 0,05 · 0,1 = 0,1633,   β2(T) = 0,1 · 1/6 + 0,9 · 0,1 = 0,1067.

γ2(T) = 0,024583 · 0,106667 / 0,004913 = 0,534.

Se conoscessimo solo i primi due lanci, avremmo P(T | O1, O2) = α2(T) / (α2(O) + α2(T)) = 0,637. Il tre uscito dopo è meno probabile sul dado truccato, e così abbassa a posteriori il sospetto sul secondo lancio a 0,534.

Definizione — decodifica a posteriori e accuratezza

La decodifica a posteriori sceglie in ogni istante separatamente x̂t = argmaxi γt(i). L’accuratezza di una decodifica x̂ rispetto agli stati reali x è la frazione di coincidenze (1/T) ∑t [x̂t = xt], dove [·] vale 1 se l’affermazione è vera, altrimenti 0.

La decodifica a posteriori massimizza il numero atteso di lanci individuati correttamente, Viterbi la probabilità che sia corretto l’intero cammino in una volta. Per questo la sequenza a posteriori può contenere perfino una transizione che nel modello ha probabilità zero — nel casinò non ce ne sono, ma in generale può succedere.

Se per ogni lancio scegliessimo il dado con la probabilità a posteriori maggiore, indovineremmo l’87,7 % — di un soffio più di Viterbi (263 lanci su 300 contro 262, la differenza di un solo lancio). (Viterbi cerca il miglior cammino intero, la decodifica a posteriori la migliore stima di ogni lancio separatamente; non è la stessa cosa.) E attenzione a una sola simulazione: in dodici esperimenti diversi l’accuratezza dell’algoritmo di Viterbi è variata dal 73,3 al 90,0 %, in media l’83,4 %. Dipende da quanto lunghi sono i tratti di imbroglio che produce il caso.

6. Apprendimento: l’algoritmo di Baum-Welch

Finora conoscevamo i parametri del casinò. E se sapessimo solo che i dadi sono due, e tutto il resto andasse ricavato dai lanci?

Algoritmo — algoritmo di Baum-Welch

È un algoritmo EM (Baum et al., intorno al 1970). Ripeti:

  1. passo E: con i parametri attuali calcola con l’algoritmo forward-backward γt(i) e i conteggi attesi delle transizioni ξt(i, j) = P(Xt = i, Xt+1 = j | O);
  2. passo M: nuovo aij = ∑t ξt(i, j) / ∑t γt(i), nuovo bi(o) = quota di γ negli istanti in cui è uscito o.

La verosimiglianza P(O | λ) non diminuisce in nessuna iterazione; l’algoritmo converge a un massimo locale.

In parole semplici

Se sapessimo quando era in gioco ciascun dado, otterremmo i parametri semplicemente contando. Non lo sappiamo — ma con una qualche stima dei parametri sappiamo calcolare con che probabilità ciascun dado era dove. Calcoliamo quindi conteggi «morbidi», da questi parametri migliori, da questi conteggi migliori… Ogni giro migliora il modello.

Il numero atteso di transizioni si calcola dagli stessi α e β di γ, solo inserendo tra loro un passo della catena:

ξt(i, j) = αt(i) aij bj(Ot+1) βt+1(j) / P(O | λ)

αt(i)
i lanci fino all’istante t e il dado i all’istante t — l’estremo sinistro della transizione
aij
la transizione i → j stessa, di cui stimiamo la frequenza
bj(Ot+1)
il lancio t + 1 deve generarlo il nuovo dado j
βt+1(j)
il resto dei lanci da t + 2 in poi, spiegato a partire dal dado j — l’estremo destro della transizione
P(O | λ)
dividendo, dalla probabilità congiunta «transizione e tutti i lanci» otteniamo quella condizionata «transizione, noti i lanci»

âij = ∑t=1T−1 ξt(i, j) / ∑t=1T−1 γt(i)

âij
la nuova stima della probabilità di transizione — una frequenza relativa, come se contassimo delle tacche
∑ ξt(i, j)
il numero atteso di transizioni i → j; invece di «c’è stata una transizione o no» (1 o 0) sommiamo probabilità, perché gli stati non li vediamo
∑ γt(i)
il numero atteso di uscite dallo stato i; si somma solo fino a T − 1, perché dall’ultimo stato non parte più alcuna transizione. Grazie a questo ogni riga della nuova matrice somma a 1
Se vedessimo gli stati, ξ e γ sarebbero zeri e uno e la formula sarebbe la normale stima di massima verosimiglianza dai conteggi.
ripeti (qui 100 volte):
    α, β, P(O) ← algoritmo forward-backward con π, A, B attuali
    # passo E: conteggi morbidi
    per ogni t, i:            γ[t][i]    ← α[t][i] · β[t][i] / P(O)
    per ogni t < T, i, j:     ξ[t][i][j] ← α[t][i] · a[i][j] · b[j](O[t+1]) · β[t+1][j] / P(O)
    # passo M: frequenze relative
    π[i]    ← γ[1][i]
    a[i][j] ← ∑_{t<T} ξ[t][i][j] / ∑_{t<T} γ[t][i]
    b[i](o) ← ∑_{t: O[t]=o} γ[t][i] / ∑_t γ[t][i]

Siamo partiti da una stima sbagliata: i dadi quasi non differiscono e vengono scambiati spesso. Da 300 lanci Baum-Welch è arrivato a un modello che non scambia quasi mai i dadi e dà al dado truccato il sei con probabilità di appena 0,39 — per una stima affidabile 300 lanci sono pochi. Da 3 000 lanci (un’altra simulazione dello stesso casinò) è risultato molto vicino alla verità:

ParametroRealtàStima da 3 000 lanci
onesto → truccato0,050,039
truccato → onesto0,100,090
sei sul dado truccato0,500,491
sei sul dado onesto0,1670,163

Perché 300 lanci non bastano lo mostra la verosimiglianza. In 100 iterazioni sui nostri 300 lanci è salita da 10−231,26 a 10−227,14 — ed è più dei 10−230,03 del vero modello del casinò. Il modello appreso spiega quindi proprio questi lanci meglio della verità: si è adattato anche alle fluttuazioni casuali di questi dati concreti. La stima di massima verosimiglianza si avvicina alla verità solo man mano che i dati aumentano.

7. I modelli di Markov nascosti nella pratica

  • Riconoscimento vocale. Dagli anni Ottanta fino all’arrivo delle reti profonde intorno al 2010, quasi tutti i sistemi si basavano sugli HMM: gli stati nascosti sono parti di fonemi, le osservazioni brevi tratti dello spettro sonoro, e Viterbi cerca la sequenza di parole più probabile.
  • Lingua. Etichettatura delle parti del discorso: lo stato nascosto è la parte del discorso, l’osservazione la parola. «Porta» può essere un sostantivo o un verbo — decide il contesto.
  • Bioinformatica. Ricerca dei geni nel DNA (le regioni codificanti e non codificanti sono gli stati nascosti) o profile HMM per le famiglie di proteine nella banca dati Pfam.
  • Stati continui. Quando lo stato nascosto non è «dado A o B» ma una posizione e una velocità, al posto di un HMM si usa il filtro di Kalman — che nel programma ha una parte tutta sua.

8. Riepilogo

  • Un modello di Markov nascosto è una catena di Markov di stati nascosti di cui vediamo solo le emissioni; lo determinano π, A e B.
  • L’algoritmo forward calcola la probabilità delle osservazioni in tempo O(N2T) invece di sommare su 2300 cammini; il casinò che bara spiega i nostri lanci circa 2 600 volte meglio del gioco onesto.
  • L’algoritmo di Viterbi trova il cammino di stati nascosti più probabile con la programmazione dinamica — nella nostra simulazione ha individuato il dado giusto nell’87,3 % dei lanci, ma ha mancato i tratti brevi.
  • L’algoritmo forward-backward dà la probabilità a posteriori dello stato in ogni istante.
  • L’algoritmo di Baum-Welch apprende i parametri dalle sole osservazioni — ma ha bisogno di abbastanza dati: 300 lanci non sono bastati, 3 000 sì.

Vuoi toccarlo con mano? Fai addestrare a un agente IA un HMM a due stati sui rendimenti giornalieri di un indice azionario — e guarda se trova da solo i periodi «calmi» e quelli «turbolenti» del mercato.

La vedi diversamente? Scrivi a support@coden-app.com.

Un Agente sul tuo computer, guidato dal telefono