Counterfactual class-conditional VAE single-sample discriminator
Source:R/singlesample-cvae-scorer.R
singlesample-cvae.RdSingle-sample within-cohort discriminator (family N) implementing a counterfactual CLASS-conditional variational autoencoder used as a generative classifier. A class-conditional VAE (encoder \(q(z|x,y)\) + decoder \(p(x|z,y)\), both plain BatchNorm-free MLPs conditioned on a one-hot of the class label \(y\)) is trained on the TRAINING set by the reparameterized ELBO via reticulate-python torch, then FROZEN; a new specimen is scored by the COUNTERFACTUAL evidence log-likelihood-RATIO $$s(x) = \mathrm{ELBO}(x, y=\mathrm{case}) - \mathrm{ELBO}(x, y=\mathrm{control}),$$ computed in PURE base-R with the latent MEAN \(z = \mu\) (DETERMINISTIC, no sampling, no RNG at score). Larger score = more case-like.
PRESPEC DEPARTURE (kit-conditional -> class-conditional). The present
fit_kit_conditional_vae (R/singlesample-02-learned-kit-aware-vae.R)
conditions a VAE on KIT metadata – an external cross-sample covariate that
VIOLATES single-sample deployability (the kit of a held-out specimen need not be
known/seen at score). The single-sample cvae instead conditions on the
CLASS label \(y\) and is used as a COUNTERFACTUAL generative classifier: it
models \(p(x|y)\) for each class and scores a query by the counterfactual
evidence ratio \(\mathrm{ELBO}(x,\mathrm{case}) -
\mathrm{ELBO}(x,\mathrm{control})\). This needs NO kit/cohort input at score (only
the query \(x\); the class conditioning is internal – both classes are
evaluated and differenced), so it is single-sample-faithful. This reinterpretation
is flagged for the gate.
Torch is confined to FIT. The class-conditional VAE is trained with torch
on the rCLR'd, universe-aligned training matrix. After training, the encoder,
mu-head, logvar-head, decoder, and xhat-head weight matrices and biases, plus the
scalar decoder variance \(\sigma^2\), are EXPORTED to R as plain numeric
matrices/vectors; the python module is DISCARDED. The fitted model therefore holds
NO external pointer and NO python dependency at score time: the encoder forward,
the decoder forward, and the per-class ELBO are all pure base-R. This makes the
default model_digest a stable deterministic snapshot and lets the package
suite run the scorer on any host without the venv.
Fit/score consistency despite float32. Torch trains in float32; an R
float64 forward on the exported (float32-valued) weights differs from the torch
forward by \(\sim 10^{-6}\). The score uses ONLY the exported weights through the
SAME pure-R forwards (.cvae_encode / .cvae_decode) at both fit-time
verification and score, so there is no second code path to drift (the
deepmaha / proto-net / ssl-vicreg fit/score-consistency lesson).
The forwards. The encoder is
\(\mathrm{concat}[x, e_y](p+2) \to [\mathrm{Linear} \to \phi]_{1..H_e}
\to (\mathrm{Linear}\to\mu,\ \mathrm{Linear}\to\log\sigma^2_z)\); the activation
("relu" default, or "tanh") follows every hidden Linear, and the two
output heads (mu, logvar) are bare Linear (no activation). The decoder is
\(\mathrm{concat}[z, e_y](L+2) \to [\mathrm{Linear} \to \phi]_{1..H_d}
\to \mathrm{Linear} \to \hat{x}(p)\); activation follows every hidden Linear, the
xhat-head is bare Linear. \(e_y = \mathrm{onehot}(y)\) (length 2).
The counterfactual score (pure R, deterministic). For a query rCLR vector \(x\), for EACH class \(c \in \{0,1\}\): $$(\mu_c, \log\sigma^2_{z,c}) = \mathrm{encode}(x, e_c);\quad z_c = \mu_c\ \mathrm{(latent\ mean)};\quad \hat{x}_c = \mathrm{decode}(\mu_c, e_c);$$ $$\log N_c = -\tfrac12\big(p\,\log(2\pi\sigma^2) + \tfrac{1}{\sigma^2}\sum_j (x_j - \hat{x}_{c,j})^2\big);$$ $$\mathrm{KL}_c = \tfrac12 \sum_l \big(\mu_{c,l}^2 + e^{\log\sigma^2_{z,c,l}} - \log\sigma^2_{z,c,l} - 1\big);$$ $$\mathrm{ELBO}_c = \log N_c - \beta\,\mathrm{KL}_c;\qquad s(x) = \mathrm{ELBO}_{\mathrm{case}} - \mathrm{ELBO}_{\mathrm{ctrl}}.$$ Using \(z = \mu\) (NOT a sample) makes the score DETERMINISTIC and RNG-free, and it depends only on \(x\) and the frozen model -> single-sample. Per-row -> single-row == batch EXACTLY 0. \(\beta = \mathrm{kl\_beta}\) (default 1.0).
Single-sample transform. Each specimen is mapped to the self-contained
per-sample robust CLR over its OWN strictly-positive support (geometric-mean
centring on v > 0) in the FROZEN feature universe; this is exactly invariant
to per-specimen scaling and uses no cross-row statistic. Universe features absent
from a specimen carry the neutral rCLR value 0. The SAME rCLR transform is
applied to the frozen training rows at fit and to every query at score, so a
specimen's score depends only on that specimen and the frozen model – it passes
singlesample_assert_row_equivariant.
Degenerate-neutral. A query returns the neutral score 0 if fewer
than hp$min_features universe features overlap the query columns (a
column-overlap floor, batch-independent), or it has empty positive support over the
frozen universe tested on the (pre-rCLR) aligned ORIGINAL abundances
(!any(X_use[i, ] > 0)). A FLAT all-equal-positive composition maps to the
rCLR origin but is a VALID specimen and is scored normally (NOT floored) – the
empty-support test is on the ORIGINAL abundances, NOT on all(z == 0).
References
Kingma DP, Welling M. (2014) Auto-Encoding Variational Bayes. International Conference on Learning Representations (ICLR). arXiv:1312.6114.
Sohn K, Lee H, Yan X. (2015) Learning Structured Output Representation using Deep Conditional Generative Models. Advances in Neural Information Processing Systems (NeurIPS) 28.