Vyntri
Classify images without training. Extract features from a pretrained backbone, project them analytically, and classify -- all in 4 lines of Python.
from vyntri import Vyntri
from vyntri.data import split
s = split("./my-dataset", train=0.7, test=0.2, seed=42)
model = Vyntri()
model.fit(train=s.train, val=s.val)
model.predict("./image.jpg") # Prediction(label=cat, confidence=0.94)
No GPU required. No training loops. Useful defaults require little or no tuning.
Table of Contents
- Why Vyntri?
- Quick Start
- How It Works
- After Fitting
- Configuration
- Custom Backbones
- Fine-Tuning
- API Reference
- Examples
- Installation
Why Vyntri?
| Training-based | Vyntri | |
|---|---|---|
| Time to first prediction | Minutes to hours | ~4 seconds |
| Data needed | Hundreds+ per class | Works with 10-50 per class |
| GPU required | Essentially yes | No |
| Hyperparameters to tune | Dozens | Few (sensible defaults) |
| Overfitting risk | High on small data | Low (analytic, no gradient loops) |
Vyntri replaces the training loop with analytic (closed-form) projection and classification. The result is fast to fit, resistant to overfitting (no gradient loops to memorize noise), and works on small datasets where traditional training fails.
Quick Start
1. Prepare your dataset
Organize images into folders by class name:
my-dataset/
cats/
cat_001.jpg
cat_002.jpg
dogs/
dog_001.jpg
birds/
bird_001.jpg
Recommended: At least 10 images per class for good results.
2. Split, fit, and predict
from vyntri import Vyntri
from vyntri.data import split
# Create an explicit train/val/test split (no files copied)
s = split("./my-dataset", train=0.7, test=0.2, seed=42)
model = Vyntri()
model.fit(train=s.train, val=s.val)
result = model.predict("./test-photo.jpg")
print(result.label) # cats
print(result.confidence) # 0.94
3. Evaluate on the held-out test set
result = model.evaluate(s.test)
print(result.accuracy) # 0.91
print(result.macro_f1) # Per-class F1 (macro-averaged)
print(result.per_class) # Per-class precision/recall/F1
4. Save and reload
model.save("./my-model.vyntri")
model = Vyntri.load("./my-model.vyntri")
model.predict("./new-image.jpg")
How It Works
Image
|
Pretrained backbone (frozen) <- extracts features, no training
|
Analytic projection <- closed-form dim reduction
|
Shrinkage estimation <- stabilizes covariance
|
Analytic Ridge classifier <- closed-form weights
|
Prediction
Every step is analytic -- mathematically derived, not learned through gradient descent:
- Low overfitting risk -- analytic fitting avoids iterative gradient-based optimization
- No GPU needed -- CPU matrix ops are fast enough
- Sensible defaults -- few or no parameters to tune for common workflows
- Designed for determinism -- analytic fitting on fixed data and config produces consistent results
The pretrained backbone provides general visual features. Vyntri never modifies it -- it only learns the projection and classifier on top.
After Fitting
Model info
model.classes_ # ["cats", "dogs", "birds"]
model.class_to_idx_ # {"cats": 0, "dogs": 1, "birds": 2}
model.feature_dim_ # 576 (backbone feature dimension)
model.state # "fitted", "fine_tuned", etc.
Predictions
result = model.predict("./image.jpg")
result.label # cats
result.confidence # 0.94
Batch predictions
results = model.predict_batch(["./img1.jpg", "./img2.jpg", "./img3.jpg"])
for path, label, conf in zip(results.paths, results.labels, results.confidences):
print(f"{label}: {conf:.1%}")
Inspect internals
model.config # Current configuration
model.fitted_config # Config that produced this fit
model.projection_ # Learned projection matrix
model.classifier_ # Learned classifier weights
model.validation_accuracy_
Configuration
from vyntri import Vyntri
from vyntri.data import split
# All parameters can be passed directly as kwargs
model = Vyntri(
backbone="auto",
adaptation="fk",
shrinkage="diagonal",
cache_dir="./vyntri-cache",
)
s = split("./dataset", train=0.7, test=0.2, seed=42)
model.fit(train=s.train, val=s.val)
Config.describe() # Returns structured docs for all parameters
Key configuration groups
| Group | Options | What they control |
|---|---|---|
| Backbone | backbone, candidates | Feature extraction model, auto-selection subset |
| Projection | adaptation, shrinkage, shrinkage_alpha, projection_dim | Dim reduction |
| Classifier | regularization | Ridge classification strength |
| Runtime | device, dtype, batch_size, num_workers | Execution control |
| Cache | cache_dir, cache_enabled | Feature caching |
| Execution | batch_size, num_workers, device, dtype | Runtime behavior |
Custom Backbones
Vyntri supports five vision backbones:
| Backbone | Feature dim | Speed (CPU) | Notes |
|---|---|---|---|
| mobilenet_v3_small | 576 | Fastest | Default, great for CPU |
| resnet18 | 512 | Fast | Good balance |
| resnet50 | 2048 | Moderate | Highest capacity ResNet |
| efficientnet_b0 | 1280 | Moderate | Efficient compound scaling |
| convnext_tiny | 768 | Moderate | Modern ConvNet design |
from vyntri import Vyntri
from vyntri.data import split
s = split("./dataset", train=0.7, test=0.2, seed=42)
# Use a specific backbone
model = Vyntri(backbone="efficientnet_b0")
model.fit(train=s.train, val=s.val)
# Or let Vyntri auto-select from a subset
model = Vyntri(
backbone="auto",
candidates=["resnet18", "efficientnet_b0", "convnext_tiny"],
)
model.fit(train=s.train, val=s.val)
print(f"Selected: {model.backbone_name_}")
Fine-Tuning
For the highest accuracy on your specific domain:
from vyntri.data import split
s = split("./dataset", train=0.7, test=0.2, seed=42)
model = Vyntri(backbone="resnet50")
model.fit(train=s.train, val=s.val)
model.fine_tune(s, epochs=10, lr=1e-3, scope="last_layer")
| Scope | What unfreezes | When to use |
|---|---|---|
| last_layer | Final classification head | Default -- safe, fast |
| last_block | Final feature extraction block | 50+ images/class |
| full | Entire backbone | 1000+ images/class |
Note: Fine-tuning requires a GPU for reasonable speed.
API Reference
| Method | Description |
|---|---|
vyntri.data.split(path, train, test, seed) |
Create explicit train/val/test split |
| Vyntri(**kwargs) | Create model instance |
| model.fit(train=..., val=...) | Fit on dataset (accepts FolderDataset, Path, or str) |
| model.predict(image) | Classify single image |
| model.predict_batch(images) | Batch classify |
| model.evaluate(path or FolderDataset) | Evaluate on test set |
| model.save(path) / Vyntri.load(path) | Serialize/deserialize |
| model.fine_tune(dataset, scope, epochs, lr) | Optional fine-tuning (accepts SplitResult/FolderDataset/path) |
| model.update(dataset) | Add new data/classes (accepts FolderDataset/Path/path) |
| model.analyze(dataset) | Inspect dataset (accepts FolderDataset/Path/path) |
| model.select_backbone(dataset) | Auto-select backbone |
| model.clear_cache() | Remove feature cache |
| Property | Description |
|---|---|
| model.classes_ | Class names list |
| model.class_to_idx_ | Class to index mapping |
| model.state | Current model state |
| model.config | Current configuration |
| model.fitted_config | Config used for last fit |
| model.feature_dim_ | Backbone feature dimension |
| model.validation_accuracy_ | Validation accuracy from fit |
Examples
Image classification
from vyntri import Vyntri
from vyntri.data import split
s = split("./flowers", train=0.7, test=0.2, seed=42)
model = Vyntri()
model.fit(train=s.train, val=s.val)
print(model.classes_) # ["daisy", "rose", "sunflower", "tulip"]
result = model.predict("./test-rose.jpg")
print(result.label, result.confidence)
Incremental learning
from vyntri import Vyntri
from vyntri.data import split
from vyntri.data.dataset import FolderDataset
s = split("./dataset-v1", train=0.7, test=0.2, seed=42)
model = Vyntri()
model.fit(train=s.train, val=s.val)
# update() accepts FolderDataset, Path, or string
new_data = FolderDataset("./dataset-v2")
model.update(new_data) # Adds new data without forgetting
Comparing backbones
from vyntri import Vyntri
from vyntri.data import split
s = split("./dataset", train=0.7, test=0.2, seed=42)
for name, bb in [("mobile", "mobilenet_v3_small"), ("resnet", "resnet50")]:
model = Vyntri(backbone=bb)
model.fit(train=s.train, val=s.val)
print(name, model.validation_accuracy_)
Installation
pip install vyntri
Requirements: Python 3.9+, PyTorch, torchvision
Optional for fine-tuning: CUDA-capable GPU
Migrating from v1.1.x
v1.5.0: Backbone expansion and API cleanup
- New backbones:
efficientnet_b0(1280-d) andconvnext_tiny(768-d) candidatesparameter: control which backbonesautoevaluateswhitening=removed: useadaptation=onlyval_fractionremoved: usevyntri.data.split()for explicit roles- Result objects now print nicely:
print(result)gives readable output
# v1.4.0 (still works for most things)
model = Vyntri(backbone="auto")
model.fit(train=s.train, val=s.val)
# v1.5.0 (recommended)
model = Vyntri(
backbone="auto",
candidates=["resnet18", "efficientnet_b0", "convnext_tiny"],
)
model.fit(train=s.train, val=s.val)
print(model.evaluate(s.test)) # Human-readable output
v1.4.0: Explicit dataset roles and adaptation
fit() requires explicit train= and optional val= parameters. SplitResult is no longer accepted directly — use s.train and s.val explicitly.
v1.2.0: Explicit dataset splitting
vyntri.data.split() replaces implicit automatic splitting.
v1.3.0: API consistency and stability
update(),fine_tune(), andanalyze()acceptFolderDataset,Path, or strings.save()/load()preserves training history and class registry.loaded.statereflects the pre-save state ("fitted","updated", or"fine_tuned").- Scheduled fit with no validation set no longer crashes.
- Corrupt images raise
DatasetErrorinstead of raw PIL tracebacks.
Vyntri -- classify images without training.
Metadata
Release files for vyntri 1.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| vyntri-1.5.0.tar.gz | 113.6 kB | With provenance | Details for vyntri-1.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| vyntri-1.5.0-py3-none-any.whl | Python 3 | none | any | Details for vyntri-1.5.0-py3-none-any.whl |
Total release size: 198.0 kB
Release files / vyntri-1.5.0.tar.gz
| Download URL | vyntri-1.5.0.tar.gz |
|---|---|
| Size | 113.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
01d124419d5cbe7114ef339cacfa9f5430b5d13c037420ac8b0212fdf5964f44
|
|
BLAKE2b-256 checksum How to use checksums |
ca41cb4d95db91d48619581ba5a3ffa3dee8ba9dc56c8f576295187f9b2afbcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logRelease files / vyntri-1.5.0-py3-none-any.whl
| Download URL | vyntri-1.5.0-py3-none-any.whl |
|---|---|
| Size | 84.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
40e384e8a7ec14940be60120892a9b522995cbf2c21263e3023b8fece26c921b
|
|
BLAKE2b-256 checksum How to use checksums |
7b7f6d5015db06edfe13ff7d156900f7eb6c23876164c4292a914294cca461bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logSecurity for vyntri 1.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vyntri-1.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.