tbnpy.probability¶
Overview¶
A probability object quantifies one node of a Bayesian network. tbnpy recognises one by what it has, not by what it inherits from, so a class written for one model needs no tbnpy base class to work. This module makes the protocol explicit and adds three things on top of it:
Probability, an optional base class that makes an object readable when it is printed;describe(), the same reading for any probability object, whether it inherits the base class or not;check_model(), which samples a few rows through a whole model to find an object that breaks the protocol before an inference run does.
Cpt and every node compiled by tbnpy.compile inherit
Probability. The three names are also available as
tbnpy.Probability, tbnpy.describe and tbnpy.check_model.
The protocol¶
Member |
Meaning |
|---|---|
|
List of child |
|
List of parent |
|
For a root (no parents). Returns |
|
For an object with parents. |
|
|
|
Where the object’s tensors live ( |
In sample’s return value, Cs is (N,) for a single child or
(N, n_childs), and logp is (N,): the log probability of each sampled row
given its parents. log_prob of the same rows must return the same numbers.
Discrete values¶
A discrete variable’s value is its state index into Variable.values, not its
label. It may be held in any dtype that stores whole numbers exactly: inference
builds Cs_pars with torch.stack(), which promotes a mix of integer and float
columns to a common dtype. So a class receiving discrete parents should cast them
with .long() before using them as indices, and look up labels with
Variable.values:
A_values = torch.tensor(A.values, dtype=torch.float32) # labels, by state index
mean = A_values[Cs_pars[:, 0].long()]
Reading an object¶
With the base class, typing an object’s name shows the distribution it defines, and
print gives each variable’s domain:
>>> probs
{'A': Cpt(P(A): table of 2 rows), 'B': Cpt(P(B): table of 3 rows),
'C': C(P(C | A, B): normal(loc=A + B, scale=0.6)), ...}
>>> print(probs['C'])
P(C | A, B) [C]
form normal(loc=A + B, scale=0.6)
child C continuous (-inf, inf)
parent A discrete, 2 states: -0.3, 0.3
parent B discrete, 3 states: -0.2, 0.0, 0.2
The form is what the class says it is. Override
form() to return one line, and
form_details() to add lines such as a table
(as Cpt does). Both are optional:
from tbnpy.probability import Probability
class C(Probability):
def __init__(self, childs, parents, sigma=0.6, device="cpu"):
super().__init__(childs, parents, device) # sets childs, parents, device
self.sigma = sigma
def form(self):
return f"normal(loc=A + B, scale={self.sigma:g})"
def sample(self, Cs_pars): ...
def log_prob(self, Cs): ...
For an object that does not inherit the base class, tbnpy.describe(obj) prints the
same reading (without the form, unless the object has a form() method).
Checking a model¶
>>> print(tbnpy.check_model(probs))
Model check: 6 probability objects, 6 variables, 8 sampled rows: no problems found
check_model() raises ModelError
when it finds an error, listing every issue; pass raise_on_error=False to receive
the ModelCheck instead. It reports:
Without sampling: a missing
childs,parents,sampleorlog_prob; a variable that is the child of two objects; a parent that is no object’s child; a parent declared with a different domain from its owner’s (a warning); a cycle.By sampling
n_samplerows in topological order, astbnpy.inference.sample()does: an object that cannot be deep-copied or whosesampleraises;Csorlogpof the wrong shape; a discrete value that is not a state index or a continuous value outside its support; alogpthat is NaN or infinite; and alog_probthat disagrees withsample’slogp. An object whoselog_probraisesNotImplementedErrorgets a warning, since forward sampling does not need it.
The check uses its own seed and restores the random number generator’s state when it finishes, so running it never changes the results of a seeded run that follows. Inference does not run it for you: call it once after building a model.
API¶
- class tbnpy.probability.Probability(childs, parents=None, device='cpu')[source]¶
Optional base class for probability objects.
Subclassing it is not required: tbnpy accepts any object that follows the protocol in this module’s docstring. What it adds is a reading:
repr(obj)gives one line,ClassName(P(C | A, B): form), so aprobsdictionary prints as a list of distributions;str(obj)(andprint(obj)) givesdescribe()’s longer reading, with each variable’s domain.
Override
form()to say what the distribution is, andform_details()to add lines such as a table, for both readings.- Parameters:
childs – Lists of
Variable.parents – Lists of
Variable.device –
"cpu","cuda"or atorch.device.sets (Calling this __init__ is optional too; a subclass that)
childs
readings. (parents and device itself gets the same)
- form_details() list[source]¶
Extra lines for
describe(), such as a probability table.
- tbnpy.probability.describe(obj) str[source]¶
Several lines on any probability object: what it is and what it reads.
Works on any object with
childsandparents. When the object definesform()andform_details()(asProbabilitysubclasses may), their text is included.
- tbnpy.probability.check_model(probs, n_sample=8, seed=0, raise_on_error=True) ModelCheck[source]¶
Check that every object in a model follows the protocol, by using it.
The check has two parts. Without sampling, it confirms that each object has
childs,parents,sampleandlog_prob; that every variable is the child of exactly one object; that every parent is some object’s child, with the same domain; and that the graph has no cycle. Then it forward-samplesn_samplerows through the whole model, astbnpy.inference.sample()does, and for each object confirms:samplereturns(Cs, logp)of the right shapes;sampled values lie in their variables’ domains (state indices for discrete variables, the support for continuous ones);
logpis finite;log_probof the sampled rows equalslogp.
The random number generator’s state is restored afterwards, so a check never changes the results of a seeded run that follows it.
- Parameters:
probs –
{node name: probability object}.n_sample – Rows to sample. A few are enough to find a broken object.
seed – Seed for the check’s own sampling, so that it is reproducible.
raise_on_error – Raise
ModelErrorwhen there are errors. WithFalse, theModelCheckis returned either way.
- Returns:
printit to read the issues;.okisTruewithout errors.- Return type:
- class tbnpy.probability.ModelCheck(issues: list = <factory>, n_objects: int = 0, n_variables: int = 0, n_sample: int = 0)[source]¶
What
check_model()found.- property ok: bool¶
No errors. Warnings are allowed.
- class tbnpy.probability.ModelIssue(node: str, severity: str, issue: str, detail: str = '')[source]¶
One thing wrong (
"error") or doubtful ("warning") in a model.
- class tbnpy.probability.ModelError(check: ModelCheck)[source]¶
Raised by
check_model()when a model has errors.
- tbnpy.probability.is_probability(obj) bool[source]¶
Whether
objhas everything tbnpy calls on a probability object.