# Model Card — FlowForge / FlowFuse

**Author: 晨星** · v0.1.0 · MIT · last updated 2026-10-11

---

## Model details

| Field | Value |
|---|---|
| Name | FlowForge (`FlowFuse` = flagship) |
| Type | Normalizing-flow density estimator (exact likelihood generative model) |
| Components | RealNVP (6 affine-coupling blocks) · MAF-stack (2 masked-autoregressive blocks) · log-linear mixture fusion |
| Parameters | 3,424 (flagship: RealNVP 2,130 + MAF 1,292 + 2 mixture weights) |
| Implementation | pure NumPy + scikit-learn, CPU-only, no pretrained weights |
| Training | exact maximum likelihood, Adam, cosine LR, early stopping on validation |

## Intended use

**In scope**
- Density estimation / likelihood evaluation on low-to-moderate dimensional
  continuous tabular data (d ≈ 3–20).
- Anomaly scoring via log-density thresholds.
- Sampling from a learned continuous density.
- Research baseline for exact-likelihood generative modelling.

**Out of scope (not validated)**
- High-dimensional image / text generation.
- Discrete or mixed-type data (no dequantisation is applied).
- Safety-critical decision making.

## Training data

Synthetic benchmarks generated by this repository (no external data, no PII):

| Suite | Datasets | Purpose |
|---|---|---|
| Discriminative (d ≥ 3) | `warp6`, `warp8`, `banana6`, `banana8`, `torus3` | primary evaluation — non-Gaussian, a GMM is **not** near-optimal |
| Diagnostic (2-D) | `checkerboard`, `moons`, `rings`, `gmm8`, `spirals` | visualisation / sanity only, **not** used for the gate |

Each split is drawn independently from one fixed distribution; the standardiser
is fitted on the training split only.

## Performance (3 seeds, mean ± std, test NLL in nats, lower is better)

Measured on the delivery machine (CPython 3.13.14, NumPy 2.5.3, CPU, 73.4 s).
"Strongest baseline" = lowest-NLL baseline on that dataset.

| Dataset | Strongest baseline | FlowFuse | Δ nats | Δ % | Verdict |
|---|---|---|---|---|---|
| warp6 | Gauss-full 7.9924 | **7.7696 ± 0.0219** | +0.2228 | +2.79% | WIN |
| warp8 | Gauss-full 10.2360 | **10.1569 ± 0.0121** | +0.0790 | +0.77% | WIN |
| banana6 | GMM 7.9497 | **7.4582 ± 0.0633** | +0.4915 | +6.18% | WIN |
| banana8 | GMM 10.7880 | **10.1062 ± 0.0327** | +0.6819 | +6.32% | WIN |
| torus3 | GMM 3.3499 | 3.6812 ± 0.1064 | −0.3313 | −9.89% | **LOSE** |

**Gate: 4/5.** Significance rule: win iff
`Δmean > 0.5 · (σ_baseline + σ_flagship)`.

**Ablation — fusion on vs. best single member** (positive ⇒ fusion helped):

| Dataset | Best member | FlowFuse | Δ |
|---|---|---|---|
| warp6 | RealNVP-b6 7.8233 | 7.7696 | +0.0537 |
| warp8 | MAF-x2 10.1943 | 10.1569 | +0.0374 |
| banana6 | MAF-x2 7.5845 | 7.4582 | +0.1263 |
| banana8 | MAF-x2 10.2580 | 10.1062 | +0.1518 |
| torus3 | RealNVP-b6 3.6959 | 3.6812 | +0.0146 |

The fusion improves every dataset, including the one it loses — and it helps
*most* where the two families disagree, because the winner is dataset-dependent
(MAF wins the warps, RealNVP wins the bananas).

## Known limitations

1. **`torus3` is a genuine loss.** A torus is a 2-D surface in 3-D that a
   16-component GMM tiles cheaply; a smooth diffeomorphism must bend all of
   space to represent it. More training narrows but does not close the gap.
2. **Narrow wins are fragile.** `warp8` (+0.77%) is barely above the noise
   floor; do not read sub-2% gains as robust.
3. **2-D toys are non-discriminative.** See below.
4. **Under-training shows up as losses, not errors.** A flow contains the
   Gaussian as a special case, so any loss to a Gaussian baseline is an
   optimisation-budget failure rather than a modelling failure.

### Why the gate is defined on the d ≥ 3 suite

In 2-D, a 16-component full-covariance GMM is essentially Bayes-optimal; on
`gmm8` it is *literally the generating process* and therefore unbeatable by
construction. Reporting a flow-vs-GMM win on 2-D toys would be unachievable by
design, so those datasets are reported for diagnostics only and the performance
gate is defined on the discriminative suite. Reproduce with
`python flowforge/examples/run_demo.py --diagnostic-2d`.

## Verification

| Invariant | Result |
|---|---|
| `inverse(forward(x)) == x` | ≤ 4.4e-16 |
| coupling `log|det| == Σ s` | exactly 0.0 |
| analytic vs finite-difference Jacobian `log|det|` | < 1e-5 |
| composite `log|det|` == Σ parts | < 1e-12 |
| analytic gradient vs central differences | 1.8e-10 / 7.8e-10 |
| MAF output `c` independent of `x_{≥c}` | 0 violations |
| EM validation log-likelihood monotone | never decreases |
| gain gate ships ≥ best single member | enforced by test |
| same seed ⇒ bit-identical metrics | worst abs diff 0.0 |
| scaler fitted on train only | asserted |

50/50 tests pass; demo end-to-end 73.4 s on CPU.

## Ethical / legal

Synthetic data only; no personal data, no licensed corpora, no pretrained
weights. Dependencies are NumPy (BSD-3), SciPy (BSD-3), scikit-learn (BSD-3) —
all MIT-compatible. Optional `torch`/`nflows` are excluded from the default path.
