Skip to content

Added base class for variational methods #1600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 87 commits into from
Closed
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
2d6fee8
Added mode argument to several step methods and advi to allow mode se…
fonnesbeck Nov 17, 2016
4e5b9c2
Fixed namespace bugs in mode attribute
fonnesbeck Nov 17, 2016
0ebaacd
Reverted function in delta_logp to not accept mode argument
fonnesbeck Nov 17, 2016
55c8ce6
ENH User model (#1525)
ferrine Nov 28, 2016
9ab04da
added new elbo implementation
ferrine Dec 4, 2016
9811220
Added mode argument to several step methods and advi to allow mode se…
fonnesbeck Nov 17, 2016
fbd1d5b
Fixed namespace bugs in mode attribute
fonnesbeck Nov 17, 2016
208aa79
Reverted function in delta_logp to not accept mode argument
fonnesbeck Nov 17, 2016
40d0146
ENH User model (#1525)
ferrine Nov 28, 2016
fc0673b
ENH User model (#1525)
ferrine Nov 28, 2016
ea82ebd
Refactor Hamiltonian methods into single class
Nov 8, 2016
140a80c
Reformat docs
Dec 3, 2016
168b113
added replacements class and mean field approximation
ferrine Dec 9, 2016
c1211a6
moved local to local constructor
ferrine Dec 9, 2016
9690562
property for deterministic replacements
ferrine Dec 9, 2016
34da7c8
refactored replacements to make them more unitary
ferrine Dec 9, 2016
07a248a
shape problem when sampling
ferrine Dec 8, 2016
889b50e
tests passed
ferrine Dec 9, 2016
0d486fb
deleted unused modules
ferrine Dec 13, 2016
125f6ad
added replacement names for global/local dict
ferrine Dec 13, 2016
1af91c0
Merge branch '3.1' into refactor_advi
ferrine Dec 13, 2016
69f07a1
refactored replacements
ferrine Dec 15, 2016
9614bf9
refactored replacements
ferrine Dec 15, 2016
32a2eb7
refactored GARCH and added Mv(Gaussian/StudentT)RandomWalk (#1603)
ferrine Dec 15, 2016
5e68b95
Merge branch '3.1' into refactor_advi
ferrine Dec 15, 2016
0f2c38f
added flatten_list
ferrine Dec 15, 2016
63e57d7
added tests
ferrine Dec 16, 2016
4d4cb82
refactored local/global dicts
ferrine Dec 16, 2016
82c7996
moved __type__ assignment to better place
ferrine Dec 16, 2016
2cd6bc5
Don't do replacements too early or else it will be not possible to tr…
ferrine Dec 16, 2016
4d810f2
refactored docs
ferrine Dec 16, 2016
16a226b
fixed memory consumption during test
ferrine Dec 18, 2016
d8e9886
set nmc samples to 1000 in test
ferrine Dec 18, 2016
1bb349e
optimized code a lot
ferrine Dec 19, 2016
87e7e2d
changed expectations to sampling, added docs
ferrine Dec 19, 2016
9eb79a0
code style
ferrine Dec 19, 2016
be1ca80
validate model
ferrine Dec 19, 2016
e8f6644
added tests for dynamic number of samples
ferrine Dec 19, 2016
4add3bc
added `set_params` method
ferrine Dec 19, 2016
43a8638
added `params` property
ferrine Dec 19, 2016
6a88fde
ENH KL-weighting
taku-y Nov 24, 2016
a3bad35
Fix bugs
taku-y Nov 26, 2016
7ed2cb5
Remove unnecessary comments
taku-y Nov 28, 2016
163b1be
Fix typo
taku-y Dec 5, 2016
fad9410
Minor fixes
taku-y Dec 7, 2016
e1a88e0
Check transformed RVs using hasattr
taku-y Dec 8, 2016
ae349e9
Update conv-vae notebook
taku-y Dec 15, 2016
9e237ef
Implementation of path derivative gradient estimator (NIPS 2016) #1615
ferrine Dec 20, 2016
63c1285
local vars nee this path trick too
ferrine Dec 20, 2016
02f5fa6
bug in local size calculation
ferrine Dec 21, 2016
8cc9558
bug in global subset view
ferrine Dec 21, 2016
4e302e6
improved performance
ferrine Dec 21, 2016
e5df6ee
changed the way for calling posterior
ferrine Dec 22, 2016
23ed175
deleted accidental added nuts file
ferrine Dec 22, 2016
7a7cdc3
Merge remote-tracking branch 'upstream/3.1' into refactor_advi
ferrine Dec 22, 2016
ac949d2
changed zero grad usage
ferrine Dec 22, 2016
26adf3b
refactor apply replacements
ferrine Dec 23, 2016
63000fb
added useful functions to replacements
ferrine Dec 24, 2016
5240260
added `approximate` function
ferrine Dec 24, 2016
7802a78
changed name MeanFieald to Advi
ferrine Dec 25, 2016
2407d78
added docs, renamed classes
ferrine Dec 25, 2016
fbf26d4
add deterministics to posterior to point function
ferrine Dec 25, 2016
c394d5e
trying to fix reweighting
ferrine Dec 26, 2016
2162d4c
weight log_p_W{local|global} correctly
ferrine Dec 26, 2016
7609f72
local and global weighting
ferrine Dec 27, 2016
d55d258
added docs
ferrine Dec 27, 2016
dca919c
preparing mnist vae, fixed bugs
ferrine Dec 27, 2016
cb2e219
Took in account suggestions for refactoring
ferrine Dec 30, 2016
d94e7e7
refactored dist math
ferrine Jan 3, 2017
8d1f088
Added mode argument to several step methods and advi to allow mode se…
fonnesbeck Nov 17, 2016
37843af
Created Generator Op with simple Test
ferrine Jan 11, 2017
3dc6f1b
added ndim test
ferrine Jan 11, 2017
a16512e
updated test
ferrine Jan 11, 2017
7127c23
updated test, added test value check
ferrine Jan 11, 2017
23b14ff
added test for replacing generator with shared variable
ferrine Jan 11, 2017
96cd5bb
added shortcut for generator op
ferrine Jan 11, 2017
633e4e9
refactored test
ferrine Jan 11, 2017
0629adc
added population kwarg (no tests yet)
ferrine Jan 11, 2017
06099a2
added population kwarg for free var(autoencoder case)
ferrine Jan 11, 2017
75a4849
Revert "Added mode argument to several step methods and advi to allow…
ferrine Jan 12, 2017
ff325d8
add docstring to generator Op
ferrine Jan 14, 2017
79ac934
rename population -> total_size
ferrine Jan 14, 2017
57dbe47
update docstrings in model
ferrine Jan 14, 2017
f8bce58
fix typo in `as_tensor` function
ferrine Jan 14, 2017
244bf21
Merge branch 'generator_op' into refactor_advi
ferrine Jan 14, 2017
8d91fee
add simple test for density scaling via `total_size`
ferrine Jan 17, 2017
1a9fa3d
raise an error when density scaling is done on scalar
ferrine Jan 17, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion pymc3/data.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import itertools
import pkgutil
import io

__all__ = ['get_data_file']
import theano.tensor as tt

__all__ = ['get_data_file', 'DataGenerator']


def get_data_file(pkg, path):
@@ -19,3 +22,28 @@ def get_data_file(pkg, path):
"""

return io.BytesIO(pkgutil.get_data(pkg, path))


class DataGenerator(object):
"""
Helper class that helps to infer data type of generator with looking
at the first item, preserving the order of the resulting generator
"""
def __init__(self, generator):
self.test_value = next(generator)
self.gen = itertools.chain([self.test_value], generator)
self.tensortype = tt.TensorType(
self.test_value.dtype,
((False, ) * self.test_value.ndim))

def __next__(self):
return next(self.gen)

def __iter__(self):
return self

def __eq__(self, other):
return id(self) == id(other)

def __hash__(self):
return hash(id(self))
64 changes: 64 additions & 0 deletions pymc3/distributions/dist_math.py
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@

from .special import gammaln, multigammaln

c = - 0.5 * np.log(2 * np.pi)


def bound(logp, *conditions, **kwargs):
"""
Bounds a log probability density with several conditions.
@@ -95,3 +98,64 @@ def i1(x):
x**9 / 1474560 + x**11 / 176947200 + x**13 / 29727129600,
np.e**x / (2 * np.pi * x)**0.5 * (1 - 3 / (8 * x) + 15 / (128 * x**2) + 315 / (3072 * x**3)
+ 14175 / (98304 * x**4)))


def sd2rho(sd):
"""
`sd -> rho` theano converter
:math:`mu + sd*e = mu + log(1+exp(rho))*e`"""
return tt.log(tt.exp(sd) - 1)


def rho2sd(rho):
"""
`rho -> sd` theano converter
:math:`mu + sd*e = mu + log(1+exp(rho))*e`"""
return tt.log1p(tt.exp(rho))


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not used, I can delete it

def log_normal(x, mean, **kwargs):
"""
Calculate logarithm of normal distribution at point `x`
with given `mean` and `std`
Parameters
----------
x : Tensor
point of evaluation
mean : Tensor
mean of normal distribution
kwargs : one of parameters `{sd, tau, w, rho}`
Notes
-----
There are four variants for density parametrization.
They are:
1) standard deviation - `std`
2) `w`, logarithm of `std` :math:`w = log(std)`
3) `rho` that follows this equation :math:`rho = log(exp(std) - 1)`
4) `tau` that follows this equation :math:`tau = std^{-1}`
----
"""
sd = kwargs.get('sd')
w = kwargs.get('w')
rho = kwargs.get('rho')
tau = kwargs.get('tau')
eps = kwargs.get('eps', 0.0)
check = sum(map(lambda a: a is not None, [sd, w, rho, tau]))
if check > 1:
raise ValueError('more than one required kwarg is passed')
if check == 0:
raise ValueError('none of required kwarg is passed')
if sd is not None:
std = sd
elif w is not None:
std = tt.exp(w)
elif rho is not None:
std = rho2sd(rho)
else:
std = tau**(-1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

std += eps
return c - tt.log(tt.abs_(std)) - (x - mean) ** 2 / (2 * std ** 2)
7 changes: 4 additions & 3 deletions pymc3/distributions/distribution.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
from .dist_math import bound


__all__ = ['DensityDist', 'Distribution', 'Continuous', 'Bound',
__all__ = ['DensityDist', 'Distribution', 'Continuous', 'Bound',
'Discrete', 'NoDistribution', 'TensorType', 'draw_values']


@@ -30,8 +30,9 @@ def __new__(cls, name, *args, **kwargs):

if isinstance(name, string_types):
data = kwargs.pop('observed', None)
total_size = kwargs.pop('total_size', None)
dist = cls.dist(*args, **kwargs)
return model.Var(name, dist, data)
return model.Var(name, dist, data, total_size)
else:
raise TypeError("Name needs to be a string but got: %s" % name)

@@ -410,7 +411,7 @@ def __init__(self, distribution, lower, upper, transform='infer', *args, **kwarg
self.transform = transforms.upperbound(upper)
if default >= upper:
self.testval = upper - 1

if issubclass(distribution, Discrete):
self.transform = None

4 changes: 4 additions & 0 deletions pymc3/math.py
Original file line number Diff line number Diff line change
@@ -22,3 +22,7 @@ def invlogit(x, eps=sys.float_info.epsilon):

def logit(p):
return tt.log(p / (1 - p))


def flatten_list(tensors):
return tt.concatenate([var.ravel() for var in tensors])
72 changes: 55 additions & 17 deletions pymc3/model.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@

import pymc3 as pm
from .memoize import memoize
from .theanof import gradient, hessian, inputvars
from .theanof import gradient, hessian, inputvars, generator
from .vartypes import typefilter, discrete_types, continuous_types
from .blocking import DictToArrayBijection, ArrayOrdering

@@ -458,7 +458,7 @@ def cont_vars(self):
"""All the continuous variables in the model"""
return list(typefilter(self.vars, continuous_types))

def Var(self, name, dist, data=None):
def Var(self, name, dist, data=None, total_size=None):
"""Create and add (un)observed random variable to the model with an
appropriate prior distribution.
@@ -469,6 +469,8 @@ def Var(self, name, dist, data=None):
data : array_like (optional)
If data is provided, the variable is observed. If None,
the variable is unobserved.
total_size : scalar
upscales logp of variable with :math:`coef = total_size/var.shape[0]`
Returns
-------
@@ -477,11 +479,13 @@ def Var(self, name, dist, data=None):
name = self.name_for(name)
if data is None:
if getattr(dist, "transform", None) is None:
var = FreeRV(name=name, distribution=dist, model=self)
var = FreeRV(name=name, distribution=dist, model=self,
total_size=total_size)
self.free_RVs.append(var)
else:
var = TransformedRV(name=name, distribution=dist, model=self,
transform=dist.transform)
transform=dist.transform,
total_size=total_size)
pm._log.debug('Applied {transform}-transform to {name}'
' and added transformed {orig_name} to model.'.format(
transform=dist.transform.name,
@@ -491,7 +495,7 @@ def Var(self, name, dist, data=None):
return var
elif isinstance(data, dict):
var = MultiObservedRV(name=name, data=data, distribution=dist,
model=self)
model=self, total_size=total_size)
self.observed_RVs.append(var)
if var.missing_values:
self.free_RVs += var.missing_values
@@ -500,7 +504,8 @@ def Var(self, name, dist, data=None):
self.named_vars[v.name] = v
else:
var = ObservedRV(name=name, data=data,
distribution=dist, model=self)
distribution=dist, model=self,
total_size=total_size)
self.observed_RVs.append(var)
if var.missing_values:
self.free_RVs.append(var.missing_values)
@@ -717,15 +722,18 @@ class FreeRV(Factor, TensorVariable):
"""Unobserved random variable that a model is specified in terms of."""

def __init__(self, type=None, owner=None, index=None, name=None,
distribution=None, model=None):
distribution=None, model=None, total_size=None):
"""
Parameters
----------
type : theano type (optional)
owner : theano owner (optional)
name : str
distribution : Distribution
model : Model"""
model : Model
total_size : scalar Tensor (optional)
needed for upscaling logp
"""
if type is None:
type = distribution.type
super(FreeRV, self).__init__(type, owner, index, name)
@@ -736,7 +744,14 @@ def __init__(self, type=None, owner=None, index=None, name=None,
self.distribution = distribution
self.tag.test_value = np.ones(
distribution.shape, distribution.dtype) * distribution.default()
self.logp_elemwiset = distribution.logp(self)
logp_elemwiset = distribution.logp(self)
if total_size is None:
coef = tt.as_tensor(1)
else:
assert logp_elemwiset.ndim >= 1, ('Variable with scaled density '
'needs to be at least 1 dimensional')
coef = tt.as_tensor(total_size) / logp_elemwiset.shape[0]
self.logp_elemwiset = logp_elemwiset * coef
self.model = model

incorporate_methods(source=distribution, destination=self,
@@ -759,6 +774,8 @@ def pandas_to_array(data):
return data
elif isinstance(data, theano.gof.graph.Variable):
return data
elif hasattr(data, '__next__'):
return generator(data)
else:
return np.asarray(data)

@@ -792,7 +809,7 @@ class ObservedRV(Factor, TensorVariable):
"""

def __init__(self, type=None, owner=None, index=None, name=None, data=None,
distribution=None, model=None):
distribution=None, model=None, total_size=None):
"""
Parameters
----------
@@ -801,6 +818,8 @@ def __init__(self, type=None, owner=None, index=None, name=None, data=None,
name : str
distribution : Distribution
model : Model
total_size : scalar Tensor (optional)
needed for upscaling logp
"""
from .distributions import TensorType
if type is None:
@@ -814,7 +833,14 @@ def __init__(self, type=None, owner=None, index=None, name=None, data=None,
data = as_tensor(data, name, model, distribution)
self.missing_values = data.missing_values

self.logp_elemwiset = distribution.logp(data)
logp_elemwiset = distribution.logp(data)
if total_size is None:
coef = tt.as_tensor(1)
else:
assert logp_elemwiset.ndim >= 1, ('Variable with scaled density '
'needs to be at least 1 dimensional')
coef = tt.as_tensor(total_size) / logp_elemwiset.shape[0]
self.logp_elemwiset = logp_elemwiset * coef
self.model = model
self.distribution = distribution

@@ -835,7 +861,7 @@ class MultiObservedRV(Factor):
Potentially partially observed.
"""

def __init__(self, name, data, distribution, model):
def __init__(self, name, data, distribution, model, total_size=None):
"""
Parameters
----------
@@ -844,14 +870,23 @@ def __init__(self, name, data, distribution, model):
name : str
distribution : Distribution
model : Model
total_size : scalar Tensor (optional)
needed for upscaling logp
"""
self.name = name
self.data = {name: as_tensor(data, name, model, distribution)
for name, data in data.items()}

self.missing_values = [datum.missing_values for datum in self.data.values()
if datum.missing_values is not None]
self.logp_elemwiset = distribution.logp(**self.data)
logp_elemwiset = distribution.logp(**self.data)
if total_size is None:
coef = tt.as_tensor(1)
else:
assert logp_elemwiset.ndim >= 1, ('Variable with scaled density '
'needs to be at least 1 dimensional')
coef = tt.as_tensor(total_size) / logp_elemwiset.shape[0]
self.logp_elemwiset = logp_elemwiset * coef
self.model = model
self.distribution = distribution

@@ -896,17 +931,20 @@ def Potential(name, var, model=None):
class TransformedRV(TensorVariable):

def __init__(self, type=None, owner=None, index=None, name=None,
distribution=None, model=None, transform=None):
distribution=None, model=None, transform=None,
total_size=None):
"""
Parameters
----------
type : theano type (optional)
owner : theano owner (optional)
name : str
distribution : Distribution
model : Model"""
model : Model
total_size : scalar Tensor (optional)
needed for upscaling logp
"""
if type is None:
type = distribution.type
super(TransformedRV, self).__init__(type, owner, index, name)
@@ -916,7 +954,7 @@ def __init__(self, type=None, owner=None, index=None, name=None,

transformed_name = "{}_{}_".format(name, transform.name)
self.transformed = model.Var(
transformed_name, transform.apply(distribution))
transformed_name, transform.apply(distribution), total_size=total_size)

normalRV = transform.backward(self.transformed)

11 changes: 10 additions & 1 deletion pymc3/tests/test_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
import theano.tensor as tt
from theano import theano, tensor as tt
import pymc3 as pm
from pymc3.distributions import HalfCauchy, Normal
from pymc3 import Potential, Deterministic
@@ -118,3 +118,12 @@ def test_model_root(self):
self.assertTrue(model is model.root)
with pm.Model() as sub:
self.assertTrue(model is sub.root)

def test_density_scaling(self):
with pm.Model() as model1:
Normal('n', observed=[[1]], total_size=1)
p1 = theano.function([], model1.logpt)
with pm.Model() as model2:
Normal('n', observed=[[1]], total_size=2)
p2 = theano.function([], model2.logpt)
self.assertEqual(p1() * 2, p2())
Loading