Defining an entire Bayesian Network in a single let expression
as above can be quite cumbersome. Fortunately there is a convenient
alternative. You can simply define each variable in turn on the IBAL
command line:
IBAL> burglary = flip 0.01
IBAL> earthquake = flip 0.001
IBAL> alarm = case <burglary, earthquake> of \
> # <false, false> : flip 0.01 \
> # <false, true> : flip 0.1 \
> # <true, false> : flip 0.7 \
> # <true, true> : flip 0.8
In general, you can define any variable at the command line with a
definition of the form x = e, where x is an
identifier, and e is an expression. A definition is just one
form of declaration; we will see others later. After a
variable has been defined, it is available for use in future
definitions or declarations. Note that IBAL does not evaluate
anything after you enter the definition. Instead, the defining
expression is stored and referred to when needed.
One can also collect a sequence of declarations in a block.
A block has the form
{ d_1 ; ...; d_n } where the d_i are declarations. Note
that a block is surrounded by curly braces, and the declarations are
separated by semi-colons.
A block is actually an expression, whose result is a tuple containing
all the variables defined in the block.
Variables defined in earlier declarations are available for use in
later declarations.
Blocks give us the means to define the notion of a probabilistic
object, as used in object-oriented Bayesian networks. An object is
an entity with a number of attributes. The attributes of an object
may be simple, or they may themselves be other objects. The model of
the object indicates how the various attributes depend on each other
probabilistically. There are three kinds of attributes:
inputs, which are defined outside the object and used within
it; outputs, which are defined inside the object and available
outside it; and encapsulated attributes, which are defined for
use only within the object itself. In IBAL, an object is defined by
creating a block with a definition for every output and encapsulated
attribute. To indicate that an attribute is encapsulated, the keyword
private is used before the definition. Variables used in the
definitions within a block that are not themselves defined in the
block are inputs.
For example, we can define a simple model describing the performance of a student in a course. First we create objects representing the student, and course, then create a performance object that depends on both of them. The course object itself depends on two other objects, the professor and the field of the course. In this model, we assume that the professor's field is the same as that of the course.
student = {
smart = flip 0.4;
hard_working = flip 0.7;
good_test_taker = if smart then flip 0.8 else flip 0.3
};
field = {
hard = flip 0.5;
high_standards = flip 0.3
};
prof = {
private mean = flip 0.1;
clear = ~ mean & (if field.hard then flip 0.5 else flip 0.8)
};
course = {
well_taught = (prof.clear | ~ field.hard) & flip 0.9
};
performance = {
private understands =
if student.hard_working
then case <student.smart, course.well_taught> of
# <true, true> : flip 0.95
# <false, false> : flip 0.35
# _ : flip 0.7
else case student.smart of
# true : flip 0.7
# false : flip 0.1;
exam_grade =
case <understands, student.good_test_taker> of
# <true, true> : dist [ 0.6 : 'A, 0.3 : 'B, 0.1 : 'C ]
# <true, false> : dist [ 0.4 : 'A, 0.2 : 'B, 0.4 : 'C ]
# <false, true> : dist [ 0.5 : 'A, 0.2 : 'B, 0.3 : 'C ]
# <false, false> : dist [ 0.1 : 'A, 0.4 : 'B, 0.5 : 'C ];
homework_grade =
case <understands, student.hard_working> of
# <true, true> : dist [ 0.6 : 'A, 0.3 : 'B, 0.1 : 'C ]
# <true, false> : dist [ 0.4 : 'A, 0.2 : 'B, 0.4 : 'C ]
# <false, true> : dist [ 0.5 : 'A, 0.2 : 'B, 0.3 : 'C ]
# <false, false> : dist [ 0.1 : 'A, 0.4 : 'B, 0.5 : 'C ];
}
In the above example, each of the blocks defines a single object. We will see in the next section how to use blocks in functions to define classes of objects.
Block notation is actually the most convenient way to create larger
models.
As models get larger,
it is more convenient to create them in a file rather than type them
in at the command line.
Libraries allow you to do that.
A library has the form of a block, without the surrounding curly
braces.
The standard suffix for IBAL libraries is .ibl.
For example, the above block is contained in a file named
oobn1.ibl in the Examples directory.
To load it into IBAL, use the command :l oobn1, or
:l oobn1.ibl. (In general, commands at the IBAL prompt begin
with a colon. You can use :? to see a list of available
commands.)
This creates a variable called oobn1, and
defines it to be the contents of oobn1.ibl. You can now access
any of the variables defined in oobn1.ibl as components of the
variable block. For example, you could ask for the joint probability
of a student's smartness and their exam grade with
< oobn1.student.smart, oobn1.performance.exam_grade >In general, one can use this method to ask arbitrary queries on a model. In the case of a Bayesian network, IBAL's inference algorithm amounts to performing variable elimination, which is a standard Bayesian network inference algorithm. Future plans for IBAL include implementing approximate Bayesian network inference algorithms as an alternative.
You can also load libraries on the UNIX command line. Any library to
be loaded can be passed as an additional argument to IBAL. The
.ibl suffix will be added automatically if it is not present.
So, for example, ibal alarm will load the alarm.ibl library.
All definitions in alarm.ibl will be available as part of an
alarm variable. Furthermore, if multiple libraries are loaded,
the contents of earlier libraries are available to later ones in a
similar way.
One can also save the contents of the current IBAL session in a
library. Entering :s filename creates a library named
filename, containing the declarations that have been entered in
the current IBAL session.
It is also possible to create a file containing an expression (as
opposed to a library), and evaluate it. This is achieved by using the
-b option to IBAL. The file to be evaluated will be the last
one on the command line, and it may be preceded by libraries.
For example, look at the contents of file1.ibl, file2.ibl
and file3.ibl in the Examples directory, and run
ibal -b file1 file2 file3.
A word about paths. IBAL uses a path variable defined in
global.ml to tell it where to look for files. By default it
looks first in the current directory, and then in the Examples
subdirectory of the IBAL distribution. You can change the path in
three ways. To change it permanently, just edit the definition of
path in global.ml and recompile IBAL. Alternatively, you
can add a directory to the front of the path using the -i
command line argument, followed by the directory, with no space
between the -i and the directory. Finally, you can add a
directory in the IBAL interpreter, using the :i command,
followed by the directory.