Answered by:
Calling a function in open "using" block
Question

I am trying to implement the model from Ch. 2 of the "ModelBased Machine Learning" on assessing people skills. A bunch of people a number of multianswer questions. To answer a question, a person must possess some skills (questiondependent). The actual observation is a noisy bool RV which is obtained using AND over necessary skills.
The link to the page and the graphical model is: http://www.mbmlbook.com/LearningSkills_Moving_to_real_data.html
There is a universal set of skills. Each person can possess a subset of those skills (including all skills). To model this, I use the SubArray factor. The factor accepts the list of universal skills and a list of integers that a person must possess to answer a given question. This factor returns a variable array of bool RVs, a subset of universal skills.
In order to answer the question, a person must have all the skills from that subset. This is modeled as AND over all bool RVs. To model the observed variable, some noise is introduced. Two coins are flipped: if #1 is true, the observed variable coincides with AND result. If #1 is false, the result of #2 coin is returned.
I have the question about the AND operation over all VariableArray<bool>. Is it correctly implemented? I wrote the following code:
public static Variable<bool> VariableArrayAnd(VariableArray<bool> x) { Variable<bool> result = Variable.New<bool>(); Range r = x.Range; using(var b = Variable.ForEach(r)) { var idx = b.Index; using (Variable.If(idx==0)) result = x[r]; using (Variable.If(idx>0)) result = result & x[r]; } return result; }
This function is called in the main "using" block of my model:
using (Variable.ForEach(questions)) { // pick subset of skills that are needed to answer the question relevantSkills[questions] = Variable.Subarray<bool>(skill, skillsNeeded[questions]); // all skills are required to answer the question hasSkills[questions] = VariableArrayAnd(relevantSkills[questions]); // AddNoise factor: flip the coin #1 for picking what to return Variable<bool> coin1 = Variable.Bernoulli(0.5); // flip the coin #2 in case coin #1 shows that a random result should be returned Variable<bool> coin2 = Variable.Bernoulli(0.5); // AddNoise logic using (Variable.If(coin1)) isCorrect[questions] = hasSkills[questions]; using (Variable.IfNot(coin1)) isCorrect[questions] = coin2; }
The "isCorrect" and "skillsNeeded" arrays are observed. The other variables are defined as such:
Range skills = new Range(numSkills).Named("skills"); Range questions = new Range(numQuestions).Named("questions"); VariableArray<bool> skill = Variable.Array<bool>(skills); skill[skills] = Variable.Bernoulli(0.5).ForEach(skills); // helper variable array: question sizes // each question has some number of skills to be answered correctly var questionSizesArray = Variable.Array<int>(questions); questionSizesArray.ObservedValue = sizes; Range questionSizes = new Range(questionSizesArray[questions]).Named("questionSizes"); // skillsNeeded: building a jagged 1D array of 1D arrays var skillsNeeded = Variable.Array(Variable.Array<int>(questionSizes), questions); skillsNeeded.ObservedValue = skillsNeededData; // relevantSkills: building a jagged 1D array of 1D arrays var relevantSkills = Variable.Array(Variable.Array<bool>(questionSizes), questions); VariableArray<bool> hasSkills = Variable.Array<bool>(questions); VariableArray<bool> isCorrect = Variable.Array<bool>(questions);
For completeness, here are the data arrays:
int[][] skillsNeededData = GetSkillsNeededData(skillsQuestionsData); int[] sizes = GetQuestionSizes(skillsNeededData);

In my test run to check if inference works in the model, I loop (C# loop) over persons and observe the "isCorrect" variable array. I try to infer the "hasSkills" array (array of Beta distributions):
for (int i = 0; i < numPersons; i++) { isCorrect.ObservedValue = BuildIsCorrect(trueAnswers, personAnswers[i]); Beta[] hasSkillsMarginal = engine.Infer<Beta[]>(hasSkills); Console.WriteLine("PERSON #{0} has skills:", i+1); Console.WriteLine(hasSkillsMarginal); Console.WriteLine(""); }
During runtime, I get an error at the following line in the model:
hasSkills[questions] = VariableArrayAnd(relevantSkills[questions]);
and the error message reads:
"The lefthand side indices (questions) do not include the range 'questionSizes', which appears on the righthand side (perhaps implicitly by an open ForEach block)."
It is something about the "questionSizes" range, as the "releventSkills" is a jagged 1D array of 1D arrays. The "questions" is a range over all questions of the test. The "questionSizes" is an internal array and tells how many skills are needed for a particular question. To me it is not clear how to do this "AND" operation correctly and whether there no issues somewhere else in the model...
The full code + data is on GitHub: https://github.com/usptact/MBMLSkills
Sorry for the lengthy post but I tried to include as much information about the model and what is actually implemented.
 Edited by usptact Tuesday, October 10, 2017 8:43 AM
Tuesday, October 10, 2017 8:29 AM
Answers

You could use Variable.AllTrue. See the Student skills example since it is very similar. The correct way to implement an arrayreducing function like VariableArrayAnd is described at Markov chains and grids.
 Marked as answer by usptact Wednesday, October 11, 2017 4:22 PM
Tuesday, October 10, 2017 11:14 AMOwner
All replies

You could use Variable.AllTrue. See the Student skills example since it is very similar. The correct way to implement an arrayreducing function like VariableArrayAnd is described at Markov chains and grids.
 Marked as answer by usptact Wednesday, October 11, 2017 4:22 PM
Tuesday, October 10, 2017 11:14 AMOwner 
Thank you, Tom! The Variable.AllTrue did the trick (+ few other smaller changes).
Yes, the Student Skills example is very similar. I should've started with it.
Wednesday, October 11, 2017 4:22 PM