Fatti probabilistici e Regole deterministiche


In ProbLog la dichiarazione dei fatti, tutti tra loro indipendenti, può essere preceduta da una probabilità.
Dunque query(a)., che in Prolog, linguaggio per dimostrare teoremi, significa "dimostra a", in ProbLog significa invece "calcola la probabilità di a".

prog = r""" 0.5::freddo. 0.3::umido. nuvoloso. freddo_or_umido :- freddo. freddo_or_umido :- umido. freddo_vel_umido :- freddo; umido. freddo_et_umido :- freddo, umido. ne_freddo_ne_umido :- not(freddo), not(umido). freddo_aut_umido :- (not(freddo),umido) ; (freddo, \+ umido). query(nuvoloso). query(not(umido)). query(freddo_o_umido). query(freddo_vel_umido). query(freddo_et_umido). query(ne_freddo_ne_umido). query(freddo_aut_umido). query(not(ne_freddo_ne_umido)). """ get_evaluatable().create_from(prog).evaluate()
{\+umido: 0.7000000000000001, ne_freddo_ne_umido: 0.35000000000000003, freddo_or_umido: 0.65, freddo_vel_umido: 0.65, \+ne_freddo_ne_umido: 0.65, freddo_aut_umido: 0.5, freddo_et_umido: 0.15, nuvoloso: 1.0}
Si noti come freddo_o_umido = 1-ne_freddo_ne_umido = 1-(1-0.5)(1-0.3). Infatti più regole con la stessa "testa" portano a un effetto noisy-OR (o disgiunzione probabilistica), una regola di probabilità condizionata usata nelle reti bayesiane e in psicologia cognitiva per calcolare la probabilità di un effetto quando agiscono più cause indipendenti, ammettendo un margine di "rumore" o incertezza sul fatto che ciascuna causa attivi l'effetto.
Dal punto di vista logico e del risultato non c'è nessuna differenza tra le definizioni date per freddo_or_umido e freddo_vel_umido mentre dal punto di vista operazionale c'è una differenza molto sottile: poiché c :- a ; b. è una sola clausola con una disgiunzione nel corpo mentre c :- a. c :- b. sono due clausole distinte. Questo può influenzare aspetti come:

Un altro semplice esempio, sul lancio di due monete, si avvale di una function run creata per rendere più amichevole il lancio dell'esecuzione.

def run(prog): return get_evaluatable().create_from(prog).evaluate() prog = """ % Probabilistic facts: 0.5::heads1. 0.6::heads2. % Rules: twoHeads :- heads1, heads2. % Queries: query(heads1). query(heads2). query(twoHeads). """ run(prog)
{heads2: 0.6000000000000001, heads1: 0.5000000000000001, twoHeads: 0.30000000000000004}
Rielaborando il precedente esempio, introducendo il fatto evidence(letterale,true/false), si può illustrare l'uso del teorema di Bayes in ProbLog.
prog = """ % Probabilistic facts: 0.5::heads1. 0.6::heads2. % Rules: twoHeads :- heads1, heads2. evidence(twoHeads,false). % Queries: query(heads1). query(heads2). query(twoHeads). """) run(prog)
{heads2: 0.4285714285714286, heads1: 0.2857142857142857, twoHeads: 0.0}
Un altro esempio, più complesso, ancora sul lancio di monete è il seguente. Si noti che la stringa che contiene il testo del programma ProbLog è preceduta dal carattere r poiché la stringa contiene caratteri speciali come '\'.
prog = r""" % Probabilistic facts: 0.6::lands_heads(c1). 0.5::lands_heads(c2). 0.4::lands_heads(c3). 0.4::lands_heads(c4). % 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. % Queries: query(someHeads). query(twoHeads). """) run(prog)
{someHeads: 0.928, twoHeads: 0.652}

Ecco un esempio di valutazione di un percorso in un grafo.

run(r""" 0.6::edge(1,2). 0.1::edge(1,3). 0.4::edge(2,5). 0.3::edge(2,6). 0.3::edge(3,4). 0.8::edge(4,5). 0.2::edge(5,6). path(X,Y) :- edge(X,Y). path(X,Y) :- edge(X,Z), Y \= Z, path(Z,Y). query(path(1,6)). """)
{path(1,6): 0.21672959999999997}

ProbLog può essere eseguito in modalità di campionamento (sampling) per generare possibili assegnazioni alle query nel modello. Il risultato sarà un elenco di dizionari che associano gli atomi della query al loro valore campionato.

from problog.tasks import sample from problog.program import PrologString model = PrologString(""" 0.3::a. 0.5::b. c :- a; b. query(a). query(b). query(c). """) result = sample.sample(model, n=5, format='dict') [s for s in result]
[{a: False, b: False, c: False}, {a: False, b: True, c: True}, {a: True, b: True, c: True}, {a: True, b: False, c: True}, {a: False, b: False, c: False}]
Una probabilità può anche essere attribuita come valore casuale secondo una data distribuzione.
model = PrologString(""" uniform(0,10)::a. 0.5::b. c :- value(a, X), X >= 3; b. query(a). query(b). query(c). """) result = sample.sample(model, n=5, format='dict') [s for s in result]
[{a: 3.180430477934109, b: True, c: True}, {a: 5.541442656505606, b: False, c: True}, {a: 8.61674079885767, b: True, c: True}, {a: 1.826125249580199, b: False, c: False}, {a: 2.351612526633722, b: True, c: True}]
Oltre a uniform(min,max) sono predefinite gaussian(media, deviazione), poisson(Lambda), exponential(Lambda), beta(Alpha, Beta), gamma(Alpha, Beta) e altre ancora.
Si può passare la probabilità di un fatto anche anche definendola con Python e passandola al modello ProbLog attraverso il parametro distributions di sample.sample().
model = PrologString(""" f(0.2,0.4,0.6,0.8,2/6,1/6,1/6,2/6)::a. 0.5::b. c :- value(a, A), A >= 3; b. query(a). query(b). query(c). """) import numpy as np def f(*args): n = len(args) // 2 return np.random.choice(list(args[:n]), p=list(args[n:])) result = sample.sample(model, n=5, format='dict', distributions={'f': f}) [s for s in result]
[{a: 0.8, b: False, c: False}, {a: 0.2, b: True, c: True}, {a: 0.2, b: True, c: True}, {a: 0.8, b: True, c: True}, {a: 0.2, b: True, c: True}]