Construction & inference in C#
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NetworkExample.cs" company="Bayes Server">
// Copyright (C) Bayes Server. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace BayesServer.HelpSamples
{
using System;
using BayesServer.Inference.RelevanceTree;
public static class NetworkExample
{
public static void Main()
{
// In this example we programatically create a simple Bayesian network.
// Note that you can automatically define nodes from data using
// classes in BayesServer.Data.Discovery,
// and you can automatically learn the parameters using classes in
// BayesServer.Learning.Parameters,
// however here we build a Bayesian network from scratch.
var network = new Network("Demo");
// add the nodes (variables)
var aTrue = new State("True");
var aFalse = new State("False");
var a = new Node("A", aTrue, aFalse);
var bTrue = new State("True");
var bFalse = new State("False");
var b = new Node("B", bTrue, bFalse);
var cTrue = new State("True");
var cFalse = new State("False");
var c = new Node("C", cTrue, cFalse);
var dTrue = new State("True");
var dFalse = new State("False");
var d = new Node("D", dTrue, dFalse);
network.Nodes.Add(a);
network.Nodes.Add(b);
network.Nodes.Add(c);
network.Nodes.Add(d);
// add some directed links
network.Links.Add(new Link(a, b));
network.Links.Add(new Link(a, c));
network.Links.Add(new Link(b, d));
network.Links.Add(new Link(c, d));
// at this point we have fully specified the structural (graphical) specification of the Bayesian Network.
// We must define the necessary probability distributions for each node.
// Each node in a Bayesian Network requires a probability distribution conditioned on it's parents.
// NewDistribution() can be called on a Node to create the appropriate probability distribution for a node
// or it can be created manually.
// The interface IDistribution has been designed to represent both discrete and continuous variables,
// As we are currently dealing with discrete distributions, we will use the
// Table class.
// To access the discrete part of a distribution, we use IDistribution.Table.
// The Table class is used to define distributions over a number of discrete variables.
var tableA = a.NewDistribution().Table; // access the table property of the Distribution
// IMPORTANT
// Note that calling Node.NewDistribution() does NOT assign the distribution to the node.
// A distribution cannot be assigned to a node until it is correctly specified.
// If a distribution becomes invalid (e.g. a parent node is added), it is automatically set to null.
tableA[aTrue] = 0.1;
tableA[aFalse] = 0.9;
// now tableA is correctly specified we can assign it to Node A;
a.Distribution = tableA;
// node B has node A as a parent, therefore its distribution will be P(B|A)
var tableB = b.NewDistribution().Table;
tableB[aTrue, bTrue] = 0.2;
tableB[aTrue, bFalse] = 0.8;
tableB[aFalse, bTrue] = 0.15;
tableB[aFalse, bFalse] = 0.85;
b.Distribution = tableB;
// specify P(C|A)
var tableC = c.NewDistribution().Table;
tableC[aTrue, cTrue] = 0.3;
tableC[aTrue, cFalse] = 0.7;
tableC[aFalse, cTrue] = 0.4;
tableC[aFalse, cFalse] = 0.6;
c.Distribution = tableC;
// specify P(D|B,C)
var tableD = d.NewDistribution().Table;
// we could specify the values individually as above, or we can use a TableIterator as follows
var iteratorD = new TableIterator(tableD, new Node[] { b, c, d });
iteratorD.CopyFrom(new double[] { 0.4, 0.6, 0.55, 0.45, 0.32, 0.68, 0.01, 0.99 });
d.Distribution = tableD;
// The network is now fully specified
// If required the network can be saved...
if (false) // change this to true to save the network
{
// network.Save("fileName.bayes"); // replace 'fileName.bayes' with your own path and uncomment start of line
}
// Now we will calculate P(A|D=True), i.e. the probability of A given the evidence that D is true
// use the factory design pattern to create the necessary inference related objects
var factory = new RelevanceTreeInferenceFactory();
var inference = factory.CreateInferenceEngine(network);
var queryOptions = factory.CreateQueryOptions();
var queryOutput = factory.CreateQueryOutput();
// we could have created these objects explicitly instead, but as the number of algorithms grows
// this makes it easier to switch between them
inference.Evidence.SetState(dTrue); // set D = True
var queryA = new Table(a);
inference.QueryDistributions.Add(queryA);
inference.Query(queryOptions, queryOutput); // note that this can raise an exception (see help for details)
Console.WriteLine("P(A|D=True) = {" + queryA[aTrue] + "," + queryA[aFalse] + "}.");
// Expected output ...
// P(A|D=True) = {0.0980748663101604,0.90192513368984}
// to perform another query we reuse all the objects
// now lets calculate P(A|D=True, C=True)
inference.Evidence.SetState(cTrue);
// we will also return the log-likelihood of the case
queryOptions.LogLikelihood = true; // only request the log-likelihood if you really need it, as extra computation is involved
inference.Query(queryOptions, queryOutput);
Console.WriteLine(string.Format("P(A|D=True, C=True) = [{0},{1}], log-likelihood = {2}.", queryA[aTrue], queryA[aFalse], queryOutput.LogLikelihood.Value));
// Expected output ...
// P(A|D=True, C=True) = {0.0777777777777778,0.922222222222222}, log-likelihood = -2.04330249506396.
// Note that we can also calculate joint queries such as P(A,B|D=True,C=True)
}
}
}