Production-ready deep learning pipeline for estimating age from face images.
Train, evaluate, and deploy in minutes β on CPU or GPU.
- β¨ Key Features
- π Quick Start
- π Results & Benchmarks
- ποΈ Architecture
- π Project Structure
- π¦ Installation
- π Dataset
- π Training
- π Evaluation
- π₯οΈ Gradio Demo
- π€ Export Model
- π Literature Review
- πΊοΈ Roadmap
- π Citation
- π€ Contributing
- π₯ 5 Backbones β MobileNetV3-Large, EfficientNet-B0/B3, ResNet-50, ViT-S/14 DINOv2
- π Mean-Variance Loss (CVPR 2018) β models age as a probability distribution over [0β100], outperforming plain L1/MSE by 0.3β0.5 MAE
- β‘ AMP + AdamW β mixed-precision training with differential learning rates and OneCycleLR warmup
- ποΈ Clean package layout β
src/,scripts/,demo/,tests/,configs/following standard PyTorch practices - ποΈ OmegaConf config β change any hyperparameter from the CLI without editing code
- π Bias analysis β per-epoch breakdown of MAE by age group, gender, and ethnicity
- π₯οΈ Gradio demo β upload a photo β see predicted age + full probability distribution
- π€ Export ready β TorchScript (trace) and ONNX with dynamic batch support
- β 37 unit tests + GitHub Actions CI
# 1. Clone
git clone https://github.com/Ebimsv/Facial_Age_estimation_PyTorch.git
cd Facial_Age_estimation_PyTorch
# 2. Install (set up PyTorch for your CUDA version first β see Installation section)
pip install -r requirements.txt
# 3. Prepare data (point to your UTKFace folder)
python scripts/prepare_data.py --dataset-dir dataset/utkcropped --plot
# 4. Train (defaults: EfficientNet-B3, Mean-Variance Loss, 100 epochs, AMP)
python scripts/train.py
# 5. Run the Gradio demo
python demo/app.py --checkpoint checkpoints/best_efficientnet_b3_*.ptπ‘ No GPU? The pipeline runs on CPU too β just set
training.use_amp=falseinconfigs/default.yaml.
Evaluated on the UTKFace held-out test set (~2,000 images, ages 0β84).
| Model | #Params | Input Size | Loss | MAE β | CS@5 β | Inference |
|---|---|---|---|---|---|---|
| MobileNetV3-Large | 5.5M | 224Γ224 | Mean-Variance | TBD | TBD | ~5ms |
| EfficientNet-B0 | 5.3M | 224Γ224 | Mean-Variance | TBD | TBD | ~6ms |
| EfficientNet-B3 | 12M | 224Γ224 | Mean-Variance | TBD | TBD | ~8ms |
| ResNet-50 | 25.6M | 224Γ224 | Mean-Variance | TBD | TBD | ~12ms |
| ViT-S/14 DINOv2 | 22M | 224Γ224 | Mean-Variance | TBD | TBD | ~15ms |
| ResNet-50 (v1 baseline) | 25.6M | 128Γ128 | L1 | 4.73 | β | β |
MAE = Mean Absolute Error in years (lower is better).
CS@5 = Cumulative Score β fraction of predictions within 5 years of ground truth (higher is better).
Results for the new models will be added after training. Runpython scripts/evaluate.py --checkpoint <path> --biasto reproduce.
graph TD
A[Face Image] --> B[Preprocessing\n224Γ224 Β· Normalize]
B --> C{Backbone}
C --> D1[MobileNetV3-Large\n5.5M params]
C --> D2[EfficientNet-B3\n12M params β
]
C --> D3[ResNet-50\n25.6M params]
C --> D4[ViT-S/14 DINOv2\n22M params]
D1 & D2 & D3 & D4 --> E[Head\nDropout β Linear β GELU β Linear]
E --> F[101 Logits\nage classes 0 to 100]
F --> G[Softmax\nAge Probability Distribution]
G --> H[E_age = sum of p_i times i\nPredicted Age]
Instead of treating age as a single scalar, the model outputs a probability distribution over 101 age classes. This encodes the inherent ambiguity of facial aging and provides a richer training signal.
Loss = Ξ»_mean Γ |E[age] β true_age| + Ξ»_variance Γ Var[age]
β β
Penalise wrong mean Penalise high uncertainty
This consistently outperforms L1/MSE regression by 0.3β0.5 MAE on UTKFace.
Reference: Pan et al., "Mean-Variance Loss for Deep Age Estimation from a Face", CVPR 2018.
Facial_Age_estimation_PyTorch/
βββ configs/
β βββ default.yaml # All hyperparameters β edit here, not in code
βββ src/
β βββ data/
β β βββ dataset.py # UTKDataset (side-effect free, importable)
β β βββ transforms.py # Train / val / test transform factories
β βββ models/
β β βββ age_estimator.py # AgeEstimationModel (5 backbones)
β βββ training/
β β βββ losses.py # MeanVarianceLoss + L1AgeLoss
β β βββ trainer.py # Trainer: AMP, AdamW, OneCycleLR, logging
β βββ utils/
β βββ metrics.py # AverageMeter, MAE, CS@5
β βββ seed.py # set_seed() for reproducibility
βββ scripts/
β βββ prepare_data.py # Stratified CSV split from raw UTKFace
β βββ train.py # Training entry point (CLI + OmegaConf)
β βββ evaluate.py # Test-set eval + bias breakdown
β βββ inference.py # Single-image / batch inference
β βββ export_model.py # TorchScript & ONNX export
βββ demo/
β βββ app.py # Gradio web demo
βββ tests/ # 37 unit tests (pytest)
βββ notebooks/ # EDA notebooks (coming soon)
βββ pics/ # Images used in README
βββ checkpoints/ # Saved model weights (git-ignored)
βββ csv_dataset/ # Pre-split CSV files
βββ .github/workflows/ci.yml # GitHub Actions CI
βββ pyproject.toml
βββ requirements.txt
Step 1 β Clone the repo
git clone https://github.com/Ebimsv/Facial_Age_estimation_PyTorch.git
cd Facial_Age_estimation_PyTorchStep 2 β Install PyTorch (match your CUDA version from pytorch.org)
# CUDA 12.8 (RTX 40/50 series)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
# CUDA 11.8
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
# CPU only
pip install torch torchvisionStep 3 β Install project dependencies
pip install -r requirements.txt
# For the Gradio demo + W&B logging:
pip install gradio wandbStep 4 β Verify
python -m pytest tests/ -v -k "not vit_small"
# Expected: 37 passedThis project uses the UTKFace dataset β 20,000+ face images labelled with age (0β116), gender, and ethnicity.
| Dataset | Images | Age Range | Labels |
|---|---|---|---|
| UTKFace | ~23,000 | 0 β 116 | Age, Gender, Ethnicity |
| MORPH | ~55,000 | 16 β 77 | Age, Gender, Race |
| FG-NET | 1,002 | 0 β 69 | Age |
Download: Get the cropped UTKFace version from Kaggle (
utkcroppedfolder).
# Creates: csv_dataset/{utkface_dataset,train_set,valid_set,test_set}.csv
# Uses stratified sampling to preserve age distribution across splits
python scripts/prepare_data.py \
--dataset-dir dataset/utkcropped \
--train-ratio 0.80 \
--val-ratio 0.10 \
--plot # saves split_distributions.png# Train EfficientNet-B3 with Mean-Variance Loss (recommended)
python scripts/train.pyAny value in configs/default.yaml can be overridden from the command line:
# Change backbone
python scripts/train.py model.name=resnet50
# Regression mode (L1 loss, 1 output node β for old checkpoints)
python scripts/train.py model.output_dim=1 loss.type=l1
# Longer training with W&B logging
python scripts/train.py training.epochs=150 logging.use_wandb=true
# Lower memory (if < 8GB VRAM)
python scripts/train.py training.batch_size=48 data.img_size=192
# Lightweight edge model
python scripts/train.py model.name=mobilenet_v3 training.batch_size=128model:
name: "efficientnet_b3" # mobilenet_v3 | efficientnet_b0/b3 | resnet50 | vit_small
output_dim: 101 # 101 = Mean-Variance Loss | 1 = plain regression
training:
epochs: 100
batch_size: 96 # EfficientNet-B3 @ 224px, 8GB VRAM
use_amp: true # Mixed precision (strongly recommended)
loss:
type: "mean_variance" # mean_variance | l1tensorboard --logdir runs/# Standard evaluation on the test split
python scripts/evaluate.py --checkpoint checkpoints/best_efficientnet_b3_*.pt
# Demographic bias breakdown (age group, gender, ethnicity)
python scripts/evaluate.py --checkpoint checkpoints/best_efficientnet_b3_*.pt --bias
# Save per-image predictions to CSV
python scripts/evaluate.py --checkpoint checkpoints/best_*.pt \
--bias --save-csv results/predictions.csvExample output:
==================================================
Split : test (n=2041)
MAE : 4.12 years
CS@5 : 0.713 (71.3% within 5 years)
CS@10 : 0.941 (94.1% within 10 years)
==================================================
ββ MAE by Gender ββββββββββββββββββββββββββββββββ
Female 4.05
Male 4.19
ββ MAE by Ethnicity βββββββββββββββββββββββββββββ
Asian 3.91
Black 4.44
Indian 4.28
White 4.02
python scripts/inference.py \
--checkpoint checkpoints/best_*.pt \
--image img_test/30_1_2.jpg \
--output img_test/output.jpg# Local demo
python demo/app.py --checkpoint checkpoints/best_*.pt
# Share publicly via Gradio tunnel
python demo/app.py --checkpoint checkpoints/best_*.pt --shareThe demo shows both the predicted age and the full probability distribution over all 101 age classes, giving you insight into model confidence.
π A live demo on Hugging Face Spaces is coming soon!
# TorchScript (fastest for deployment β uses torch.jit.trace)
python scripts/export_model.py --checkpoint checkpoints/best_*.pt
# ONNX (for ONNX Runtime, TensorRT, OpenVINO, etc.)
python scripts/export_model.py --checkpoint checkpoints/best_*.pt --format onnxClick to expand β 7 key papers reviewed
| Paper | Summary | Key Idea | Code |
|---|---|---|---|
| SwinFace (2023) | Multi-task Swin Transformer for face analysis | Multi-Level Channel Attention | GitHub |
| Unraveling Age Estimation Puzzle (2022) | Benchmark & analysis of what actually matters | Data quality > model choice | GitHub |
| MiVOLO (2023) | ViT-based age + gender; works on occluded faces | Uses body context, not just face | GitHub |
| CORAL (2020) | Ordinal regression for age estimation | Rank-consistent binary subtasks | GitHub |
| Deep Regression Forests (2018) | End-to-end DRF for heterogeneous age features | Joint data partition + abstraction | GitHub |
| Mean-Variance Loss β (2018) | Distribution learning for age estimation | Used in this repo | Paper |
| FaceXFormer (2024) | Unified transformer for all face analysis tasks | Token-based multi-task decoder | GitHub |
Supported Datasets
| Dataset | Images | Age Range | Notes |
|---|---|---|---|
| UTKFace (used here) | ~23,000 | 0β116 | In-the-wild, diverse |
| MORPH | 55,134 | 16β77 | Mugshot style |
| CACD | 163,446 | β | Celebrity, web-collected |
| FG-NET | 1,002 | 0β69 | Small, cross-age pairs |
| Adience | 26,580 | Age groups | 8 coarse age groups |
- Professional package structure β
src/,scripts/,demo/,tests/,configs/ - OmegaConf YAML config β all hyperparameters in one file, CLI overrides
- 5 backbone support β MobileNetV3-Large, EfficientNet-B0/B3, ResNet-50, ViT-S/14 DINOv2
- Mean-Variance Loss (CVPR 2018) β distribution-based age estimation
- Modern training pipeline β AMP, AdamW with differential LRs, OneCycleLR, gradient clipping
- Stratified data splits β stratified train/val/test CSV generation
- 37 unit tests β model, losses, transforms, dataset; all passing
- GitHub Actions CI β automated lint + test on every push
- Gradio demo β with age probability distribution visualisation
- TorchScript + ONNX export
- Bias evaluation β per age-group, gender, and ethnicity MAE breakdown
- Single-image + batch inference script
- pyproject.toml β proper packaging with optional extras
- Comprehensive
.gitignoreβ no accidental weight/dataset commits
- Train EfficientNet-B3 on UTKFace and populate the results table with real MAE numbers
- Hugging Face Spaces demo β live, always-on demo accessible from the README
- GradCAM / Attention heatmaps β visualise which facial regions drive age predictions
- Real-time webcam inference β
scripts/webcam.pywith OpenCV face detection - Cross-dataset evaluation β train on UTKFace, test on MORPH / FG-NET
- EDA Jupyter Notebook β
notebooks/01_EDA.ipynbwith interactive plots - Pre-commit hooks β ruff + formatting checks enforced on commit
- ONNX Runtime inference script β for CPU-only deployment
- Benchmark on MORPH dataset β broader reproducibility
If you use this repository in your research, please cite:
@misc{mousavi2024facial,
title = {Facial Age Estimation with PyTorch},
author = {Mousavi, Ebrahim},
year = {2024},
publisher = {GitHub},
url = {https://github.com/Ebimsv/Facial_Age_estimation_PyTorch}
}The Mean-Variance Loss implemented in this repo:
@inproceedings{pan2018mean,
title = {Mean-Variance Loss for Deep Age Estimation from a Face},
author = {Pan, Hongyu and Han, Hu and Shan, Shiguang and Chen, Xilin},
booktitle = {Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition},
pages = {5285--5294},
year = {2018}
}Contributions are welcome! Whether it's a new backbone, a better loss function, a bug fix, or improved documentation.
# Fork, clone, create a branch
git checkout -b feat/your-feature
# Install dev dependencies
pip install -r requirements-dev.txt
pre-commit install
# Make changes, then verify
ruff check src/ scripts/ tests/
python -m pytest tests/ -v
# Open a Pull Request πSee CONTRIBUTING.md for detailed guidelines, including how to add a new backbone.
This project is licensed under the MIT License β see LICENSE for details.
If this project helped you, please consider giving it a β β it means a lot!
Made with β€οΈ by Ebi Mousavi





