mixle.inference.bayesian_network module¶
Heterogeneous Bayesian network learning – a directed graph over mixed-type fields with parametric edges.
This deepens mixle.inference.structure (a single-parent forest with quantile-binned continuous parents)
into the real thing: a DAG where a field may have several parents, and continuous dependence is a
parametric conditional, not a binning. A continuous child is a conditional-linear-Gaussian node – child ~
N(w . [continuous parents, one-hot(discrete parents)] + b, sigma^2) – so a real driven by two reals, or by a
category and a real, is modeled exactly and cheaply (closed-form least squares). A discrete/count child with
all-discrete parents conditions on their joint configuration (marginal backoff for unseen configs); with a
continuous driver it becomes a GLM node (logistic / Poisson log-link / multinomial softmax), so category<->real
dependence is representable in BOTH orientations and BIC picks the cheaper one.
learn_bayesian_network grows each node’s parent set greedily by description-length gain, up to max_parents,
keeping the graph acyclic. The result scores, samples, and composes like any mixle distribution – the moat:
automatic discovery and fitting of a heterogeneous graphical model across arbitrary families, which no
mainstream tool does (Stan/PyMC: you write it; sklearn/pomegranate: independence; bnlearn/pgmpy: discrete-or-Gaussian).
- class HeterogeneousBayesianNetwork(factors)[source]
Bases:
FitProvenanceCarrierA DAG joint over a heterogeneous record:
log p(x) = sum_i log P(x_i | parents(i))over fitted factors.Carries the fitting receipt like every other public fitting result. This is what
optimize(data)andfit(data)return by default, and it was the one such result that could not say how it had been fitted at all –fit_provenance()andnumerical_repairs()did not exist on it (P03-F01), even though the sibling structure learner already carried both.- Parameters:
factors (Sequence[Any])
- edges()[source]
Return DAG edges as
(parent_field, child_field)pairs.
- describe(field_names=None)[source]
A real, structured explanation of what the fit found – every field’s per-factor report (
_MarginalFactor.describe()and friends: fitted regression coefficients, GLM weights, the conditional table, or the marginal summary), split intoedges(fields with >=1 parent) androots(independent fields). Passfield_names(e.g. the tuple position -> column name map) to label everything by name instead of integer index. Every value is read off this network’s own fitted parameters – nothing here is a placeholder or a re-derived approximation.
- log_density(x)[source]
Evaluate the joint log density of one record.
The record’s width is checked against the network’s: a short record used to raise a bare
IndexError: tuple index out of rangeand a long one was scored on its first fields with the extras ignored, so a five-tuple got a plausible number from a four-field net (P02-F07). A field the factors cannot score (a NaN in a Gaussian factor, say) makes the whole record impossible rather than NaN, which is what every other law in the package returns for evidence outside its support.
- seq_log_density(encoded)[source]
Evaluate joint log density for encoded records.
A record the factors cannot score is impossible here for the same reason it is impossible in
log_density(), and is reported the same way:-inf, notNaN. The scalar route already did this and the vectorized one did not, so the same NaN field read as an impossible record one way and poisoned the sum, mean or comparison of a whole batch the other (R02-F09). A condition disclosed differently by route is not disclosed.
- dist_to_encoder()[source]
Return the encoder for record batches consumed by
seq_log_density.- Return type:
- class MixtureOfBayesianNetworks(components, weights)[source]
Bases:
objectA latent mixture whose components each carry their own heterogeneous DAG (regression edges and all).
The fullest form of the moat: it discovers the clustering and each cluster’s cross-field graphical model, so the slope of one field on another (or the whole dependency graph) can differ between clusters – which a single network cannot represent.
log p(x) = logsumexp_k(log w_k + log p_k(x))overHeterogeneousBayesianNetworkcomponents. Fit bylearn_mixture_bayesian_network().- Parameters:
components (Sequence[HeterogeneousBayesianNetwork])
weights (Sequence[float])
- log_density(x)[source]
Evaluate the mixture joint log density of one record.
- seq_log_density(encoded)[source]
Evaluate mixture joint log density for encoded records.
- dist_to_encoder()[source]
Return the record encoder shared by all mixture components.
- Return type:
- responsibilities(data)[source]
Return posterior component probabilities for each record.
- sampler(seed=None)[source]
Return a sampler for the mixture of Bayesian networks.
- property n_components: int
Return the number of mixture components.
- learn_mixture_bayesian_network(data, n_components, *, restarts=3, max_iter=12, seed=0, max_parents=2, min_gain=0.0, max_its=30, em='hard')[source]
Fit a
MixtureOfBayesianNetworksby EM: discover clusters and each cluster’s DAG.em="hard"(default): each iteration re-learns a network per cluster on its assigned points and reassigns every record to its most-probable cluster, until assignments stabilize.em="soft": proper EM – every record contributes to EVERY cluster with its responsibility, each component’s structure search and factor fits are responsibility-weighted (learn_bayesian_network(weights=)), and convergence is on the observed-data log-likelihood; boundary points shape both clusters instead of whipsawing between them. Both run from the same restarts (k-means-seeded + random); best final log-likelihood wins. Starved clusters are re-seeded (hard) / responsibility-floored (soft).
- bayesian_network_bic(model, data)[source]
BIC (lower is better) for a fitted network or mixture:
-2 LL + n_params log n.
- select_mixture_components(data, k_values=(1, 2, 3, 4), *, em='hard', seed=0, **kwargs)[source]
Model selection over the number of clusters by BIC.
Fits
learn_bayesian_network()fork=1andlearn_mixture_bayesian_network()for each largerk, scores each bybayesian_network_bic(), and returns(best_model, report)wherereport = {"k": chosen, "bic": {k: score, ...}}. BIC’sn_params log npenalty is what stops a mixture of per-cluster DAGs from always preferring more clusters.
- learn_bayesian_network(data, *, max_parents=2, min_gain=0.0, max_its=30, weights=None, _routing=None)[source]
Discover a heterogeneous DAG for
dataand return the fitted network.Each field greedily gains up to
max_parentsparents by BIC-penalized conditional likelihood, keeping the graph acyclic. Continuous children become conditional-linear-Gaussian factors (regression on continuous + one-hot discrete parents); discrete/count children condition on the joint config of their discrete parents, or become GLM nodes when a driver is continuous. Withweights(soft-EM responsibilities), every factor fit and the BIC search itself are responsibility-weighted, with effective sample sizesum(weights).A field the conditional-linear-Gaussian machinery cannot read as a real number – a string column with more than 64 distinct values, or a numeric column carrying missing values – is modeled by the same automatic detector
mixle.utils.automatic.get_estimator()uses (a categorical/ignored leaf, or anOptional-wrapped one) and takes no part in the edge search; every other field still gets the full search. Such a column used to be handed a Gaussian template and crash inside the Gaussian encoder. See_column_routing(), which makes that decision;_routingis a private hook letting a caller that fits many components on SLICES of one dataset decide it once on the whole dataset instead.