crowd-anon-0.1b — anonymity-set size estimator

Ethical conditions (read before anything else)

This model does not identify people, and cannot be made to. It maps a description to how many people in a reference population could match it. The reference population is a probability model built from published aggregate statistics; there is no index of individuals anywhere in the system, nothing takes a person as input, and no output type has a field that could carry an identity. This is enforced by tests/test_no_identification_path.py, not promised in prose.

Outputs are a risk estimate and a set of masking candidates. Nothing else. There is no code path that produces "this description refers to X".

Counts are censored at a reporting floor of 5. Any combination matching fewer than five people is reported as "≤5". The model is trained with a censored (Tobit) likelihood so that it learns to say that, rather than learning to interpolate below the floor. A model that could tell one person from two would be a re-identification oracle.

The attack code that evaluates this model reproduces published techniques only, and is applied only to this project's own outputs. No new attack is developed, and the attack modules do not take a person as input.

This model does not guarantee anonymity and must not be described as if it did. Information usable for re-identification is unbounded in principle. This is a tool for estimation and prioritisation. Every number it emits is conditional on the reference population and the assumptions listed below.

What it does

Input: a Japanese (or English) description of a person. Output: μ, σ over log10(number of people in the reference population matching the quasi-identifiers in the text), converted to a calibrated interval by a split-conformal wrapper fitted on held-out data.

from crowd.estimator import NeuralEstimator
est = NeuralEstimator("crowd-anon-0.1b")
e = est.predict(["都内で四十代前半の男性が子どもの心臓を診る医師として働いている。"])[0]
print(e.human())        # "~33 people (90% interval 6–190)"
print(e.meets(k=1000))  # False -> this description needs masking

Paired with crowd.optimize.MinimalMaskOptimizer it becomes a redactor that hides the minimum needed to reach a target crowd size.

Architecture

parameters 110.3M (0.11B — inside the 0.1–0.3B design target)
layers / d_model / heads 12 / 768 / 12
feed-forward 3072
context 48 tokens (corpus p99 = 31)
vocabulary 32,000, byte-level BPE trained on this corpus
head mean-pooled → MLP → (μ, log σ²)
loss censored Gaussian (Tobit) NLL at the reporting floor
base model none — trained from scratch, no download

Attention is written out in plain matmuls rather than using a fused op, because ONNX, Core ML and the hand-written numpy runtime each lower a fused attention differently (or refuse). Plain matmuls export identically to all three, which is what makes "same weights, four runtimes" a checkable claim.

Why a censored likelihood

About an eighth of the corpus sits at or below the reporting floor. Training those rows as exact observations of the floor teaches the model that very rare combinations sit at exactly five people, which pulls its estimate for the rarest — most dangerous — descriptions upward. Over-estimating a crowd is the failure mode that gets someone identified. The Tobit term instead maximises P(Y ≤ floor), which is both true and safe.

Formats

Exported and verified by scripts/export.py, which fails the export if any backend disagrees with the torch reference by more than 0.05 log10 (about a tenth of the calibrated 90% interval width):

  • ONNX (opset 17), verified with onnxruntime
  • Core ML (mlprogram, fp16, iOS 16+), verified with coremltools
  • GGUF — f16 and Q8_0, carrying weights and tokenizer, verified against crowd/gguf.py's pure-numpy runtime (no torch, no tokenizers)

general.architecture is crowd-anon, which llama.cpp does not know: the file is valid GGUF and any tool can read its metadata and tensors, but the bundled numpy runtime is the reference implementation and the one the benchmark measures.

Training data

crowd-anonymity-sets — ~250k synthetic descriptions paired with exactly computed anonymity-set sizes. The label is not annotated: it is belief propagation over a tree-structured log-linear model raked to published Japanese marginals. Splits are on a hash of the attribute combination, so the test set contains crowds the model has never been told the size of.

The data describes nobody. It is generated from a distribution, not sampled from a population of people.

Measured results

All figures produced by scripts/evaluate.py, scripts/export.py and benchmarks/ in this repository; the artefacts they were read from are committed at github.com/NagaYu/crowd.

Accuracy and calibration (test split, unseen attribute combinations)

estimator MAE RMSE bias ECE 90% interval coverage ms/doc peak RSS
analytic (no model) 0.289 1.186 -0.288 0.289 93.0% (nominal 90%, width 0.83 log10) 0.89 84 MB
crowd-anon-0.1b 0.350 0.543 +0.025 0.169 85.3% (nominal 90%, width 1.71 log10) 4.12 1284 MB
same weights via GGUF + numpy 0.347 0.557 +0.061 0.170 86.6% (nominal 90%, width 1.61 log10) 85.1 1384 MB

