Acoustic Species Identification in the Pantanal, South America
234 species · Macro ROC-AUC · Public LB 0.950
GitHub's renderer can't handle large notebooks. View full notebook on nbviewer →
This repository contains my iteration on the BirdCLEF+ 2026 competition — an acoustic species-identification challenge over 234 bird species of the Pantanal wetlands (South America). The task is to predict per-species presence probabilities for every 5-second window of 1-minute test soundscapes, scored by macro ROC-AUC.
The notebook in this repo, birdclef-2026-eos-7-bz-efficientnet-b0.ipynb, reaches Public LB = 0.950 by combining a four-model ensemble (Perch v2 + ProtoSSM + Distilled-SED + Karnakbayev Power-Optimization) with a custom 5-fold EfficientNet-B0 diversity branch that I trained from scratch on 35,549 Xeno-Canto + iNaturalist clips (CV macro-AUC 0.9564), plus a taxonomy-aware smoothing post-processor (genus α=0.15, class α=0.05).
My personal contribution: the entire EfficientNet-B0 5-fold branch (Cell 33), its training pipeline, ONNX export, species-mapping logic, and probability-blend integration. All other ensemble members are properly credited to the original community authors below.
| Submission | Public LB |
|---|---|
| Baseline (EoS.7 bz ensemble + TAX_SMOOTHING) | 0.950 |
| My EfficientNet-B0 iteration on EoS.6 bz | 0.949 |
EfficientNet-B0 5-fold cross-validation (my training, 35,549 clips):
| Fold | Val macro-AUC |
|---|---|
| 0 | 0.9562 |
| 1 | 0.9585 |
| 2 | 0.9540 |
| 3 | 0.9525 |
| 4 | 0.9610 |
| Mean | 0.9564 |
The pipeline is a multi-branch heterogeneous ensemble orchestrated by a master solutions dictionary (Cell 2). Each branch is a stand-alone inference graph; outputs are fused via weighted blending plus a final taxonomy-aware smoothing pass.
┌────────────────────────────────────────────────────────────────────────────┐
│ BirdCLEF+ 2026 · 60s soundscape (.ogg) │
│ 12 windows × 5s · SR=32 kHz · 234 classes │
└────────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────┬───────────┴───────────┬──────────────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Model_22 │ │ Model_51 │ │ Model_74 │ │ Model_1 │
│ ProtoSSMv5 │ │ Karnakbayev │ │ Karnakbayev │ │ Distilled │
│ + Perch v2 │ │ PowerOpt │ │ PowerOpt(74) │ │ SED EffB0 │
│ (TF/ONNX) │ │ LB 0.948 │ │ LB 0.948 │ │ (PyTorch) │
│ LB 0.928 │ │ xSED 0.6/0.4 │ │ xSED 0.6/0.4 │ │ LB 0.917 │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────────────┘
│ w=0.022 │ w=0.9695 │ w=0.0085
│ │ │ (PSSM saved as subm_74p)
└──────────────────┴─────────────────────┘
│
▼
┌──────────────────────────────┐
│ TAX_SMOOTHING POSTPROC │ ← v221 (genus α=0.15,
│ genus + class group means │ class α=0.05)
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ submission.csv (anchor) │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────────────┐
│ 🦅 EfficientNet-B0 5-Fold Branch │ ← MY CONTRIBUTION
│ (Cell 33 · ONNX · weight 0.05) │ (Cell 32–33)
│ Trained on 35,549 XC+iNat clips │
│ CV macro-AUC 0.9564 │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────┐
│ Final submission.csv │ → Public LB 0.950
└──────────────────────────────┘
solutions = {
'type_add' : 'TAX_SMOOTHING',
'task1' : 'run SED once',
'task2' : {'save PSSM for Model_74 as': 'subm_74p.csv'},
'Models' : [
{'Model': 'Model_22', 'subm': 'subm_22.csv', 'weight': 0.022, 'xSED': [], 'LB': '0.928'},
{'Model': 'Model_74', 'subm': 'subm_74p.csv', 'weight': 0.0085, 'xSED': [], 'LB': '0.949'},
{'Model': 'Model_51', 'subm': 'subm_51.csv', 'weight': 0.9695, 'xSED': [0.60,0.40], 'LB': '0.949'},
]
}Stylised HTML header identifying the iteration, baseline LB, and the EfficientNet contribution.
Markdown table cataloguing every public model referenced by ID (Model_1 / 21 / 22 / 51 / 52 / 73 / 74) with original Kaggle authors, LB scores, and blend recipes. Documents the single-line patch taken from the Japanese expert swordsman to the Model_51 / Model_74 post-processing:
before: proto_cont = (xctx > 0.88) & (rank_proto > 0.75) & (p_sed < 0.12) & (~fake_only)
after : proto_cont = (xctx > 0.88) & (rank_proto > 0.77) & (p_sed < 0.14) & (~fake_only)
solutions dict (ver.11): three-way blend of Model_22 (ProtoSSM, w=0.022), Model_74 (post-PSSM, w=0.0085) and Model_51 (Karnakbayev, w=0.9695) with TAX_SMOOTHING as the additive type. Model_51 receives the cross-SED schema [0.60, 0.40].
Flattens the solutions dict into parallel lists (_ensemble_models, _files_subm, _weights, _xsed, _lbs) and resolves the one-time SED flag.
Skipped under ver.11 — but fully wired for the "run SED once" task. Architecture:
- Backbone:
tf_efficientnet_b0.ns_jft_in1k(timm), 256-mel spectrograms, n_fft=2048, hop=512, fmin=20, fmax=16000. - GeM frequency pooling with learnable p (init 3.0) — sharpens focus on bird-vocalisation bands.
- Attention bottleneck: 512-dim dense → 1D conv → frame-wise → clip-level aggregation.
- Perch v2 distillation head: GAP + linear projection into the frozen 1536-dim Perch embedding space (MSE loss, α=1.0).
- Augmentation: gain jitter ±6 dB, noise SNR 10–30 dB, SpecAugment (GPU), Focal-Focal + Focal-Soundscape MixUp (β=0.4), per-class minimum sample = 20.
- Training: 5 folds, 25 epochs, AdamW lr=5e-4, cosine + 2-epoch warmup, batch 64.
Installs ONNX Runtime 1.24 + TensorFlow 2.20 from bundled wheels. CPU-only by competition constraint. Operates on the 1536-d Perch v2 embeddings extracted from 12 × 5-s windows per file.
Same Perch-embedding feature path as Model_21 but with the V18 sequential upgrade:
- Metadata injection:
site_id+hour_utc→ 24-dim → added to embeddings. - Selective SSM (Mamba-style): 4-layer bidirectional, d_model=320, d_state=32.
- Temporal cross-attention: 8-head, captures long-range (dawn-chorus / counter-call) interactions across the full minute.
- Prototypical classification: temperature-scaled cosine similarity vs learnable class prototypes (not a linear head).
- MLP / LightGBM probes: class-specific stacking on the sequence embeddings.
Documents the exp019 / exp017 scalar-probe sequence used by Sun-Derek-kiz to land on LB 0.948 → 0.949:
apply_prior(λ_prior): 0.4 → 0.5 (exp017)rank_aware_scaling(power): 0.5 → 0.6 (exp019)- Single-scalar-at-a-time methodology, fully attributable.
Embeds the entire Karnakbayev_PowerOptimization_LB0948 pipeline. Writes subm_karnakbayev_power_optimization.csv and, via cross-SED [0.60, 0.40], also produces subm_51.csv.
Identical to Model_51 but pulls its xSED schedule from solutions['Models'][1] instead of [2]. Provides the alternative blending lane used in earlier versions.
Discusses the dual-pipeline architecture (SED + ProtoSSMv5) and the exp_067 / exp_066_v6_prior060 scalar-probe series; lambda_prior 0.60 → 0.65.
Same code as Model_51/52 but writes subm_karnakbayev_power_optimization_73.csv. Uses xSED from solutions['Models'][1].
Same code as Model_73 but uses xSED from solutions['Models'][2]. This is the model whose PSSM is captured and re-emitted as subm_74p.csv per solutions['task2'].
Reads sample_submission.csv, partitions the 234 species into two halves by name ('son' not in label), prints the cut: half_1 = 117, half_2 = 117. Sets up the split-weighting scheme used by division_attention.
Defines direct_add2 / 3 / 4 (weighted linear blend) and rank_1_add2 / 3 (rank-percentile blend with ε-clipping and row_id reconciliation). All operate on the per-model subm_*.csv outputs.
The active blender for the division_attention mode. Three submissions are merged on row_id; each species column is reweighted by sub_w1 = [0.014, 0.021, 0.965] if i < 117 (half_1) else sub_w2 = [0.0137, 0.0213, 0.965] (half_2).
Post-processor that contributed the final jump from 0.949 → 0.950:
- Calls the underlying additive blender (
func_add, e.g.direct/division_attention). - Loads
taxonomy.csv, builds species→genus and species→class maps. - For each multi-member genus group:
probs ← (1−α_genus) · probs + α_genus · genus_meanwith α_genus = 0.15. - For each multi-member class group: same with α_class = 0.05.
- Returns the smoothed DataFrame.
This pulls correlated siblings toward their group mean — a Bayesian-style shrinkage that reduces single-class overconfidence and consistently adds ≈+0.001 macro-AUC on this dataset.
Selects f_add based on solutions['type_add']:
'rank.1'→rank_1'direct'→direct'division_attention'→division_attention'TAX_SMOOTHING'→f_TAX_SMOOTHING_POSTPROC✅ (active)
submission = f_add()
submission.to_csv("submission.csv", index=True)This anchor file is what the EfficientNet branch in Cell 33 then blends against.
This is the entire iteration I added to the public EoS.7 baseline.
| Backbone | tf_efficientnet_b0.ns_jft_in1k (timm) |
| Input | 5-second 32 kHz audio → mel-spectrogram (128 mels) → resized to 224×224 |
| Dataset | 35,549 clips from Xeno-Canto + iNaturalist, 234 target species |
| Augmentation | MixUp + SpecAugment (frequency / time masking) |
| Optimiser | AdamW, lr=1e-3, weight-decay 1e-4 |
| Schedule | Cosine annealing + 3-epoch warm-up |
| Epochs | 30 with early stopping (patience 8) |
| Splits | 5-fold stratified (by primary_label) |
5-fold validation reported in the Results section above (mean macro-AUC 0.9564).
- Auto-discovery — locates the ONNX bundle at
/kaggle/input/birdclef-2026-anuj-efficientnet-b0-onnx/(falls back to a recursive glob). - Species alignment — reads
species.jsonshipped alongside the ONNX weights, computes a permutation to matchsample_submission.csvcolumn ordering exactly (234 classes). - Fold selection — knob
EFFNET_USE_ALL_FOLDS; defaults to fold 4 only (best CV = 0.9610) for runtime safety since the full notebook already runs ≈80–90 min on hidden test. Flip toTrueto enable the full 5-fold average when wall-time permits. - Window-wise inference — soundfile + librosa → 5-s mel → ONNX Runtime → sigmoid probabilities for every 5-second window of every test soundscape.
- Probability blend with the anchor
submission.csv:withfinal = (1 − EFFNET_WEIGHT) · anchor + EFFNET_WEIGHT · effnetEFFNET_WEIGHT = 0.05— conservative on a 0.95 anchor. - Dry-run safety — if
sample_submission.csvhas only the 3 placeholder rows (Kaggle dry-run), the branch skips the blend and leaves the anchor untouched. The hidden-test run has matching schemas and the blend applies. - Overwrites
submission.csvin place.
The notebook is designed to run end-to-end on a Kaggle T4×2 / CPU kernel with the BirdCLEF 2026 competition data attached. Locally, you need:
# 1. Clone
git clone https://github.com/anujdevsingh/birdclef-2026-model-and-analysis.git
cd birdclef-2026-model-and-analysis
# 2. Environment (Python 3.12 recommended)
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install torch torchaudio timm onnxruntime tensorflow==2.20 \
pandas numpy scikit-learn scipy soundfile librosa lightgbm tqdm
# 3. Download competition data from Kaggle into /kaggle/input/competitions/birdclef-2026
# (or adjust the COMP_DIR paths inside the notebook)
# 4. Launch
jupyter lab birdclef-2026-eos-7-bz-efficientnet-b0.ipynbRequired Kaggle datasets (attach when running on Kaggle):
competitions/birdclef-2026datasets/tuckerarrants/perch-v2-no-dft-onnxdatasets/tuckerarrants/birdclef-2026-waveform-cachedatasets/rishikeshjani/perch-onnx-for-birdclef-2026notebooks/ashok205/tf-wheelsmodels/google/bird-vocalization-classifier/tensorflow2/perch_v2_cpu/1datasets/anujdevsingh/birdclef-2026-anuj-efficientnet-b0-onnx(my EfficientNet weights)
birdclef-2026-model-and-analysis/
├── birdclef-2026-eos-7-bz-efficientnet-b0.ipynb # Main notebook (34 cells)
├── README.md # This file
└── .gitattributes
nina2025 Tucker Arrants
Anuj Dev Singh AI Engineer · IIT Madras
Released under the MIT License. The original community model code retained in cells 4–21 is the property of its respective authors and is included under the terms of their public Kaggle notebook licenses (Apache 2.0 / open-source). The EfficientNet-B0 branch (Cells 32–33) and this README are © 2026 Anuj Dev Singh.
If this work helps you, a ⭐ on the repo and an upvote on the Kaggle notebook are deeply appreciated.