Esercizio - Prior sampling sulla rete burglary (laboratorio 6)
Testo (laboratorio 6, Approximate inference and Bayesian networks, part B, corso di Intelligenza Artificiale, Ing. dell'Informazione UniPD). Implementare l'algoritmo di prior sampling per fare inferenza approssimata sulla rete Burglary della scorsa settimana. Verificare che l'algoritmo approssimi correttamente la probabilità . Provare numeri di campioni diversi (per esempio ) e confrontare i risultati.
La rete (CPT dalla figura delle slide): , , per , , per .
Soluzione
Algoritmo (Inferenza approssimata nelle reti bayesiane - campionamentoL'inferenza esatta (enumerazione, eliminazione di variabili) diventa proibitiva nelle reti grandi; i metodi Monte Carlo la approssimano estraendo $N$ campioni dalla rete e contando. Prior sampling: si campiona ogni variabile dopo i genitori, $P(\text{evento})\approx N_{PS}/N$. Rejection sampling: per $\mathbf P(X\mid\mathbf e)$ si scartano i campioni incoerenti con $\mathbf e$ (spreca molto se $P(\mathbf e)$ è piccola). Likelihood weighting: si fissano le variabili di evidenza e ogni campione pesa il prodotto delle $P(e_i\mid\text{parents})$; non spreca campioni. Gibbs sampling (MCMC): partendo da uno stato, si ricampiona una variabile libera alla volta da $P(Z\mid\text{Markov blanket})$, dove il blanket è genitori, figli e altri genitori dei figli. Tutte convergono al valore vero per $N\to\infty$ con errore dell'ordine di $1/\sqrt N$.Inferenza approssimata nelle reti bayesiane - campionamento →). Per ogni campione si percorrono le variabili in ordine topologico () e si estrae ciascuna dalla sua CPT con i valori dei genitori già estratti. La probabilità dell'evento si stima con la frequenza: .
La funzione base samplegen estrae un booleano: restituisce vero se random.random() (uniforme in ) è minore di . Per leggere la riga giusta della CPT si usano i valori dei genitori come indici dell'array: Pdist[t][genitori], dove il primo indice è il valore della variabile ( vero) e gli altri sono i valori dei genitori. Esempio: P_A_BE[t][f][t] è .
Dettaglio utile del codice. Gli eventi sono salvati come liste di 't'/'f' nell'ordine ['B','E','A','J','M']: l'evento cercato è ['f','f','t','t','t'] (non , non , , , ). Attenzione all'inversione: nel codice t = 0 e f = 1, quindi 'f' if val[x] else 't' converte l'indice in 'f'.
Codice
import numpy as np
import random as rnd
t, f = 0, 1 # indici di 'vero' e 'falso'
def samplegen(Pdist, Parents=[]):
"""Campiona vero/falso da una CPT booleana; Pdist[t][genitori] = P(X = vero | genitori)."""
assert len(Parents) < len(Pdist.shape)
if rnd.random() < Pdist[t][tuple(Parents)]:
return t
return f
def parents(X):
return [val[i] for i in par[X]] # valori correnti dei genitori di X
def priorSampling(n_events):
event = []
for n in range(n_events):
for x in var: # ordine topologico: B, E, A, J, M
val[x] = samplegen(prd[x], parents(x))
event.append(['f' if val[x] else 't' for x in var])
return event
# rete burglary
P_B = np.array([0.001, 0.999])
P_E = np.array([0.002, 0.998])
P_A_BE = np.array([[[0.95, 0.94], [0.29, 0.001]], [[0.05, 0.06], [0.71, 0.999]]]) # [a][b][e]
P_J_A = np.array([[0.9, 0.05], [0.1, 0.95]]) # [j][a]
P_M_A = np.array([[0.7, 0.01], [0.3, 0.99]]) # [m][a]
var = ['B', 'E', 'A', 'J', 'M']
prd = {'B': P_B, 'E': P_E, 'A': P_A_BE, 'J': P_J_A, 'M': P_M_A}
par = {'B': [], 'E': [], 'A': ['B', 'E'], 'J': ['A'], 'M': ['A']}
val = {'B': f, 'E': f, 'A': f, 'J': f, 'M': f}
rnd.seed(42)
for N in [10, 100, 1000, 10000, 100000]:
data = priorSampling(N)
stima = data.count(['f', 'f', 't', 't', 't']) / len(data) # B=f, E=f, A=t, J=t, M=t
print(N, stima)Output (seme ): 10 0.0, 100 0.0, 1000 0.002, 10000 0.0008, 100000 0.00071. La soluzione del corso (senza seme) dà , , , , e, con campioni, .
Cosa si osserva
L'evento ha probabilità : in campioni se ne aspettano .
| eventi attesi | stima (seme ) | scarto tipico atteso | |
|---|---|---|---|
- Con e ci si aspetta meno di un evento: la stima è quasi sempre , che è troppo lontano per dire se l'algoritmo funziona.
- Con basta un evento in più o in meno per cambiare la stima di : una stima di è un errore relativo del .
- Con la stima si avvicina a . Lo scarto tipico scende come : per dimezzarlo servono volte i campioni.
- Verifica: ripetendo volte l'esperimento (ogni stima è ) la media delle stime coincide con il valore esatto e lo scarto tipico osservato coincide con quello atteso: a media , scarto (atteso ); a media , scarto (atteso ). Dunque la stima è corretta, solo rumorosa. Un campionatore indipendente scritto con vettori
numpydà con campioni.
Conclusione: il prior sampling approssima correttamente , ma per eventi rari servono molti campioni; per probabilità condizionate con evidenza rara conviene il likelihood weighting (Esercizio - Campionamento - rejection sampling e likelihood weighting sulla rete sprinkler (slide del corso)).
Versione ripasso
- Obiettivo: stimare , esatto ().
- Prior sampling (prior samplingogni variabile estratta dalla sua CPT dopo i genitori, in ordine topologicoInferenza approssimata nelle reti bayesiane - campionamento →):
samplegenrestituisce vero serandom.random(); stima conteggio dell'evento (evento['f','f','t','t','t'], con e ). - Risultati (seme ): : ; : ; : ; : ; corso, : .
- Perché: evento raro, eventi attesi; scarto tipico (quadruplicare dimezza l'errore).
- Errori tipici: confondere gli indici () e l'ordine delle variabili nell'evento; concludere che l'algoritmo è sbagliato da una stima con pochi campioni; non fissare il seme per ripetere l'esperimento.
- Ripetizioni: su ripetizioni lo scarto tipico osservato coincide con quello atteso ( contro a ): la stima è corretta, solo rumorosa.
- Struttura del codice:
samplegenestrae una variabile,parentslegge i valori dei genitori,priorSamplingcicla sulle variabili nell'ordinevar; l'arrayP_A_BE[a][b][e]ha il valore della variabile come primo indice.