From 22bd8e97db2458ff7ea88024fe2f800a45897ddc Mon Sep 17 00:00:00 2001 From: Mike Reposa Date: Fri, 1 May 2015 11:35:58 -0400 Subject: [PATCH 01/10] Updates for new version --- Figaro/META-INF/MANIFEST.MF | 2 +- Figaro/figaro_build.properties | 2 +- FigaroExamples/META-INF/MANIFEST.MF | 4 ++-- README.md | 2 +- doc/Figaro Release Notes.pdf | Bin 478482 -> 478484 bytes project/Build.scala | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Figaro/META-INF/MANIFEST.MF b/Figaro/META-INF/MANIFEST.MF index 2b33889f..52ab34a2 100644 --- a/Figaro/META-INF/MANIFEST.MF +++ b/Figaro/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Figaro Bundle-SymbolicName: com.cra.figaro -Bundle-Version: 3.1.0 +Bundle-Version: 3.2.0 Export-Package: com.cra.figaro.algorithm, com.cra.figaro.algorithm.decision, com.cra.figaro.algorithm.decision.index, diff --git a/Figaro/figaro_build.properties b/Figaro/figaro_build.properties index 9b3f6b46..cdb3799a 100644 --- a/Figaro/figaro_build.properties +++ b/Figaro/figaro_build.properties @@ -1 +1 @@ -version=3.1.0.0 +version=3.2.0.0 diff --git a/FigaroExamples/META-INF/MANIFEST.MF b/FigaroExamples/META-INF/MANIFEST.MF index 9c4a0ab6..45a4a81e 100644 --- a/FigaroExamples/META-INF/MANIFEST.MF +++ b/FigaroExamples/META-INF/MANIFEST.MF @@ -2,8 +2,8 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: FigaroExamples Bundle-SymbolicName: com.cra.figaro.examples -Bundle-Version: 3.1.0 -Require-Bundle: com.cra.figaro;bundle-version="3.1.0", +Bundle-Version: 3.2.0 +Require-Bundle: com.cra.figaro;bundle-version="3.2.0", org.scala-lang.scala-library Bundle-Vendor: Charles River Analytics Bundle-RequiredExecutionEnvironment: JavaSE-1.6 diff --git a/README.md b/README.md index fc177931..383cab37 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ Figaro is a probabilistic programming language that supports development of very Figaro makes it possible to express probabilistic models using the power of programming languages, giving the modeler the expressive tools to create a wide variety of models. Figaro comes with a number of built-in reasoning algorithms that can be applied automatically to new models. In addition, Figaro models are data structures in the Scala programming language, which is interoperable with Java, and can be constructed, manipulated, and used directly within any Scala or Java program. -Figaro is free and is released under an [open-source license](https://github.com/p2t2/figaro/blob/master/LICENSE). The current, stable binary release of Figaro can be found [here](https://www.cra.com/figaro). For more information please see the [Figaro Guide](https://www.cra.com/pdf/Figaro-Quick-Start-Guide.pdf) and [Figaro Tutorial](https://www.cra.com/pdf/Figaro-tutorial.pdf). Documentation of the Figaro library interface can be found [here](https://www.cra.com/files/Figaro_Scaladoc/index.html). +Figaro is free and is released under an [open-source license](https://github.com/p2t2/figaro/blob/master/LICENSE). The current, stable binary release of Figaro can be found [here](https://www.cra.com/figaro). For more information please see the [Figaro Release Notes](https://www.cra.com/pdf/Figaro-Release-Notes.pdf) and [Figaro Tutorial](https://www.cra.com/pdf/Figaro-tutorial.pdf). Documentation of the Figaro library interface can be found [here](https://www.cra.com/files/Figaro_Scaladoc/index.html). diff --git a/doc/Figaro Release Notes.pdf b/doc/Figaro Release Notes.pdf index 2704c0e3c9a3544642141e2e183287710422cedb..d7394855e4b8d7adf79531f51c3e2829e16f1549 100644 GIT binary patch delta 25991 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 delta 25938 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 b8?EO~tbh4IQj*~}xZ~dxgo8x?qu_r5NSdRb diff --git a/project/Build.scala b/project/Build.scala index 26d3a612..14605a73 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -37,8 +37,8 @@ object FigaroBuild extends Build { override val settings = super.settings ++ Seq( organization := "com.cra.figaro", description := "Figaro: a language for probablistic programming", - version := "3.1.0.0", - scalaVersion := "2.11.4", + version := "3.2.0.0", + scalaVersion := "2.11.6", crossPaths := true, publishMavenStyle := true, pomExtra := From f0879669774d0432ff7650b0b9ef3714be866cc3 Mon Sep 17 00:00:00 2001 From: mhoward2718 Date: Tue, 5 May 2015 13:46:19 -0400 Subject: [PATCH 02/10] Anytime annealing update For #447 --- .../sampling/MetropolisHastingsAnnealer.scala | 1 - .../algorithm/sampling/AnnealingTest.scala | 66 +++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala index d347f256..7f45620e 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala @@ -105,7 +105,6 @@ abstract class MetropolisHastingsAnnealer(universe: Universe, proposalScheme: Pr if (dissatisfied.isEmpty) { sampleCount += 1 val toUpdate = if (currentEnergy > bestEnergy) { - allLastUpdates.clear saveState } else Map[Element[_], Any]() (true, toUpdate) diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala index 83691621..af89d7eb 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala @@ -22,6 +22,9 @@ import com.cra.figaro.language._ import com.cra.figaro.library.atomic.continuous._ import com.cra.figaro.library.compound.^^ import com.cra.figaro.test._ +import scala.util.Random +import com.cra.figaro.library.atomic.discrete.SwitchingFlip +import com.cra.figaro.library.compound.If class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { @@ -63,7 +66,7 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { "converge to the most likely value with an interval using anytime" in { Universe.createNew() val elems = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer = MetropolisHastingsAnnealer(ProposalScheme.default, Schedule.default(2.0),1,2) + val annealer = MetropolisHastingsAnnealer(ProposalScheme.default, Schedule.default(2.0), 1, 2) annealer.start() Thread.sleep(500) annealer.stop() @@ -73,11 +76,11 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { } annealer.kill } - + "converge to the most likely value with an interval using one-time" in { Universe.createNew() val elems = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer = MetropolisHastingsAnnealer(10000, ProposalScheme.default, Schedule.default(2.0),1,2) + val annealer = MetropolisHastingsAnnealer(10000, ProposalScheme.default, Schedule.default(2.0), 1, 2) annealer.start() annealer.stop() for { i <- 1 to 4 } { @@ -86,40 +89,39 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { } annealer.kill } - - + "produce higher temperatue with higher k" in { Universe.createNew() val elems1 = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer1 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(4.0),100) + val annealer1 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(4.0), 100) annealer1.start() val temp1 = annealer1.getTemperature val elems2 = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer2 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(2.0),100) + val annealer2 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(2.0), 100) annealer2.start() val temp2 = annealer2.getTemperature temp2 should be > temp1 - + annealer1.kill annealer2.kill } - + "produce higher temperatue with higher k with burn-in" in { Universe.createNew() val elems1 = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer1 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(4.0),100) + val annealer1 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(4.0), 100) annealer1.start() val temp1 = annealer1.getTemperature val elems2 = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer2 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(2.0),100) + val annealer2 = MetropolisHastingsAnnealer(1000, ProposalScheme.default, Schedule.default(2.0), 100) annealer2.start() val temp2 = annealer2.getTemperature temp2 should be > temp1 - + annealer1.kill annealer2.kill } @@ -142,11 +144,11 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { temp2 should be > temp1 annealer1.kill } - + "increase the temperature with more iterations, including with burn-in" in { Universe.createNew() val elems1 = buildUndirected(4, Flip(0.5), (b: (Boolean, Boolean)) => if (b._1 && b._2) 2.0 else 1.0) - val annealer1 = MetropolisHastingsAnnealer(ProposalScheme.default, Schedule.default(2.0),100) + val annealer1 = MetropolisHastingsAnnealer(ProposalScheme.default, Schedule.default(2.0), 100) annealer1.start() Thread.sleep(250) annealer1.stop @@ -164,4 +166,40 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { } + "Running anytime annealing" should { + + def buildComplicatedModel(): Universe = { + val u = Universe.createNew() + val flips = Seq.fill(10000)(Math.pow(0.01, Random.nextDouble)).map(SwitchingFlip(_)) + val ifs = for ((f, i) <- flips.zipWithIndex) yield { + val fi = If(f, SwitchingFlip(Math.pow(0.01, Random.nextDouble)), Constant(false))(i.toString(), u) + if (Random.nextDouble > 0.80) fi.observe(Random.nextBoolean()) + } + u + } + + "give a most likely value at any time" in { + try { + val u = buildComplicatedModel() + val annealer = MetropolisHastingsAnnealer(ProposalScheme.default, Schedule.default(2.0))(u) + if (annealer.isActive) annealer.resume else annealer.start + Thread.sleep(100) + annealer.stop() + for (i <- 1 to 10000) { + if (annealer.isActive) annealer.resume else annealer.start + Thread.sleep(25) + annealer.stop() + println("loop once") + for (i <- 0 until 10000) { + annealer.mostLikelyValue(u.getElementByReference[Boolean](i.toString())) + } + } + annealer.kill + } catch { + case knf: Exception => fail("running algorithm should not produce exceptions.") + case _ => + } + } + } + } From 6e78bcb67e06b29fb95d893fae6d3b339857fbc3 Mon Sep 17 00:00:00 2001 From: lfkellogg Date: Tue, 5 May 2015 16:10:19 -0400 Subject: [PATCH 03/10] Use a filter, instead of a set subtraction, to avoid iterating through state.assigned every time --- .../scala/com/cra/figaro/algorithm/sampling/Importance.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Importance.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Importance.scala index d5760dd1..b70ef303 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Importance.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Importance.scala @@ -251,8 +251,9 @@ abstract class Importance(universe: Universe, targets: Element[_]*) @tailrec private def sampleArgs(element: Element[_], state: State, args: Set[Element[_]]): Unit = { args foreach (sampleOne(state, _, None)) - val newArgs = Set[Element[_]](element.args:_*) -- state.assigned - if (newArgs.nonEmpty) sampleArgs(element, state, newArgs) else return + val newArgs = element.args.filter(!state.assigned.contains(_)) + if (newArgs.nonEmpty) + sampleArgs(element, state, Set[Element[_]](newArgs: _*)) } /** From c6c8c1356249105d2ef5559af33870671df99a0a Mon Sep 17 00:00:00 2001 From: Mike Reposa Date: Thu, 7 May 2015 14:02:04 -0400 Subject: [PATCH 04/10] Removing println from test --- .../com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala index af89d7eb..09112119 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala @@ -189,7 +189,6 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { if (annealer.isActive) annealer.resume else annealer.start Thread.sleep(25) annealer.stop() - println("loop once") for (i <- 0 until 10000) { annealer.mostLikelyValue(u.getElementByReference[Boolean](i.toString())) } From f359880809bdc7cf464535c27424539e8bdd0d57 Mon Sep 17 00:00:00 2001 From: lfkellogg Date: Fri, 8 May 2015 11:10:55 -0400 Subject: [PATCH 05/10] Two other instances where a set subtraction operation would be faster as a filter --- .../com/cra/figaro/algorithm/factored/FactoredAlgorithm.scala | 3 ++- .../main/scala/com/cra/figaro/algorithm/sampling/Forward.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/FactoredAlgorithm.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/FactoredAlgorithm.scala index 0e2f327a..5747db53 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/FactoredAlgorithm.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/FactoredAlgorithm.scala @@ -73,7 +73,8 @@ trait FactoredAlgorithm[T] extends Algorithm { if (depth >= 0) { val includeThisElement = depth > LazyValues(element.universe).expandedDepth(element).getOrElse(-1) // Keeping track of what's been chased so far avoids infinite recursion - val toChase = element.universe.directlyUsedBy(element).toSet ++ dependentUniverseCoparents.getOrElse(element, Set()) -- chasedSoFar + val related = element.universe.directlyUsedBy(element).toSet ++ dependentUniverseCoparents.getOrElse(element, Set()) + val toChase = related.filter(!chasedSoFar.contains(_)) val rest = toChase.flatMap((elem: Element[_]) => chaseDown(elem, depth - 1, chasedSoFar ++ toChase)) if (includeThisElement) rest + ((element, depth)); else rest } else Set() diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Forward.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Forward.scala index 790cff64..e1b73a16 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Forward.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/Forward.scala @@ -80,7 +80,7 @@ object Forward { var argsRemaining = initialArgs while (!argsRemaining.isEmpty) { state1 = sampleInState(argsRemaining.head, state1, universe, useObservation) - val newArgs = element.args.toSet -- initialArgs + val newArgs = element.args.filter(!initialArgs.contains(_)) initialArgs = initialArgs ++ newArgs argsRemaining = argsRemaining.tail ++ newArgs } From 5e71fea755131717d59d53ff4aeeaa067c394659 Mon Sep 17 00:00:00 2001 From: Mike Reposa Date: Thu, 14 May 2015 15:38:41 -0400 Subject: [PATCH 06/10] Using retrieveManaged setting instead of copy-deps --- project/Build.scala | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 14605a73..28640f36 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -21,19 +21,6 @@ import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys object FigaroBuild extends Build { - // Copy dependency JARs to /target//lib - // Courtesy of - // http://stackoverflow.com/questions/7351280/collecting-dependencies-under-sbt-0-10-putting-all-dependency-jars-to-target-sc - lazy val copyDependencies = TaskKey[Unit]("copy-deps") - - def copyDepTask = copyDependencies <<= (update, crossTarget, scalaVersion) map { - (updateReport, out, scalaVer) => - updateReport.allFiles foreach { srcPath => - val destPath = out / "lib" / srcPath.getName - IO.copyFile(srcPath, destPath, preserveLastModified=true) - } - } - override val settings = super.settings ++ Seq( organization := "com.cra.figaro", description := "Figaro: a language for probablistic programming", @@ -101,6 +88,8 @@ object FigaroBuild extends Build { "com.storm-enroute" %% "scalameter" % "0.6" % "provided", "org.scalatest" %% "scalatest" % "2.2.4" % "provided, test" )) + // Copy all managed dependencies to \lib_managed directory + .settings(retrieveManaged := true) // Enable forking .settings(fork := true) // Increase max memory for JVM for both testing and runtime @@ -122,8 +111,6 @@ object FigaroBuild extends Build { val cp = (fullClasspath in assembly).value cp filter {_.data.getName == "arpack_combined_all-0.1-javadoc.jar"} }) - // Copy dependency JARs - .settings(copyDepTask) // ScalaMeter settings .settings(testFrameworks += new TestFramework("org.scalameter.ScalaMeterFramework")) .settings(logBuffered := false) @@ -133,8 +120,6 @@ object FigaroBuild extends Build { lazy val examples = Project("FigaroExamples", file("FigaroExamples")) .dependsOn(figaro) .settings(packageOptions := Seq(Package.JarManifest(examplesManifest))) - // Copy dependency JARs - .settings(copyDepTask) // SBTEclipse settings .settings(EclipseKeys.eclipseOutput := Some("target/scala-2.11/classes")) From b762a8205c481b9b60cb7f5067d47b7593227c5a Mon Sep 17 00:00:00 2001 From: bruttenberg Date: Thu, 14 May 2015 21:09:00 -0400 Subject: [PATCH 07/10] Fix for issue #453. Removed the observation init from MH. This means that the sampler might start from a state that violates the conditions, but the models is still in a valid state (ie, valid according to its generative model). This fix also uncovered another MH bug that was found from the decision tests. In the MultiDecisionTest example, it was recording decisions values and expected utilities for non-valid combinations. Meaning, the expected utility of a decision was a value that was not possible based on the model. It turns out that this bug has to do with the update function in MH. This function was repeatedly visiting elements to update, but not in the correct order. I couldn't figure out the exact nature of the bug, but when you have a model with A->B and (A,B)->C, you would get B updated, then C, then B again. This was incorrect. The solution is I just rewrote the updateMany function to do a recursive pass through the dependencies. Visiting elements in generative order is a repeated operation throughout Figaro (MH, IS, Forward sampling) and we really should create a unified visiting method for all algorithms. --- .../sampling/MetropolisHastings.scala | 51 ++++++++----------- .../sampling/MetropolisHastingsAnnealer.scala | 6 ++- .../algorithm/sampling/AnnealingTest.scala | 6 +-- .../test/algorithm/sampling/MHTest.scala | 11 ++++ .../test/example/MultiDecisionTest.scala | 4 +- .../test/example/MutableMovieTest.scala | 3 +- 6 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastings.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastings.scala index 0f5189f2..e727243a 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastings.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastings.scala @@ -217,37 +217,26 @@ abstract class MetropolisHastings(universe: Universe, proposalScheme: ProposalSc * is not empty, we recursively update the intersecting elements. Once those updates are completed, * we update an element and move on to the next element in the set. */ - protected def updateMany[T](state: State, toUpdate: Set[Element[_]]): State = { - var returnState = state - var updatesLeft = toUpdate - while (!updatesLeft.isEmpty) { - // Check the intersection of an element's arguments with the updates that still need to occur - var argsRemaining = universe.uses(updatesLeft.head).intersect(updatesLeft) - while (!argsRemaining.isEmpty) { - // update the element's arguments first - returnState = updateManyHelper(returnState, argsRemaining.toSet) - argsRemaining = argsRemaining.tail + @tailrec + private def updateMany(state: State, currentStack: List[Element[_]], currentArgs: Set[Element[_]], updateQ: Set[Element[_]]): State = { + + if (currentStack.isEmpty && currentArgs.isEmpty && updateQ.isEmpty) state + + else if (currentStack.isEmpty && currentArgs.isEmpty && updateQ.nonEmpty) { + val argsRemaining = universe.uses(updateQ.head).intersect(updateQ.tail) + updateMany(state, List(updateQ.head), argsRemaining.toSet, updateQ.tail -- argsRemaining) + } else if (currentStack.nonEmpty && currentArgs.isEmpty) { + val newState = updateOne(state, currentStack.head) + updateMany(newState, currentStack.tail, currentArgs, updateQ) + } else { + val argsRemaining = universe.uses(currentArgs.head).intersect(currentArgs.tail) + if (argsRemaining.isEmpty) { + val newState = updateOne(state, currentArgs.head) + updateMany(newState, currentStack, currentArgs.tail, updateQ) + } else { + updateMany(state, currentArgs.head +: currentStack, currentArgs.tail, updateQ) } - // once the args are updated, update this element - returnState = updateOne(returnState, updatesLeft.head) - updatesLeft = updatesLeft.tail } - returnState - } - - /* - * A recursive function to work in conjunction with updateMany to check the order of the element - * updates. - */ - @tailrec - private def updateManyHelper(state: State, toUpdate: Set[Element[_]]): State = { - var returnState = state - var updatesLeft = toUpdate - var argsRemaining = universe.uses(updatesLeft.head).intersect(updatesLeft) - if (argsRemaining.isEmpty) { - returnState = updateOne(returnState, updatesLeft.head) - returnState - } else { updateManyHelper(returnState, argsRemaining.toSet) } } /* @@ -257,7 +246,7 @@ abstract class MetropolisHastings(universe: Universe, proposalScheme: ProposalSc protected def proposeAndUpdate(): State = { val state1 = runScheme() val updatesNeeded = state1.oldValues.keySet flatMap (elem => universe.usedBy(elem)) - updateMany(state1, updatesNeeded.toSet) + updateMany(state1, List(), Set(), updatesNeeded.toSet) } protected var dissatisfied: Set[Element[_]] = _ @@ -365,7 +354,7 @@ abstract class MetropolisHastings(universe: Universe, proposalScheme: ProposalSc protected def doInitialize(): Unit = { // Need to prime the universe to make sure all elements have a generated value - Forward(true)(universe) + Forward(false)(universe) initConstrainedValues() dissatisfied = universe.conditionedElements.toSet filter (!_.conditionSatisfied) for { i <- 1 to burnIn } mhStep() diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala index 7f45620e..ed361e91 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/sampling/MetropolisHastingsAnnealer.scala @@ -104,7 +104,7 @@ abstract class MetropolisHastingsAnnealer(universe: Universe, proposalScheme: Pr if (dissatisfied.isEmpty) { sampleCount += 1 - val toUpdate = if (currentEnergy > bestEnergy) { + val toUpdate = if (currentEnergy >= bestEnergy) { saveState } else Map[Element[_], Any]() (true, toUpdate) @@ -114,7 +114,7 @@ abstract class MetropolisHastingsAnnealer(universe: Universe, proposalScheme: Pr } override def doInitialize(): Unit = { - Forward(true)(universe) + Forward(false)(universe) initConstrainedValues() dissatisfied = universe.conditionedElements.toSet filter (!_.conditionSatisfied) currentEnergy = universe.constrainedElements.map(_.constraintValue).sum @@ -122,6 +122,8 @@ abstract class MetropolisHastingsAnnealer(universe: Universe, proposalScheme: Pr val nextState = mhStep() currentEnergy += nextState.modelProb } + initUpdates() + if (dissatisfied.nonEmpty) bestEnergy = Double.MinValue else bestEnergy = currentEnergy } def mostLikelyValue[T](target: Element[T]): T = { diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala index 09112119..4a7cf5e3 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/AnnealingTest.scala @@ -170,7 +170,7 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { def buildComplicatedModel(): Universe = { val u = Universe.createNew() - val flips = Seq.fill(10000)(Math.pow(0.01, Random.nextDouble)).map(SwitchingFlip(_)) + val flips = Seq.fill(1000)(Math.pow(0.01, Random.nextDouble)).map(SwitchingFlip(_)) val ifs = for ((f, i) <- flips.zipWithIndex) yield { val fi = If(f, SwitchingFlip(Math.pow(0.01, Random.nextDouble)), Constant(false))(i.toString(), u) if (Random.nextDouble > 0.80) fi.observe(Random.nextBoolean()) @@ -185,11 +185,11 @@ class AnnealingTest extends WordSpec with Matchers with PrivateMethodTester { if (annealer.isActive) annealer.resume else annealer.start Thread.sleep(100) annealer.stop() - for (i <- 1 to 10000) { + for (i <- 1 to 1000) { if (annealer.isActive) annealer.resume else annealer.start Thread.sleep(25) annealer.stop() - for (i <- 0 until 10000) { + for (i <- 0 until 1000) { annealer.mostLikelyValue(u.getElementByReference[Boolean](i.toString())) } } diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala index bab3bc92..c367dd56 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala @@ -430,6 +430,17 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { alg.start() alg.probability(a1, 1) should be(0.7 +- 0.01) } + + "not record invalid states" taggedAs(NonDeterministic) in { + Universe.createNew() + val c1 = Flip(0.9) + val a1 = If(c1, com.cra.figaro.library.atomic.discrete.Uniform(1, 2), Select(0.999 -> 2, 0.001 -> 3)) + a1.observe(3) + val alg = MetropolisHastings(10000, ProposalScheme.default, a1, c1) + alg.start() + alg.distribution(c1).toList.exists(s => s._2 == true) should be(false) + } + } def newState(mh: MetropolisHastings): State = { diff --git a/Figaro/src/test/scala/com/cra/figaro/test/example/MultiDecisionTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/example/MultiDecisionTest.scala index 72fe3e06..61953081 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/example/MultiDecisionTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/example/MultiDecisionTest.scala @@ -58,13 +58,13 @@ class MultiDecisionTest extends WordSpec with Matchers { override def oneTest = { val result = doTest((e1: List[Element[Double]], e2: List[Decision[_, _]]) => - MultiDecisionMetropolisHastings(200000, propmaker, 10000, e1, e2: _*)) + MultiDecisionMetropolisHastings(300000, propmaker, 20000, e1, e2: _*)) update(result(0), NDTest.BOOLEAN, "MHMulti-DecisionFound(-1)", true, .90) update(result(1), NDTest.BOOLEAN, "MHMulti-DecisionFound(0)", false, .90) update(result(2), NDTest.BOOLEAN, "MHMulti-DecisionFound(1)", true, .90) update(result(3), NDTest.BOOLEAN, "MHMulti-DecisionFound(2)", true, .90) - update(result(4), NDTest.BOOLEAN, "MHMulti-DecisionTest(0)", true, .90) + update(result(4), NDTest.BOOLEAN, "MHMulti-DecisionTest(0)", true, .70) } } ndtest.run(10) diff --git a/Figaro/src/test/scala/com/cra/figaro/test/example/MutableMovieTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/example/MutableMovieTest.scala index f781b137..4d330d43 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/example/MutableMovieTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/example/MutableMovieTest.scala @@ -35,8 +35,7 @@ class MutableMovieTest extends WordSpec with Matchers { } "produce the correct probability under Metropolis-Hastings" taggedAs (Example, NonDeterministic) in { - test((e: Element[Boolean]) => MetropolisHastings(200000, chooseScheme, 10000, e)) - println(com.cra.figaro.util.seed) + test((e: Element[Boolean]) => MetropolisHastings(300000, chooseScheme, 20000, e)) } } From 6cd6eb8a4b0d4845d49b96dd2aeebdbaeb43c2f4 Mon Sep 17 00:00:00 2001 From: bruttenberg Date: Fri, 15 May 2015 08:32:58 -0400 Subject: [PATCH 08/10] Added test for issue #453 --- .../test/algorithm/sampling/MHTest.scala | 59 ++++++++++++++----- .../structured/FactorMakerTest.scala | 4 +- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala index c367dd56..28a6815d 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/sampling/MHTest.scala @@ -32,7 +32,7 @@ import com.cra.figaro.test.tags.Performance class MHTest extends WordSpec with Matchers with PrivateMethodTester { "Anytime MetropolisHastings" should { - + "for a constant produce the constant with probability 1" in { Universe.createNew() val c = Constant(1) @@ -263,7 +263,7 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { } } - "with a condition on the result of an If in which both consequences are random, correctly condition the test" taggedAs(NonDeterministic) in { + "with a condition on the result of an If in which both consequences are random, correctly condition the test" taggedAs (NonDeterministic) in { val numSamples = 100000 val tolerance = 0.01 Universe.createNew() @@ -336,8 +336,8 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { } "Testing a Metropolis-Hastings algorithm" should { - - "produce correct results with a typed scheme" in { + + "produce correct results with a typed scheme" in { val numSamples = 200000 val tolerance = 0.01 Universe.createNew() @@ -366,23 +366,22 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { mh.kill() } } - - //Need better use cases for these two schemes. + + //Need better use cases for these two schemes. "produce correct results with a switching scheme" in { Universe.createNew() val s1 = Select(0.4 -> 1, 0.6 -> 4) val s2 = Select(0.2 -> 2, 0.8 -> 3) s1.setConstraint((i: Int) => if (i == 1) 1; else 0.25) val p1 = Predicate(s1, (i: Int) => i == 4) - val scheme = SwitchScheme(() => (s1,s2),Some(FinalScheme(() => Universe.universe.randomStochasticElement()))) + val scheme = SwitchScheme(() => (s1, s2), Some(FinalScheme(() => Universe.universe.randomStochasticElement()))) val alg = MetropolisHastings(scheme) s1.value = 1 val (_, scores, _) = alg.test(10000, List(p1), List()) // probability of s1 changing to 4 is 0.5 (prob. s1 proposed) * 0.6 (prob 4 is chosen if s1 proposed) * 0.25 scores(p1) should be(0.075 +- 0.01) } - - + "produce values satisfying a predicate the correct amount of the time without conditions or constraints" in { Universe.createNew() val s1 = Select(0.4 -> 1, 0.6 -> 4) @@ -421,7 +420,7 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { } "Fixed bugs" should { - "propose temporary elements when non-temporary elements are non-stochastic" taggedAs(NonDeterministic) in { + "propose temporary elements when non-temporary elements are non-stochastic" taggedAs (NonDeterministic) in { Universe.createNew() val c1 = NonCachingChain(Constant(0), (i: Int) => Flip(0.7)) val a1 = If(c1, Constant(1), Constant(0)) @@ -430,17 +429,47 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { alg.start() alg.probability(a1, 1) should be(0.7 +- 0.01) } - - "not record invalid states" taggedAs(NonDeterministic) in { + + "not record invalid states" taggedAs (NonDeterministic) in { Universe.createNew() val c1 = Flip(0.9) - val a1 = If(c1, com.cra.figaro.library.atomic.discrete.Uniform(1, 2), Select(0.999 -> 2, 0.001 -> 3)) + val a1 = If(c1, com.cra.figaro.library.atomic.discrete.Uniform(1, 2), Select(0.999 -> 2, 0.001 -> 3)) a1.observe(3) val alg = MetropolisHastings(10000, ProposalScheme.default, a1, c1) alg.start() alg.distribution(c1).toList.exists(s => s._2 == true) should be(false) } - + "correctly update elements in order" taggedAs (NonDeterministic) in { + // bug test for issue #453 + + Universe.createNew() + val Market = Select(0.5 -> 0, 0.3 -> 1, 0.2 -> 2) + val Survey = CPD(Constant(true), Market, + (false, 0) -> Constant(-1), (false, 1) -> Constant(-1), (false, 2) -> Constant(-1), + (true, 0) -> Select(0.6 -> 0, 0.3 -> 1, 0.1 -> 2), + (true, 1) -> Select(0.3 -> 0, 0.4 -> 1, 0.3 -> 2), + (true, 2) -> Select(0.1 -> 0, 0.4 -> 1, 0.5 -> 2)) + val Found = Chain(Survey, (i: Int) => com.cra.figaro.library.atomic.discrete.Uniform(true, false)) + def Value_fcn(f: Boolean, m: Int): Double = { + if (f) { + m match { + case 0 => -7.0 + case 1 => 5.0 + case 2 => 20.0 + } + } else { + 0.0 + } + } + val Value = Apply(Found, Market, Value_fcn) + val Z = ^^(Found, Value) + + val alg = MetropolisHastings(20000, ProposalScheme.default, Z) + alg.start() + + val a = alg.distribution(Z).toList + alg.distribution(Z).toList.exists(s => s._2._1 == false && s._2._2 != 0.0) should be(false) + } } def newState(mh: MetropolisHastings): State = { @@ -519,7 +548,7 @@ class MHTest extends WordSpec with Matchers with PrivateMethodTester { mh.start() Thread.sleep(1000) mh.stop() -// println(mh.getSampleCount.toString + " samples") + // println(mh.getSampleCount.toString + " samples") mh.probability(target, predicate) should be(prob +- tolerance) } finally { mh.kill() diff --git a/Figaro/src/test/scala/com/cra/figaro/test/experimental/structured/FactorMakerTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/experimental/structured/FactorMakerTest.scala index 1f857968..9b96bcb2 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/experimental/structured/FactorMakerTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/experimental/structured/FactorMakerTest.scala @@ -3588,7 +3588,7 @@ class FactorMakerTest extends WordSpec with Matchers { factor1L.get(List(e2Index2, 0)) should equal (1.0) factor2L.size should equal (2) factor2L.get(List(e2IndexStar, 0)) should equal (0.3 +- .0001) - factor2L.get(List(e2Index2, 0)) should equal (0.3) + factor2L.get(List(e2Index2, 0)) should equal (0.3 +- .0001) val List(factor1U) = c11.constraintUpper val List(factor2U) = c12.constraintUpper factor1U.variables should equal (List(c2.variable, c11.variable)) @@ -3598,7 +3598,7 @@ class FactorMakerTest extends WordSpec with Matchers { factor1U.get(List(e2Index2, 0)) should equal (1.0) factor2U.size should equal (2) factor2U.get(List(e2IndexStar, 0)) should equal (1.0) - factor2U.get(List(e2Index2, 0)) should equal (0.3) + factor2U.get(List(e2Index2, 0)) should equal (0.3 +- .0001) } } From 979eba7b4c1312da8b2e7d5dbefc4311da8055c4 Mon Sep 17 00:00:00 2001 From: Avi Pfeffer Date: Wed, 13 May 2015 15:01:07 -0400 Subject: [PATCH 09/10] Fixes to support dynamic programming with SFI --- .../DecisionVariableElimination.scala | 42 +++++++++---------- .../factored/MPEVariableElimination.scala | 21 +++++----- ...ficientStatisticsVariableElimination.scala | 25 +++++------ .../factored/VariableElimination.scala | 34 ++++++++------- .../factors/factory/ComplexFactory.scala | 10 ++--- .../experimental/structured/Problem.scala | 9 ++++ .../structured/ProblemComponent.scala | 2 +- .../structured/factory/ChainFactory.scala | 2 +- .../structured/solver/VEBPChooser.scala | 6 ++- .../structured/solver/VESolver.scala | 5 ++- .../test/algorithm/factored/VETest.scala | 14 +++---- .../figaro/test/example/MultiValuedTest.scala | 8 ++-- 12 files changed, 98 insertions(+), 80 deletions(-) diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/decision/DecisionVariableElimination.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/decision/DecisionVariableElimination.scala index e9ce5c16..66c9f744 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/decision/DecisionVariableElimination.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/decision/DecisionVariableElimination.scala @@ -1,13 +1,13 @@ /* * DecisionVariableElimination.scala * Variable elimination for Decisions algorithm. - * + * * Created By: Brian Ruttenberg (bruttenberg@cra.com) * Creation Date: Oct 1, 2012 - * + * * Copyright 2013 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. - * + * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ @@ -28,7 +28,7 @@ import scala.language.existentials /* Trait only extends for double utilities. User needs to provide another trait or convert utilities to double * in order to use - * + * */ /** * Trait for Decision based Variable Elimination. This implementation is hardcoded to use. @@ -39,12 +39,12 @@ trait ProbabilisticVariableEliminationDecision extends VariableElimination[(Doub */ /* Implementations must define this */ def getUtilityNodes: List[Element[_]] - + /** * Semiring for Decisions uses a sum-product-utility semiring. */ override val semiring = SumProductUtilitySemiring() - + /** * Makes a utility factor an element designated as a utility. This is factor of a tuple (Double, Double) * where the first value is 1.0 and the second is a possible utility of the element. @@ -60,14 +60,14 @@ trait ProbabilisticVariableEliminationDecision extends VariableElimination[(Doub override def starterElements = getUtilityNodes ::: targetElements /** - * Create the factors for decision factors. Each factor is hardcoded as a tuple of (Double, Double), - * where the first value is the probability and the second is the utility. - */ + * Create the factors for decision factors. Each factor is hardcoded as a tuple of (Double, Double), + * where the first value is the probability and the second is the utility. + */ def getFactors(neededElements: List[Element[_]], targetElements: List[Element[_]], upper: Boolean = false): List[Factor[(Double, Double)]] = { if (debug) { println("Elements (other than utilities) appearing in factors and their ranges:") - for { element <- neededElements } { - println(Variable(element).id + "(" + element.name.string + "@" + element.hashCode + ")" + ": " + element + ": " + Variable(element).range.mkString(",")) + for { element <- neededElements } { + println(Variable(element).id + "(" + element.name.string + "@" + element.hashCode + ")" + ": " + element + ": " + Variable(element).range.mkString(",")) } } @@ -99,7 +99,7 @@ trait ProbabilisticVariableEliminationDecision extends VariableElimination[(Doub f.mapTo[(Double, Double)]((d: Double) => (d, 0.0), semiring.asInstanceOf[Semiring[(Double, Double)]]) } else { if (f.variables.length > 1) throw new IllegalUtilityNodeException - + val newF = f.mapTo[(Double, Double)]((d: Double) => (d, 0.0), semiring.asInstanceOf[Semiring[(Double, Double)]]) for {i <- 0 until f.variables(0).range.size} { newF.set(List(i), (newF.get(List(i))._1, f.variables(0).range(i).asInstanceOf[Double])) @@ -125,17 +125,17 @@ class ProbQueryVariableEliminationDecision[T, U](override val universe: Universe lazy val queryTargets = List(target) /** - * The variable elimination eliminates all variables except on all decision nodes and their parents. + * The variable elimination eliminates all variables except on all decision nodes and their parents. * Thus the target elements is both the decision element and the parent element. */ - val targetElements = List(target, target.args(0)) + val targetElements = List(target, target.args(0)) def getUtilityNodes = utilityNodes private var finalFactors: Factor[(Double, Double)] = Factory.defaultFactor[(Double, Double)](List(), List(), semiring) /* Marginalizes the final factor using the semiring for decisions - * + * */ private def marginalizeToTarget(factor: Factor[(Double, Double)], target: Element[_]): Unit = { val unnormalizedTargetFactor = factor.marginalizeTo(semiring, Variable(target)) @@ -148,13 +148,13 @@ class ProbQueryVariableEliminationDecision[T, U](override val universe: Universe private def marginalize(resultFactor: Factor[(Double, Double)]) = queryTargets foreach (marginalizeToTarget(resultFactor, _)) - private def makeResultFactor(factorsAfterElimination: Set[Factor[(Double, Double)]]): Factor[(Double, Double)] = { + private def makeResultFactor(factorsAfterElimination: MultiSet[Factor[(Double, Double)]]): Factor[(Double, Double)] = { // It is possible that there are no factors (this will happen if there are no decisions or utilities). // Therefore, we start with the unit factor and use foldLeft, instead of simply reducing the factorsAfterElimination. factorsAfterElimination.foldLeft(Factory.unit(semiring))(_.product(_)) } - def finish(factorsAfterElimination: Set[Factor[(Double, Double)]], eliminationOrder: List[Variable[_]]) = + def finish(factorsAfterElimination: MultiSet[Factor[(Double, Double)]], eliminationOrder: List[Variable[_]]) = finalFactors = makeResultFactor(factorsAfterElimination) /** @@ -176,7 +176,7 @@ class ProbQueryVariableEliminationDecision[T, U](override val universe: Universe (0.0 /: computeDistribution(target))(_ + get(_)) } - /** + /** * Returns the computed utility of all parent/decision tuple values. For VE, these are not samples * but the actual computed expected utility for all combinations of the parent and decision. */ @@ -194,14 +194,14 @@ class ProbQueryVariableEliminationDecision[T, U](override val universe: Universe // find the variables of the parents. val parentVariable = factor.variables.filterNot(_ == decisionVariable)(0) - // index of the decision variable + // index of the decision variable val indexOfDecision = indices(factor.variables, decisionVariable) val indexOfParent = indices(factor.variables, parentVariable) for { indices <- factor.getIndices} { - /* for each index in the list of indices, strip out the decision variable index, + /* for each index in the list of indices, strip out the decision variable index, * and retrieve the map entry for the parents. If the factor value is greater than * what is currently stored in the strategy map, replace the decision with the new one from the factor */ @@ -257,7 +257,7 @@ object DecisionVariableElimination { } /** * Create a decision variable elimination algorithm with the given decision variables and indicated utility - * nodes and using the given dependent universes in the current default universe. Use the given dependent + * nodes and using the given dependent universes in the current default universe. Use the given dependent * algorithm function to determine the algorithm to use to compute probability of evidence in each dependent universe. */ def apply[T, U]( diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/MPEVariableElimination.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/MPEVariableElimination.scala index a8e1f2c3..709c3194 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/MPEVariableElimination.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/MPEVariableElimination.scala @@ -1,13 +1,13 @@ /* * MPEVariableElimination.scala * Variable elimination algorithm for computing most probable explanation. - * + * * Created By: Avi Pfeffer (apfeffer@cra.com) * Creation Date: Jan 1, 2009 - * + * * Copyright 2013 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. - * + * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ @@ -20,10 +20,11 @@ import com.cra.figaro.algorithm.factored.factors._ import com.cra.figaro.util import scala.collection.mutable.{ Set, Map } import com.cra.figaro.algorithm.lazyfactored._ +import com.cra.figaro.util.MultiSet /** * Variable elimination algorithm to compute the most probable explanation. - * + * * @param showTiming Produce timing information on steps of the algorithm */ class MPEVariableElimination(override val universe: Universe)( @@ -33,12 +34,12 @@ class MPEVariableElimination(override val universe: Universe)( override val comparator = Some((x: Double, y: Double) => x < y) override val semiring = MaxProductSemiring() - + /* * We are trying to find a configuration of all the elements, so we must make them all starter elements for expansion. */ override val starterElements = universe.activeElements - + /** * Empty for MPE Algorithms. */ @@ -47,15 +48,15 @@ class MPEVariableElimination(override val universe: Universe)( private val maximizers: Map[Variable[_], Any] = Map() private def getMaximizer[T](variable: Variable[T]): T = maximizers(variable).asInstanceOf[variable.Value] - + /* * Convert factors to use MaxProduct */ override def getFactors(allElements: List[Element[_]], targetElements: List[Element[_]], upper: Boolean = false): List[Factor[Double]] = { - val factors = super.getFactors(allElements, targetElements, upper) + val factors = super.getFactors(allElements, targetElements, upper) factors.map (_.mapTo(x => x, semiring)) } - + def mostLikelyValue[T](target: Element[T]): T = getMaximizer(Variable(target)) private def backtrackOne[T](factor: Factor[_], variable: Variable[T]): Unit = { @@ -64,7 +65,7 @@ class MPEVariableElimination(override val universe: Universe)( maximizers += variable -> factor.get(indices) } - def finish(factorsAfterElimination: Set[Factor[Double]], eliminationOrder: List[Variable[_]]): Unit = + def finish(factorsAfterElimination: MultiSet[Factor[Double]], eliminationOrder: List[Variable[_]]): Unit = for { (variable, factor) <- eliminationOrder.reverse.zip(recordingFactors) } { backtrackOne(factor, variable) } } diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/SufficientStatisticsVariableElimination.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/SufficientStatisticsVariableElimination.scala index 3cf5786d..f37aaa41 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/SufficientStatisticsVariableElimination.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/SufficientStatisticsVariableElimination.scala @@ -1,13 +1,13 @@ /* * SufficientStatisticsVariableElimination.scala * Variable elimination algorithm for sufficient statistics factors - * + * * Created By: Michael Howard (mhoward@cra.com) * Creation Date: Jun 1, 2013 - * + * * Copyright 2013 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. - * + * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ @@ -19,12 +19,13 @@ import com.cra.figaro.language._ import com.cra.figaro.algorithm.factored.factors._ import scala.collection._ import scala.collection.mutable.{ Map, Set } +import com.cra.figaro.util.MultiSet /** - * Variable elimination for sufficient statistics factors. + * Variable elimination for sufficient statistics factors. * The final factor resulting from variable elimination contains a mapping of parameters to sufficient statistics vectors * which can be used to maximize parameter values. - * + * * @param parameterMap A map of parameters to their sufficient statistics. */ class SufficientStatisticsVariableElimination( @@ -47,14 +48,14 @@ class SufficientStatisticsVariableElimination( } /** - * Particular implementations of probability of evidence algorithms must define the following method. + * Particular implementations of probability of evidence algorithms must define the following method. */ def getFactors(neededElements: List[Element[_]], targetElements: List[Element[_]], upper: Boolean = false): List[Factor[(Double, mutable.Map[Parameter[_], Seq[Double]])]] = { val allElements = neededElements.filter(p => p.isInstanceOf[Parameter[_]] == false) if (debug) { println("Elements appearing in factors and their ranges:") - for { element <- allElements } { - println(Variable(element).id + "(" + element.name.string + "@" + element.hashCode + ")" + ": " + element + ": " + Variable(element).range.mkString(",")) + for { element <- allElements } { + println(Variable(element).id + "(" + element.name.string + "@" + element.hashCode + ")" + ": " + element + ": " + Variable(element).range.mkString(",")) } } @@ -70,12 +71,12 @@ class SufficientStatisticsVariableElimination( * Empty for this algorithm. */ val targetElements = List[Element[_]]() - + override def starterElements = universe.conditionedElements ++ universe.constrainedElements private var result: (Double, Map[Parameter[_], Seq[Double]]) = _ - def finish(factorsAfterElimination: Set[Factor[(Double, Map[Parameter[_], Seq[Double]])]], eliminationOrder: List[Variable[_]]): Unit = { + def finish(factorsAfterElimination: MultiSet[Factor[(Double, Map[Parameter[_], Seq[Double]])]], eliminationOrder: List[Variable[_]]): Unit = { // It is possible that there are no factors (this will happen if there is no evidence). // Therefore, we start with the unit factor and use foldLeft, instead of simply reducing the factorsAfterElimination. val finalFactor = factorsAfterElimination.foldLeft(Factory.unit(semiring))(_.product(_)) @@ -86,14 +87,14 @@ class SufficientStatisticsVariableElimination( } /** - * Returns a mapping of parameters to sufficient statistics resulting from + * Returns a mapping of parameters to sufficient statistics resulting from * elimination of the factors. */ def getSufficientStatisticsForAllParameters = { result._2.toMap } val semiring = SufficientStatisticsSemiring(parameterMap) - override def cleanUp() = { + override def cleanUp() = { statFactor.removeFactors super.cleanUp() } diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/VariableElimination.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/VariableElimination.scala index a1d49c97..ec030306 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/VariableElimination.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/VariableElimination.scala @@ -66,13 +66,17 @@ trait VariableElimination[T] extends FactoredAlgorithm[T] with OneTime { // The first element of FactorMap is the complete set of factors. // The second element maps variables to the factors mentioning that variable. - private type FactorMap[T] = Map[Variable[_], Set[Factor[T]]] + // The previous implementation used sets, but that resulted in bugs where an identical factor appeared more than once. + // The new implementation uses multisets. + private type FactorMap[T] = Map[Variable[_], MultiSet[Factor[T]]] + // Add a factor to the list, even if it appears already. private def addFactor[T](factor: Factor[T], map: FactorMap[T]): Unit = - factor.variables foreach (v => map += v -> (map.getOrElse(v, Set()) + factor)) + factor.variables foreach (v => map += v -> (map.getOrElse(v, HashMultiSet()).addOne(factor))) + // Remove one instance of the factor from the list. private def removeFactor[T](factor: Factor[T], map: FactorMap[T]): Unit = - factor.variables foreach (v => map += v -> (map.getOrElse(v, Set()) - factor)) + factor.variables foreach (v => map += v -> (map.getOrElse(v, HashMultiSet()).removeOne(factor))) protected def initialFactorMap(factors: Traversable[Factor[T]]): FactorMap[T] = { val map: FactorMap[T] = Map() @@ -95,7 +99,7 @@ trait VariableElimination[T] extends FactoredAlgorithm[T] with OneTime { private def eliminate( variable: Variable[_], - factors: Set[Factor[T]], + factors: MultiSet[Factor[T]], map: FactorMap[T]): Unit = { val varFactors = map(variable) if (debug) { @@ -106,23 +110,23 @@ trait VariableElimination[T] extends FactoredAlgorithm[T] with OneTime { if (varFactors nonEmpty) { val productFactor = varFactors reduceLeft (_.product(_)) val resultFactor = productFactor.sumOver(variable) - varFactors foreach (removeFactor(_, map)) - addFactor(resultFactor, map) + if (debug) println("Result factor\n" + resultFactor.toReadableString) comparator match { case None => () case Some(recorder) => recordingFactors ::= productFactor.recordArgMax(variable, recorder) } + varFactors.foreach(factors.removeOne(_)) + factors.addOne(resultFactor) + varFactors.foreach(removeFactor(_, map)) map -= variable - factors --= varFactors - if (debug) println("Result factor\n" + resultFactor.toReadableString) - factors += resultFactor + addFactor(resultFactor, map) } } protected def eliminateInOrder( order: List[Variable[_]], - factors: Set[Factor[T]], - map: FactorMap[T]): Set[Factor[T]] = + factors: MultiSet[Factor[T]], + map: FactorMap[T]): MultiSet[Factor[T]] = order match { case Nil => factors @@ -147,7 +151,7 @@ trait VariableElimination[T] extends FactoredAlgorithm[T] with OneTime { } val (_, order) = optionallyShowTiming(VariableElimination.eliminationOrder(allFactors, targetVariables), "Computing elimination order") val factorsAfterElimination = - optionallyShowTiming(eliminateInOrder(order, Set(allFactors: _*), initialFactorMap(allFactors)), "Elimination") + optionallyShowTiming(eliminateInOrder(order, HashMultiSet(allFactors: _*), initialFactorMap(allFactors)), "Elimination") if (debug) println("*****************") if (debug) factorsAfterElimination foreach (f => println(f.toReadableString)) optionallyShowTiming(finish(factorsAfterElimination, order), "Finalizing") @@ -159,7 +163,7 @@ trait VariableElimination[T] extends FactoredAlgorithm[T] with OneTime { /** * All implementation of variable elimination must specify what to do after variables have been eliminated. */ - def finish(factorsAfterElimination: Set[Factor[T]], eliminationOrder: List[Variable[_]]): Unit + def finish(factorsAfterElimination: MultiSet[Factor[T]], eliminationOrder: List[Variable[_]]): Unit def run() = ve() @@ -211,13 +215,13 @@ class ProbQueryVariableElimination(override val universe: Universe, targets: Ele private def marginalize(resultFactor: Factor[Double]) = targets foreach (marginalizeToTarget(resultFactor, _)) - private def makeResultFactor(factorsAfterElimination: Set[Factor[Double]]): Factor[Double] = { + private def makeResultFactor(factorsAfterElimination: MultiSet[Factor[Double]]): Factor[Double] = { // It is possible that there are no factors (this will happen if there are no queries or evidence). // Therefore, we start with the unit factor and use foldLeft, instead of simply reducing the factorsAfterElimination. factorsAfterElimination.foldLeft(Factory.unit(semiring))(_.product(_)) } - def finish(factorsAfterElimination: Set[Factor[Double]], eliminationOrder: List[Variable[_]]) = + def finish(factorsAfterElimination: MultiSet[Factor[Double]], eliminationOrder: List[Variable[_]]) = marginalize(makeResultFactor(factorsAfterElimination)) /** diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/factors/factory/ComplexFactory.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/factors/factory/ComplexFactory.scala index 679b8b35..fb189b46 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/factors/factory/ComplexFactory.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/factored/factors/factory/ComplexFactory.scala @@ -1,13 +1,13 @@ /* * ComplexFactory.scala * Description needed - * + * * Created By: Glenn Takata (gtakata@cra.com) * Creation Date: Dec 15, 2014 - * + * * Copyright 2014 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. - * + * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ @@ -53,7 +53,7 @@ object ComplexFactory { selectedFactors.flatten } } - + /** * Factor constructor for a MultiValuedReferenceElement */ @@ -171,7 +171,7 @@ object ComplexFactory { } List(factor) } - + /** * Factor constructor for a FoldLeft Element */ diff --git a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/Problem.scala b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/Problem.scala index de6d2278..0670c9d2 100644 --- a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/Problem.scala +++ b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/Problem.scala @@ -60,6 +60,15 @@ class Problem(val collection: ComponentCollection, targets: List[Element[_]] = L contains(component.problem) & !targets.contains(component.element) } } + + /** + * Determines if a variable is in scope outside of this problem + */ + def global(variable: Variable[_]): Boolean = { + !collection.intermediates.contains(variable) && + !contains(collection.variableToComponent(variable).problem) + } + /** * Determines if this problem contains the given problem. diff --git a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/ProblemComponent.scala b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/ProblemComponent.scala index 91467dab..3fbb0158 100644 --- a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/ProblemComponent.scala +++ b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/ProblemComponent.scala @@ -189,7 +189,7 @@ extends ExpandableComponent[ParentValue, Value](problem, chain.parent, chain) { (parentValue, subproblem) <- subproblems factor <- subproblem.solution } yield Factory.replaceVariable(factor, problem.collection(subproblem.target).variable, actualSubproblemVariables(parentValue)) - nonConstraintFactors = subproblems.values.toList.flatMap(_.solution) ::: nonConstraintFactors + nonConstraintFactors = subproblemFactors.toList ::: nonConstraintFactors } /* diff --git a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/factory/ChainFactory.scala b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/factory/ChainFactory.scala index 8750c30f..9e8b1d5d 100644 --- a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/factory/ChainFactory.scala +++ b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/factory/ChainFactory.scala @@ -43,7 +43,7 @@ object ChainFactory { val nestedProblem = cc.expansions((chain.chainFunction, parentVal.value)) val outcomeElem = nestedProblem.target.asInstanceOf[Element[U]] val formalVar = Factory.getVariable(cc, outcomeElem) - val actualVar = if (nestedProblem.internal(formalVar)) Factory.makeVariable(cc, formalVar.valueSet) else formalVar + val actualVar = if (!nestedProblem.global(formalVar)) Factory.makeVariable(cc, formalVar.valueSet) else formalVar chainComp.actualSubproblemVariables += parentVal.value -> actualVar List(Factory.makeConditionalSelector(pairVar, parentVal, actualVar)) } diff --git a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VEBPChooser.scala b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VEBPChooser.scala index 5acd9d47..77af890c 100644 --- a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VEBPChooser.scala +++ b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VEBPChooser.scala @@ -15,6 +15,8 @@ package com.cra.figaro.experimental.structured.solver import com.cra.figaro.algorithm.factored.VariableElimination import com.cra.figaro.algorithm.factored.factors._ import com.cra.figaro.experimental.structured.Problem +import com.cra.figaro.util.HashMultiSet +import com.cra.figaro.util.MultiSet /* * The score threshold is the threshold to determine whether VE or BP will be used. @@ -33,7 +35,7 @@ extends VariableElimination[Double] { } else { // println("Choosing VE") // Since we've already computed the order, we don't call doElimination but only do the steps after computing the order - val factorsAfterElimination = eliminateInOrder(order, scala.collection.mutable.Set(factors: _*), initialFactorMap(factors)) + val factorsAfterElimination = eliminateInOrder(order, HashMultiSet(factors: _*), initialFactorMap(factors)) finish(factorsAfterElimination, order) } result @@ -43,7 +45,7 @@ extends VariableElimination[Double] { private var result: List[Factor[Double]] = _ - def finish(factorsAfterElimination: scala.collection.mutable.Set[Factor[Double]], eliminationOrder: List[Variable[_]]): Unit = { + def finish(factorsAfterElimination: MultiSet[Factor[Double]], eliminationOrder: List[Variable[_]]): Unit = { result = factorsAfterElimination.toList } diff --git a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VESolver.scala b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VESolver.scala index b84a7044..27a7a006 100644 --- a/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VESolver.scala +++ b/Figaro/src/main/scala/com/cra/figaro/experimental/structured/solver/VESolver.scala @@ -14,6 +14,7 @@ package com.cra.figaro.experimental.structured.solver import com.cra.figaro.algorithm.factored.factors._ import com.cra.figaro.experimental.structured.Problem +import com.cra.figaro.util.MultiSet private[figaro] class VESolver(problem: Problem, toEliminate: Set[Variable[_]], toPreserve: Set[Variable[_]], factors: List[Factor[Double]]) extends com.cra.figaro.algorithm.factored.VariableElimination[Double] { @@ -22,13 +23,13 @@ extends com.cra.figaro.algorithm.factored.VariableElimination[Double] { result } - debug = true + debug = false val semiring: Semiring[Double] = SumProductSemiring() private var result: List[Factor[Double]] = _ - def finish(factorsAfterElimination: scala.collection.mutable.Set[Factor[Double]], eliminationOrder: List[Variable[_]]): Unit = { + def finish(factorsAfterElimination: MultiSet[Factor[Double]], eliminationOrder: List[Variable[_]]): Unit = { result = factorsAfterElimination.toList } diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/factored/VETest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/factored/VETest.scala index b2ed3474..f41ad157 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/factored/VETest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/factored/VETest.scala @@ -1,13 +1,13 @@ /* - * VETest.scala + * VETest.scala * Variable elimination tests. - * + * * Created By: Avi Pfeffer (apfeffer@cra.com) * Creation Date: Jan 1, 2009 - * + * * Copyright 2013 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. - * + * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ @@ -306,7 +306,7 @@ class VETest extends WordSpec with Matchers { val a = If(f, Select(0.3 -> 1, 0.7 -> 2), Constant(2)) test(f, (b: Boolean) => b, 0.6) } - + "on a different universe from the current universe, produce the correct result" in { val u1 = Universe.createNew() val u = Select(0.25 -> 0.3, 0.25 -> 0.5, 0.25 -> 0.7, 0.25 -> 0.9) @@ -381,7 +381,7 @@ class VETest extends WordSpec with Matchers { ve.probability(y, true) should be(((0.1 * 0.2 + 0.9 * 0.2) / (0.1 * 0.2 + 0.9 * 0.2 + 0.9 * 0.8)) +- 0.0000000001) ve.kill } - + } "MPEVariableElimination" should { @@ -407,7 +407,7 @@ class VETest extends WordSpec with Matchers { alg.kill } } - + def test[T](target: Element[T], predicate: T => Boolean, prob: Double) { val tolerance = 0.0000001 val algorithm = VariableElimination(target) diff --git a/Figaro/src/test/scala/com/cra/figaro/test/example/MultiValuedTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/example/MultiValuedTest.scala index 34e3cf8b..144a7ece 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/example/MultiValuedTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/example/MultiValuedTest.scala @@ -1,13 +1,13 @@ /* - * MultiValuedTest.scala + * MultiValuedTest.scala * Multi-valued reference uncertainty example tests. - * + * * Created By: Avi Pfeffer (apfeffer@cra.com) * Creation Date: Jan 1, 2009 - * + * * Copyright 2013 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. - * + * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ From d75d956e0971d411577938165ce38d7029608578 Mon Sep 17 00:00:00 2001 From: mhoward2718 Date: Wed, 20 May 2015 12:23:43 -0400 Subject: [PATCH 10/10] Fix OnlineEM bug Fix OnlineEM bug (#461) --- .../algorithm/learning/GeneralizedEM.scala | 42 ++-- .../algorithm/learning/OnlineEMTest.scala | 234 +++++++++++++++++- 2 files changed, 258 insertions(+), 18 deletions(-) diff --git a/Figaro/src/main/scala/com/cra/figaro/algorithm/learning/GeneralizedEM.scala b/Figaro/src/main/scala/com/cra/figaro/algorithm/learning/GeneralizedEM.scala index 27d0df86..0d9c6e21 100644 --- a/Figaro/src/main/scala/com/cra/figaro/algorithm/learning/GeneralizedEM.scala +++ b/Figaro/src/main/scala/com/cra/figaro/algorithm/learning/GeneralizedEM.scala @@ -83,21 +83,23 @@ trait ExpectationMaximization extends Algorithm with ParameterLearner { */ trait OnlineExpectationMaximization extends Online with ExpectationMaximization { + override def doStart = {} + protected var lastIterationStatistics: Map[Parameter[_], Seq[Double]] = Map(targetParameters.map(p => p -> p.zeroSufficientStatistics): _*) override val initial: Universe override val transition: Function0[Universe] protected var currentUniverse: Universe = initial - private def updateStatistics(newStatistics:Map[Parameter[_], Seq[Double]]): Map[Parameter[_], Seq[Double]] = { + private def updateStatistics(newStatistics: Map[Parameter[_], Seq[Double]]): Map[Parameter[_], Seq[Double]] = { Map((for (p <- paramMap.keys) yield { val updatedStatistics = (lastIterationStatistics(p) zip newStatistics(p)).map((pair: (Double, Double)) => pair._1 + pair._2) (p, updatedStatistics) - }).toSeq:_*) - } - - /** - * Observe new evidence and perform one expectation step and one maximization step - */ + }).toSeq: _*) + } + + /** + * Observe new evidence and perform one expectation step and one maximization step + */ def update(evidence: Seq[NamedEvidence[_]] = Seq()): Unit = { currentUniverse = transition() currentUniverse.assertEvidence(evidence) @@ -124,13 +126,12 @@ class ExpectationMaximizationWithFactors(val universe: Universe, val targetParam } - /** * An online EM algorithm which learns parameters using a factored algorithm */ class OnlineExpectationMaximizationWithFactors(override val initial: Universe, override val transition: Function0[Universe], val targetParameters: Parameter[_]*)(val terminationCriteria: () => EMTerminationCriteria) extends OnlineExpectationMaximization { - + def doExpectationStep = { val algorithm = SufficientStatisticsVariableElimination(paramMap)(currentUniverse) algorithm.start @@ -141,12 +142,12 @@ class OnlineExpectationMaximizationWithFactors(override val initial: Universe, o } } - /** * An EM algorithm which learns parameters using an inference algorithm provided as an argument */ class GeneralizedEM(inferenceAlgorithmConstructor: Seq[Element[_]] => Universe => ProbQueryAlgorithm with OneTime, val universe: Universe, val targetParameters: Parameter[_]*)(val terminationCriteria: () => EMTerminationCriteria) extends ExpectationMaximization { + //Dependent universe doesn't work the same way. protected def doExpectationStep(): Map[Parameter[_], Seq[Double]] = { val inferenceTargets = universe.activeElements.filter(_.isInstanceOf[Parameterized[_]]).map(_.asInstanceOf[Parameterized[_]]) @@ -161,6 +162,7 @@ class GeneralizedEM(inferenceAlgorithmConstructor: Seq[Element[_]] => Universe = for { target <- universe.directlyUsedBy(parameter) } { + val t: Parameterized[target.Value] = target.asInstanceOf[Parameterized[target.Value]] val distribution: Stream[(Double, target.Value)] = algorithm.distribution(t) val newStats = t.distributionToStatistics(parameter, distribution) @@ -179,21 +181,27 @@ class GeneralizedEM(inferenceAlgorithmConstructor: Seq[Element[_]] => Universe = */ class GeneralizedOnlineEM(inferenceAlgorithmConstructor: Seq[Element[_]] => Universe => ProbQueryAlgorithm with OneTime, override val initial: Universe, override val transition: Function0[Universe], val targetParameters: Parameter[_]*)(val terminationCriteria: () => EMTerminationCriteria) extends OnlineExpectationMaximization { - + protected def usesParameter(l: List[Element[_]]): Map[Parameter[_], Iterable[Parameterized[_]]] = { + (l.map { x => x match { case p: Parameterized[_] => { p -> p.parameters.head } } }).groupBy(_._2).mapValues(_.map(_._1)) + } + protected def doExpectationStep(): Map[Parameter[_], Seq[Double]] = { val inferenceTargets = currentUniverse.activeElements.filter(_.isInstanceOf[Parameterized[_]]).map(_.asInstanceOf[Parameterized[_]]) val algorithm = inferenceAlgorithmConstructor(inferenceTargets)(currentUniverse) algorithm.start() - + //println("universe: " + currentUniverse.hashCode) var result: Map[Parameter[_], Seq[Double]] = Map() + val uses = usesParameter(inferenceTargets) + println("built map") for { parameter <- targetParameters } { var stats = parameter.zeroSufficientStatistics for { - target <- currentUniverse.directlyUsedBy(parameter) + target <- uses(parameter) } { + println("found used by...") val t: Parameterized[target.Value] = target.asInstanceOf[Parameterized[target.Value]] val distribution: Stream[(Double, target.Value)] = algorithm.distribution(t) val newStats = t.distributionToStatistics(parameter, distribution) @@ -208,7 +216,7 @@ class GeneralizedOnlineEM(inferenceAlgorithmConstructor: Seq[Element[_]] => Univ } object EMWithBP { - + private val defaultBPIterations = 10 def online(transition: () => Universe, p: Parameter[_]*)(implicit universe: Universe) = { @@ -270,9 +278,9 @@ object EMWithBP { } object EMWithImportance { - + private val defaultImportanceParticles = 100000 - + private def makeImportance(numParticles: Int, targets: Seq[Element[_]])(universe: Universe) = { Importance(numParticles, targets: _*)(universe) } @@ -342,7 +350,7 @@ object EMWithImportance { object EMWithMH { private val defaultMHParticles = 100000 - + private def makeImportance(numParticles: Int, targets: Seq[Element[_]])(universe: Universe) = { Importance(numParticles, targets: _*)(universe) } diff --git a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/learning/OnlineEMTest.scala b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/learning/OnlineEMTest.scala index f1d48104..205e31fa 100644 --- a/Figaro/src/test/scala/com/cra/figaro/test/algorithm/learning/OnlineEMTest.scala +++ b/Figaro/src/test/scala/com/cra/figaro/test/algorithm/learning/OnlineEMTest.scala @@ -29,7 +29,239 @@ import scala.math.abs import java.io._ import com.cra.figaro.test.tags.NonDeterministic -class OnlineEMTest extends WordSpec with PrivateMethodTester with Matchers { +class OnlineEMTest extends WordSpec with PrivateMethodTester with Matchers { + + "Online expectation maximization with BP" when + { + + "used to estimate a Beta parameter" should + { + + "detect bias after a large enough number of trials" in + { + val initial = Universe.createNew + val b = Beta(2, 2) + def transition = () => { + val next = Universe.createNew + val f = Flip(b)("f",next) + next + } + val algorithm = EMWithBP.online(transition, b)(initial) + algorithm.start() + for (i <- 1 to 7) { + algorithm.update(List(NamedEvidence("f", Observation(true)))) + } + + for (i <- 1 to 3) { + algorithm.update(List(NamedEvidence("f", Observation(false)))) + } + + + val result = b.MAPValue + algorithm.kill + result should be(0.6666 +- 0.01) + + } + + "take the prior concentration parameters into account" in + { + val initial = Universe.createNew + val b = Beta(3.0, 7.0) + def transition = () => { + val next = Universe.createNew + val f = Flip(b)("f",next) + next + } + val algorithm = EMWithBP.online(transition, b)(initial) + algorithm.start() + for (i <- 1 to 7) { + algorithm.update(List(NamedEvidence("f", Observation(true)))) + } + for (i <- 1 to 3) { + algorithm.update(List(NamedEvidence("f", Observation(false)))) + } + val result = b.MAPValue + algorithm.kill + result should be(0.50 +- 0.01) + } + + "learn the bias from observations of binomial elements" in { + val initial = Universe.createNew + val b = Beta(2, 2) + def transition = () => { + val next = Universe.createNew + val f = Binomial(5,b)("binomial",next) + next + } + val algorithm = EMWithBP.online(transition, b)(initial) + algorithm.start() + algorithm.update(List(NamedEvidence("binomial", Observation(4)))) + algorithm.update(List(NamedEvidence("binomial", Observation(3)))) + val result = b.MAPValue + algorithm.kill + result should be(0.6666 +- 0.01) + } + + "correctly use a uniform prior" in { + val initial = Universe.createNew + val b = Beta(1, 1) + def transition = () => { + val next = Universe.createNew + val f = Binomial(5,b)("binomial",next) + next + } + val algorithm = EMWithBP.online(transition, b)(initial) + algorithm.start() + algorithm.update(List(NamedEvidence("binomial", Observation(4)))) + algorithm.update(List(NamedEvidence("binomial", Observation(3)))) + val result = b.MAPValue + algorithm.kill + result should be(0.7 +- 0.01) + } + + } + + "used to estimate a Dirichlet parameter with two concentration parameters" should + { + + "detect bias after a large enough number of trials" in + { + val initial = Universe.createNew + val d = Dirichlet(2, 2) + def transition = () => { + val next = Universe.createNew + val s = Select(d, true, false)("s",next) + next + } + val algorithm = EMWithBP.online(transition, d)(initial) + algorithm.start() + for (i <- 1 to 7) { + algorithm.update(List(NamedEvidence("s", Observation(true)))) + } + for (i <- 1 to 3) { + algorithm.update(List(NamedEvidence("s", Observation(false)))) + } + val result = d.MAPValue + algorithm.kill + result(0) should be(0.6666 +- 0.01) + + } + + "take the prior concentration parameters into account" in + { + val initial = Universe.createNew + val d = Dirichlet(3, 7) + def transition = () => { + val next = Universe.createNew + val s = Select(d, true, false)("s",next) + next + } + val algorithm = EMWithBP.online(transition, d)(initial) + algorithm.start() + for (i <- 1 to 7) { + algorithm.update(List(NamedEvidence("s", Observation(true)))) + } + for (i <- 1 to 3) { + algorithm.update(List(NamedEvidence("s", Observation(false)))) + } + val result = d.MAPValue + algorithm.kill + result(0) should be(0.50 +- 0.01) + + } + + } + + "used to estimate multiple parameters" should + { + + "leave parameters having no observations unchanged" in + { + val initial = Universe.createNew + val d = Dirichlet(2.0, 4.0, 2.0) + val b = Beta(2.0, 2.0) + val outcomes = List(1, 2, 3) + + def transition = () => { + val next = Universe.createNew + val s = Select(d, outcomes:_*)("s",next) + next + } + val algorithm = EMWithBP.online(transition,d,b)(initial) + algorithm.start() + for (i <- 1 to 4) { + algorithm.update(List(NamedEvidence("s", Observation(1)))) + } + + for (i <- 1 to 2) { + algorithm.update(List(NamedEvidence("s", Observation(2)))) + } + + for (i <- 1 to 4) { + algorithm.update(List(NamedEvidence("s", Observation(3)))) + } + + + + val result = d.MAPValue + + result(0) should be(0.33 +- 0.01) + result(1) should be(0.33 +- 0.01) + result(2) should be(0.33 +- 0.01) + + val betaResult = b.MAPValue + betaResult should be(0.5) + algorithm.kill + } + + "correctly estimate all parameters with observations" in + { + val initial = Universe.createNew + val d = Dirichlet(2.0, 3.0, 2.0) + val b = Beta(3.0, 7.0) + val outcomes = List(1, 2, 3) + + def transition = () => { + val next = Universe.createNew + val s = Select(d, outcomes:_*)("s",next) + val f = Flip(b)("f",next) + next + } + val algorithm = EMWithBP.online(transition,d,b)(initial) + algorithm.start() + for (i <- 1 to 4) { + algorithm.update(List(NamedEvidence("s", Observation(1)))) + } + + for (i <- 1 to 2) { + algorithm.update(List(NamedEvidence("s", Observation(2)))) + } + + for (i <- 1 to 4) { + algorithm.update(List(NamedEvidence("s", Observation(3)))) + } + + for (i <- 1 to 7) { + algorithm.update(List(NamedEvidence("f", Observation(true)))) + } + + for (i <- 1 to 3) { + algorithm.update(List(NamedEvidence("f", Observation(false)))) + } + + val result = d.MAPValue + + result(0) should be(0.35 +- 0.01) + result(1) should be(0.28 +- 0.01) + result(2) should be(0.36 +- 0.01) + + val betaResult = b.MAPValue + betaResult should be(0.5 +- 0.01) + algorithm.kill + } + } + } + "Online expectation maximization" when {