Training¶
Basic usage¶
python -m birdnet_stm32 train \
--data_path_train data/train \
--checkpoint_path checkpoints/my_model.keras
The defaults are the 2.1 release recipe: raw frontend at 448 frames with the
two-exposure filterbank (gain 16), pwl magnitude scaling, 24 kHz, 2.5 s
chunks, width 1.5 with stage widths 48 / 128 / 512 / 1024 (the last stage is the
1024-d embedding) and 5 × 5 depthwise kernels, 50
epochs of one pass each under a per-class cap of 3,000 files, at learning rate
2e-4 after a half-epoch warm-up. With --teacher_cache the teacher's scores
make up half of each target and choose the training chunk. On a GPU, float
training runs XLA-compiled in mixed precision. The 2.1 release then runs the
head fine-tune.
For a leakage-safe precomputed validation split and stable output order, pass a separate validation root and a one-label-per-line classes file:
python -m birdnet_stm32 train \
--data_path_train data/train \
--data_path_val data/validation \
--classes_file data/labels.txt
The class file controls model-output order. Folders named noise, silence,
background, or other are still loaded as all-zero examples and must not be
listed as outputs. When --data_path_val is present, --val_split is ignored.
The script saves these files alongside the checkpoint:
my_model.keras— trained Keras modelmy_model_model_config.json— conversion metadata (frontend, shapes, etc.)my_model_labels.txt— ordered class namesmy_model_history.csv— per-epoch training metrics (loss, ROC-AUC)my_model_curves.png— loss and exact validation cMAP curves plotmy_model_train_state.json— epoch counter for--resume
Audio frontends¶
| Frontend | Input to model | Description |
|---|---|---|
raw (default) |
Peak-normalized waveform | Model applies a mel-seeded, trainable Gabor quadrature filterbank. The release frontend: the whole pipeline runs on the NPU. |
hybrid |
Linear magnitude STFT | Model applies a learned mel mixer and magnitude scaling. The STFT runs outside the model (host, or the Cortex-M55). With --input_compression sqrt the best INT8 accuracy measured, at 148 ms per file against 75 ms for raw (1.4 models). |
librosa |
Mel spectrogram | Mel spectrogram computed outside the model. Smallest quantization loss, weakest float model. |
hybrid and librosa inputs are defined by birdnet_stm32.audio.stft and
specified in Spectrogram Input.
These three are the only frontends. mfcc and log_mel were removed in 1.2.0:
both were host-precomputed variants of the same librosa path, and no release
ever used them.
Raw frontend memory limit
The raw input must contain fewer than 65,536 samples. At 24 kHz, use a chunk no longer than about 2.7 seconds; 2.5 seconds is the tested setting.
Magnitude scaling¶
| Mode | Description | Quantization friendliness |
|---|---|---|
pwl (default) |
Piecewise-linear learned compression | Excellent — recommended for deployment |
none |
No compression | Baseline only, for ablations |
pcen and db were removed in 1.2.0. dB's log op produces exactly the wide
dynamic range INT8 cannot hold — the failure this frontend exists to avoid —
and PCEN was never used by a release. cpwl, a compressive variant of pwl,
was removed in 1.3.0: best float of any raw model, worst INT8.
Model architecture¶
--backbone picks the network behind the frontend. The default from 2.3 is
efficientnet_b0_n6w, N6-B0 with wider bottlenecks: expansion 4 in stages 2–4
and the residual stream doubled in stages 4–6 (12.1 M parameters, 1.18 G MACs,
62 ms per inference on the STM32N6570-DK). N6-B0 (efficientnet_b0_n6,
2.2) is EfficientNet-B0's inverted-residual blocks
with the three changes that let it survive INT8 on the N6 — ReLU6 instead of
SiLU, no squeeze-and-excite, and a bounded residual stream (ReLU6 after each
projection and residual add). B0 as published lost 14% of its WABAD AP to
post-training quantization. N6-B0 runs at 128 bands (--num_mels) into a
1,280-channel top convolution (--embeddings_size), 4.9 M parameters and 523 M
MACs, 34 ms per inference on the STM32N6570-DK.
dscnn is the backbone of every release up to 2.1. It is scaled with two knobs:
--alpha(width multiplier): scales channel counts across all stages. Default 1.5 (2.0 release). Values like 0.75 or 1.0 produce smaller models.--depth_multiplier: repeats each depthwise-separable block. Default 1. Increase to 2 for deeper models.
Two options change the backbone shape:
--dw_kernel_size: depthwise kernel size in stages 2–4,5(default) or3. Stage 1 stays 3 × 3.--stage_widths: base output channels of the four stages before--alpha, default32 86 342 683(2.1: 48 / 128 / 512 / 1024 at width 1.5). 2.0 used32 64 128 256, which configs saved before the option existed are still read as. The 2.1 widths go where the 2.0 model used its width: the late stages, where the feature map is small and extra channels are cheap in activation memory. When the last stage is as wide as--embeddings_size, the separate 1 × 1 embedding convolution is left out.
No convolution kernel in the DS-CNN blocks carries weight decay. On the depthwise kernels it killed 54–62% of the depthwise channels during 2.0 training; on the pointwise kernels (1e-4) it killed 35–49% of them in a model widened in stages 3–4, which then trailed the narrower model. Without either, none died, and the widened model learned several times faster.
Channel alignment
Keep channel counts as multiples of 8 for optimal NPU vectorization. The
model builder enforces this automatically via _make_divisible.
Training options¶
Data augmentation¶
- Mixup: controlled by
--mixup_alpha(default 0.2, 0 disables) and--mixup_probability(default 0.25). Uses Dirichlet multi-source mixing (2–3 sources per sample) to realistically emulate overlapping bird vocalizations. Labels are combined via element-wise max. - SpecAugment: enabled by default. Applies random frequency and time
masking to spectrograms during training. Disable with
--no_spec_augment. Control mask widths with--freq_mask_max(default 8 bins) and--time_mask_max(default 25 frames). - Teacher targets:
--teacher_cachepoints at per-window scores from a larger model, computed once offline over every training recording, and--teacher_weight(default 0.5 with a cache, otherwise off) blends them into each chunk's label as(1 - w) * label + w * teacheron the classes the teacher covers. Training labels are weak: a recording carries one species label, so every chunk drawn from it trains as that species, whether it holds the call, silence, or a different bird. The teacher says which. Classes the teacher has no output for, and recordings missing from the cache, keep their hard label. Validation and checkpoint selection always use hard labels. The cache format is documented inbirdnet_stm32/data/teacher.py. - Teacher embeddings: when the cache also holds the teacher's embedding of
each window (
emb.npy), training adds a cosine loss between the student's pooled embedding, mapped through a training-only linear projector, and that embedding. It is on by default at--teacher_embedding_weight 0.2whenever the cache has embeddings;0turns it off. The embedding carries what the teacher hears beyond our classes, and a signal for the non-bird classes and noise recordings, which get no teacher scores. Mixed chunks and chunks without a teacher window are left out of it. The projector never reaches the checkpoint, so the deployed model is unchanged. A run that starts from trained weights (--init_checkpoint,--resume) fits the projector to them by ridge regression first. Trained from scratch at 448 frames, it raised the float window AUPRC on WABAD from 0.442 to 0.475 and the INT8 catalog cMAP from 0.712 to 0.728. - Smart crop: long recordings (> 2 chunks) are automatically cropped to
salient regions using short-time energy (STE) analysis, reducing label
noise from silent or irrelevant segments. Energy ranking selects the loudest
part of a recording, which in field audio is as often rain, wind or an
insect chorus as the target bird.
--crop_policy teacher(the default with--teacher_cache) ranks half-overlapping candidates by the teacher's score for the recording's labelled species and keeps the best, so the chunk is chosen for holding the species rather than for being loud. It falls back to energy for noise recordings, classes the teacher has no output for, and recordings missing from the cache, and works with--teacher_weight 0to change only which chunk is drawn. Evaluation is unaffected by every policy: it always scores whole files with overlapping windows. - Multi-chunk I/O reuse: long files (e.g. 60 s recordings) yield up to
--max_chunks_per_file(default 1) salient chunks per file open, stored in a memory-bounded shuffled reservoir. This avoids redundant FLAC decode + resample for the same file across epochs.
Loss function¶
The classifier head is always sigmoid + binary crossentropy. Soundscape recordings are inherently multi-label, so we always optimise per-class probabilities even when the source label is single-class.
Optimizer¶
Select with --optimizer (default adam):
| Optimizer | Description |
|---|---|
adam |
Adaptive moment estimation (default) |
sgd |
SGD with momentum 0.9 |
adamw |
AdamW with decoupled weight decay |
Set weight decay with --weight_decay (default 0, only used by adamw).
Deterministic mode¶
Training is always deterministic — all random seeds (Python, NumPy,
TensorFlow) are set and TF_DETERMINISTIC_OPS is enabled automatically.
Use --seed (default 42) to change the RNG seed.
Gradient clipping¶
Gradient clipping by global norm is enabled by default (--grad_clip 1.0).
Set to 0 to disable. Prevents exploding gradients, especially useful with
large models or unstable training.
Mixed precision and XLA¶
On a GPU, float training runs in mixed precision (FP16 compute, FP32 accumulation) with the training step XLA-compiled: the raw 448 model trains about 3x faster, since the depthwise backbone is most of the step and XLA fuses it far better than graph execution does. QAT and linear probing stay in float32 without XLA, and so does training on a CPU. Checkpoints are loaded in float32 for conversion and evaluation.
Resumable training¶
Use --resume to continue training from a previously saved checkpoint.
The optimizer state is recompiled and training resumes from the last saved
epoch. Example:
# Initial training (interrupted or completed at epoch 30)
python -m birdnet_stm32 train --epochs 30 --checkpoint_path ckpt/model.keras ...
# Resume and extend to 50 epochs
python -m birdnet_stm32 train --epochs 50 --resume --checkpoint_path ckpt/model.keras ...
Quantization-Aware Training (QAT)¶
Use --qat to fine-tune a pretrained model with simulated INT8 quantization
noise, teaching the weights to survive quantization.
Part of the recipe again from 2.2
Up to 2.1 QAT never beat its starting point (2.0 Raw: INT8 cMAP 0.545 at epoch 0, 0.520–0.541 after each of 8 epochs). It had two bugs: it drew from the raw, unbalanced training list instead of the class-capped draw with field clips the float run uses, and it simulated and selected a probability output on the INT8 grid, while the releases ship logits. With both fixed, two epochs at learning rate 1e-5 took 2.2's N6-B0 from INT8 validation cMAP 0.7665 to 0.7745 (float 0.781) and from 5.2% to 4.5% INT8 loss on WABAD. 2.3 runs four (two of warm-up, then two of decay, which gave the best epochs): INT8 validation cMAP 0.8081 to 0.8109.
QAT requires a pretrained model
Always train normally first, then fine-tune with --qat. Do not use
--qat from scratch — the quantization noise destabilizes randomly
initialized weights and the model will not converge. The dataset must
have the same classes as the pretrained model; use --linear_probe to
adapt to a different class set first.
QAT calibrates activation ranges on the converter's exact deterministic, class-stratified training manifest (1,024 samples by default), then trains with per-channel INT8 kernel grids and per-tensor INT8 activation grids. The simulation includes the quantized waveform input and the kernels and elementwise boundaries inside the custom raw frontend. BatchNorm layers are frozen. Standard variables are shared with a clean deployment graph; cloned frontend variables are synchronized before each checkpoint. Only the clean graph is saved, so no FakeQuant ops remain in the model.
A frozen copy of the untouched checkpoint acts as the teacher. QAT minimizes the normal label loss, per-output Bernoulli KL divergence, and both mean and worst-sample per-sample cosine distance from that teacher. The defaults apply the tail loss to the worst 10% of each batch with 0.75 weight; both values are configurable. This constrains background and low-confidence probabilities while directly optimizing the lower tail that the release parity gate measures.
With --qat, the defaults switch to the fine-tuning schedule of 2.3: 4 epochs
at learning rate 1e-5, two of warm-up and two of cosine decay (with
--steps_per_epoch 2850), with activation ranges recalibrated on the current weights after every
epoch (--qat_range_refresh, on by default). QAT draws its training audio as
the float run does (--class_cap_per_epoch, --field_table), uses the
converter's activation-range percentiles and simulates the logit output the
releases ship. The simulation quantizes every tensor the converter quantizes,
including each partial filterbank convolution, so its validation cMAP
(val_sim_int8_cmap) tracks the converted model's (val_int8_cmap) to within
a few thousandths; selection converts each epoch as the release does (logit
output, two-exposure PWL) and keeps every epoch's INT8 candidate.
The full raw pipeline (the 2.2 recipe):
# Step 1: Normal training (with a teacher cache, as the releases are trained)
python -m birdnet_stm32 train --data_path_train data/train \
--data_path_val data/validation --classes_file data/labels.txt \
--teacher_cache data/teacher_cache --checkpoint_path checkpoints/model.keras
# Step 2: Refit the classifier head on every training file (see below)
python -m birdnet_stm32 finetune-head --checkpoint_path checkpoints/model.keras \
--data_path_train data/train --data_path_val data/validation \
--teacher_cache data/teacher_cache --output_path checkpoints/model_ft.keras
# Step 3: Equalize the frontend's per-band ranges (raw only; exact in float)
python -m birdnet_stm32 equalize --checkpoint_path checkpoints/model_ft.keras \
--data_path_train data/train --output_path checkpoints/model_eq.keras
# Step 4: Four epochs of quantization-aware training on the equalized checkpoint
python -m birdnet_stm32 train --qat --checkpoint_path checkpoints/qat/model.keras \
--data_path_train data/train --data_path_val data/validation --classes_file data/labels.txt \
--teacher_cache data/teacher_cache --field_table data/field_table.csv \
--steps_per_epoch 2850
# (checkpoints/qat/model.keras is a copy of model_eq.keras; QAT writes model_qat.keras)
# Step 5: Convert
python -m birdnet_stm32 convert \
--checkpoint_path checkpoints/qat/model_qat.keras \
--model_config checkpoints/qat/model_qat_model_config.json \
--data_path_train data/train
A QAT run is saved as {name}_qat.keras alongside the original.
equalize rescales each band of the raw filterbank and PWL so that every band
gets the same share of the tensors' INT8 grids, and refuses to save if the
float output changes. See INT8 quality for the
measurements.
For hybrid and librosa, skip step 3 (QAT on hybrid models scored below
plain post-training quantization at every epoch up to 2.0).
Head fine-tuning¶
finetune-head is a post-training step that refits only the classifier
(pred) on the frozen backbone, over every training file. It embeds each
training file once: one chunk, cut and targeted as the training loader cuts it
(crop policy, teacher targets, label sidecar). The embeddings go to a disk cache
(--cache_dir; 2 kB per file for the embedding plus 2 bytes per output, so
about 12 GB for 3 million files and 1,013 outputs; resumable, and reused when
the files and settings match). The head then trains from its trained weights:
each epoch draws the cached files class-capped as training does
(--class_cap_per_epoch, with the field table's share). The epoch with the best
validation class-mean AP plus micro AP is kept (--epochs 30, --patience 5,
--learning_rate 3e-4). Only pred changes; the config is copied unchanged.
Optionally, --cooccurrence_csv makes the head weigh its negatives by
geography. The CSV lists label,group rows (for example each species' regions);
a negative from a species that shares no group with an output counts
--far_weight (0.3). Labels not in the CSV, such as non-bird outputs,
co-occur with everything. With a species range filter at inference, the head
then spends its capacity on the confusions that remain; without one, it costs
nothing.
Measured on 2.1 Raw (1,013 outputs, INT8, all 61 WABAD sites, no species mask), micro window AP:
- +3.3% for the plain refit;
- +3.9% with co-occurrence weighting.
The released 2.1 Raw INT8 model, built this way, reaches 0.356 against 0.342 for the same checkpoint without the refit (+4.1%).
With each region's species mask, the gain over the unrefitted head is +10% in North America and Europe and +16% to +19% in the Neotropics. Starting from the trained rows matters: heads fitted from scratch, or on a few hundred files per class, lose to the trained head.
Two-exposure raw frontend¶
The raw frontend carries linear amplitude through its INT8 grids, so quiet and
dense field audio lands in their bottom few levels. The raw model therefore
runs a second exposure of its filterbank: the same filters at gain 16
(--raw_exposure_gain, default 16; 1 turns it off), clamped to ±1 at the
convolution's own requantization, and mixed with the first per band into a
knee compressor before the band stage. Every grid after the mix then carries a
compressed envelope, much as hybrid compresses its spectrogram before its
first INT8 tensor, and all of it stays on the NPU. On the 1.6 raw model it cut
the field INT8 loss by more than half (WABAD INT8 recall 0.255 → 0.309).
From 2.0 the model trains with both exposures from the start; 1.7 to 1.9
trained on the linear envelope first and switched afterwards. equalize
leaves the two-exposure filterbank alone (it is already normalized per band).
Linear probing¶
Use --linear_probe to freeze a pretrained backbone and train only a new
classification head on your custom species dataset. This is useful when you
have a pretrained model (e.g. a large BirdNET checkpoint) and want to adapt
it to a different set of species with limited data.
python -m birdnet_stm32 train --data_path_train data/my_species \
--linear_probe --checkpoint_path checkpoints/pretrained.keras \
--data_path_val data/my_species_val \
--classes_file data/my_species/labels.txt \
--epochs 20 --learning_rate 0.001
The probe model is saved as {name}_probe.keras with a new labels file and
{name}_probe_model_config.json.
Pass --classes_file whenever the head will be shipped. The head's output order
is its labels file, and without an explicit schema that order comes from a
directory listing, which is not a contract. --data_path_val gives the probe a
fixed validation root instead of a random slice of the training set, so repeated
runs are comparable.
Probing a checkpoint from an earlier compression step needs --model_config:
a QAT checkpoint writes no config of its own, since its architecture is the base
model's.
python -m birdnet_stm32 train --data_path_train data/my_species \
--linear_probe --checkpoint_path checkpoints/pretrained_qat.keras \
--model_config checkpoints/pretrained_model_config.json \
--classes_file data/my_species/labels.txt --epochs 20
To ship the resulting head on its own, convert it against the backbone already on the device — see Updating the head against a flashed backbone.
Learning rate¶
A linear warm-up of --warmup_epochs (default 0.5) reaches --learning_rate (default 2e-4), followed
by cosine decay to near-zero over --epochs (default 50). Best-checkpoint
selection and early stopping maximize exact validation cMAP. Early stopping
(patience 10) only starts after 80% of --epochs, once the learning rate has
come down: in the middle of a long schedule validation cMAP plateaus noisily,
and stopping there ends the run before most of its gain. Standard CLI
training evaluates files with the configured overlap and pooling. QAT selects
on converted INT8 file cMAP, including epoch zero, and saves the exact
TFLite artifact that was scored alongside its matching Keras checkpoint.
The chunk PR-AUC metric is logged as pr_auc and does not select checkpoints.
Full argument reference¶
| Argument | Default | Description |
|---|---|---|
--data_path_train |
(required) | Path to training data |
--data_path_val |
None | Separate validation root; disables random validation splitting |
--classes_file |
None | Ordered one-label-per-line output schema |
--max_classes |
None | Use only the N most populated classes |
--max_samples |
None | Max files per class |
--sample_rate |
24000 | Audio sample rate (Hz) |
--num_mels |
128 | Number of mel frequency bins (up to 2.1: 64) |
--spec_width |
per frontend | Frames per chunk: 448 raw, 384 hybrid, 256 librosa |
--fft_length |
512 | FFT window length |
--chunk_duration |
2.5 | Chunk duration (seconds) |
--max_duration |
60 | Max seconds to load per file |
--audio_frontend |
raw | raw, hybrid, or librosa — raw models trained before the NPU fixes must be retrained |
--mag_scale |
pwl | pwl or none |
--input_compression |
none | none, sqrt or log: compress a librosa/hybrid spectrogram before its first INT8 quantization; the firmware applies the same compression |
--embeddings_size |
1280 | Embedding channels before head (N6-B0's top conv; up to 2.1: 1024) |
--alpha |
1.5 | Model width scaling |
--depth_multiplier |
1 | Block repeats per stage |
--dw_kernel_size |
5 | Depthwise kernel in stages 2–4: 5 or 3 |
--stage_widths |
32 86 342 683 | Base channels of the four stages, before --alpha (2.0: 32 64 128 256) |
--backbone |
efficientnet_b0_n6w | efficientnet_b0_n6w (2.3: N6-B0 with wider bottlenecks, expansion 4 in stages 2–4 and the residual stream ×2 in stages 4–6), efficientnet_b0_n6 (2.2, N6-B0: EfficientNet-B0 with ReLU6, no squeeze-and-excite and a bounded residual stream) or dscnn (releases up to 2.1); --alpha, --depth_multiplier, --dw_kernel_size and --stage_widths apply to dscnn only |
--raw_exposure_gain |
16 (raw) | Gain of the raw filterbank's second, clamped exposure, mixed per band into a knee compressor; 1 = one exposure (raw only) |
--mixup_alpha |
0.2 | Mixup alpha (0 disables) |
--mixup_probability |
0.25 | Fraction of batch to mix |
--no_spec_augment |
False | Disable SpecAugment masking (on by default) |
--freq_mask_max |
8 | Max frequency mask width (bins) |
--time_mask_max |
25 | Max time mask width (frames) |
--teacher_cache |
None | Directory of cached per-window teacher scores, and optionally embeddings |
--teacher_weight |
0.5 with --teacher_cache, else 0 |
Teacher share of the training target in [0, 1] (0 = hard labels only). Background birds: a species the file does not name takes the teacher's own score from 0.3 and 1 from 0.8 instead of the blend (always on with teacher targets from 2.3, as in finetune-head) |
--label_sidecar |
None | CSV of per-file label additions (sample_id, positives, negatives; classes separated by ;): positives join the folder's class in the hard label (recordings with several species), negatives are confirmed absent, held at 0 and never raised by the teacher's soft target |
--teacher_embedding_weight |
0.2 with embeddings | Weight of a cosine loss to the teacher's embedding of each chunk; on when --teacher_cache holds emb.npy, 0 disables |
--crop_policy |
teacher with --teacher_cache, else energy |
How training chunks are chosen: energy or teacher (needs --teacher_cache) |
--dropout |
0.5 | Dropout rate before classifier head |
--optimizer |
adam | adam, sgd, or adamw |
--weight_decay |
0.0 | Weight decay (adamw only) |
--grad_clip |
1.0 | Max gradient norm for clipping (0 = disabled) |
--field_table |
None | CSV sample_id,site,recording of field clips; with --class_cap_per_epoch they are drawn for diversity: round-robin over sites and recordings, at most --field_share of a class's cap |
--field_share |
0.5 | Max share of a class's per-epoch cap filled by field clips (more only when focal files cannot fill the rest) |
--field_per_recording |
2 | Max segments of one field recording per class and epoch |
--resume |
False | Resume training from checkpoint |
--init_checkpoint |
— | Start from this model's weights at epoch 0; its frontend must match the architecture flags. Checkpoint selection and early stopping start after the learning-rate warm-up |
--seed |
42 | Random seed |
--batch_size |
32 | Batch size |
--num_workers |
8 | Parallel data loading workers (0 = sequential) |
--max_chunks_per_file |
1 | Max salient chunks per file open (reduces redundant I/O) |
--prefetch_batches |
2 | Loader prefetch depth in batches |
--epochs |
50 (4 with --qat) |
Number of epochs |
--steps_per_epoch |
0 | Training steps per epoch; 0 = one pass over the training files. Fix it to keep a schedule unchanged when the dataset grows |
--warmup_epochs |
0.5 | Linear learning-rate warm-up before the cosine decay, in epochs; may be fractional |
--class_cap_per_epoch |
3000 | At most this many files per class in each epoch (0 = off): a larger class contributes a different subset each epoch, rotating through all its files, so the draw is more balanced without repeating or discarding files. Noise folders are not capped |
--learning_rate |
2e-4 (1e-5 with --qat, 1e-3 with --linear_probe) |
Initial learning rate |
--val_split |
0.2 | Validation split fraction when --data_path_val is not supplied |
--checkpoint_path |
checkpoints/best_model.keras | Output path (.keras) |
--qat |
False | Quantization-aware fine-tuning |
--qat_calibration_samples |
1024 | Exact stratified samples used for QAT and conversion calibration |
--qat_distillation_weight |
1.0 | Frozen-teacher Bernoulli-KL weight |
--qat_cosine_weight |
0.10 | Mean teacher/student cosine-loss weight |
--qat_cosine_tail_weight |
0.75 | Worst-sample cosine-loss weight |
--qat_cosine_tail_fraction |
0.10 | Fraction of each batch included in the worst-sample loss |
--qat_range_refresh |
on | Recalibrate activation ranges on the current weights after every epoch, so QAT trains against the grid conversion will use; --no-qat_range_refresh disables it |
--linear_probe |
False | Freeze backbone and train only classifier head |
--model_config |
(inferred) | Architecture config for --qat, --linear_probe; required when the checkpoint has no sibling config |
--validation_overlap |
half the chunk duration | File validation overlap in seconds |
--validation_pooling |
max | File validation pooling |
--validation_subset |
0 | Score checkpoint selection (training and QAT) on a fixed class-balanced draw of N validation files, seed 1234 (0 = all). Biased upward; not comparable to full-manifest cMAP |
Data pipeline¶
The training pipeline uses a multiprocessing pool for parallel data loading, bypassing the GIL so FLAC decode, resampling, smart-crop, and spectrogram computation run across separate CPU cores.
When --max_chunks_per_file is greater than 1 (default 3), each file open
extracts multiple salient chunks which are buffered in a shuffled in-memory
reservoir sized from the sample representation and the loader's memory
budget. This dramatically reduces I/O for
long recordings: a 60 s file decoded once yields 3 usable chunks instead of
re-opening the same file 3 times across epochs.
The reservoir maintains batch diversity by shuffling samples from many different files before yielding them.
Training also checks host-available memory every 25 batches. It aborts before available RAM falls below an adaptive reserve (20% of host RAM, bounded between 2 and 12 GiB), leaving the last completed-epoch checkpoint available for a safer restart. This protects the host from multiprocessing and TensorFlow memory spikes; it is not a substitute for choosing a conservative worker count and batch size.
Tune with:
--num_workers N— number of worker processes (default 8, 0 = sequential)--max_chunks_per_file N— chunks per file open (default 3, 1 = original behavior)--prefetch_batches N— queued batches (default 2; higher uses more RAM)
Noise classes¶
Place audio in folders named noise, silence, background, or other
under data/train/. These receive all-zero label vectors and help the model
learn to reject non-bird sounds.