Fatti probabilistici parametrici
I fatti probabilistici parametrici sono uno strumento potente in ProbLog per modellare situazioni in cui si ha un numero indefinito o molto grande di oggetti simili.
Un fatto probabilistico parametrico è semplicemente un fatto probabilistico che contiene una o più variabili, come 0.5::lands_heads(C) nel seguente primo esempio, cioè rappresenta in modo compatto un insieme potenzialmente infinito di fatti probabilistici, nell'esempio uno per ogni possibile istanza della variabile C.
Il seguente esempio didattico riguarda l'esito nel lancio di diverse monete.
run(r"""
% Probabilistic facts:
0.5::lands_heads(C).
% Background information:
coin(c1).
coin(c2).
coin(c3).
coin(c4).
% Rules:
heads(C) :- coin(C), lands_heads(C).
someHeads :- heads(_).
twoHeads :- heads(C1), heads(C2), C1 \= C2.
allHeads :- \+ (coin(C), \+ heads(C)).
% Queries:
query(heads(X)).
query(twoHeads).
query(someHeads).
query(allHeads).
""")
{heads(c4): 0.5,
someHeads: 0.9375,
heads(c3): 0.5,
twoHeads: 0.6875000000000001,
heads(c2): 0.5,
heads(c1): 0.5,
allHeads: 0.0625}
In questo caso 0.5::lands_heads(C), o anche 0.5::lands_heads(_), attribuisce probabilità 0.5 al fatto che la caduta a terra di qualunque cosa mostri testa, che poi fornisce a tutte le monete dichiarate la stessa probabilità che lanciate mostrino poi testa.
Quest'altro esempio mostra come i fatti probabilistici parametrici permettano di modellare reti bayesiane in modo compatto ed elegante. In primo luogo consideriamo le seguenti dichiarazioni.
run("""
% Iscrizioni degli studenti s1, s2, s3 ai corsi c1, c2, c3
takes(s1,c1).
takes(s1,c2).
takes(s2,c1).
takes(s2,c2).
takes(s3,c2).
takes(s3,c3).
% Definizione dei corsi e degli studenti
course(C) :- takes(_, C).
student(S) :- takes(S, _).
% Fatti probabilistici parametrici
0.6::difficult(C) :- course(C).
0.7::smart(S) :- student(S).
% Query: probabilità che un corso sia difficile, che uno studente sia intelligente
query(difficult(_)).
query(smart(_)).
""")
{difficult(c1): 0.6,
difficult(c2): 0.6,
difficult(c3): 0.6,
smart(s1): 0.7,
smart(s2): 0.7,
smart(s3): 0.7}
Usando fatti probabilistici parametrici occorre assicurarsi che le variabili vengano "groundate" (istanziate a valori concreti) durante il processo di inferenza. Se una variabile in un fatto probabilistico rimane non-ground, ProbLog solleva un errore NonGroundProbabilisticClause.Ora vediamo come le precedenti dichiarazioni diventino la base per una descrizione più complessa, per esaminare la quale infatti occorre qualche tempo di calcolo.
run(r"""
% Iscrizioni degli studenti s1, s2, s3 ai corsi c1, c2, c3
takes(s1,c1).
takes(s1,c2).
takes(s2,c1).
takes(s2,c2).
takes(s3,c2).
takes(s3,c3).
% Definizione dei corsi e degli studenti
course(C) :- takes(_, C).
student(S) :- takes(S, _).
% Fatti probabilistici parametrici
0.6::difficult(C) :- course(C).
0.7::smart(S) :- student(S).
% Regole probabilistiche per il successo
0.85::success(S,C) :- takes(S,C), difficult(C), smart(S).
0.10::success(S,C) :- takes(S,C), difficult(C), \+smart(S).
0.98::success(S,C) :- takes(S,C), \+difficult(C), smart(S).
0.45::success(S,C) :- takes(S,C), \+difficult(C), \+smart(S).
% Query interessanti
query(success(s1,c1)). % Probabilità che s1 superi c1
query(success(s2,c2)). % Probabilità che s2 superi c2
query(success(s3,c3)). % Probabilità che s3 superi c3
% Regole per query più complesse
passes_all_courses(S) :-
student(S),
findall(C, (takes(S,C), success(S,C)), Courses),
length(Courses, N),
N > 0,
% Tutti i corsi sono passati
\+ (takes(S,C), \+ success(S,C)).
at_least_one_failure(S) :-
student(S),
takes(S,C),
\+ success(S,C).
% Query complesse
query(passes_all_courses(s1)). % Probabilità che s1 passi tutti i corsi
query(at_least_one_failure(s2)). % Probabilità che s2 fallisca almeno un corso
""")
{success(s3,c3): 0.7033999999999999,
success(s2,c2): 0.7033999999999999,
at_least_one_failure(s2): 0.4131972000000001,
success(s1,c1): 0.7034,
passes_all_courses(s1): 0.5868027999999998}
Quelle quattro regole per success/2 sono il cuore del modello di rete bayesiana e servono a modellare la probabilità condizionata del successo di uno studente in un corso, data la difficoltà del corso e l'intelligenza dello studente.
Vediamo come ProbLog possa integrare diverse prove incerte per rispondere a query complesse in uno scenario di ragionamento investigativo.
run(r"""
% Contesto
person(lars). person(james). person(linda).
knife(knife1). knife(knife2). knife(knife3).
% EVIDENZA FISICA INCERTA
0.9::in_park(lars). 0.3::in_park(james). 0.3::in_park(linda).
0.8::murder_weapon(knife1). 0.1::murder_weapon(knife2). 0.1::murder_weapon(knife3).
% ACCESSO E MOTIVO INCERTI
0.8::owns_has_access(lars, knife1). 0.8::owns_has_access(james, knife2). 0.8::owns_has_access(linda, knife3).
0.1::other_access(Person, Knife) :- person(Person), knife(Knife). % accesso generico
0.5::motive(Person) :- person(Person).
% REGOLA COMPLESSA: Definizione di "assassino"
murderer(Person) :-
person(Person),
in_park(Person),
motive(Person),
( owns_has_access(Person, Weapon), murder_weapon(Weapon)
; other_access(Person, Weapon), murder_weapon(Weapon) ).
% QUERY: Probabilità che ciascuno sia l'assassino con l'evidenza disponibile
query(murderer(_)).
""")
{murderer(linda): 0.02458284000000001,
murderer(james): 0.02458284000000001,
murderer(lars): 0.29828052000000005}
L'inferenza qui è "biduttiva". Non si limita a calcolare una probabilità a partire da fatti certi, ma deve combinare una moltitudine di prove probabilistiche (presenza, movente, accesso, arma) per dedurre la probabilità di una conclusione complessa. La query murderer(james). attiva un processo inferenziale che esplora tutte le possibili combinazioni di questi fatti incerti.
Un altro esempio interessante mostra come un fatto probabilistico parametrico possa essere la testa di una regola che ne stabilisce anche la probabilità.
run(r"""
% Background information:
lands_heads(c1,0.5).
lands_heads(c2,0.4).
lands_heads(c3,0.6).
lands_heads(c4,0.55).
% Rules:
coin(C) :- lands_heads(C,_).
P::heads(C):- coin(C), lands_heads(C,P).
someHeads :- heads(_).
twoHeads :- heads(C1), heads(C2), C1 \= C2.
allHeads :- \+ (coin(C), \+ heads(C)).
% Queries:
query(heads(X)).
query(twoHeads).
query(someHeads).
query(allHeads).
""")
{heads(c4): 0.55,
someHeads: 0.9460000000000001,
heads(c3): 0.6000000000000001,
twoHeads: 0.709,
heads(c2): 0.4,
heads(c1): 0.5,
allHeads: 0.06599999999999999}
Qui supponiamo di avere una tassonomia di bevande e di voler esprimere che due bevande sono simili se sono vicine tra loro nell'albero.
run(r"""
e(drink,alcoholic).
e(drink,nonalcoholic).
e(alcoholic,beer).
e(alcoholic,wine).
e(alcoholic,spirit).
e(nonalcoholic,soda).
e(nonalcoholic,coffee).
e(beer,ale).
e(beer,stout).
e(beer,lager).
e(wine,redwine).
e(wine,whitewine).
e(spirit,gin).
e(spirit,whiskey).
member(X,[X|_]).
member(X,[_|T]) :- member(X,T).
d(B,B,[B]).
d(B,E,[B|L]) :- e(B,M), d(M,E,L),\+member(B,L).
d(B,E,[M|L]) :- e(M,B), d(M,E,L),\+member(B,L).
P::similar(B,E) :- d(B,E,L), length(L,LL), (LL=1, P is 1; LL>1, P is 1/(LL-1)).
query(similar(ale,stout)).
query(similar(ale,gin)).
query(similar(ale,soda)).
""")
{similar(ale,stout): 0.5000000000000001,
similar(ale,gin): 0.25,
similar(ale,soda): 0.2}