Infer.NET user guide : The Infer.NET Modelling API
Cloning ranges
Sometimes you want to access all pairs of array elements in an expression. For example, suppose you wanted to define a two-dimensional array outerProduct where outerProduct[i,j] = y[i]*y[j]. How do you declare ranges i and j? You might try the following:
Range i = new Range(3);
Range j = new Range(3);
VariableArray<double> y = Variable.Array<double>(i);
y.ObservedValue = new double[] { 1, 2, 3 };
VariableArray2D<double> outerProduct = Variable.Array<double>(i, j);
outerProduct[i, j] = y[i]*y[j];
But this is not accepted by Infer.NET, because y was declared with range i and cannot be indexed by range j, even though j has the same size. In order to tell Infer.NET that range j is compatible with range i, you create it by cloning range i:
Range i = new Range(3);
Range j = i.Clone();
VariableArray<double> y = Variable.Array<double>(i);
y.ObservedValue = new double[] { 1, 2, 3 };
VariableArray2D<double> outerProduct = Variable.Array<double>(i, j);
outerProduct[i, j] = y[i]*y[j];
This version works as desired.
Mixed Membership Stochastic Blockmodel
Another good example of where you need to clone a Range is in the ‘Mixed Membership Stochastic Blockmodel’ of Airoldi et al. which attempts to model relational information among nodes in a network(for example individuals in an social network). Given N nodes, K blocks and a binary matrix of known relationships between nodes (YObs in the code below), the aim is to learn the block membership mixture pi for each node at the same time as learning the pairwise relationship B between different blocks. Because this is a model of relationships, nodes are processed in pairs (initiator and receiver) and these nodes both index the same array variable pi. However the nested Variable.ForEach
statements require different ranges; the solution is to clone the range as highlighted below. Similarly the link matrix B requires different ranges for the nested Variable.Switch
statements.
// Observed interaction matrix
var YObs = new bool[5][];
YObs[0] = new bool[] { false, true, true, false, false };
YObs[1] = new bool[] { true, false, true, false, false };
YObs[2] = new bool[] { true, true, false, false, false };
YObs[3] = new bool[] { false, false, false, false, true };
YObs[4] = new bool[] { false, false, false, true, false };int K = 2; // Number of blocks
int N = YObs.Length; // Number of nodes
// Ranges
Range p = new Range(N).Named("p"); // Range for initiator
Range q = p.Clone().Named("q"); // Range for receiver
Range kp = new Range(K).Named("kp"); // Range for initiator block membership
Range kq = kp.Clone().Named("kq"); // Range for receiver block membership
// The model
var Y = Variable.Array(Variable.Array<bool>(q), p); // Interaction matrix
var pi = Variable.Array<Vector>(p).Named("pi"); // Block-membership probability vector
pi[p] = Variable.DirichletUniform(kp).ForEach(p);
var B = Variable.Array<double>(kp, kq).Named("B"); // Link probability matrix
B[kp, kq] = Variable.Beta(1, 1).ForEach(kp, kq);
using (Variable.ForEach(p)) {
using (Variable.ForEach(q)) {
var z1 = Variable.Discrete(pi[p]).Named("z1"); // Draw initiator membership indicator
var z2 = Variable.Discrete(pi[q]).Named("z2"); // Draw receiver membership indicator
z2.SetValueRange(kq);
using (Variable.Switch(z1))
using (Variable.Switch(z2))
Y[p][q] = Variable.Bernoulli(B[z1, z2]); // Sample interaction value
}
}
// Initialise to break symmetry
var piInit = new Dirichlet[N];
for (int i = 0; i < N; i++) {
Vector v = Vector.Zero(K);
for (int j = 0; j < K; j++) v[j] = 10 * Rand.Double();
piInit[i] = new Dirichlet(v);
}
// Hook up the data
Y.ObservedValue = YObs;
// Infer
var engine = new InferenceEngine(new VariationalMessagePassing());
pi.InitialiseTo(Distribution<Vector>.Array(piInit));
var posteriorPi = engine.Infer<Dirichlet[]>(pi);
var posteriorB = engine.Infer<Beta[,]>(B);