def test_gradient_logpdf(self, ma2): prior = ModelPrior(ma2) rv = prior.rvs(size=10) grads = prior.gradient_logpdf(rv) assert grads.shape == rv.shape assert np.allclose(grads, 0)
class SMC(Sampler): """Sequential Monte Carlo ABC sampler.""" def __init__(self, model, discrepancy_name=None, output_names=None, **kwargs): """Initialize the SMC-ABC sampler. Parameters ---------- model : ElfiModel or NodeReference discrepancy_name : str, NodeReference, optional Only needed if model is an ElfiModel output_names : list, optional Additional outputs from the model to be included in the inference result, e.g. corresponding summaries to the acquired samples kwargs: See InferenceMethod """ model, discrepancy_name = self._resolve_model(model, discrepancy_name) super(SMC, self).__init__(model, output_names, **kwargs) self._prior = ModelPrior(self.model) self.discrepancy_name = discrepancy_name self.state['round'] = 0 self._populations = [] self._rejection = None self._round_random_state = None def set_objective(self, n_samples, thresholds): """Set the objective of the inference.""" self.objective.update( dict(n_samples=n_samples, n_batches=self.max_parallel_batches, round=len(thresholds) - 1, thresholds=thresholds)) self._init_new_round() def extract_result(self): """Extract the result from the current state. Returns ------- SmcSample """ # Extract information from the population pop = self._extract_population() return SmcSample(outputs=pop.outputs, populations=self._populations.copy() + [pop], weights=pop.weights, threshold=pop.threshold, **self._extract_result_kwargs()) def update(self, batch, batch_index): """Update the inference state with a new batch. Parameters ---------- batch : dict dict with `self.outputs` as keys and the corresponding outputs for the batch as values batch_index : int """ super(SMC, self).update(batch, batch_index) self._rejection.update(batch, batch_index) if self._rejection.finished: self.batches.cancel_pending() if self.state['round'] < self.objective['round']: self._populations.append(self._extract_population()) self.state['round'] += 1 self._init_new_round() self._update_objective() def prepare_new_batch(self, batch_index): """Prepare values for a new batch. Parameters ---------- batch_index : int next batch_index to be submitted Returns ------- batch : dict or None Keys should match to node names in the model. These values will override any default values or operations in those nodes. """ if self.state['round'] == 0: # Use the actual prior return # Sample from the proposal, condition on actual prior params = GMDistribution.rvs(*self._gm_params, size=self.batch_size, prior_logpdf=self._prior.logpdf, random_state=self._round_random_state) batch = arr2d_to_batch(params, self.parameter_names) return batch def _init_new_round(self): round = self.state['round'] dashes = '-' * 16 logger.info('%s Starting round %d %s' % (dashes, round, dashes)) # Get a subseed for this round for ensuring consistent results for the round seed = self.seed if round == 0 else get_sub_seed(self.seed, round) self._round_random_state = np.random.RandomState(seed) self._rejection = Rejection( self.model, discrepancy_name=self.discrepancy_name, output_names=self.output_names, batch_size=self.batch_size, seed=seed, max_parallel_batches=self.max_parallel_batches) self._rejection.set_objective( self.objective['n_samples'], threshold=self.current_population_threshold) def _extract_population(self): sample = self._rejection.extract_result() # Append the sample object sample.method_name = "Rejection within SMC-ABC" w, cov = self._compute_weights_and_cov(sample) sample.weights = w sample.meta['cov'] = cov return sample def _compute_weights_and_cov(self, pop): params = np.column_stack( tuple([pop.outputs[p] for p in self.parameter_names])) if self._populations: q_logpdf = GMDistribution.logpdf(params, *self._gm_params) p_logpdf = self._prior.logpdf(params) w = np.exp(p_logpdf - q_logpdf) else: w = np.ones(pop.n_samples) if np.count_nonzero(w) == 0: raise RuntimeError( "All sample weights are zero. If you are using a prior " "with a bounded support, this may be caused by specifying " "a too small sample size.") # New covariance cov = 2 * np.diag(weighted_var(params, w)) if not np.all(np.isfinite(cov)): logger.warning( "Could not estimate the sample covariance. This is often " "caused by majority of the sample weights becoming zero." "Falling back to using unit covariance.") cov = np.diag(np.ones(params.shape[1])) return w, cov def _update_objective(self): """Update the objective n_batches.""" n_batches = sum([pop.n_batches for pop in self._populations]) self.objective[ 'n_batches'] = n_batches + self._rejection.objective['n_batches'] @property def _gm_params(self): sample = self._populations[-1] params = sample.samples_array return params, sample.cov, sample.weights @property def current_population_threshold(self): """Return the threshold for current population.""" return self.objective['thresholds'][self.state['round']]
def test_pdf(self, ma2): prior = ModelPrior(ma2) rv = prior.rvs(size=10) assert np.allclose(prior.pdf(rv), np.exp(prior.logpdf(rv)))