Skip to content

Commit

Permalink
Changes for parameter factor creation. Fix for issues charles-river-a…
Browse files Browse the repository at this point in the history
…nalytics#659 and charles-river-analytics#660.

Issue charles-river-analytics#658 fixed for Importance sampling only.
  • Loading branch information
bruttenberg committed Jan 10, 2017
1 parent 020e438 commit 0cfbcff
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ trait FactoredAlgorithm[T] extends Algorithm {
* If any of these elements has * in its range, the lower and upper bounds of factors will be different, so we need to compute both.
* If they don't, we don't need to compute bounds.
*/
def getNeededElements(starterElements: List[Element[_]], depth: Int): (List[Element[_]], Boolean) = {
def getNeededElements(starterElements: List[Element[_]], depth: Int, parameterized: Boolean = false): (List[Element[_]], Boolean) = {
// Since there may be evidence on the dependent universes, we have to include their parents as important elements
val dependentUniverseParents =
for {
Expand All @@ -63,7 +63,7 @@ trait FactoredAlgorithm[T] extends Algorithm {
}
// Make sure we compute values from scratch in case the elements have changed
LazyValues.clear(universe)
val values = LazyValues(universe)
val values = LazyValues(universe, parameterized)

/*
* Beginning with the given element at the given depth, find all elements that the given element is used by within the depth.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,11 @@ trait ProbabilisticBeliefPropagation extends BeliefPropagation[Double] {
* for all elements in the universe.
*/
def getFactors(neededElements: List[Element[_]], targetElements: List[Element[_]], upperBounds: Boolean = false): List[Factor[Double]] = {
val thisUniverseFactors = (neededElements flatMap (Factory.makeFactorsForElement(_, upperBounds, true))).filterNot(_.isEmpty)
val parameterized = this match {
case p: ParameterLearner => true
case _ => false
}
val thisUniverseFactors = (neededElements flatMap (Factory.makeFactorsForElement(_, upperBounds, parameterized))).filterNot(_.isEmpty)
val dependentUniverseFactors =
for { (dependentUniverse, evidence) <- dependentUniverses } yield Factory.makeDependentFactor(Variable.cc, universe, dependentUniverse, dependentAlgorithm(dependentUniverse, evidence))
val factors = dependentUniverseFactors ::: thisUniverseFactors
Expand Down Expand Up @@ -372,7 +376,11 @@ abstract class ProbQueryBeliefPropagation(override val universe: Universe, targe
var needsBounds: Boolean = _

def generateGraph() = {
val needs = getNeededElements(starterElements, depth)
val parameterized = this match {
case p: ParameterLearner => true
case _ => false
}
val needs = getNeededElements(starterElements, depth, parameterized)
neededElements = needs._1
needsBounds = needs._2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ object ChainFactory {
val subproblem = chainComp.subproblems(parentXV.value)
// Need to normalize subsolution in case there's any nested evidence
val subsolution = subproblem.solution.reduce(_.product(_))
val sum = subsolution.foldLeft(subsolution.semiring.zero, subsolution.semiring.sum(_, _))
//val sum = subsolution.foldLeft(subsolution.semiring.zero, subsolution.semiring.sum(_, _))
val subVars = subsolution.variables
if (subVars.length == 1) {
val subVar = subVars(0)
for { subVal <- subVar.range } {
val childIndex = childVar.range.indexOf(subVal)
val subIndex = subVar.range.indexOf(subVal)
val entry = subsolution.semiring.product(subsolution.get(List(subIndex)), 1.0 / sum)
factor.set(List(parentIndex, childIndex), entry)
//val entry = subsolution.semiring.product(subsolution.get(List(subIndex)), 1.0 / sum)
factor.set(List(parentIndex, childIndex), subsolution.get(List(subIndex)))
}
} else { // This should be a case where the subproblem is empty and the value is *
val starIndex = childVar.range.indexWhere(!_.isRegular)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,41 +207,49 @@ object Factory {
}
}


def parameterCheck(elem: Element[_], parameterized: Boolean): Boolean = {
elem match {
case parameter: DoubleParameter => parameterized
case _ => false
}
}

/**
* Invokes Factor constructors for a standard set of Elements. This method uses various
* secondary factories.
*/
def concreteFactors[T](cc: ComponentCollection, elem: Element[T], parameterized: Boolean): List[Factor[Double]] = {
elem match {
case flip: ParameterizedFlip => DistributionFactory.makeFactors(cc, flip, parameterized)
case pSelect: ParameterizedSelect[_] => SelectFactory.makeFactors(cc, pSelect, parameterized)
case pBin: ParameterizedBinomialFixedNumTrials => DistributionFactory.makeFactors(cc, pBin, parameterized)
case parameter: DoubleParameter => makeParameterFactors(cc, parameter)
case array: ArrayParameter => makeParameterFactors(cc, array)
case constant: Constant[_] => makeFactors(cc, constant)
case f: AtomicFlip => DistributionFactory.makeFactors(cc, f)
case f: CompoundFlip => DistributionFactory.makeFactors(cc, f)
case ab: AtomicBinomial => DistributionFactory.makeFactors(cc, ab)
case s: AtomicSelect[_] => SelectFactory.makeFactors(cc, s)
case s: CompoundSelect[_] => SelectFactory.makeFactors(cc, s)
case d: AtomicDist[_] => SelectFactory.makeFactors(cc, d)
case d: CompoundDist[_] => SelectFactory.makeFactors(cc, d)
case s: IntSelector => SelectFactory.makeFactors(cc, s)
case c: Chain[_, _] => ChainFactory.makeFactors(cc, c)
case a: Apply1[_, _] => ApplyFactory.makeFactors(cc, a)
case a: Apply2[_, _, _] => ApplyFactory.makeFactors(cc, a)
case a: Apply3[_, _, _, _] => ApplyFactory.makeFactors(cc, a)
case a: Apply4[_, _, _, _, _] => ApplyFactory.makeFactors(cc, a)
case a: Apply5[_, _, _, _, _, _] => ApplyFactory.makeFactors(cc, a)
case i: Inject[_] => makeFactors(cc, i)
case r: SingleValuedReferenceElement[_] => ComplexFactory.makeFactors(cc, r)
case r: MultiValuedReferenceElement[_] => ComplexFactory.makeFactors(cc, r)
case r: Aggregate[_, _] => ComplexFactory.makeFactors(cc, r)
(elem, parameterized) match {
case (flip: ParameterizedFlip, _) => DistributionFactory.makeFactors(cc, flip, parameterized)
case (pSelect: ParameterizedSelect[_], _) => SelectFactory.makeFactors(cc, pSelect, parameterized)
case (pBin: ParameterizedBinomialFixedNumTrials, _) => DistributionFactory.makeFactors(cc, pBin, parameterized)
case (parameter: DoubleParameter, true) => makeParameterFactors(cc, parameter)
case (array: ArrayParameter, true) => makeParameterFactors(cc, array)
case (constant: Constant[_], _) => makeFactors(cc, constant)
case (f: AtomicFlip, _) => DistributionFactory.makeFactors(cc, f)
case (f: CompoundFlip, _) => DistributionFactory.makeFactors(cc, f)
case (ab: AtomicBinomial, _) => DistributionFactory.makeFactors(cc, ab)
case (s: AtomicSelect[_], _) => SelectFactory.makeFactors(cc, s)
case (s: CompoundSelect[_], _) => SelectFactory.makeFactors(cc, s)
case (d: AtomicDist[_], _) => SelectFactory.makeFactors(cc, d)
case (d: CompoundDist[_], _) => SelectFactory.makeFactors(cc, d)
case (s: IntSelector, _) => SelectFactory.makeFactors(cc, s)
case (c: Chain[_, _], _) => ChainFactory.makeFactors(cc, c)
case (a: Apply1[_, _], _) => ApplyFactory.makeFactors(cc, a)
case (a: Apply2[_, _, _], _) => ApplyFactory.makeFactors(cc, a)
case (a: Apply3[_, _, _, _], _) => ApplyFactory.makeFactors(cc, a)
case (a: Apply4[_, _, _, _, _], _) => ApplyFactory.makeFactors(cc, a)
case (a: Apply5[_, _, _, _, _, _], _) => ApplyFactory.makeFactors(cc, a)
case (i: Inject[_], _) => makeFactors(cc, i)
case (r: SingleValuedReferenceElement[_], _) => ComplexFactory.makeFactors(cc, r)
case (r: MultiValuedReferenceElement[_], _) => ComplexFactory.makeFactors(cc, r)
case (r: Aggregate[_, _], _) => ComplexFactory.makeFactors(cc, r)
//case m: MakeList[_] => ComplexFactory.makeFactors(cc, m)
case m: MakeArray[_] => ComplexFactory.makeFactors(cc, m)
case f: FoldLeft[_, _] => ComplexFactory.makeFactors(cc, f)
case f: FactorMaker[_] => f.makeFactors
case a: Atomic[_] => makeFactors(cc, a)
case (m: MakeArray[_], _) => ComplexFactory.makeFactors(cc, m)
case (f: FoldLeft[_, _], _) => ComplexFactory.makeFactors(cc, f)
case (f: FactorMaker[_], _) => f.makeFactors
case (a: Atomic[_], _) => makeFactors(cc, a)

case _ => throw new UnsupportedAlgorithmException(elem)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import com.cra.figaro.algorithm.factored.ParticleGenerator
* Object for lazily computing the range of values of elements in a universe. Given a universe, you can compute the values
* of elements in the universe to any desired depth.
*/
class LazyValues(universe: Universe) {
class LazyValues(universe: Universe, paramaterized: Boolean = false) {
private def values[T](element: Element[T], depth: Int, numArgSamples: Int, numTotalSamples: Int): ValueSet[T] = {
// In some cases (e.g. CompoundFlip), we might know the value of an element without getting the values of its arguments.
// However, future algorithms rely on the values of the arguments having been gotten already.
Expand All @@ -42,8 +42,8 @@ class LazyValues(universe: Universe) {

// Override the argument list for chains since the resultElement of the chain will be processed as the chain is processed
val elementArgs = element match {
case c: Chain[_,_] => List(c.parent)
case _ => element.args
case c: Chain[_, _] => List(c.parent)
case _ => element.args
}

for { arg <- elementArgs } {
Expand All @@ -52,22 +52,23 @@ class LazyValues(universe: Universe) {
}

Abstraction.fromPragmas(element.pragmas) match {
case None => concreteValues(element, depth, numArgSamples, numTotalSamples)
case None => concreteValues(element, depth, numArgSamples, numTotalSamples)
case Some(abstraction) => abstractValues(element, abstraction, depth, numArgSamples, numTotalSamples)
}

}

private def concreteValues[T](element: Element[T], depth: Int, numArgSamples: Int, numTotalSamples: Int): ValueSet[T] =
element match {
case c: Constant[_] => withoutStar(Set(c.constant))
case f: Flip => withoutStar(Set(true, false))
case d: Select[_, _] => withoutStar(Set(d.outcomes: _*))
case d: Dist[_, _] =>
(element, paramaterized) match {
case (p: Parameter[_], true) => ValueSet.withoutStar(Set(p.MAPValue))
case (c: Constant[_], _) => withoutStar(Set(c.constant))
case (f: Flip, _) => withoutStar(Set(true, false))
case (d: Select[_, _], _) => withoutStar(Set(d.outcomes: _*))
case (d: Dist[_, _], _) =>
val componentSets = d.outcomes.map(storedValues(_))
componentSets.reduce(_ ++ _)
//case i: FastIf[_] => withoutStar(Set(i.thn, i.els))
case a: Apply1[_, _] =>
case (a: Apply1[_, _], _) =>
val applyMap = getMap(a)
val vs1 = LazyValues(a.arg1.universe).storedValues(a.arg1)
val resultsSet =
Expand All @@ -77,7 +78,7 @@ class LazyValues(universe: Universe) {
getOrElseInsert(applyMap, arg1Val, a.fn(arg1Val))
}
if (vs1.hasStar) withStar(resultsSet); else withoutStar(resultsSet)
case a: Apply2[_, _, _] =>
case (a: Apply2[_, _, _], _) =>
val applyMap = getMap(a)
val vs1 = LazyValues(a.arg1.universe).storedValues(a.arg1)
val vs2 = LazyValues(a.arg2.universe).storedValues(a.arg2)
Expand All @@ -92,7 +93,7 @@ class LazyValues(universe: Universe) {
getOrElseInsert(applyMap, (arg1Val, arg2Val), a.fn(arg1Val, arg2Val))
}
if (vs1.hasStar || vs2.hasStar) withStar(resultsList.toSet); else withoutStar(resultsList.toSet)
case a: Apply3[_, _, _, _] =>
case (a: Apply3[_, _, _, _], _) =>
val applyMap = getMap(a)
val vs1 = LazyValues(a.arg1.universe).storedValues(a.arg1)
val vs2 = LazyValues(a.arg2.universe).storedValues(a.arg2)
Expand All @@ -109,7 +110,7 @@ class LazyValues(universe: Universe) {
getOrElseInsert(applyMap, (arg1Val, arg2Val, arg3Val), a.fn(arg1Val, arg2Val, arg3Val))
}
if (vs1.hasStar || vs2.hasStar || vs3.hasStar) withStar(resultsList.toSet); else withoutStar(resultsList.toSet)
case a: Apply4[_, _, _, _, _] =>
case (a: Apply4[_, _, _, _, _], _) =>
val applyMap = getMap(a)
val vs1 = LazyValues(a.arg1.universe).storedValues(a.arg1)
val vs2 = LazyValues(a.arg2.universe).storedValues(a.arg2)
Expand All @@ -128,7 +129,7 @@ class LazyValues(universe: Universe) {
getOrElseInsert(applyMap, (arg1Val, arg2Val, arg3Val, arg4Val), a.fn(arg1Val, arg2Val, arg3Val, arg4Val))
}
if (vs1.hasStar || vs2.hasStar || vs3.hasStar || vs4.hasStar) withStar(resultsList.toSet); else withoutStar(resultsList.toSet)
case a: Apply5[_, _, _, _, _, _] =>
case (a: Apply5[_, _, _, _, _, _], _) =>
val applyMap = getMap(a)
val vs1 = LazyValues(a.arg1.universe).storedValues(a.arg1)
val vs2 = LazyValues(a.arg2.universe).storedValues(a.arg2)
Expand All @@ -149,7 +150,7 @@ class LazyValues(universe: Universe) {
getOrElseInsert(applyMap, (arg1Val, arg2Val, arg3Val, arg4Val, arg5Val), a.fn(arg1Val, arg2Val, arg3Val, arg4Val, arg5Val))
}
if (vs1.hasStar || vs2.hasStar || vs3.hasStar || vs4.hasStar || vs5.hasStar) withStar(resultsList.toSet); else withoutStar(resultsList.toSet)
case c: Chain[_, _] =>
case (c: Chain[_, _], _) =>

def findChainValues[T, U](chain: Chain[T, U], cmap: Map[T, Element[U]], pVals: ValueSet[T], samples: Int): Set[ValueSet[U]] = {
val chainVals = pVals.regularValues.map { parentVal =>
Expand All @@ -168,25 +169,25 @@ class LazyValues(universe: Universe) {

val chainMap = getMap(c)
val parentVS = LazyValues(c.parent.universe).storedValues(c.parent)
val samplesPerValue = math.max(1, (numTotalSamples.toDouble/parentVS.regularValues.size).toInt)
val samplesPerValue = math.max(1, (numTotalSamples.toDouble / parentVS.regularValues.size).toInt)

val resultVSs = findChainValues(c, chainMap, parentVS, samplesPerValue)

val startVS: ValueSet[c.Value] =
if (parentVS.hasStar) withStar[c.Value](Set()); else withoutStar[c.Value](Set())
resultVSs.foldLeft(startVS)(_ ++ _)
case i: Inject[_] =>
case (i: Inject[_], _) =>
val elementVSs = i.args.map(arg => LazyValues(arg.universe).storedValues(arg))
val incomplete = elementVSs.exists(_.hasStar)
val elementValues = elementVSs.toList.map(_.regularValues.toList)
val resultValues = homogeneousCartesianProduct(elementValues: _*).toSet.asInstanceOf[Set[i.Value]]
if (incomplete) withStar(resultValues); else withoutStar(resultValues)
case v: ValuesMaker[_] => {
case (v: ValuesMaker[_], _) => {
v.makeValues(depth)
}
case a: Atomic[_] => {
case (a: Atomic[_], _) => {
val thisSampler = ParticleGenerator(universe)
val samples = thisSampler(a, numArgSamples)
val samples = thisSampler(a, numArgSamples)
withoutStar(samples.unzip._2.toSet)
}
case _ =>
Expand All @@ -195,7 +196,7 @@ class LazyValues(universe: Universe) {
}

private def abstractValues[T](element: Element[T], abstraction: Abstraction[T], depth: Int,
numArgSamples: Int, numTotalSamples: Int): ValueSet[T] = {
numArgSamples: Int, numTotalSamples: Int): ValueSet[T] = {
val (inputs, hasStar): (List[T], Boolean) = {
element match {
case _: Atomic[_] =>
Expand Down Expand Up @@ -238,7 +239,7 @@ class LazyValues(universe: Universe) {
def apply[T](element: Element[T], depth: Int): ValueSet[T] = {
val (numArgSamples, numTotalSamples) = if (ParticleGenerator.exists(universe)) {
val pg = ParticleGenerator(universe)
(pg.numSamplesFromAtomics, pg.maxNumSamplesAtChain )
(pg.numSamplesFromAtomics, pg.maxNumSamplesAtChain)
} else {
(ParticleGenerator.defaultNumSamplesFromAtomics, ParticleGenerator.defaultMaxNumSamplesAtChain)
}
Expand Down Expand Up @@ -306,7 +307,7 @@ class LazyValues(universe: Universe) {
def storedValues[T](element: Element[T]): ValueSet[T] = {
memoValues.get(element) match {
case Some((result, _)) => result.asInstanceOf[ValueSet[T]]
case None => new ValueSet[T](Set())
case None => new ValueSet[T](Set())
}
}

Expand Down Expand Up @@ -350,7 +351,7 @@ class LazyValues(universe: Universe) {
def getMap[U](apply: Apply[U]): Map[Any, U] = {
getOrElseInsert(applyMaps, apply, Map[Any, Any]()).asInstanceOf[Map[Any, U]]
}

/**
* Gets the mapping from apply arguments to values.
*/
Expand Down Expand Up @@ -418,11 +419,11 @@ object LazyValues {
* Create an object for computing the range of values of elements in the universe. This object is only
* created once for a universe.
*/
def apply(universe: Universe = Universe.universe): LazyValues = {
def apply(universe: Universe = Universe.universe, parameterized: Boolean = false): LazyValues = {
expansions.get(universe) match {
case Some(e) => e
case None =>
val expansion = new LazyValues(universe)
val expansion = new LazyValues(universe, parameterized)
expansions += (universe -> expansion)
universe.registerUniverse(expansions)
expansion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import com.cra.figaro.algorithm.online.Online
import com.cra.figaro.algorithm.factored.VariableElimination
import com.cra.figaro.algorithm.factored.factors.Variable
import com.cra.figaro.algorithm.sampling.Forward
import com.cra.figaro.algorithm.OneTimeProbQuery
import com.cra.figaro.algorithm.factored.beliefpropagation.OneTimeProbabilisticBeliefPropagation
import com.cra.figaro.algorithm.factored.beliefpropagation.ProbQueryBeliefPropagation
import com.cra.figaro.algorithm.sampling.ProbEvidenceSampler

/**
* Expectation maximization iteratively produces an estimate of sufficient statistics for learnable parameters,
Expand Down Expand Up @@ -235,7 +239,10 @@ object EMWithBP {

private def makeBP(numIterations: Int, targets: Seq[Element[_]])(universe: Universe) = {
Variable.clearCache
BeliefPropagation(numIterations, targets: _*)(universe)
new ProbQueryBeliefPropagation(universe, targets: _*)(
List(),
(u: Universe, e: List[NamedEvidence[_]]) => () => ProbEvidenceSampler.computeProbEvidence(10000, e)(u))
with OneTimeProbabilisticBeliefPropagation with OneTimeProbQuery with ParameterLearner { val iterations = numIterations }
}
/**
* An expectation maximization algorithm using Belief Propagation sampling for inference.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ abstract class Importance(universe: Universe, targets: Element[_]*)
super.kill()
lw.clearCache()
lw.deregisterDependencies()
universe.deregisterAlgorithm(this)
}

/*
Expand Down
Loading

0 comments on commit 0cfbcff

Please sign in to comment.