All errors are in log10 head-count units: 0.30 means a factor of two.

The analytic path has the lower MAE and a much worse RMSE — it is exact when its lexicon matches and catastrophically wrong when it does not. Its PIT statistic is 0.50 (a point mass, not a distribution) against 0.17 for the model. The learned model is the better probabilistic estimator; the lexicon is the better point estimator on text it was built for.

Robustness to euphemism (matched pairs, identical attribute combinations)

Plain-form MAE 0.255 vs oblique-form 0.282 — a penalty of +0.028 log10 over 3000 pairs that differ only in register.

Out-of-lexicon text — read the sign, not the magnitude

40 hand-written sentences using only expressions absent from the reference bundle. Over-estimating the crowd is the unsafe direction: it declares a dangerous description safe and no masking follows.

estimator MAE bias over-estimated by >1 log10 (unsafe)
analytic 2.89 +2.89 95%
neural 3.80 -3.80 0%
ensemble 3.80 -3.80 0%

Neither estimator is trustworthy here. They fail asymmetrically, and that is the point: the lexicon fails unsafely, the model fails safely. EnsembleEstimator takes the minimum of the two for exactly this reason.

Export verification

An export fails if any backend disagrees with the torch reference by more than 0.05 log10.

format size max diff vs torch ms/doc verified
onnx 441.1 MB 0.0000 log10 25.2 yes
coreml 220.7 MB 0.0011 log10 30.8 yes
gguf:f16 224.4 MB 0.0001 log10 93.4 yes
gguf:q8_0 121.2 MB 0.0052 log10 93.0 yes

Training

3046 steps of a requested 4000 (stopped at a 91-minute wall-clock budget), batch 64, lr 0.00015, on mps. The model is not trained to convergence — validation loss was still improving when the budget ran out.

Intended use

  • Deciding what to mask in a document before release, and how much.
  • Showing a writer or a clinician which detail is carrying the disclosure risk, so they can make an informed trade.
  • On-device triage where sending the text to a hosted model is not acceptable.

Out of scope / misuse

  • Not for identifying anyone. It cannot; do not build something that tries.
  • Not a compliance certificate. It does not establish that a document is anonymous under GDPR, HIPAA, 個人情報保護法 or any other regime.
  • Not validated for languages, countries or domains other than the shipped Japanese reference bundle.
  • Not a substitute for review by a person accountable for the release.

Limitations

  1. The reference bundle is a research fixture. Internally exact and reproducible; only approximately faithful to Japan's published statistics. Figures are tagged published / approximate / derived / illustrative per entry. Rebuild from authoritative extracts before any real disclosure decision.
  2. Synthetic training text. Narrower vocabulary and syntax than real clinical or legal prose. Expect degradation on genuine documents.
  3. Document-level only. Aggregating several releases about the same subject defeats per-document masking; crowd.robustness's aggregation attack measures the size of that gap but the model does not close it.
  4. One value per attribute family. Multi-morbid descriptions are over-estimated — the model thinks the crowd is larger than it is.
  5. Conditional independence off the tree. Dependencies the reference forest does not model are assumed away, with an allowance added to the interval.
  6. Sensitive attributes are modelled (health condition, nationality) because they are genuine quasi-identifiers; omitting them would understate risk. They appear only as aggregate distributions.

Evaluation

The four-condition comparison (direct-identifiers-only / NER-mask-all / prompted LLM / Crowd), the Pareto frontier, the attack-resistance numbers and the figures are in the repository README at github.com/NagaYu/crowd, generated from artifacts/*.json so the prose cannot drift from the measurements.

The calibration protocol is: reliability diagram + nominal-vs-empirical coverage

  • PIT histogram, against exactly-computed truth, on attribute combinations held out by hash.

Reproduction

make all      # reference bundle -> dataset -> train -> export -> evaluate -> figures

Deterministic given the seeds. No network access required at any stage.

Licence and attribution

Apache-2.0 for the code and weights. The reference statistics derive from Japanese government publications released under 政府標準利用規約(第2.0版) (CC BY 4.0 compatible, attribution required); sources are listed in the bundle's provenance block and by crowd reference.

Downloads last month
-
GGUF
Model size
0.1B params
Architecture
crowd-anon
Hardware compatibility
Log In to add your hardware

8-bit

16-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train NagaYu/crowd-anon-0.1b