This repository provides the public training code for the AI models used in WatchMyBirds, a bird detection and recognition platform. It currently covers classifier training and tuning and will expand to include detection, data processing, active learning, and preannotation workflows.
đ EfficientNet classifiers ⢠𧪠Optuna HPO ⢠đ ONNX eval ⢠đ¤ Hugging Face export
âĄď¸ Self-contained repo: works from root â all configs, scripts, and utilities included.
đ˘ WatchMyBirds App ⢠𧪠This Repo ⢠đ¤ Model on HF ⢠đĽ Demo
This part of the project trains an image classifier to recognize different bird species from images. The training pipeline includes:
- Model selection and configuration using YAML
- Augmentation strategies including MixUp, CutMix, RandAugment, AutoAugment
- Transfer learning from pretrained EfficientNet / EfficientNetV2 models
- Fine-tuning with layer freezing and staged unfreezing
- YAML-driven configuration (see
configs/) - Robust logging with MLflow and automatic output saving to
model_registry/
We use Optuna to optimize key hyperparameters such as:
- Learning rate, optimizer type, weight decay
- Image size (guided by GPU OOM profiling)
- MixUp / CutMix probabilities
- Layer freezing depth and dropout
- Early stopping criteria
The search space is defined in model_registry/hyperparams/classifier_hpo_v1_for_optuna.yaml. We tune learning
rate, optimizer, dropout, batch size, unfreeze epoch, augmentation, and more.
OOM-aware constraints are applied using model_registry/hardware/classifier_bs_limits.yaml.
Trials log metrics like accuracy and loss. The best configurations are exported to
model_registry/hyperparams/ for reproducible training.
Open-set validation evaluates the modelâs confidence on unknown bird species using
an unlabeled folder provided via --open-val-dir (ImageFolder layout). These
images are passed through the model without ground truth labels; the modelâs
softmax confidences are used to calibrate a rejection threshold.
- Validation samples (known classes) are treated as negatives.
- Open-set samples (unknowns) are treated as positives.
We sweep thresholds and compute a binary F1-score to select a final threshold. The threshold and diagnostics are saved and visualized via:
threshold_tuning.pngconf_hist.pngmetrics.yaml
python -m optuna_tuner.tune \
--task classifier \
--study-name birds_cls_tuning \
--hp-yaml model_registry/hyperparams/classifier_hpo_v1_for_optuna.yaml \
--config config/development.yaml \
--dataset-version v1 \
--open-val-dir data/dataset/open_val- Scientific rigor in evaluation and tuning
- Engineering best practices in modular code structure and configuration
- Reproducibility of ML experiments and results
- Extensibility â this is just the first step of a full ML pipeline
Curated classifier trained on ~10k images across 29 bird species. Open-set rejection + ONNX deployment ready.
# Create env
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Prepare data (requires detection crops or raw image folder)
python -m classification_training.prepare_data_cls \
--config config/public_cls.yaml \
--model-name efficientnet_b3 \
--dataset-version v1 \
--output-version cls_v1
# Tune (optional)
python -m optuna_tuner.tune \
--task classifier \
--study-name birds_cls_tuning \
--hp-yaml model_registry/hyperparams/classifier_hpo_v1_for_optuna.yaml \
--config config/public_cls.yaml \
--dataset-version v1 \
--open-val-dir data/dataset/training/classification/images/open_val
# Train
python -m classification_training.train_efficientnet \
--config config/public_cls.yaml \
--hp-yaml model_registry/hyperparams/classifier_hpo_v1.yaml \
--model-name efficientnet_b3 \
--dataset-version v1
# Evaluate (open-set threshold tuning)
python -m classification_training.posthoc_open_val \
--model-path model_registry/classifier/<TS>/weights/best.pt \
--hp-yaml model_registry/hyperparams/classifier_hpo_v1.yaml \
--config config/public_cls.yaml \
--open-val-dir data/.../open_val \
--out-dir outputs/open_val_eval
# Evaluate ONNX
python -m classification_training.onnx_eval \
--onnx-path model_registry/classifier/<TS>/weights/best.onnx \
--class-names model_registry/classifier/<TS>/classes.txt \
--test-dir data/.../test \
--output-dir outputs/onnx_test_eval \
--visualizeclassification_training/â All classifier training scripts and helpersprepare_data_cls.pyâ Builds classifier datasets (crops + splits)train_efficientnet.pyâ Trains EfficientNet models and exports ONNXonnx_eval.pyâ Evaluates ONNX classifier on labeled test imagesevaluate_testset.pyâ Torch/ONNX quick test-set evaluatorposthoc_open_val.pyâ Openâset evaluation + threshold calibrationpublic_utils.pyâ Selfâcontained public stubs (config, logging, etc.)upload_classifier.pyâ Public wrapper; logs a hint if upstream tooling absent
config/â Example config(s) for the public pipelinepublic_cls.yamlâ Sample configuration with relative paths
model_registry/â Local model artifacts (created during training)hyperparams/classifier_hpo_v1.yamlâ Example training hyperparamshyperparams/classifier_hpo_v1_for_optuna.yamlâ Public Optuna search space
requirements.txt,environment.ymlâ Environment setupdata/â Place datasets here (see layout below)outputs/â Default location for evaluation outputs and plots
Choose one of the following:
- Pip
- python -m venv .venv && source .venv/bin/activate
- pip install -r requirements.txt
- Conda
- conda env create -f environment.yml
- conda activate watchmybirds-public
The requirements include: PyTorch (>=2.1), torchvision, timm, onnx/onnxruntime, numpy, pandas, scikitâlearn, matplotlib, Pillow, PyYAML, tqdm, tensorboard (for logs), GPUtil, OpenCV, imgaug.
The classifier expects an ImageFolderâstyle structure under your dataset root (dataset_path in the config):
data/dataset/training/classification/images/train/<class_name>/*.jpgdata/dataset/training/classification/images/val/<class_name>/*.jpgdata/dataset/training/classification/images/test/<class_name>/*.jpg
If you start from detection annotations and want to create the classifier crops and splits here, use prepare_data_cls.py.
Use config/public_cls.yaml as a starting point. Key fields:
project_name: Used for bookkeeping and run names.dataset_path: Relative path to your dataset root (e.g.,data/dataset).model_registry: Relative path for runs/artifacts (e.g.,model_registry).run_logging.output_folder: Where run metadata YAMLs are saved.models.classifier.name: EfficientNet backbone (e.g.,efficientnet_b3).
The model_registry/hyperparams/ folder contains example hyperparameters:
classifier_hpo_v1.yamlâ A strong starting point for trainingclassifier_hpo_v1_for_optuna.yamlâ Public Optuna search space (keys align with training)
Purpose: build classifier image datasets from annotations and/or existing images and produce split metadata and stats.
Highlights:
- Reads global config via
public_utils.load_config(relative paths). - Determines prepared size (square crop target) from config/limits, or CLI override.
- Builds/updates the following under
data/dataset/training/classification/:images/train|val|test/<class>/âŚâ image crops per class and split- Optionally
images/open_val/<class>/âŚâ openâset images for threshold calibration configs/cls_data_<project>_<version>.yamlâ dataset reference used by training
- Cropping from detections:
- Converts bounding boxes to centered, padded square crops with OpenCV
- Resizes crops to the prepared size
- Class balancing:
- Downsamples classes above
max_crops_per_class - Augments underrepresented classes using
imgaugto approach the target count
- Downsamples classes above
- Splitting:
- Stratified train/val/test splits; optional caps for val/test
- Stats and transparency:
- Writes summary JSON/YAML and perâsplit counts
- Records dropped rows (if any) and reasons
- CLI (from repo root):
- python -m classification_training.prepare_data_cls
--config config/public_cls.yaml
--model-name efficientnet_b3
--dataset-version v1
--output-version cls_v1
- python -m classification_training.prepare_data_cls
Purpose: train EfficientNet classifiers with strong defaults, log metrics/artifacts, and export deployable ONNX.
Key features:
- Model/backbone
- EfficientNet variants via
timm(e.g.,efficientnet_b0..b5,tf_efficientnetv2_*) - Resolves input size from model defaults or
--img-size/YAML
- EfficientNet variants via
- Data transforms
- Train: RandomResizedCrop + HorizontalFlip + AutoAugment(IMAGENET) + Normalize + RandomErasing
- Val: Resize(by crop_pct) + CenterCrop + Normalize
- Mixup/CutMix (optional)
- Switchable via hyperparams; uses SoftTarget loss when enabled
- Optimizer & schedule
- AdamW or Adam, warmup (LinearLR) + CosineAnnealingLR
- Progressive unfreezing
- Trains head first, then unfreezes and rebuilds optimizer at
unfreeze_epoch
- Trains head first, then unfreezes and rebuilds optimizer at
- Early stopping
- Monitors
lossoracc, with absolute or relative deltas
- Monitors
- Logging
- TensorBoard logs (in public build, start is logged but not autoâspawned)
- Perârun YAML metadata via
RunLogger
- Exports and registry
- Saves
best.pt/last.ptand exportsweights/best.onnx - Writes
metrics.json(accuracy, topâ5, loss, confusion matrix, git hash, notes) - Saves pointers in
model_registry/classifier/latest_models.json - Copies
used_hyperparams.yamlandused_dataset.yamlinto the run folder
- Saves
- Openâset support (optional)
- If
open_val.csvexists underdataset_path, runs CPU openâset evaluation to log confidence stats - Produces
open_val_predictions.csv,open_set_eval.yaml, optional histograms - Calibrates a rejection threshold and writes
calibrated_threshold.yaml
- If
Inputs:
- Config:
--config config/public_cls.yaml - Hyperparams:
--hp-yaml model_registry/hyperparams/classifier_hpo_v1.yaml - Backbone:
--model-name efficientnet_b3
Outputs (example):
model_registry/classifier/<TIMESTAMP>/weights/âbest.onnx,best.pt,last.pttensorboard/â TB event filesmetrics.json,used_hyperparams.yaml,used_dataset.yaml- Optional:
open_val_predictions.csv,calibrated_threshold.yaml
model_registry/classifier/latest_models.json(pointer)
CLI example:
- python -m classification_training.train_efficientnet
--config config/public_cls.yaml
--hp-yaml model_registry/hyperparams/classifier_hpo_v1.yaml
--model-name efficientnet_b3
--dataset-version v1
--notes "public training run"
Purpose: evaluate a checkpoint (.pt) on an openâset directory and find a rejection threshold.
What it does:
- Builds a CPU model from
timmwith providedhp(image_size, dropout) - Runs inference on
open_val_dir, logs confidences, and writes:open_val_predictions.csvâ perâsample resultsopen_set_eval.yamlâ summary (N, avg_conf, %below threshold)conf_hist.png,threshold_tuning.pngâ plotscalibrated_threshold.yamlâ best F1 threshold (open as positive class)
CLI example:
- python -m classification_training.posthoc_open_val
--model-path model_registry/classifier//weights/best.pt
--hp-yaml model_registry/hyperparams/classifier_hpo_v1.yaml
--config config/public_cls.yaml
--open-val-dir data/dataset/training/classification/images/open_val
--dataset-version v1
--out-dir outputs/open_val_eval
Purpose: run ONNX classifier on labeled test images and compute rich metrics.
What it does:
- Loads ONNX with CPU provider, infers input size from model input tensor
- Builds ImageFolder test set and label mapping (or from a provided
classes.txt) - For each image: preprocess â logits â probs â topâ1 + confidence
- Computes and writes:
predictions.csvâ path, true_label, pred_label, confidence, is_rejectedmetrics.yamlâ accuracy (overall + nonârejected), rejection rate, perâclass PRF, topâk accuracy, best threshold by F1conf_hist.png,threshold_tuning.pngâ plots
- Optional visualization (
--visualize): misclassifications and lowâconfidence grids
CLI example:
- python -m classification_training.onnx_eval
--onnx-path model_registry/classifier//weights/best.onnx
--class-names model_registry/classifier//classes.txt
--test-dir data/dataset/training/classification/images/test
--output-dir outputs/onnx_test_eval
--threshold 0.5
The training dataset used in this project is based on Creative Commonsâlicensed bird images from iNaturalist. To ensure compliance and modularity, a reduced public version of the dataset (including attribution, image URLs, hashes, and annotations) is maintained in a separate repository:
đ WatchMyBirds-Data (GitHub)
- Bounding box annotations in COCO format
- Attribution information and image hash for deduplication
You can use this CSV to download the training data yourself using any scripting method (e.g., wget, requests). Use the image_hash as filename to save images into data/raw/. Annotation JSON is stored per sample in the approved_annotation column.
| Dataset | Model | Top-1 Accuracy | Threshold | Rejection Rate |
|---|---|---|---|---|
| val | efficientnet_b3 | 94.8% | 0.61 | 5.2% |
| test | ONNX export | 94.2% | 0.59 | 6.4% |
- Topâ1 accuracy (overall): 0.9015
- Accuracy (nonârejected): 0.9792
- Rejection rate: 0.0794
- F1 (micro): 0.9792, F1 (macro): 0.9788
- Best threshold by F1: 0.10 (F1=0.9412)
Use model_registry/hyperparams/classifier_hpo_v1.yaml as a solid baseline for training. For hyperparameter optimization, see model_registry/hyperparams/classifier_hpo_v1_for_optuna.yaml â it defines a public search space aligned with the training scriptâs expected keys (e.g., num_epochs, adamW_weight_decay). Your Optuna runner should read that YAML and pass the chosen values via CLI or merge into the training YAML before invoking the trainer.
Artifacts to upload typically include best.onnx and classes.txt. Use huggingface-cli or huggingface_hub from your environment.
Example commands (replace placeholders):
- export HF_TOKEN=hf_âŚ
- huggingface-cli upload /<repo_name> model_registry/classifier//weights/best.onnx --path-in-repo best.onnx --repo-type model --token "$HF_TOKEN"
- huggingface-cli upload /<repo_name> model_registry/classifier//classes.txt --path-in-repo classes.txt --repo-type model --token "$HF_TOKEN"
classification_training/upload_classifier.py will attempt to call upstream project tooling if present; otherwise it logs a helpful warning.
- All scripts use relative paths and log warnings instead of failing when optional services (e.g., TensorBoard) are unavailable.
- Each training run writes:
- Run YAML metadata into
model_registry/run_logs/ - A timestamped folder under
model_registry/classifier/with weights and artifacts
- Run YAML metadata into
public_utils.pycentralizes config loading, logging, and small helpers to keep this repo standalone and publicâready.
- Import errors when running scripts directly:
- Prefer module mode:
python -m classification_training.<script>from repo root. - Scripts include fallback imports to work when invoked directly.
- Prefer module mode:
- CUDA/GPUs:
- Training uses CUDA if available; ONNX eval runs on CPU by default.
- ONNXRuntime GPU:
- Replace
onnxruntimewithonnxruntime-gpuin your environment if needed and ensure CUDA compatibility.
- Replace
Help improve bird recognition! đŚ
Open a GitHub issue titled "Join Labeling" and weâll invite you to our Label Studio instance.
đ Join the labeling project
This public package is a subset of the broader WatchMyBirds pipeline, focused on classification. Contributions and issues are welcome for the public code included here.





