Skip to main content

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?

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) and convnext_tiny (768-d)
  • candidates parameter: control which backbones auto evaluates
  • whitening= removed: use adaptation= only
  • val_fraction removed: use vyntri.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(), and analyze() accept FolderDataset, Path, or strings.
  • save()/load() preserves training history and class registry.
  • loaded.state reflects the pre-save state ("fitted", "updated", or "fine_tuned").
  • Scheduled fit with no validation set no longer crashes.
  • Corrupt images raise DatasetError instead 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)

Source distribution for vyntri 1.5.0
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)

Table of built distributions (wheels) for vyntri 1.5.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page