Skip to content

Commit b4663c8

Browse files
authored
Merge pull request #396 from elfi-dev/dev
Release v0.8.3
2 parents 1e43884 + 6d13c7a commit b4663c8

20 files changed

+2305
-604
lines changed

CHANGELOG.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
3-
3+
4+
0.8.3 (2021-02-17)
5+
------------------
6+
- Add a new inference method: BOLFIRE
7+
- Fix the hessian approximation, visualizations and the line search algorithm in ROMC
8+
- Add tests for all ROMC parts
9+
410
0.8.2 (2021-10-13)
511
------------------
612
- Relax tightly pinned dependency on a version of dask[distributed]

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
**Version 0.8.2 released!** See the [CHANGELOG](CHANGELOG.rst) and [notebooks](https://github.com/elfi-dev/notebooks).
1+
**Version 0.8.3 released!** See the [CHANGELOG](CHANGELOG.rst) and [notebooks](https://github.com/elfi-dev/notebooks).
22

33
<img src="https://raw.githubusercontent.com/elfi-dev/elfi/dev/docs/logos/elfi_logo_text_nobg.png" width="200" />
44

@@ -24,7 +24,8 @@ Currently implemented LFI methods:
2424
- SMC-ABC sampler with [adaptive threshold selection](https://projecteuclid.org/journals/bayesian-analysis/advance-publication/Adaptive-Approximate-Bayesian-Computation-Tolerance-Selection/10.1214/20-BA1211.full)
2525
- SMC-ABC sampler with [adaptive distance](https://projecteuclid.org/euclid.ba/1460641065)
2626
- [Bayesian Optimization for Likelihood-Free Inference (BOLFI)](http://jmlr.csail.mit.edu/papers/v17/15-017.html)
27-
- [Robust Optimisation Monte Carlo](https://arxiv.org/abs/1904.00670)
27+
- [Robust Optimisation Monte Carlo (ROMC)](https://arxiv.org/abs/1904.00670)
28+
- [Bayesian Optimization for Likelihood-Free Inference by Ratio Estimation (BOLFIRE)](https://helda.helsinki.fi/handle/10138/305039)
2829

2930
Other notable included algorithms and methods:
3031
- Bayesian Optimization

docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __getattr__(cls, name):
3333
'dask.delayed', 'scipy.linalg', 'scipy.optimize', 'scipy.stats', 'scipy.spatial',
3434
'scipy.sparse', 'scipy.special', 'matplotlib.pyplot', 'numpy.random', 'networkx',
3535
'ipyparallel', 'numpy.lib', 'numpy.lib.format', 'sklearn.linear_model',
36-
'sklearn.pipeline', 'sklearn.preprocessing'
36+
'sklearn.pipeline', 'sklearn.preprocessing', 'numdifftools'
3737
]
3838
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
3939

elfi/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from elfi.methods.diagnostics import TwoStageSelection
1515
from elfi.methods.model_selection import *
1616
from elfi.methods.inference.bolfi import *
17+
from elfi.methods.inference.bolfire import *
1718
from elfi.methods.inference.romc import *
1819
from elfi.methods.inference.samplers import *
1920
from elfi.methods.post_processing import adjust_posterior
@@ -30,4 +31,4 @@
3031
__email__ = 'elfi-support@hiit.fi'
3132

3233
# make sure __version_ is on the last non-empty line (read by setup.py)
33-
__version__ = '0.8.2'
34+
__version__ = '0.8.3'

elfi/classifiers/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# noqa: D104

elfi/classifiers/classifier.py

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Implementations for ratio estimation classifiers."""
2+
3+
import abc
4+
5+
import numpy as np
6+
from sklearn.linear_model import LogisticRegression as LogReg
7+
from sklearn.preprocessing import StandardScaler
8+
9+
10+
class Classifier(abc.ABC):
11+
"""An abstract base class for a ratio estimation classifier."""
12+
13+
@abc.abstractmethod
14+
def __init__(self):
15+
"""Initialize a classifier."""
16+
raise NotImplementedError
17+
18+
@abc.abstractmethod
19+
def fit(self, X, y):
20+
"""Fit a classifier.
21+
22+
Parameters
23+
----------
24+
X: np.ndarray (n_samples, n_features)
25+
Feature vectors of data.
26+
y: np.ndarray (n_samples, )
27+
Target values, must be binary.
28+
29+
"""
30+
raise NotImplementedError
31+
32+
@abc.abstractmethod
33+
def predict_log_likelihood_ratio(self, X):
34+
"""Predict a log-likelihood ratio.
35+
36+
Parameters
37+
----------
38+
X: np.ndarray (n_samples, n_features)
39+
Feature vectors of data.
40+
41+
Returns
42+
-------
43+
np.ndarray
44+
45+
"""
46+
raise NotImplementedError
47+
48+
def predict_likelihood_ratio(self, X):
49+
"""Predict a likelihood ratio.
50+
51+
Parameters
52+
----------
53+
X: np.ndarray (n_samples, n_features)
54+
Feature vectors of data.
55+
56+
Returns
57+
-------
58+
np.ndarray
59+
60+
"""
61+
return np.exp(self.predict_log_likelihood_ratio(X))
62+
63+
@property
64+
@abc.abstractmethod
65+
def attributes(self):
66+
"""Return attributes dictionary."""
67+
raise NotImplementedError
68+
69+
70+
class LogisticRegression(Classifier):
71+
"""A logistic regression classifier for ratio estimation."""
72+
73+
def __init__(self, config=None, class_min=0):
74+
"""Initialize a logistic regression classifier."""
75+
self.config = self._resolve_config(config)
76+
self.class_min = self._resolve_class_min(class_min)
77+
self.model = LogReg(**self.config)
78+
self.scaler = StandardScaler()
79+
80+
def fit(self, X, y):
81+
"""Fit a logistic regression classifier."""
82+
Xs = self.scaler.fit_transform(X)
83+
self.model.fit(Xs, y)
84+
85+
def predict_log_likelihood_ratio(self, X):
86+
"""Predict a log-likelihood ratio."""
87+
Xs = self.scaler.transform(X)
88+
class_probs = np.maximum(self.model.predict_proba(Xs)[:, 1], self.class_min)
89+
return np.log(class_probs / (1 - class_probs))
90+
91+
@property
92+
def attributes(self):
93+
"""Return an attributes dictionary."""
94+
return {
95+
'parameters': {
96+
'coef_': self.model.coef_.tolist(),
97+
'intercept_': self.model.intercept_.tolist(),
98+
'n_iter': self.model.n_iter_.tolist()
99+
}
100+
}
101+
102+
def _default_config(self):
103+
"""Return a default config."""
104+
return {
105+
'penalty': 'l1',
106+
'solver': 'liblinear'
107+
}
108+
109+
def _resolve_config(self, config):
110+
"""Resolve a config for logistic regression classifier."""
111+
if not isinstance(config, dict):
112+
config = self._default_config()
113+
return config
114+
115+
def _resolve_class_min(self, class_min):
116+
"""Resolve a class min parameter that prevents negative inf values."""
117+
if isinstance(class_min, int) or isinstance(class_min, float):
118+
return class_min
119+
raise TypeError('class_min has to be either non-negative int or float')

elfi/examples/arch.py

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
"""Example implementation of the ARCH(1) model."""
2+
3+
import logging
4+
from itertools import combinations
5+
6+
import numpy as np
7+
8+
import elfi
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def get_model(n_obs=100, true_params=None, seed_obs=None, n_lags=5):
14+
"""Return a complete ARCH(1) model.
15+
16+
Parameters
17+
----------
18+
n_obs: int
19+
Observation length of the ARCH(1) process.
20+
true_params: list, optinal
21+
Parameters with which the observed data are generated.
22+
seed_obs: int, optional
23+
Seed for the observed data generation.
24+
n_lags: int, optional
25+
Number of lags in summary statistics.
26+
27+
Returns
28+
-------
29+
elfi.ElfiModel
30+
31+
"""
32+
if true_params is None:
33+
true_params = [0.3, 0.7]
34+
logger.info(f'true_params were not given. Now using [t1, t2] = {true_params}.')
35+
36+
# elfi model
37+
m = elfi.ElfiModel()
38+
39+
# priors
40+
t1 = elfi.Prior('uniform', -1, 2, model=m)
41+
t2 = elfi.Prior('uniform', 0, 1, model=m)
42+
priors = [t1, t2]
43+
44+
# observations
45+
y_obs = arch(*true_params, n_obs=n_obs, random_state=np.random.RandomState(seed_obs))
46+
47+
# simulator
48+
Y = elfi.Simulator(arch, *priors, observed=y_obs)
49+
50+
# summary statistics
51+
ss = []
52+
ss.append(elfi.Summary(sample_mean, Y, name='MU', model=m))
53+
ss.append(elfi.Summary(sample_variance, Y, name='VAR', model=m))
54+
for i in range(1, n_lags + 1):
55+
ss.append(elfi.Summary(autocorr, Y, i, name=f'AC_{i}', model=m))
56+
for i, j in combinations(range(1, n_lags + 1), 2):
57+
ss.append(elfi.Summary(pairwise_autocorr, Y, i, j, name=f'PW_{i}_{j}', model=m))
58+
59+
# distance
60+
elfi.Distance('euclidean', *ss, name='d', model=m)
61+
62+
return m
63+
64+
65+
def arch(t1, t2, n_obs=100, batch_size=1, random_state=None):
66+
"""Generate a sequence of samples from the ARCH(1) model.
67+
68+
Parameters
69+
----------
70+
t1: float
71+
Mean process parameter in the ARCH(1) process.
72+
t2: float
73+
Variance process parameter in the ARCH(1) process.
74+
n_obs: int, optional
75+
Observation length of the ARCH(1) process.
76+
batch_size: int, optional
77+
Number of simulations.
78+
random_state: np.random.RandomState, optional
79+
80+
Returns
81+
-------
82+
np.ndarray
83+
84+
"""
85+
random_state = random_state or np.random
86+
y = np.zeros((batch_size, n_obs + 1))
87+
e = E(t2, n_obs, batch_size, random_state)
88+
for i in range(1, n_obs + 1):
89+
y[:, i] = t1 * y[:, i - 1] + e[:, i]
90+
return y[:, 1:]
91+
92+
93+
def E(t2, n_obs=100, batch_size=1, random_state=None):
94+
"""Variance process function in the ARCH(1) model.
95+
96+
Parameters
97+
----------
98+
t2: float
99+
Variance process parameter in the ARCH(1) process.
100+
n_obs: int, optional
101+
Observation length of the ARCH(1) process.
102+
batch_size: int, optional
103+
Number of simulations.
104+
random_state: np.random.RandomState
105+
106+
Returns
107+
-------
108+
np.ndarray
109+
110+
"""
111+
random_state = random_state or np.random
112+
xi = random_state.normal(size=(batch_size, n_obs + 1))
113+
e = np.zeros((batch_size, n_obs + 1))
114+
e[:, 0] = random_state.normal(size=batch_size)
115+
for i in range(1, n_obs + 1):
116+
e[:, i] = xi[:, i] * np.sqrt(0.2 + t2 * np.power(e[:, i - 1], 2))
117+
return e
118+
119+
120+
def sample_mean(x):
121+
"""Calculate the sample mean.
122+
123+
Parameters
124+
----------
125+
x: np.ndarray
126+
Simulated/observed data.
127+
128+
Returns
129+
-------
130+
np.ndarray
131+
132+
"""
133+
return np.mean(x, axis=1)
134+
135+
136+
def sample_variance(x):
137+
"""Calculate the sample variance.
138+
139+
Parameters
140+
----------
141+
x: np.ndarray
142+
Simulated/observed data.
143+
144+
Returns
145+
-------
146+
np.ndarray
147+
148+
"""
149+
return np.var(x, axis=1, ddof=1)
150+
151+
152+
def autocorr(x, lag=1):
153+
"""Calculate the autocorrelation.
154+
155+
Parameters
156+
----------
157+
x: np.ndarray
158+
Simulated/observed data.
159+
lag: int, optional
160+
Lag in autocorrelation.
161+
162+
Returns
163+
-------
164+
np.ndarray
165+
166+
"""
167+
n = x.shape[1]
168+
x_mu = np.mean(x, axis=1)
169+
x_std = np.std(x, axis=1, ddof=1)
170+
sc_x = ((x.T - x_mu) / x_std).T
171+
C = np.sum(sc_x[:, lag:] * sc_x[:, :-lag], axis=1) / (n - lag)
172+
return C
173+
174+
175+
def pairwise_autocorr(x, lag_i=1, lag_j=1):
176+
"""Calculate the pairwise autocorrelation.
177+
178+
Parameters
179+
x: np.ndarray
180+
Simulated/observed data.
181+
lag_i: int, optional
182+
Lag in autocorrelation.
183+
lag_j: int, optinal
184+
Lag in autocorrelation.
185+
186+
Returns
187+
-------
188+
np.ndarray
189+
190+
"""
191+
ac_i = autocorr(x, lag_i)
192+
ac_j = autocorr(x, lag_j)
193+
return ac_i * ac_j

0 commit comments

Comments
 (0)