SMILE Tutorial 1: Creating a Bayesian Network

From DSL
Jump to: navigation, search

In this tutorial we will go through the steps required to create a very simple Bayesian network. Imagine a venture capitalist that considers a risky investment in a startup company. A major source of uncertainty about her investment is the success of the company. She is aware of the fact that only around 20% of all startup companies succeed. She can reduce this uncertainty somewhat by asking expert opinion. Her expert, however, is not perfect in his forecasts. Of all startup companies that eventually succeed, he judges about 40% to be good prospects, 40% to be moderate prospects, and 20% to be poor prospects. Of all startup companies that eventually fail, he judges about 10% to be good prospects, 30% to be moderate prospects, and 60% to be poor prospects.

How can our investor use the information from the expert? What is the chance for success if the expert judges the prospects for success to be good? What if he judges them to be poor? We will create a Bayesian network that will allow us to determine the exact numerical implications of the expert's opinion on the investor's expectation of success of the venture. The Bayesian network will contain two nodes representing random variables: Success of the venture and Expert forecast.

We will go step by step explaining what each line of the code does. The full code listing can be found in the appendices.

We start by declaring an instance of a network.

  DSL_network theNet;

Now we are going to create a node called Success. The node will be of type DSL_CPT (see DSL_nodeDefinition class for more information), which means that it represents a random discrete event.

  int success = theNet.AddNode(DSL_CPT,"Success");

Note that the value returned by the function AddNode is the handle of the node. We store this handle for future references to the node. Following the description of the problem, we need this node to have two states. Let us name them "Success" and "Failure":

  DSL_stringArray someNames;
  someNames.Add("Success");
  someNames.Add("Failure");
  theNet.GetNode(success)->Definition()->SetNumberOfOutcomes(someNames);

The number and name of the outcomes belongs to the definition of the node. We can (and must) define them before any useful inference can be made on the network. Following the same procedure for the node Forecast, we create a new node and let it have three states:

  int forecast = theNet.AddNode(DSL_CPT,"Forecast");
  someNames.Flush();someNames.Add("Good"); 
  someNames.Add("Moderate");
  someNames.Add("Poor");
  theNet.GetNode(forecast)->Definition()->SetNumberOfOutcomes(someNames);

Now, according to the model, we must add an arc from "Success" to "Forecast" to represent the conditional dependence of the latter on the former:

  theNet.AddArc(success,forecast);

Note that we should always use the handles of the nodes to perform operations on them.

Now we need to fill in the distribution of the nodes. There are several ways of doing this and we are going to use here the most "direct" one. We know that this node has two states and no parents, so we just need two numbers that represent the probability of each of the states coming true. These two numbers are (from the problem description) P(Success = Success) = 0.2 and P(Success = Failure) = 0.8. Note that these two values must add up to one.

  DSL_doubleArray theProbs;
  theProbs.SetSize(2);

As we are going to use a "direct" method, we need to set the size of the data structure to its exact value. If this is not done correctly, things will go really bad.

  theProbs[0] = 0.2;
  theProbs[1] = 0.8;
  theNet.GetNode(success)->Definition()->SetDefinition(theProbs);

Note that the function SetDefinition does not perform any checking. This is the fastest method of setting the definition of any node but it is also the most dangerous if used carelessly. Now we have to fill the distribution of the node Forecast conditioned on the node Success. There is a more tedious but safer method of setting the distributions. This method consists of using a system of "coordinates" that, in this case, will have two dimensions: one for the states of the parent (Success) and one for the states of the child (Forecast). The probabilities we have to fill in are:

P("Forecast" = Good | "Success" = Success) = 0.4

P("Forecast" = Moderate | "Success" = Success) = 0.4

P("Forecast" = Poor | "Success" = Success) = 0.2

P("Forecast" = Good | "Success" = Failure) = 0.1

P("Forecast" = Moderate | "Success" = Failure) = 0.3

P("Forecast" = Poor | "Success" = Failure) = 0.6

The order of these probabilities, as explained in the section on implementation of conditional distributions, is given by considering the state of the first parent of the node as the "most significant" (thinking of the coordinates in terms of "bits") coordinate, then the second parent, then the third (and so on), and finally considering the coordinate of the node itself as the least significant one.

  DSL_sysCoordinates theCoordinates(*theNet.GetNode(forecast)->Definition());

This line creates and "links" a DSL_sysCoordinates object to a DSL_nodeDefinition object. If it is the case that the definition contains a DSL_Dmatrix (as it is in this example), the system of coordinates will adjust its size to the number of dimensions of that matrix and will set all of the coordinates to 0 (i.e., the first element of the matrix). In the current example, theCoordinates will reserve space to hold two integer values: the first will hold the state of the node Success and the second will hold the state of the node Forecast. Right after creation, theCoordinates will contain (0,0). The method UncheckedValue allows us to access the matrix directly through theCoordinates, using the actual contents of theCoordinates to indicate which element of the matrix is to be accessed:

  theCoordinates.UncheckedValue() = 0.4;

This sets the probability of the first element of the matrix to 0.4 (i.e., P(Forecast = Good | Success = Success) = 0.4).

  theCoordinates.Next();

This tells theCoordinates to go to the next point in our 2x3 space. In this case it is (0,1).

  theCoordinates.UncheckedValue() = 0.4;

This sets the probability of the second element of the matrix to 0.4 (i.e., P("Forecast" = Moderate | "Success" = Success) = 0.4).

  theCoordinates.Next();

This last line makes theCoordinates to go to the next point in our 2x3 space. In this case it is (0,2).

  theCoordinates.UncheckedValue() = 0.2;
  theCoordinates.Next();

After the last line, theCoordinates contains (1,0). We keep doing the same thing until we have all the numbers filled in:

  theCoordinates.UncheckedValue() = 0.1;
  theCoordinates.Next();
  theCoordinates.UncheckedValue() = 0.3;
  theCoordinates.Next();
  theCoordinates.UncheckedValue() = 0.6;

We finally store the network in a file called "tutorial.dsl" so we can retrieve it later. It will be used in the rest of the tutorials. The format of the file will be the SMILE native format, as indicated by the extension ".dsl".

  theNet.WriteFile("tutorial.dsl");

Now we have created our first Bayesian network. In the following tutorial we will learn how to perform inference with it.

Personal tools