locked
Work around plates by manual variable synchronization (Migrated from community.research.microsoft.com) RRS feed

  • Question

  • laura posted on 02-04-2009 11:55 AM

    This is another "Laura learned a lesson" post.

     

    I am working on a probabilistic model that models aspects of a graph. Given the graph structure, properties of nodes and edges are inferred. The model has plates for nodes and edges (which are treated as pairs of nodes). An issue is that the edge plate may theoretically have nodes X nodes many instantiations, but actually just a few edges are contained in the graph.

     We circumvented this problem by duplicating the edge variables and moving them into the node plate of both endpoints. Now the model should behave as if these duplicated variables are just one. We do this after each inference iteration, messages received by a pair of variables are exchanged. E.g. In VMP the output messages of both variables are a product of both input messages. Now the manually computed output messages are fed back to the model.

    Having int-valued edge variables "edgeLabel", this is done by the following (sorry f sharp syntax)

     

    let output = inferenceEngine.GetOutputMessage<DistributionRefArray<DistributionRefArray<Discrete, int>, int[>>(edgeLabel)


    for all variable pairs do the following... first variable stored in (uId,fIdx) its duplicate stored in (fId,uIdx)

                            let uMessage = output.[uId].[fIdx]
                            let fMessage = output.[fId].[uIdx]
                            edgeLabelSync.ObservedValue.[uId].[fIdx].SetToProduct(uMessage, fMessage)
                            edgeLabelSync.ObservedValue.[fId].[uIdx] <- (edgeLabelSync.ObservedValue.[uId].[fIdx])

    edgeLabelSync is an "Observed" variable that is used as a prior for edgeLabel.

                edgeLabel.[uRange].[fRange] <- Variable.Random(edgeLabelSync.[uRange].[fRange]).Attrib(new ValueRange(intValueRange))
     

    You are able to set outMessages to edgeLabelSync if it is marked as writable

     let edgeLabelSync = (Variable.Constant<Discrete>((edgeLabelInitPrior:_[[), uRange , fRange)).Named("edgeLabelSync")
        do edgeLabelSync.AddAttribute(intValueRange)
        do edgeLabelSync.IsReadOnly <- false

     

    !!!!! Attention !!!!!!

     Be careful to not create copies of the output array!You could do this calling temp = output.ToArray() and edgeLabelSync.ObservedValue <- temp

    This slows down the performance tremendously because in each iteration this memory must be allocated and freed... 

    Note that you can use the index based accessors to operate on the infer.net VariableArrays.

     

     Cheers,

     Laura

     

    Friday, June 3, 2011 4:45 PM

Answers

  • laura replied on 03-03-2009 6:57 AM

    I forgot to mention, that I am using the Compiled Algorithm approach now. It was a five minute thing to work through the user guide... Thanks a lot for the hint!!!

     

    For that reason I think this thread should be replaced by the solution using the CompiledAlgorithm object instead.

     

    Laura

    Friday, June 3, 2011 4:46 PM

All replies

  • minka replied on 02-04-2009 2:00 PM

    Hi Laura,

     Thanks for the post.  Note that it is possible to represent edges in Infer.NET by creating an edge range along with arrays fromNode[edge] and toNode[edge] giving the endpoints of each edge.  This is illustrated in the tutorial on creating large irregular graphs: http://research.microsoft.com/en-us/um/cambridge/projects/infernet/docs/How%20to%20represent%20large%20irregular%20graphs.aspx

    Would this have worked in your application?

    Friday, June 3, 2011 4:45 PM
  • laura replied on 02-05-2009 8:49 AM

     I tried the following solution, but model compilation died with "Invalid OperationException: Range 'uRange' is already open in a ForEach block"

     

     

    edgeLabel is an array array indexed over users (uRange) and his friends (fVaryRange). If we have three users where u1 is a mutual friend of u2 u3, we have a situation where indices (0,0) and (1,0) represent the edge (u1,u2); (0,1) and (2,0) represent (u1,u3)

    We maintain two further jagged arrays of the same shape. 

    u1 -> u2 u3

    u2 -> u1

    u3 -> u1

     

    fRow links each entry to the row of its counterpart. E.g.


    1 -> 0

    2 -> 0

     

    fCol links to the column of its counterpart, E.g.

    0 -> 0, 0

    1 -> 0

    2 -> 1

     

     we wrap these jagged arrays by Variable.Constant

         let fRowData = Variable.Constant(fRow, uRange, fVaryRange).Named("fRowData").Attrib(new ValueRange(uRange))
        let fColData = Variable.Constant(fCol, uRange, fVaryRange).Named("fColData").Attrib(new ValueRange(fVaryRange))

    Then we have a model specification:

        do using(Variable.ForEach(uRange)) (fun _ ->

            // Friends plate
            using(Variable.ForEach(fVaryRange)) (fun _ ->
                // draw an edgeLable from the prior distribution
                edgeLabel.[uRange].[fVaryRange] <- Variable.Random(edgeLabelSync.[uRange].[fVaryRange]).Attrib(new ValueRange(cRange))
               


                // get row and column of the counterpart

                let fId = (fRowData.[uRange].[fVaryRange]).Named("fId")
                let uIdx = (fColData.[uRange].[fVaryRange]).Named("uIdx")
                using(Variable.Switch(fId)) (fun _ ->
                    using(Variable.Switch(uIdx)) (fun _ ->

                        // constrain the counterparts to be equal. Ok, an issue may be that each pair is constrained twice.

                       Variable.ConstrainEqual(edgeLabel.[uRange].[fVaryRange], edgeLabel.[fId].[uIdx])
                        )
                    )
                )
           

            // do something with edgeLabel
            )


     The problem is that uRange is opened twice. The first time explicitly, the second time by Variable.Switch(fId).

    I was thinking about duplicating uRange and fVaryRange, but then the duplicate is prohibited to index edgeLabel.

     

    Because this didn't work, we did the synchronization externally as described above...

     

    Laura

    Friday, June 3, 2011 4:45 PM
  • minka replied on 02-05-2009 10:53 AM

    Yes, I agree that approach would not work.  However my suggestion is very different from that.   In my version, each edge is only represented once.

    Friday, June 3, 2011 4:45 PM
  • laura replied on 02-25-2009 2:36 PM

    I just figured, that the code in the original post leads to strange artifacts in the inference. In my case the edgeLabels were correctly inferred, but a dependent latent variables (epsilon) was not updated correctly. (This is also the solution to my problem with learning Betas)

     

    laura:


    for all variable pairs do the following... first variable stored in (uId,fIdx) its duplicate stored in (fId,uIdx)

                            let uMessage = output.[uId].[fIdx]
                            let fMessage = output.[fId].[uIdx]
                            edgeLabelSync.ObservedValue.[uId].[fIdx].SetToProduct(uMessage, fMessage)
                            edgeLabelSync.ObservedValue.[fId].[uIdx] <- (edgeLabelSync.ObservedValue.[uId].[fIdx])

     

    This code needs to be extended by the following line (after the loop that iterates over all matrix entries):

                edgeLabelSync.ObservedValue <- edgeLabelSync.ObservedValue

    This may look stupid, but I guess that the indexed setters (ObservedValue. [ u ] . [ f ]) modify the data structure, but do not set a flag or trigger an event. This is only done when calling the setter on the array itself.

    Laura

     

     

     

     

    Friday, June 3, 2011 4:45 PM
  • laura replied on 02-25-2009 2:39 PM

     

    By the way: After the outer iteration for updating the edgeLabels I still need to set the numberOfIterations to a reasonable value.

     

    To conclude: The Beta called epsilon is only reasonably inferred if ALL of the following conditions are met

    a) "edgeLabelSync.ObservedValue <- edgeLabelSync.ObservedValue" is added

    b) After the sync-loop, I need to set inferenceEngine.numberOfIterations<-50

     

    I guess that whenever I set an ObservedValue, inference is reset. (I have to admit that one would expect this behaviour if reusing a compiled model, .oO ( doh! ) )

    But if each "message sync" loop resets the inference, the number of iterations should probably be set to a large value than 1. Right?

     

    Laura

    Friday, June 3, 2011 4:45 PM
  • laura replied on 02-26-2009 5:31 AM

    If "var.ObservedValue <- xxx" resets inference, then it is probably not a good idea to use this mechanism for syncing variables. Even with more inference iterations in between / after the sync loop, besides wasting CPU time, the results are probably not as expected. I remember that VMP got overly confident on the edge Labels when more than one iteration is used before synchronization.

     

    The original idea was to get output messages from variables, transform the messages, and feed them back to the message passing graph. The variable edgeLabelSync was meant to be a point for feeding the messages into the graph. What we actually want to have is

        inferenceEngine.SetOutputMessage(edgeLabelSync, outTransformedMsg)

    as an equivalent to inferenceEngine.GetOutputMessage.

     

    Right?

    Laura

    Friday, June 3, 2011 4:45 PM
  • John Guiver replied on 02-27-2009 7:23 AM

    Laura

    You have to use the CompiledAlgorithm mechanism if you don't want a reset when setting observed value. This is discussed in general terms in http://research.microsoft.com/infernet/docs/Using%20a%20precompiled%20inference%20algorithm.aspx. Also see John Winn's post in http://community.research.microsoft.com/forums/t/2804.aspx

    John

    Friday, June 3, 2011 4:46 PM
  • laura replied on 03-03-2009 6:34 AM

    Working through the user guide on  "Controlling how inference is performed" (as recommended by John W), I came across the following remark

    "in addition, the CompiledAlgorithm instance needs to be informed that these have changed by calling its SetObservedValue method."

     

    Does this explain why I have to do the trivial call noted above?

     

    laura:

    laura:


    for all variable pairs do the following... first variable stored in (uId,fIdx) its duplicate stored in (fId,uIdx)

                            let uMessage = output.[uId].[fIdx]
                            let fMessage = output.[fId].[uIdx]
                            edgeLabelSync.ObservedValue.[uId].[fIdx].SetToProduct(uMessage, fMessage)
                            edgeLabelSync.ObservedValue.[fId].[uIdx] <- (edgeLabelSync.ObservedValue.[uId].[fIdx])

     

    This code needs to be extended by the following line (after the loop that iterates over all matrix entries):

                edgeLabelSync.ObservedValue <- edgeLabelSync.ObservedValue

     

    Laura

     

    P.S. maybe its time to delete this thread in order not to confuse others....

    Friday, June 3, 2011 4:46 PM
  • jwinn replied on 03-03-2009 6:46 AM

    The InferenceEngine provides a number of services on top of a CompiledAlgorithm, including caching inferred marginal.  It needs to know if an observed value has been changed, so as to invalidate these cached values.  Setting ObservedValue on a variable will do this.  If you just mutate the object returned by ObservedValue, the framework has no way of knowing that it has changed and so cannot invalidate the previous inference.

    So, if you want to change an observed value, you must set the ObservedValue property.

    Using a CompiledAlgorithm directly, there is no caching and so this issue does not apply.  The SetObservedValue() method is used to set observed values for next time inference is run.  Mutating this value and re-running inference manually should also work, but is not recommended.

    John W.

    Friday, June 3, 2011 4:46 PM
  • laura replied on 03-03-2009 6:57 AM

    I forgot to mention, that I am using the Compiled Algorithm approach now. It was a five minute thing to work through the user guide... Thanks a lot for the hint!!!

     

    For that reason I think this thread should be replaced by the solution using the CompiledAlgorithm object instead.

     

    Laura

    Friday, June 3, 2011 4:46 PM