none
Calling a function in open "using" block

    Question

  • I am trying to implement the model from Ch. 2 of the "Model-Based Machine Learning" on assessing people skills. A bunch of people a number of multi-answer questions. To answer a question, a person must possess some skills (question-dependent). 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 1-D array of 1-D arrays
    var skillsNeeded = Variable.Array(Variable.Array<int>(questionSizes), questions);
    skillsNeeded.ObservedValue = skillsNeededData;
    
    // relevantSkills: building a jagged 1-D array of 1-D 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 left-hand side indices (questions) do not include the range 'questionSizes', which appears on the right-hand side (perhaps implicitly by an open ForEach block)."

    It is something about the "questionSizes" range, as the "releventSkills" is a jagged 1-D array of 1-D 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/MBML-Skills

    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

All replies

  • You could use Variable.AllTrue.  See the Student skills example since it is very similar.  The correct way to implement an array-reducing 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 AM
  • 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