Working with arrays and ranges

When defining a large model, it is a good idea to avoid creating too many Variable objects. One way to do this is to use Infer.NET's built-in concept of a VariableArray. VariableArrays are processed as a unit during model compilation and inference, allowing dramatic speedups. This section covers single dimensional and multi-dimension arrays. Jagged arrays (arrays of arrays) are described in a later section.

Ranges

A Range object represents a stream of integers from 0 to N-1. This code creates a range from 0 to 9,

Range pixel = new Range(10);

It is also possible to create named ranges, such as

Range image = new Range(4).Named("image");

The size of a range does not have to be fixed when defining your model, for example:

Variable<int> nImages = Variable.New<int>();
Range image = new Range(nImages);

Variable arrays

Having defined ranges, you can then declare a variable array using the syntax:

Variable.Array<T>(Range r1,Range r2,...)

The provided ranges must be distinct. The dimensionality of the array is determined from the number of ranges. Currently, arrays of one or two dimensions are supported. This code creates a 1D and then a 2D array:

VariableArray<bool> bools = Variable.Array<bool>(pixel);
VariableArray2D<double> doubles2D = Variable.Array<double>(pixel,image);

At this point, the arrays are declared but not initialized. You can fill an array in two ways: the SetTo method or using an indexer [].

The SetTo method is used in conjunction with a factor that returns an array, e.g. Factor.MatrixMultiply. The SetTo method takes a factor along with factor arguments, and defines the array contents to be the output of the factor:

doubles2D.SetTo(Factor.MatrixMultiply, someArray2D, anotherArray2D);

The indexer approach is used in conjunction with a factor that returns a single item, e.g. Variable.Bernoulli. The right hand side of the indexer call must be a stream of variables, tagged with the same ranges used to create the VariableArray. The simplest way to create a stream of variables is with the .ForEach method:

expression.ForEach(Range r1,Range r2,...)

The result of this call is a stream of independent variables of type T, each having the same definition as the provided expression. For example, this code fills in the contents of the bools array declared above:

bools[pixel] = Variable.Bernoulli(0.7).ForEach(pixel);

Each element of bools is now an independent Bernoulli variable with mean 0.7. When you pass a stream of variables to a factor, the result is also a stream, so the following is also valid:

bools[pixel] = !(Variable.Bernoulli(0.7).ForEach(pixel));

In this case, each element of bools is an independent Bernoulli variable defined as the logical NOT of a Bernoulli variable of mean 0.7. You can also mix streams and individual variables, like so:

bools[pixel] = Variable.Bernoulli(0.7).ForEach(pixel) | Variable.Bernoulli(0.9);

In this case, we take N independent Bernoulli variables with mean 0.7 and OR them with a single Bernoulli variable with mean 0.9, giving a new set of N variables. The resulting variables are not independent since they all depend on the outcome of the 0.9 variable. If that variable is true, then all of the results are true.

In general, the arguments to the factor on the right-hand side may be streams over different ranges, as long as the overall set of ranges on the right-hand side matches the indices on the left-hand side.

Besides .ForEach,you can create a stream of variables by indexing an array by its range object. For example, if we had an array of doubles called allProbTrues, then we could write:

bools[pixel] = Variable.Bernoulli(allProbTrues[pixel]);

Because there is no loop, this notation allows large numbers of regularly-structured variables to be defined very compactly.

See also:
Indexing arrays by observed variables

At the moment, arrays can only be indexed by ranges or integer variables. That is, bools can only be indexed by pixel or a Variable<int>. To index by a constant, e.g. bools[0], use bools[Variable.Constant(0)]. (This will be simplified in a future release.)

If your model has a lot of statements requiring the ForEach method to be appended for a given range, it may be more convenient to use the ForEach block.

Streams of variables

This section describes in more detail the stream concept introduced above. The basic idea is that, besides arrays, Infer.NET has the concept of a stream of variables. To illustrate why this is needed, consider a factor like Factor.Bernoulli. This factor takes one scalar double. Passing it an array of doubles should be a type error. However, we also want to allow compact code such as:

