Vyntri
Rapid analytic adaptation of pretrained vision representations, CPU-first.
Vyntri adapts pretrained image backbones to your classification task in seconds without gradient training loops. It extracts frozen features, applies FK discriminative whitening with diagonal covariance shrinkage, and solves an analytic ridge classifier in closed form.
from vyntri import Vyntri
model = Vyntri()
model.fit("./dataset") # folder-per-class, ~1-2 s on a laptop CPU
print(model.evaluate("./test").accuracy)
model.predict("./image.jpg")
Why Vyntri
- No training loop. Adaptation is linear algebra — milliseconds to a few seconds on CPU, no GPU required.
- Simple by default, configurable by design.
model = Vyntri() # validated defaults
model = Vyntri( # controlled experiment
backbone="mobilenet_v3_small",
whitening="fk",
shrinkage="diagonal",
shrinkage_alpha=0.25,
regularization=1e-3,
projection_dim=32,
seed=42,
)
- Honest about trade-offs. Vyntri seeks a better accuracy/computation trade-off, not guaranteed maximum accuracy. In the reduced-sample regimes it targets, analytic adaptation beats last-layer fine-tuning in both speed and accuracy (research evidence, below); in regimes where fine-tuning wins, that result is reported, not hidden.
Installation
pip install vyntri
Requires Python ≥ 3.9. Dependencies: numpy, torch, torchvision,
pillow. Vyntri is designed for CPU-first use; CUDA can be selected when
available, but GPU acceleration is not required. The pretrained backbone
weights download on first use.
Quick start
Dataset layout — one folder per class:
dataset/
cat/
img1.jpg
img2.jpg
dog/
img1.jpg
Or explicit splits:
dataset/
train/cat/...
val/cat/...
test/cat/...
Fit, evaluate, predict:
from vyntri import Vyntri
model = Vyntri()
model.fit("./dataset") # single folder -> deterministic 80/20 split
result = model.evaluate("./test")
print(result.accuracy, result.macro_f1, result.confusion_matrix)
model.predict("./cat.jpg") # -> PredictionResult(label, confidence)
model.predict_batch("./images") # -> BatchPredictionResult (CSV/JSON export)
model.analyze("./dataset") # cheap dataset inspection, no extraction
model.save("model.vyntri")
model = Vyntri.load("model.vyntri")
fit() prints a concise summary; model.summary() returns the same
information with full timing breakdown. Feature extraction is cached on disk
(keyed by dataset fingerprint + backbone + preprocessing + dtype) — the
second fit of the same data is much faster, and model.clear_cache() resets
it. A cache hit/miss is always reported.
Continual learning (v0.2):
model.fit("./initial") # classes: cat, dog
result = model.update("./new_data") # adds dog examples + a new class: bird
print(result.new_classes) # -> ["bird"]
update() maintains sufficient statistics (X^T X, per-class sums/counts)
in the raw feature space and re-solves the FK projection and ridge classifier
from them — old raw features are never retained. The result matches a joint
refit over all data (measured, V3 Stage 7); updates are order-invariant and
handle mixed old/new class batches. It does not claim "zero forgetting":
per-task accuracy can shift because the joint solution legitimately
re-allocates boundaries as classes arrive. Memory note: the statistics are
O(d²), so they beat raw features when n >> d and are larger at n < d.
Features
| Area | What |
|---|---|
| Data | folder-per-class and explicit train/val/test layouts, deterministic hash-based stratified split, tiny-class handling |
| Backbone | MobileNetV3-Small + ResNet18 + ResNet50 (frozen, ImageNet pretrained); registry validated at construction |
| Whitening | FK/discriminative whitening with eigenvalue flooring (float64-stable) |
| Shrinkage | diagonal covariance shrinkage (shrinkage_alpha) |
| Classifier | analytic ridge, float64 solve, condition diagnostics |
| Evaluation | accuracy, macro/weighted F1, balanced accuracy, confusion matrix |
| Persistence | .vyntri zip (JSON + npy) — schema-versioned, no pickle on load |
| Continual | update() via sufficient statistics; stable class registry; sequential == joint (measured) |
| Config (v0.3) | validated advanced controls (whitening/shrinkage/alpha/regularization/projection_dim/dtype/seed/device/cache), describe(), JSON export/import, interaction notes, non-default repr |
| Fine-tuning (v0.5) | optional fine_tune() — linear head / last block / full backbone, validation-based checkpoint selection, full training metadata; kept separate from the analytic path |
| Reproducibility | seed, resolved config stored with the fitted model |
Roadmap
Future releases may add user-requested functionality, performance
improvements, additional backbones, and additional training workflows.
Research-only techniques and experimental algorithms remain in the separate
vyntri-research repository unless they prove useful and appropriate for
the public API.
Backbones
| Backbone | Features | Params | Pretrained weights | CPU |
|---|---|---|---|---|
mobilenet_v3_small (default) |
576 | 2.5M | MobileNet_V3_Small_Weights.IMAGENET1K_V1 |
✅ fast |
resnet18 |
512 | 11.7M | ResNet18_Weights.IMAGENET1K_V1 |
✅ ~4× slower than MobileNet |
resnet50 |
2048 | 25.6M | ResNet50_Weights.IMAGENET1K_V1 |
✅ ~2× slower than ResNet18 |
All three share one ImageNet preprocessing system (resize-256 → center-crop
→ normalize) and expose a frozen feature endpoint (classifier/fc
replaced with an identity). Switch with Vyntri(backbone="resnet18") or
Vyntri(backbone="resnet50"). The registry validates names at construction
and embeds the exact pretrained weights in the feature-cache key, so
switching backbones never reuses another backbone's cached features.
Fine-tuning (v0.5)
Optional gradient fine-tuning, deliberately separate from the analytic path:
model.fit("./dataset")
result = model.fine_tune("./dataset", scope="last_layer", epochs=3)
print(result.best_validation_accuracy) # best-on-validation checkpoint
model.evaluate("./test") # now runs through the fine-tuned model
Scopes are architecture-aware (no module paths hardcoded):
| Scope | Trains |
|---|---|
last_layer |
new linear head only, backbone frozen (linear probe) |
last_block |
head + last feature block (ResNet layer4 / MobileNet's final block) |
full |
head + the entire backbone |
Defaults follow the V3 Stage 9 protocol (Adam, lr=1e-3, weight_decay=1e-4,
3 epochs, batch from config). Validation is used every epoch and the
best-on-validation checkpoint is restored — the test set is never touched
during fitting. Every run records epochs, gradient steps, trainable
parameters, optimizer, learning rate, weight decay, and training /
validation / total time. A fine-tuned model persists through
save()/load() (weights stored as plain numpy in the no-pickle archive).
Honest trade-off: fine-tuning replaces the analytic classifier, needs
enough gradient steps to converge (more than 3 epochs on small datasets),
and on reduced-sample data the analytic path is typically both faster and
more accurate (V3 head-to-head). Analytic update() is unavailable after
fine_tune() until you re-fit().
Configuration
All defaults are documented with provenance:
backbone="mobilenet_v3_small"— chosen for the ordinary-laptop classroom use case (V3 research environment: CPU-only).whitening="fk"+shrinkage="diagonal"— the configuration that won the V3 research matrix (fk+diag best in 14/20 dataset × backbone cells).regularization=1e-4— the V3 baseline lambda; a λ sweep is not yet part of the research archive, so this is a documented baseline, not a claimed optimum.val_fraction=0.2— deterministic 80/20 train/validation split; V3 used 60/20/20 including a research test fold.
Advanced controls (all validated at construction): whitening, shrinkage,
shrinkage_alpha, regularization, projection_dim, dtype,
eigenvalue_floor, floor_ratio, device, cache_dir, cache_enabled,
batch_size, num_workers, input_size, seed. Every parameter is
documented in code with type, valid values, example, interaction notes, and
cache implications:
from vyntri import Config
model.config.describe_param("shrinkage_alpha") # one parameter
Config.describe() # all parameters
# JSON export / import round-trips through validation
cfg_json = model.config.to_json()
restored = Config.from_json(cfg_json)
print(model.config) # shows only non-default values
model.config.interaction_notes() # e.g. shrinkage is ignored when whitening="none"
The resolved configuration (e.g. the auto-picked projection_dim) is
stored with the fitted model and is authoritative for all post-fit
operations: mutating model.config after fit() affects only the next
fit(), never the interpretation of the current model (Master spec §18).
model.config.classify_changes(other) reports which pipeline stages a
config change would invalidate (extraction / fit / safe).
Research foundation
Vyntri's defaults and design are backed by the experiments in the separate
research repository — github.com/AreebShahid07/vyntri-research (V3
protocol, 1,571 result rows): the FK + diagonal-shrinkage + analytic-ridge
pipeline, the float32 numerical-stability fixes, the n≈d ridge-collapse
regime, and the fine-tuning comparison are all measured there. The public
library is a clean rebuild around stable interfaces; research-only
algorithms (DS-AL, pooled shrinkage, SLCE, intrinsic-dimension diagnostics,
zero-cost proxies) intentionally stay in the research repository.
Limitations
- Image classification on folder-per-class data only (no detection / segmentation).
- Three backbones (MobileNetV3-Small, ResNet18, ResNet50); MobileNetV3-Large is not currently planned.
- CPU timings are single-machine; relative cost ratios are robust, absolute times vary.
- In regimes the research did not cover (very large per-class sample counts, distribution shift), fine-tuning may be competitive or better — measured, not assumed.
Release notes
- v0.5 — optional gradient fine-tuning (
fine_tune): architecture-aware scopes (linear head / last block / full backbone), Adam protocol from V3 Stage 9, validation-based checkpoint selection, full training metadata, fine-tuned models save/load without pickle. - v0.4 — three backbones (MobileNetV3-Small, ResNet18, ResNet50) behind one registry and preprocessing system; backbone names validated at construction; cache keys proven distinct per backbone.
- v0.3 — advanced configuration layer: validated controls for whitening,
shrinkage, alpha, regularization, projection dimension, dtype, seed,
device, cache;
Config.describe(), JSON export/import, interaction notes, non-defaultrepr; resolved config stored with the model. - v0.2 — continual learning:
update()via sufficient statistics, stable class registry, mixed old/new classes, sequential == joint (measured). - v0.1 — clean core: folder datasets, deterministic split, frozen MobileNetV3-Small features, FK whitening, diagonal shrinkage, analytic ridge, fit/evaluate/predict/predict_batch, save/load, CPU-first.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vyntri-0.5.0.tar.gz.
File metadata
- Download URL: vyntri-0.5.0.tar.gz
- Upload date:
- Size: 66.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d315c951b8e17a7a77787372419280a65a3423468957a4885610a2204836f071
|
|
| MD5 |
34b465710e1083f575ea3dee15435cbc
|
|
| BLAKE2b-256 |
7e6824b465ede331e05ddaeeb61d733437509550aedc019b21a0fe8fdda97fd9
|
Provenance
The following attestation bundles were made for vyntri-0.5.0.tar.gz:
Publisher:
workflow.yml on AreebShahid07/vyntri
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vyntri-0.5.0.tar.gz -
Subject digest:
d315c951b8e17a7a77787372419280a65a3423468957a4885610a2204836f071 - Sigstore transparency entry: 2498873620
- Sigstore integration time:
-
Permalink:
AreebShahid07/vyntri@df431b6893c8dc148fa9e6584c9e694038bc833b -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/AreebShahid07
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@df431b6893c8dc148fa9e6584c9e694038bc833b -
Trigger Event:
push
-
Statement type:
File details
Details for the file vyntri-0.5.0-py3-none-any.whl.
File metadata
- Download URL: vyntri-0.5.0-py3-none-any.whl
- Upload date:
- Size: 57.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c277acd30509683858fab8ff8922f13dfa9ad8595681c15c2e6a2ca2d902c6b
|
|
| MD5 |
d4f397ab5a12241153786308ed0f6a9a
|
|
| BLAKE2b-256 |
31291aa5dc590123bca97ca8c28984c2ca75a62e47582c239d182403545d8e8c
|
Provenance
The following attestation bundles were made for vyntri-0.5.0-py3-none-any.whl:
Publisher:
workflow.yml on AreebShahid07/vyntri
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vyntri-0.5.0-py3-none-any.whl -
Subject digest:
8c277acd30509683858fab8ff8922f13dfa9ad8595681c15c2e6a2ca2d902c6b - Sigstore transparency entry: 2498873627
- Sigstore integration time:
-
Permalink:
AreebShahid07/vyntri@df431b6893c8dc148fa9e6584c9e694038bc833b -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/AreebShahid07
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@df431b6893c8dc148fa9e6584c9e694038bc833b -
Trigger Event:
push
-
Statement type: