Skip to content

Commit d13da82

Browse files
authored
Merge pull request #243 from elfi-dev/dev
Release 0.6.3
2 parents 53effb0 + 077308b commit d13da82

19 files changed

+250
-104
lines changed

CHANGELOG.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Changelog
22
=========
33

4+
0.6.3 (2017-09-28)
5+
------------------
6+
7+
- Further performance improvements for rerunning inference using stored data via caches
8+
- Added the general Gaussian noise example model (fixed covariance)
9+
- restrict NetworkX to versions < 2.0
10+
411
0.6.2 (2017-09-06)
512
------------------
613

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
**Version 0.6.2 released!** See the CHANGELOG and [notebooks](https://github.com/elfi-dev/notebooks).
1+
**Version 0.6.3 released!** See the CHANGELOG and [notebooks](https://github.com/elfi-dev/notebooks).
2+
3+
**NOTE:** For the time being NetworkX 2 is incompatible with ELFI.
24

35
ELFI - Engine for Likelihood-Free Inference
46
===========================================
@@ -7,6 +9,7 @@ ELFI - Engine for Likelihood-Free Inference
79
[![Code Health](https://landscape.io/github/elfi-dev/elfi/dev/landscape.svg?style=flat)](https://landscape.io/github/elfi-dev/elfi/dev)
810
[![Documentation Status](https://readthedocs.org/projects/elfi/badge/?version=latest)](http://elfi.readthedocs.io/en/latest/?badge=latest)
911
[![Gitter](https://badges.gitter.im/elfi-dev/elfi.svg)](https://gitter.im/elfi-dev/elfi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
12+
[![DOI](https://zenodo.org/badge/69855441.svg)](https://zenodo.org/badge/latestdoi/69855441)
1013

1114
<img src="https://cloud.githubusercontent.com/assets/1233418/20178983/6e22ee44-a75c-11e6-8345-5934b55b9dc6.png" width="15%" align="right"></img>
1215

@@ -79,6 +82,7 @@ Resolving these may sometimes go wrong:
7982
- On OS X with Anaconda virtual environment say `conda install python.app` and then use
8083
`pythonw` instead of `python`.
8184
- Note that ELFI requires Python 3.5 or greater so try `pip3 install elfi`.
85+
- Make sure your Python installation meets the versions listed in `requirements.txt`.
8286

8387

8488
Citation

docs/installation.rst

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ Resolving these may sometimes go wrong:
5353
* On OS X with Anaconda virtual environment say `conda install python.app` and then use `pythonw` instead of `python`.
5454
* Note that ELFI requires Python 3.5 or greater
5555
* In some environments ``pip`` refers to Python 2.x, and you have to use ``pip3`` to use the Python 3.x version
56+
* Make sure your Python installation meets the versions listed in requirements_.
57+
58+
.. _requirements: https://github.com/elfi-dev/elfi/blob/dev/requirements.txt
5659

5760
Developer installation from sources
5861
-----------------------------------

elfi/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@
2323
__email__ = 'elfi-support@hiit.fi'
2424

2525
# make sure __version_ is on the last non-empty line (read by setup.py)
26-
__version__ = '0.6.2'
26+
__version__ = '0.6.3'

elfi/client.py

+3
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,7 @@ def load_data(cls, compiled_net, context, batch_index):
326326
loaded_net = RandomStateLoader.load(context, loaded_net, batch_index)
327327
loaded_net = PoolLoader.load(context, loaded_net, batch_index)
328328

329+
# Add cache from the contect
330+
loaded_net.graph['_executor_cache'] = context.caches['executor']
331+
329332
return loaded_net

elfi/examples/bignk.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def BiGNK(a1, a2, b1, b2, g1, g2, k1, k2, rho, c=.8, n_obs=150, batch_size=1,
9898
term_product_misaligned = np.swapaxes(term_product, 1, 0)
9999
y_misaligned = np.add(a, term_product_misaligned)
100100
y = np.swapaxes(y_misaligned, 1, 0)
101-
# print(y.shape)
101+
102102
return y
103103

104104

elfi/examples/gauss.py

+107-24
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,78 @@
1-
"""An example implementation of a Gaussian noise model."""
1+
"""Example implementations of Gaussian noise models."""
22

33
from functools import partial
44

55
import numpy as np
66
import scipy.stats as ss
77

88
import elfi
9+
from elfi.examples.gnk import euclidean_multidim
910

1011

11-
def Gauss(mu, sigma, n_obs=50, batch_size=1, random_state=None):
12-
"""Sample the Gaussian distribution.
12+
def gauss(mu, sigma, n_obs=50, batch_size=1, random_state=None):
13+
"""Sample the 1-D Gaussian distribution.
1314
1415
Parameters
1516
----------
1617
mu : float, array_like
1718
sigma : float, array_like
1819
n_obs : int, optional
1920
batch_size : int, optional
20-
random_state : RandomState, optional
21+
random_state : np.random.RandomState, optional
22+
23+
Returns
24+
-------
25+
y_obs : array_like
26+
1-D observation.
27+
28+
"""
29+
# Handling batching.
30+
batches_mu = np.asanyarray(mu).reshape((-1, 1))
31+
batches_sigma = np.asanyarray(sigma).reshape((-1, 1))
32+
33+
# Sampling observations.
34+
y_obs = ss.norm.rvs(loc=batches_mu, scale=batches_sigma,
35+
size=(batch_size, n_obs), random_state=random_state)
36+
return y_obs
37+
38+
39+
def gauss_nd_mean(*mu, cov_matrix, n_obs=15, batch_size=1, random_state=None):
40+
"""Sample an n-D Gaussian distribution.
41+
42+
Parameters
43+
----------
44+
*mu : array_like
45+
Mean parameters.
46+
cov_matrix : array_like
47+
Covariance matrix.
48+
n_obs : int, optional
49+
batch_size : int, optional
50+
random_state : np.random.RandomState, optional
51+
52+
Returns
53+
-------
54+
y_obs : array_like
55+
n-D observation.
2156
2257
"""
23-
# Standardising the parameter's format.
24-
mu = np.asanyarray(mu).reshape((-1, 1))
25-
sigma = np.asanyarray(sigma).reshape((-1, 1))
26-
y = ss.norm.rvs(loc=mu, scale=sigma, size=(batch_size, n_obs), random_state=random_state)
27-
return y
58+
n_dim = len(mu)
59+
60+
# Handling batching.
61+
batches_mu = np.zeros(shape=(batch_size, n_dim))
62+
for idx_dim, param_mu in enumerate(mu):
63+
batches_mu[:, idx_dim] = param_mu
64+
65+
# Sampling the observations.
66+
y_obs = np.zeros(shape=(batch_size, n_obs, n_dim))
67+
for idx_batch in range(batch_size):
68+
y_batch = ss.multivariate_normal.rvs(mean=batches_mu[idx_batch],
69+
cov=cov_matrix,
70+
size=n_obs,
71+
random_state=random_state)
72+
if n_dim == 1:
73+
y_batch = y_batch[:, np.newaxis]
74+
y_obs[idx_batch, :, :] = y_batch
75+
return y_obs
2876

2977

3078
def ss_mean(x):
@@ -39,36 +87,71 @@ def ss_var(x):
3987
return ss
4088

4189

42-
def get_model(n_obs=50, true_params=None, seed_obs=None):
43-
"""Return a complete Gaussian noise model.
90+
def get_model(n_obs=50, true_params=None, seed_obs=None, nd_mean=False, cov_matrix=None):
91+
"""Return a Gaussian noise model.
4492
4593
Parameters
4694
----------
4795
n_obs : int, optional
48-
the number of observations
4996
true_params : list, optional
50-
true_params[0] corresponds to the mean,
51-
true_params[1] corresponds to the standard deviation
97+
Default parameter settings.
5298
seed_obs : int, optional
53-
seed for the observed data generation
99+
Seed for the observed data generation.
100+
nd_mean : bool, optional
101+
Option to use an n-D mean Gaussian noise model.
102+
cov_matrix : None, optional
103+
Covariance matrix, a requirement for the nd_mean model.
54104
55105
Returns
56106
-------
57107
m : elfi.ElfiModel
58108
59109
"""
110+
# Defining the default settings.
60111
if true_params is None:
61-
true_params = [10, 2]
112+
if nd_mean:
113+
true_params = [4, 4] # 2-D mean.
114+
else:
115+
true_params = [4, .4] # mean and standard deviation.
62116

63-
y_obs = Gauss(*true_params, n_obs=n_obs, random_state=np.random.RandomState(seed_obs))
64-
sim_fn = partial(Gauss, n_obs=n_obs)
117+
# Choosing the simulator for both observations and simulations.
118+
if nd_mean:
119+
sim_fn = partial(gauss_nd_mean, cov_matrix=cov_matrix, n_obs=n_obs)
120+
else:
121+
sim_fn = partial(gauss, n_obs=n_obs)
122+
123+
# Obtaining the observations.
124+
y_obs = sim_fn(*true_params, n_obs=n_obs, random_state=np.random.RandomState(seed_obs))
65125

66126
m = elfi.ElfiModel()
67-
elfi.Prior('uniform', -10, 50, model=m, name='mu')
68-
elfi.Prior('truncnorm', 0.01, 5, model=m, name='sigma')
69-
elfi.Simulator(sim_fn, m['mu'], m['sigma'], observed=y_obs, name='Gauss')
70-
elfi.Summary(ss_mean, m['Gauss'], name='S1')
71-
elfi.Summary(ss_var, m['Gauss'], name='S2')
72-
elfi.Distance('euclidean', m['S1'], m['S2'], name='d')
127+
128+
# Initialising the priors.
129+
eps_prior = 5 # The longest distance from the median of an initialised prior's distribution.
130+
priors = []
131+
if nd_mean:
132+
n_dim = len(true_params)
133+
for i in range(n_dim):
134+
name_prior = 'mu_{}'.format(i)
135+
prior_mu = elfi.Prior('uniform', true_params[i] - eps_prior,
136+
2 * eps_prior, model=m, name=name_prior)
137+
priors.append(prior_mu)
138+
else:
139+
priors.append(elfi.Prior('uniform', true_params[0] - eps_prior,
140+
2 * eps_prior, model=m, name='mu'))
141+
priors.append(elfi.Prior('truncnorm',
142+
np.amax([.01, true_params[1] - eps_prior]),
143+
2 * eps_prior, model=m, name='sigma'))
144+
elfi.Simulator(sim_fn, *priors, observed=y_obs, name='gauss')
145+
146+
# Initialising the summary statistics.
147+
sumstats = []
148+
sumstats.append(elfi.Summary(ss_mean, m['gauss'], name='ss_mean'))
149+
sumstats.append(elfi.Summary(ss_var, m['gauss'], name='ss_var'))
150+
151+
# Choosing the discrepancy metric.
152+
if nd_mean:
153+
elfi.Discrepancy(euclidean_multidim, *sumstats, name='d')
154+
else:
155+
elfi.Distance('euclidean', *sumstats, name='d')
73156

74157
return m

elfi/examples/gnk.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ def euclidean_multidim(*simulated, observed):
134134
array_like
135135
136136
"""
137-
pts_sim = np.column_stack(simulated)
138-
pts_obs = np.column_stack(observed)
139-
d_multidim = np.sum((pts_sim - pts_obs)**2., axis=1)
140-
d_squared = np.sum(d_multidim, axis=1)
141-
d = np.sqrt(d_squared)
137+
pts_sim = np.stack(simulated, axis=1)
138+
pts_obs = np.stack(observed, axis=1)
139+
d_ss_merged = np.sum((pts_sim - pts_obs)**2., axis=1)
140+
d_dim_merged = np.sum(d_ss_merged, axis=1)
141+
d = np.sqrt(d_dim_merged)
142142

143143
return d
144144

@@ -185,8 +185,8 @@ def ss_robust(y):
185185
ss_g = _get_ss_g(y)
186186
ss_k = _get_ss_k(y)
187187

188-
ss_robust = np.stack((ss_a, ss_b, ss_g, ss_k), axis=1)
189-
188+
# Combining the summary statistics by expanding the dimensionality.
189+
ss_robust = np.hstack((ss_a, ss_b, ss_g, ss_k))
190190
return ss_robust
191191

192192

@@ -209,7 +209,8 @@ def ss_octile(y):
209209
octiles = np.linspace(12.5, 87.5, 7)
210210
E1, E2, E3, E4, E5, E6, E7 = np.percentile(y, octiles, axis=1)
211211

212-
ss_octile = np.stack((E1, E2, E3, E4, E5, E6, E7), axis=1)
212+
# Combining the summary statistics by expanding the dimensionality.
213+
ss_octile = np.hstack((E1, E2, E3, E4, E5, E6, E7))
213214

214215
return ss_octile
215216

elfi/executor.py

+36-21
Original file line numberDiff line numberDiff line change
@@ -99,27 +99,42 @@ def get_execution_order(cls, G):
9999
nodes that require execution
100100
101101
"""
102-
nodes = set()
103-
order = nx_constant_topological_sort(G)
104-
dep_graph = nx.DiGraph(G.edges())
105-
106-
for node in order:
107-
attr = G.node[node]
108-
if attr.keys() >= {'operation', 'output'}:
109-
raise ValueError('Generative graph has both op and output present')
110-
111-
# Remove nodes from dependency graph whose outputs are present
112-
if 'output' in attr:
113-
dep_graph.remove_node(node)
114-
elif 'operation' not in attr:
115-
raise ValueError('Generative graph has no op or output present')
116-
117-
for output_node in G.graph['outputs']:
118-
if dep_graph.has_node(output_node):
119-
nodes.update([output_node])
120-
nodes.update(nx.ancestors(dep_graph, output_node))
121-
122-
return [n for n in order if n in nodes]
102+
# Get the cache dict if it exists
103+
cache = G.graph.get('_executor_cache', {})
104+
105+
output_nodes = G.graph['outputs']
106+
# Filter those output nodes who have an operation to run
107+
needed = tuple(sorted(node for node in output_nodes if 'operation' in G.node[node]))
108+
109+
if needed not in cache:
110+
# Resolve the nodes that need to be executed in the graph
111+
nodes_to_execute = set(needed)
112+
113+
if 'sort_order' not in cache:
114+
cache['sort_order'] = nx_constant_topological_sort(G)
115+
sort_order = cache['sort_order']
116+
117+
# Resolve the dependencies of needed
118+
dep_graph = nx.DiGraph(G.edges())
119+
for node in sort_order:
120+
attr = G.node[node]
121+
if attr.keys() >= {'operation', 'output'}:
122+
raise ValueError('Generative graph has both op and output present')
123+
124+
# Remove those nodes from the dependency graph whose outputs are present
125+
if 'output' in attr:
126+
dep_graph.remove_node(node)
127+
elif 'operation' not in attr:
128+
raise ValueError('Generative graph has no op or output present')
129+
130+
# Add the dependencies of the needed nodes
131+
for needed_node in needed:
132+
nodes_to_execute.update(nx.ancestors(dep_graph, needed_node))
133+
134+
# Turn in to a sorted list and cache
135+
cache[needed] = [n for n in sort_order if n in nodes_to_execute]
136+
137+
return cache[needed]
123138

124139
@staticmethod
125140
def _run(fn, node, G):

elfi/loader.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,8 @@ def load(cls, context, compiled_net, batch_index):
164164
elif isinstance(seed, (int, np.int32, np.uint32)):
165165
# TODO: In the future, we could use https://pypi.python.org/pypi/randomstate to enable
166166
# jumps?
167-
sub_seed, context.sub_seed_cache = get_sub_seed(seed,
168-
batch_index,
169-
cache=context.sub_seed_cache)
167+
cache = context.caches.get('sub_seed', None)
168+
sub_seed = get_sub_seed(seed, batch_index, cache=cache)
170169
random_state = np.random.RandomState(sub_seed)
171170
else:
172171
raise ValueError("Seed of type {} is not supported".format(seed))

elfi/methods/post_processing.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ def adjust_posterior(sample, model, summary_names, parameter_names=None, adjustm
242242
>>> import elfi
243243
>>> from elfi.examples import gauss
244244
>>> m = gauss.get_model()
245-
>>> res = elfi.Rejection(m['d'], output_names=['S1', 'S2']).sample(1000)
246-
>>> adj = adjust_posterior(res, m, ['S1', 'S2'], ['mu'], LinearAdjustment())
245+
>>> res = elfi.Rejection(m['d'], output_names=['ss_mean', 'ss_var']).sample(1000)
246+
>>> adj = adjust_posterior(res, m, ['ss_mean', 'ss_var'], ['mu'], LinearAdjustment())
247247
248248
"""
249249
adjustment = _get_adjustment(adjustment)

elfi/model/elfi_model.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def random_name(length=4, prefix=''):
120120
return prefix + str(uuid.uuid4().hex[0:length])
121121

122122

123-
# TODO: move to another file
123+
# TODO: move to another file?
124124
class ComputationContext:
125125
"""Container object for key components for consistent computation results.
126126
@@ -167,9 +167,11 @@ def __init__(self, batch_size=None, seed=None, pool=None):
167167

168168
self._batch_size = batch_size or 1
169169
self._seed = random_seed() if seed is None else seed
170-
self.sub_seed_cache = {}
171170
self._pool = pool
172171

172+
# Caches will not be used if they are not found from the caches dict
173+
self.caches = {'executor': {}, 'sub_seed': {}}
174+
173175
# Count the number of submissions from this context
174176
self.num_submissions = 0
175177

0 commit comments

Comments
 (0)