bools[pixel] = Variable.Bernoulli(allProbTrues[pixel]);

Infer.NET thus provides a stream syntax to allow code that is both compact and type-safe. Infer.NET VariableArrays are special in that they are indexed by ranges rather than integers. When a VariableArray is indexed by a range object, i.e. varray[range], the result is a stream of variables tagged with that range. This stream can then be given to a factor which expects the variable type, returning a stream of result variables. The stream of result variables is then converted back into an array by assignment, again using the range as index. Notice the code is very similar to what you would write as the body of a loop, except no looping construct is needed. Conceptually, the syntax allProbTrues[pixel] is creating a stream of double variables, Variable.Bernoulli is then creating a stream of boolean variables, and bools[pixel] = ... is filling the bools array with that stream. In implementation, a stream is represented by a single object, with no looping involved. Thus the code is more efficient than an explicit loop.

The result of Variable.Bernoulli above is a stream that can be passed through any number of factors before it is assigned to an array. Because streams are tagged with range objects, you can also combine multiple streams into one expression yielding a multidimensional VariableArray, like so:

doubles2D[pixel,image] = y[pixel]+z[image];

Constant arrays

A constant array is created using Variable.Constant, passing a .NET array and a set of ranges:

VariableArray<double> data = Variable.Constant(
new double[] { 1, 2, 3, 4 }, image);
VariableArray2D<double> data2D = Variable.Constant(
new double[,] { {5,6}, {7,9} }, range1, range2);

The resulting array can only be indexed by the ranges that it was created with. Constant arrays can be used just like random variable arrays. In particular, they can appear on the left-hand side of an indexed assignment. But the effect is to impose a constraint, rather than change the contents of the array. For example:

data[image] = Variable.GaussianFromMeanAndPrecision(mean, 1).ForEach(image);

is equivalent to:

Variable.ConstrainEqual(
data[image],
Variable.GaussianFromMeanAndPrecision(mean, 1).ForEach(image));

Arrays of observed variables

Similarly, arrays of observed values can be created using Variable.Observed,

VariableArray<double> obs = Variable.Observed(
new double[] { 1, 2, 3, 4 }, image);
VariableArray2D<double> obs2D = Variable.Observed(
new double[,] { {5,6}, {7,9} }, range1, range2);

Alternatively, you can define the observed variable arrays without specifying ranges, and define the ranges from the arrays:

VariableArray<double> obs = Variable.Observed(new double[] { 1, 2, 3, 4 });
VariableArray2D<double> obs2D = Variable.Observed(new double[,] { { 5, 6 }, { 7, 9 } });
Range r = obs.Range;
Range r0 = obs2D.Range0;
Range r1 = obs2D.Range1;

If you use Variable.Observed, you must supply initial values. If you want to omit the initial observed values, you should use Variable.Array instead. Here is an example for a 2D array where the array sizes and the array itself are not specified when definiting the model:

Variable<int> sizeX = Variable.New<int>().Named("NX");
Variable<int> sizeY = Variable.New<int>().Named("NY");
Range x = new Range(sizeX).Named("x");
Range y = new Range(sizeX).Named("y");
VariableArray2D<double> arrayXY= Variable.Array<double>(x, y).Named("ArrayXY");

Later, when you want to run inference, you can supply the observed value as follows. Infer.NET will expect the array to be the same size as the ranges:

double[,] obs2DData = new double[,] { { 5, 6 }, { 7, 9 }, {6, 7} };
sizeX.ObservedValue = obs2DData.GetLength(0);
sizeY.ObservedValue = obs2DData.GetLength(1);
arrayXY.ObservedValue = obs2DData;

Last modified at 7/24/2009 6:48 PM  by Elise Langham (Elanit) 
©2009 Microsoft Corporation. All rights reserved.  Terms of Use | Trademarks | Privacy Statement