FujiCV
Lightweight image classification & regression for PyTorch — from prototype to production in 10 lines.
Why FujiCV?
You want to train a ResNet50 on your image dataset — with AMP, early stopping, and a best-model checkpoint.
Here is how much code that takes:
| Framework | Lines of code | What you get |
|---|---|---|
| Raw PyTorch | ~90 lines | Full control, full boilerplate |
| PyTorch Lightning | ~45 lines | Great for large-scale training; heavy for a weekend project |
| FujiCV | ~12 lines | AMP · Early stopping · Checkpointing · Metrics · W&B · HPO · Grad-CAM |
FujiCV is not trying to replace Lightning for distributed training at scale.
It is designed for: rapid prototyping, Kaggle competitions, students, and researchers who need results — not boilerplate.
Quick Start — CIFAR-10 (auto-downloads, no setup needed)
import torch
from torch.utils.data import DataLoader
import fujicv
from fujicv.data.datasets import get_default_dataset
from fujicv.data.transforms import get_train_transforms, get_val_transforms
from fujicv.engine.trainer import Trainer
from fujicv.losses.classification import CrossEntropyLoss
from fujicv.metrics.classification import Accuracy
from fujicv.models.builder import ModelBuilder
# Download CIFAR-10 automatically
train_ds, val_ds, class_to_idx = get_default_dataset(
"cifar10", root="data",
train_transform=get_train_transforms(32, level="medium"),
val_transform=get_val_transforms(32),
)
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)
val_loader = DataLoader(val_ds, batch_size=128, shuffle=False, num_workers=2)
model = ModelBuilder("resnet18", task="classification", num_outputs=10, image_size=32).build()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
trainer = Trainer(
model=model, train_loader=train_loader, val_loader=val_loader,
loss_fn=CrossEntropyLoss(),
metrics={"accuracy": Accuracy()},
optimizer=optimizer,
epochs=10, task="classification", output_dir="runs/cifar10",
monitor_metric="val_accuracy",
)
history = trainer.train() # → best.pt last.pt history.csv
# Best val accuracy after 10 epochs on CIFAR-10: ~80%
Feature Matrix
| Category | What's included |
|---|---|
| Backbones | Any timm, torchvision, or Hugging Face transformers model |
| Task heads | Classification · Regression · Multi-label |
| Metric learning | ArcFace · CosFace · Sub-center ArcFace margin heads |
| Custom layers | LinearBNDropout · GeM Pooling · AttentionPool · SqueezeExcite |
| Head builder | Pluggable pooling (avg/max/gem/attention) · Linear/Dropout/LayerNorm/BatchNorm/Activation blocks |
| Losses | 15 losses — CrossEntropy · Focal · LabelSmoothing · CORAL · Ordinal · Huber · Quantile · … |
| Metrics | 16 metrics — Accuracy · F1 · AUROC · mAP · MAE · RMSE · R² · … |
| Augmentation | Albumentations presets · RandAugment · Mixup · CutMix |
| Trainer | AMP · Gradient clipping · Gradient accumulation · EMA · SWA · Model soups · Early stopping · Checkpointing · History CSV |
| Fine-tuning | Layer freezing · Gradual unfreezing · Frozen BN stats · LLRD |
| LR utilities | LR Finder · Cosine warmup · OneCycleLR · LLRD |
| Multi-GPU | DistributedDataParallel via torchrun (use_ddp=True) · rank-guarded checkpoints · all-gathered metrics |
| HPO | Optuna hyperparameter search with pruning (pip install "fujicv[hpo]") |
| Explainability | Grad-CAM · Grad-CAM++ · Attention rollout · Confusion matrix |
| Export | ONNX · TorchScript · INT8 quantization (ONNX · dynamic PTQ · static FX PTQ) |
| Inference | Predictor.from_checkpoint · batch predict with IDs · built-in TTA · EnsemblePredictor |
| Logging | W&B · TensorBoard (offline) · MLflow · pluggable BaseLogger |
| Performance | torch.compile (graph optimisation, PyTorch 2.x) |
| CV / Imbalance | K-Fold · Stratified K-Fold · WeightedRandomSampler |
| Distillation | DistillationTrainer with temperature scaling |
Installation
# Core
pip install fujicv
# With experiment logging (W&B / TensorBoard / MLflow)
pip install "fujicv[wandb]" "fujicv[tensorboard]" "fujicv[mlflow]"
# With ONNX export + quantization
pip install "fujicv[onnx]"
# With Optuna HPO
pip install "fujicv[hpo]"
# With Hugging Face transformers backbones
pip install "fujicv[hf-models]"
# Everything
pip install "fujicv[wandb,tensorboard,mlflow,onnx,hpo,hf-models]"
# Dev / testing
pip install "fujicv[dev]"
Install PyTorch separately following pytorch.org/get-started to pick the correct CUDA build.
Selected Code Recipes
Grad-CAM visualization
from fujicv.eval.gradcam import GradCAM, overlay_heatmap
import torchvision.transforms as T
from PIL import Image
cam = GradCAM(model, target_layer=model.backbone.layer4[-1])
image = T.ToTensor()(Image.open("dog.jpg")).unsqueeze(0)
heatmap = cam.generate(image)
result = overlay_heatmap(image.squeeze(), heatmap)
result.save("cam_result.png")
Optuna HPO with pruning
from fujicv.hpo import run_hpo
def objective(trial):
lr = trial.suggest_float("lr", 1e-4, 1e-2, log=True)
wd = trial.suggest_float("weight_decay", 0, 1e-2)
# ... build model + trainer, return val_accuracy
result = run_hpo(objective, n_trials=30, pruner="median")
print(result["best_params"])
TorchScript export for production
from fujicv.export import export_torchscript, verify_torchscript
import torch
scripted = export_torchscript(model, "model.pt", torch.randn(1, 3, 224, 224))
verify_torchscript(scripted, torch.randn(1, 3, 224, 224), original_model=model)
LR Finder
from fujicv.training import LRFinder
finder = LRFinder(model, optimizer, criterion)
finder.range_test(train_loader, start_lr=1e-7, end_lr=10, num_iter=100)
finder.plot()
best_lr = finder.suggestion()
finder.reset() # restore model before training
Stochastic Weight Averaging
from fujicv.training import SWA
swa = SWA(model, swa_lr=1e-4)
for epoch in range(total_epochs):
train_one_epoch(...)
if epoch >= swa_start:
swa.update()
swa.finalize(train_loader) # update BN stats
evaluate(swa.averaged_model, val_loader)
Multi-GPU training (DDP)
# Launch with torchrun
torchrun --nproc_per_node=4 train.py
# In train.py, just add use_ddp=True
trainer = Trainer(model, ..., use_ddp=True)
Gradient accumulation (large effective batch on a small GPU)
# Effective batch = batch_size × grad_accum_steps = 32 × 4 = 128
trainer = Trainer(model, ..., grad_accum_steps=4)
history = trainer.train()
TensorBoard logging (offline, no account)
from fujicv.engine import TensorBoardLogger
tb = TensorBoardLogger(log_dir="runs/exp1", config={"lr": 1e-3})
trainer = Trainer(model, ..., tb_logger=tb)
trainer.train()
# then: tensorboard --logdir runs/exp1
Staged fine-tuning (freeze → gradual unfreeze)
from fujicv.training import freeze_backbone, GradualUnfreezing
freeze_backbone(model) # epoch 0: train the head only
unfreezer = GradualUnfreezing(model, unfreeze_epoch=2, layers_per_epoch=1)
for epoch in range(epochs):
unfreezer.step(epoch) # unfreeze one backbone block per epoch
train_one_epoch(...)
ArcFace margin head (fine-grained / retrieval)
import torch.nn.functional as F
from fujicv.models import ArcMarginProduct
head = ArcMarginProduct(in_features=512, num_classes=5000, s=30.0, m=0.5)
# training — margin applied to the ground-truth class
logits = head(embeddings, labels)
loss = F.cross_entropy(logits, labels)
# inference / retrieval — plain scaled cosine
scores = head(embeddings) # labels=None
Model soups (ensemble accuracy, single-model inference)
import torch
from fujicv.training import uniform_soup, greedy_soup
states = [torch.load(p)["model_state_dict"] for p in checkpoint_paths]
uniform_soup(model, states) # plain weight average
# or keep only ingredients that help a validation metric:
kept = greedy_soup(model, states, eval_fn=lambda m: evaluate(m, val_loader))
Hugging Face backbone
from fujicv.models.builder import ModelBuilder
model = ModelBuilder(
backbone_name="google/vit-base-patch16-224", # any HF vision repo id
backbone_source="hf",
task="classification", num_outputs=10, image_size=224,
).build()
MLflow logging + torch.compile
from fujicv.engine import MLflowLogger
mlf = MLflowLogger("fujicv-experiments", run_name="run1", params={"lr": 1e-3})
trainer = Trainer(
model, ..., loggers=[mlf], # pluggable — also WandbLogger/TensorBoardLogger
compile_model=True, # torch.compile graph optimisation (PyTorch 2.x)
)
trainer.train()
INT8 quantization for edge deployment
from fujicv.export import quantize_dynamic, quantize_static, measure_model_size
fp32_mb = measure_model_size(model)
# Dynamic PTQ — no calibration data required (great for transformer/MLP heads)
qmodel = quantize_dynamic(model)
# Static FX PTQ — calibrate on a few batches (best for CNNs)
qmodel = quantize_static(model, calibration_loader, backend="x86")
print(f"{fp32_mb:.1f} MB → {measure_model_size(qmodel):.1f} MB")
Custom head + GeM pooling
model = ModelBuilder(
"resnet50", task="classification", num_outputs=100,
pooling="gem", # learnable Generalised-Mean pool
custom_layers=[
{"type": "Linear", "out_features": 512},
{"type": "LayerNorm"},
{"type": "Activation", "fn": "gelu"},
{"type": "Dropout", "p": 0.2},
],
).build()
Package Layout
fujicv/
models/ backbone factory · heads · custom layers · ModelBuilder
data/ CSVImageDataset · transforms · Mixup/CutMix · RandAugment · sampler
losses/ 15 losses + LOSS_REGISTRY
metrics/ 16 metrics + METRIC_REGISTRY
engine/ Trainer · DistillationTrainer · callbacks · WandbLogger
training/ EMA · SWA · SAM · LR Finder · schedulers · LLRD · K-Fold
eval/ Grad-CAM · confusion matrix · calibration · Ensemble
hpo/ Optuna search + pruning + visualization
export/ ONNX · ONNX quantization · TorchScript
inference/ Predictor · EnsemblePredictor · TTA
utils/ Registry · set_seed · config loader
tests/ 255 tests covering every module
Validated Results
| Dataset | Model | Epochs | Val Accuracy |
|---|---|---|---|
| MNIST | ResNet-18 (scratch) | 5 | 98.6% |
More benchmarks coming — see GitHub Discussions.
Supported Tasks
| Task | Loss examples | Metric examples |
|---|---|---|
| Classification / Multiclass | CrossEntropyLoss · FocalLoss · LabelSmoothingCE | Accuracy · F1 · AUROC |
| Regression | MSELoss · HuberLoss · QuantileLoss | MAE · RMSE · R² |
| Multi-label | BCEWithLogitsLoss · AsymmetricLoss · FocalBCELoss | HammingLoss · mAP · PerLabelAUROC |
Security
- No hardcoded credentials anywhere in the codebase.
- W&B API key read from
WANDB_API_KEYenvironment variable only. detect-secretspre-commit hook blocks accidental credential commits.
See SECURITY.md for the full policy.
Contributing
Contributions are welcome! See CONTRIBUTING.md for the full guide.
# Quick setup
git clone https://github.com/dsabarinathan/fujicv.git
cd fujicv && pip install -e ".[dev]"
pre-commit install
pytest # all 255 tests should pass
Community
- Questions / ideas: GitHub Discussions
- Bug reports: GitHub Issues
- Changelog: CHANGELOG.md
Changelog
See CHANGELOG.md for full release history.
License
Apache 2.0 — Copyright (c) 2025 FujiCV Contributors.
Metadata
Release files for fujicv 1.14.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 | |
|---|---|---|---|
| fujicv-1.14.0.tar.gz | 138.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fujicv-1.14.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 264.6 kB
Release files / fujicv-1.14.0.tar.gz
| Download URL | fujicv-1.14.0.tar.gz |
|---|---|
| Size | 138.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9e110b5d7ad5203f8e95c799c1e9735f42e450e22c291752622abfbd73ff4837
|
|
BLAKE2b-256 checksum How to use checksums |
b8585df763a5cf4166321690b736473f26042290cc0f08159289cdb880dd07ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.11
|
Release files / fujicv-1.14.0-py3-none-any.whl
| Download URL | fujicv-1.14.0-py3-none-any.whl |
|---|---|
| Size | 126.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5ff67a40101c6306801814cf3b5fce1f98beec1e91de5803c736d5573295662c
|
|
BLAKE2b-256 checksum How to use checksums |
8dd91760e22f2161a9b8d83a3fd19123c055ea34d4a0e65663a845221036d75e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.11
|