Age & Gender Estimator
Finds every face in a photo, then estimates each person's age and gender. Our FaceDetector (a single-shot face + landmark CNN trained in the face-recognition project) finds the faces. Each face crop goes through one EfficientNet-B2 (ImageNet init, fine-tuned on UTKFace) with two heads: a softmax over 90 age bins and a male/female softmax. Grad-CAM shows which regions of the face decided each estimate.
Model
| Face detector | FaceDetector from the face-recognition project (trained there from scratch; same code and face_detector.safetensors). Faces scoring ≥ 0.5 are kept, most confident first, and each is padded by 20 px before cropping |
| Network | torchvision efficientnet_b2 trunk (features + global average pool, 1408-d) → age_head = Dropout(0.3) → Linear(1408, 90) and gender_head = Dropout(0.3) → Linear(1408, 2) |
| Parameters | 7,830,622 (age/gender net; the face detector adds 2,700,598) |
| Preprocessing | model.get_transform(): resize 288 (bicubic), center-crop 288×288, ImageNet mean/std |
| Age bins | 0-1, one bin per year 2 … 89, 90+ (UTKFace has no age-0 faces and < 10 faces for most ages ≥ 90) |
| Headline age | expected value Σ pᵢ·repᵢ, where repᵢ is the mean true age of the training faces in bin i (stored in config.json) |
| Output | {"faces": [{"box", "age", "age_range", "age_top", "gender", "confidence", "detected"}], "n_faces"} (see below) |
| Files | model.safetensors + config.json (PyTorchModelHubMixin; bins, labels, representative ages, detector settings), model.py, face_detector.safetensors, handler.py, requirements.txt |
Per face: box = [x1, y1, x2, y2] pixels · age = expected age in years · age_range = central 80 %
interval of the predicted age distribution · age_top = top-5 bins → probability · gender =
{"male": p, "female": p} · confidence = face-detector score · detected = false when no face
was found and the whole image was classified instead (then n_faces is 0).
Usage
from huggingface_hub import hf_hub_download, snapshot_download
import sys
path = snapshot_download("shalev396/age-estimator")
sys.path.insert(0, path)
import model
predictor = model.load(path, device="cpu") # or "cuda"
image = hf_hub_download("shalev396/age-estimator", "examples/face_age_032.jpg", repo_type="space")
result = predictor.predict(image) # a PIL image works too
print(result["faces"][0]["age"], result["faces"][0]["gender"])
views = predictor.gradcam(image, result) # {"age": PIL image, "gender": PIL image}
model.annotate(image, result).save("annotated.jpg")
Requirements: torch, torchvision, numpy, pillow, huggingface_hub, safetensors.
- Space API: shalev396/age-estimator (
/predict, free). - Inference Endpoint: Deploy → Inference Endpoints.
handler.pytakes{"inputs": <base64 image>}(or raw image bytes) and returns the dict above. It usescudawhen the endpoint has a GPU.
Training
- Data: UTKFace via
nu-delta/utkface: 23,705 aligned face crops with age (1–116) and gender. Random 90/10 split with seed 42: 21,334 training and 2,371 validation faces. UTKFace is for non-commercial research only. - Recipe: ImageNet EfficientNet-B2, whole network fine-tuned. Loss = CE(age bins) + CE(gender),
Adam(lr=1e-3), batch 64, 10 epochs, horizontal flip + mild colour jitter, mixed precision on CUDA. The epoch with the lowest validation age MAE is kept. - This checkpoint: converted from the original training run (the project's earlier standalone
repo,
train.py, best epoch 9 of 10). The.pthstate_dict was mapped 1:1 intoAgeGenderNetand saved as safetensors. On the Space examples the converted pipeline returns the same probabilities as the original code; boxes are now clamped to the image edges. Training hardware and time were not recorded.
Full code: training/ · Colab.
Evaluation
| metric (validation) | value |
|---|---|
| age_mae | 4.5939 |
| age_rmse | 6.7993 |
| age_within5 | 0.6719 |
| gender_accuracy | 0.9325 |
All numbers are on the 2,371-face validation split (the split that also picked the best epoch;
UTKFace has no official test split), computed locally on CPU with this repo's training/src code.
They reproduce the metrics stored in the original checkpoint to 7 decimals (age MAE 4.594 y,
±5 years 67.2 %, gender 93.25 %). age_within5 is the share of faces with |error| ≤ 5 years. The
face detector is not involved here: UTKFace images are already face crops, so the network sees the
whole 200×200 image.
Experiments
| variant (validation, 2,371 faces) | age MAE (y) | age RMSE (y) | within ±5 y | gender accuracy |
|---|---|---|---|---|
| EfficientNet-B2, expected age (deployed) | 4.59 | 6.80 | 67.2 % | 93.3 % |
| same weights, argmax bin instead of expected age | 5.33 | 8.03 | 65.1 % | 93.3 % |
| baseline: median training age (29) + majority gender (male) | 15.02 | 20.53 | 34.0 % | 51.5 % |
Rows come from metrics.json (comparison). Only one network was trained (the original run). The
other rows are a different decoding of the same weights and a no-learning reference. Turning the
90-bin softmax into an expected value cuts the age error by 0.74 years compared with taking the
most likely bin. Training curves of the original run were not saved, so there is no
training_curves.png here. A retrain with training/ writes one.
The error is lowest for children (2.0 y for 0–12) and young adults (3.2 y for 20–29). It is about 7 years for every group from 40 to 79, where the training data is thinner and apparent age varies more.
Limitations
- Apparent age from a photo. The model estimates how old a face looks on UTKFace-style crops. Errors of 5+ years are common (about a third of validation faces) and grow for older people.
- Binary gender labels. UTKFace labels gender as male/female from appearance. The gender head can only output those two and says nothing about a person's gender identity.
- Dataset bias. UTKFace is skewed toward certain ages (many 20–35 year-olds and infants) and ethnicities. Accuracy on under-represented groups, lighting, poses, make-up or image styles is not measured here.
- Validation, not a held-out test set. The reported numbers are on the validation split that also picked the best epoch, so they are slightly optimistic.
- Detection. The detector can miss very small, profile or occluded faces. Then the whole image is classified (
detected: false), which is much less reliable. - Not for decisions about people. Do not use it for age verification, access control, hiring or any consequential decision. The UTKFace licence also restricts use to non-commercial research.
- Downloads last month
- 13
Dataset used to train shalev396/age-estimator
Space using shalev396/age-estimator 1
Evaluation results
- age_mae on UTKFacevalidation set self-reported4.594
- age_rmse on UTKFacevalidation set self-reported6.799
- age_within5 on UTKFacevalidation set self-reported0.672
- gender_accuracy on UTKFacevalidation set self-reported0.933



