Families/ adding base version of exponential family#67
Conversation
779438f to
12516ff
Compare
f67f3fd to
2d470c9
Compare
2d470c9 to
189afea
Compare
1605771 to
1d58077
Compare
83f5584 to
2500678
Compare
ec832d5 to
d5a2be1
Compare
d5a2be1 to
804cfa4
Compare
… for exponential family
| return ContinuousExponentialClassFamily( | ||
| log_partition=conjugate_log_partition, | ||
| sufficient_statistics=conjugate_sufficient, | ||
| normalization_constant=lambda _: 1, | ||
| support=self._parameter_space, | ||
| sufficient_statistics_values=self._parameter_space, | ||
| parameter_space=PredicateSupport(predicate=conjugate_sufficient_accepts), | ||
| name=self.name, | ||
| distr_type=self._distr_type, | ||
| distr_parametrizations=self.parametrization_names, | ||
| support_by_parametrization=self.support_resolver, | ||
| ) |
There was a problem hiding this comment.
_support is changed for the conjugate/transformed family, but the family still reuses self.support_resolver.
This makes the density use the new support while distribution.support can remain None or point to the original support. Expose the same transformed support through support_by_parametrization as well, e.g. for transform() return a resolver for PredicateSupport(new_support) and for the conjugate prior return the natural-parameter support.
| return ContinuousExponentialClassFamily( | ||
| log_partition=self._log_partition, | ||
| sufficient_statistics=new_sufficient, | ||
| normalization_constant=new_normalization, | ||
| support=PredicateSupport(predicate=new_support), | ||
| parameter_space=self._parameter_space, | ||
| sufficient_statistics_values=self._sufficient_statistics_values, | ||
| name=f"Transformed{self._name}", | ||
| distr_type=self._distr_type, | ||
| distr_parametrizations=self.parametrization_names, | ||
| support_by_parametrization=self.support_resolver, | ||
| ) |
There was a problem hiding this comment.
Same issue as in comment above
| def posterior_hyperparameters( | ||
| self, | ||
| parametrization: ExponentialConjugateHyperparameters, | ||
| sample: list[Any] | Any, | ||
| ) -> ExponentialConjugateHyperparameters: | ||
| """ | ||
| Update the conjugate prior hyperparameters given observed data. | ||
|
|
||
| For prior hyperparameters ``(nu_0, n_0)`` and observations ``x_i``, | ||
| the posterior hyperparameters are | ||
|
|
||
| ``nu = nu_0 + sum_i T(x_i)`` and ``n = n_0 + N``. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| parametrization : ExponentialConjugateHyperparameters | ||
| Current conjugate hyperparameters. | ||
| sample : list[Any] or Any | ||
| Observations used for the update. A non-string iterable is treated | ||
| as a sample; any other value is treated as one observation. | ||
|
|
||
| Returns | ||
| ------- | ||
| ExponentialConjugateHyperparameters | ||
| Updated hyperparameters after incorporating ``sample``. | ||
| """ | ||
| if hasattr(sample, "__iter__") and not isinstance(sample, str): | ||
| posterior_effective_suff_stat_value = np.array( | ||
| parametrization.effective_suff_stat_value | ||
| ) + np.sum( | ||
| [self._sufficient(x) for x in sample], | ||
| axis=0, | ||
| ) | ||
| posterior_effective_sample_size = parametrization.effective_sample_size + len(sample) | ||
| else: | ||
| posterior_effective_suff_stat_value = np.array( | ||
| parametrization.effective_suff_stat_value, | ||
| ) + np.asarray(self._sufficient(sample)) # type: ignore[arg-type] | ||
| posterior_effective_sample_size = parametrization.effective_sample_size + 1 | ||
|
|
||
| return ExponentialConjugateHyperparameters( | ||
| effective_suff_stat_value=posterior_effective_suff_stat_value, | ||
| effective_sample_size=posterior_effective_sample_size, | ||
| ) |
There was a problem hiding this comment.
AI finding
hasattr(sample, "__iter__") misclassifies zero-dimensional NumPy arrays as samples.
posterior_hyperparameters(..., sample=np.asarray(0.5)) enters the iterable branch and raises TypeError: iteration over a 0-d array. Normalize the input explicitly, e.g. distinguish scalar/0-D observations from batches with np.ndim(sample).
| def conjugate_log_partition( | ||
| parametrization: ExponentialConjugateHyperparameters, | ||
| ) -> Number: | ||
| conjugate_value = self.conjugate_prior_family._log_partition( | ||
| parametrization.transform_to_base_parametrization().theta | ||
| ) | ||
| return cast(Number, np.exp(conjugate_value).item()) | ||
|
|
||
| def posterior_density(parametrization: Parametrization, x: NumericArray) -> Number: | ||
| parametrization = cast(ExponentialConjugateHyperparameters, parametrization) | ||
| if not self._support.contains(np.asarray(x)): | ||
| return cast(np.float32, 0.0) | ||
| return cast( | ||
| np.float32, | ||
| self._normalization(x) | ||
| * conjugate_log_partition(parametrization) | ||
| / conjugate_log_partition( | ||
| self.posterior_hyperparameters(parametrization=parametrization, sample=[x]) |
There was a problem hiding this comment.
AI finding
posterior_density recomputes the conjugate-prior family and its normalizing integral on every density evaluation.
conjugate_log_partition(...) accesses self.conjugate_prior_family, and each call eventually runs nquad; posterior_density calls it twice per x. Construct/cache the conjugate family once in posterior_predictive and avoid recomputing invariant normalizers for every point.
| family_characteristics: CharacteristicsMap = { | ||
| CharacteristicName.PDF: self.density, | ||
| CharacteristicName.MEAN: self._mean, | ||
| CharacteristicName.VAR: self._var, | ||
| } | ||
| distr_characteristics = dict(distr_characteristics or {}) | ||
| merged_characteristics = dict(family_characteristics) | ||
| merged_characteristics.update(distr_characteristics) |
There was a problem hiding this comment.
The constructor now preserves user characteristics, but the merge is still shallow and does not keep the generic exponential-family methods under separate labels.
If a user provides MEAN/VAR/PDF, the generic fallback is replaced entirely. The intended contract is to "expand under other labels". Merge by label and add a test for the collision case, not only for adding CDF.
… for exponential family
8b99bff to
16115cb
Compare
No description provided.