naturelm-cats is a soft fork of Earth Species Project's NatureLM-audio that adds a cat-vocalization workflow: a NatureLM-audio to GPT-OSS-20B bridge plus a CatMeows-trained context classifier for brushing, isolation, and waiting-for-food audio. The repository preserves the upstream NatureLM-audio code and documents the added bridge scripts, classifier experiments, licensing constraints, and limitations.
Extending NatureLM-audio (Earth Species Project) to cat vocalization understanding, with a more powerful reasoning LLM bridge.
A soft fork of earthspecies/NatureLM-audio that adds:
llama-server — getting structured behavioral reasoning on top of NatureLM's bioacoustic captions, running on a single consumer GPU (RTX 4070 Laptop, 8 GB).Both pieces work end-to-end against arbitrary cat audio today.
The original NatureLM-audio README is preserved at
README_UPSTREAM.md.
For LLM, search, and answer-engine context, see llms.txt. It summarizes the repository scope, canonical files, model/data licensing caveats, and citation guidance without changing model code, classifier scripts, datasets, weights, or benchmark claims.
audio (any cat sound, wav/mp3/flac/ogg)
│
├─► NatureLM-audio (CPU)
│ BEATs + Q-Former + Llama-3.1-8B + LoRA
│ → generic bioacoustic caption: "Domestic cats vocalizing."
│
└─► BEATs encoder + statistical pooling (mean+std+max) + trained MLP head (CPU)
→ cat-context softmax:
{brushing: 86.2%, isolation: 10.2%, waiting_for_food: 3.6%}
│
▼ merge both signals
GPT-OSS-20B Q4_K_M (RTX 4070, llama-server :9002)
→ structured behavioral analysis with confidence calibration
| Script | Inputs to GPT-OSS-20B | When to use |
|---|---|---|
scripts/bridge_caption.py | NatureLM caption (single generic query) | Simplest. Pre-classifier baseline. |
scripts/bridge_with_context.py | Caption + cat-context classifier probs | Default. 82.2% CV classifier grounding. |
scripts/bridge_multiquery.py | 3 NatureLM queries (acoustic / emotion / sentence) + classifier | Richest. GPT-OSS-20B can flag caption-classifier disagreements. |
The multi-query bridge runs NatureLM three times per clip (~3× the audio inference cost, ~15s warm) but gives GPT-OSS-20B enough independent signals to reason about which caption to trust when they disagree. Probe results in scripts/probe_naturelm_queries.py showed:
Generic caption:
"Domestic cats vocalizing."Classifier:brushing (86.2%), isolation (10.2%), waiting_for_food (3.6%)GPT-OSS-20B reasoning: Confident interpretation with explicit uncertainty calibration based on the classifier's softmax distribution. The reasoning is grounded in the top-predicted context but acknowledges the residual probability mass.
(Note: ESC-50 cat clips are out-of-distribution for the classifier — it was trained only on CatMeows' 3 contexts. The high brushing probability here is the classifier's best mapping of an unfamiliar context to its closest CatMeows class.)
NatureLM-audio is pretrained for general bioacoustics across many species. Replacing its Llama-3.1-8B head with a larger reasoner (GPT-OSS-20B) would require retraining the audio→LLM projection, which is expensive. Bridging is cheaper and modular:
On an 8 GB consumer GPU this works because GPT-OSS-20B is MoE: only ~3.6B params active per token, so llama.cpp -ngl 12 with Q4_K_M fits the always-on backbone on the GPU while expert weights stream from system RAM. We measured 32 tok/s generation on RTX 4070 Laptop.
Cat-context classifier on CatMeows, evaluated via 5-fold stratified cross-validation across all 276 clips, hyperparameters tuned per-variant via grid sweep:
| Feature variant | 5-fold CV accuracy | Best hparams |
|---|---|---|
| Hybrid: BEATs-stats + classical (2406-dim) | 82.2% | hidden=128, dropout=0.0, wd=1e-1 |
| BEATs-stats only (2304-dim, mean+std+max) | 81.5% | hidden=256, dropout=0.3, wd=1e-1 |
| BEATs-mean only (768-dim) | 76.5% | hidden=128, dropout=0.7, wd=1e-1 |
| Q-Former only (768-dim) | 73.9% | hidden=256, dropout=0.0, wd=1e-2 |
| Classical only (102-dim, MFCC+spectral+ZCR+RMS) | 72.5% | hidden=64, dropout=0.3, wd=1e-2 |
Chance = 33%. The Ludovico et al. 2020 paper reports 86% on the same dataset using hand-crafted features (MFCC + spectral centroid + ZCR + formants) + SVM, with full-dataset CV. Our 82.2% hybrid uses learned BEATs features + classical acoustic features + a small MLP head — within 4 points of the hand-crafted SVM baseline, despite a meaningfully different model class.
| Stage | Accuracy | Lift |
|---|---|---|
| Single 201/75 split (initial, misleading) | 65.3% | baseline |
| 5-fold CV, BEATs-mean, default hparams | 76.5% | +11.2 (single-split was unlucky) |
| BEATs-stats pooling (mean+std+max) | 78.6% | +2.1 |
| BEATs-stats, tuned hparams | 81.5% | +2.9 (regularization is the unlock) |
| Hybrid with classical features | 82.2% | +0.7 (marginal but free) |
Counterintuitive but reproducible: feeding the cat audio through NatureLM-audio's Q-Former (post-BEATs) and using the Q-Former output as classifier input gives 73.9%, ~5-8 points lower than raw BEATs features. Our interpretation: NatureLM-audio's Q-Former is designed as a tight bottleneck (1 query token per 333ms window) optimized for downstream Llama text generation; that bottleneck throws away acoustic detail needed for fine-grained cat-context discrimination. Statistical pooling on raw BEATs preserves more variability than the Q-Former bottleneck allows through.
Time-stretch (0.9-1.1×) + pitch-shift (±2 semitones) + light noise augmentation dropped CV from 78.6% to 75.4% with the BEATs-stats default hparams. Pitch-shift in particular seems to move meows out of the discriminative acoustic range. SEM also went up from ±1.5% to ±2.3%, indicating augmentation introduced fold-to-fold variance. Documented in scripts/extract_beats_stats_augmented.py for transparency.
Across folds, the most-confused pair is brushing ↔ isolation (both produce gentler, non-distressed meows). Food-context meows are most distinguishable (sharper onsets, more urgent envelope).
Prereqs:
meta-llama/Meta-Llama-3.1-8B-Instruct (gated)uv for Python env managementgit clone https://github.com/Scottcjn/naturelm-cats
cd naturelm-cats
uv sync --no-group gpu # or "uv sync" if you want CUDA torch
Download required models:
# GPT-OSS-20B GGUF (~11 GB)
hf download unsloth/gpt-oss-20b-GGUF gpt-oss-20b-Q4_K_M.gguf \
--local-dir ~/models/gpt-oss-20b-gguf
# NatureLM-audio head (~1.5 GB)
hf download EarthSpeciesProject/NatureLM-audio --local-dir ~/models/naturelm-audio
# Llama-3.1-8B base (NatureLM-audio's LoRA backbone, ~16 GB)
hf download meta-llama/Meta-Llama-3.1-8B-Instruct --exclude "original/*"
# CatMeows dataset (for training the classifier yourself, ~18 MB)
hf download oliveirabruno01/openfarm-catmeows --repo-type dataset \
--local-dir ~/datasets/cats/openfarm-catmeows
Start the LLM server (terminal 1):
~/llama.cpp/build-cuda/bin/llama-server \
--model ~/models/gpt-oss-20b-gguf/gpt-oss-20b-Q4_K_M.gguf \
--host 127.0.0.1 --port 9002 \
--n-gpu-layers 12 --ctx-size 4096 --threads 8
Run the bridge against any cat audio (terminal 2):
# Caption-only bridge (no trained classifier)
CUDA_VISIBLE_DEVICES="" uv run python scripts/bridge_caption.py path/to/cat.wav
# Bridge v2: classifier-grounded reasoning (default)
CUDA_VISIBLE_DEVICES="" uv run python scripts/bridge_with_context.py path/to/cat.wav
# Bridge v3: multi-query — runs 3 NatureLM queries, flags disagreements
CUDA_VISIBLE_DEVICES="" uv run python scripts/bridge_multiquery.py path/to/cat.wav
# Batch over many clips at once (writes /tmp/cat_batch_{results.jsonl,report.md})
CUDA_VISIBLE_DEVICES="" uv run python scripts/batch_bridge.py --source mixed --n-each 5
# Probe which NatureLM queries produce informative captions
CUDA_VISIBLE_DEVICES="" uv run python scripts/probe_naturelm_queries.py
CUDA_VISIBLE_DEVICES="" forces NatureLM-audio to CPU so it doesn't fight llama-server for VRAM. ~60s cold start (Llama-3.1-8B loads into 16 GB of system RAM), <10s warm.
# 1. Extract BEATs-stats + classical features for all 276 CatMeows clips
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_beats_stats_features.py
uv run python scripts/extract_classical_features.py
# Build the hybrid concat (saved as hybrid_features_{train,test}.npy)
uv run python -c "
import numpy as np
from pathlib import Path
D = Path.home() / 'datasets/cats'
for split in ['train', 'test']:
h = np.concatenate([np.load(D/f'stats_features_{split}.npy'),
np.load(D/f'classical_features_{split}.npy')], axis=1)
np.save(D/f'hybrid_features_{split}.npy', h.astype(np.float32))
"
# 2. Train final classifier on ALL data (no held-out — use CV results for accuracy estimate)
uv run python scripts/train_context_classifier.py --variant hybrid --all
# 3. (Optional) Confirm via 5-fold CV + full hyperparam sweep across all variants
uv run python scripts/cross_validate.py
uv run python scripts/sweep_features.py
Outputs ~/datasets/cats/context_classifier_hybrid_all.pt (used by bridge_with_context.py) plus per-variant cv_results.json and cv_results_augmented.json.
# BEATs-mean only — 76.5% CV (simpler baseline)
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_beats_features.py
uv run python scripts/train_context_classifier.py --variant mean --all
# BEATs-stats only — 81.5% CV (no classical features)
uv run python scripts/train_context_classifier.py --variant stats --all
# Q-Former features — 73.9% CV (negative result, documents bottleneck)
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_qformer_features.py
# Augmented BEATs-stats — 75.4% CV (negative result, off-the-shelf aug hurt)
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_beats_stats_augmented.py
uv run python scripts/cross_validate_augmented.py
scripts/
├── bridge_caption.py v1 bridge: NatureLM caption → GPT-OSS-20B
├── bridge_with_context.py v2 bridge: + hybrid classifier (82.2% CV)
├── bridge_multiquery.py v3 bridge: 3 NatureLM queries + classifier
├── batch_bridge.py batch-process many clips, save JSONL + Markdown
├── batch_bridge_multiquery.py batch-process clips with v3 multi-query
├── probe_naturelm_queries.py evaluate which NatureLM queries are informative
├── sara_recorder.py interactive per-cat corpus collection (turn-taking aware)
├── extract_beats_features.py BEATs mean-pool features (baseline)
├── extract_beats_stats_features.py BEATs mean+std+max stats features
├── extract_classical_features.py 102-dim MFCC+spectral+ZCR+RMS via librosa
├── extract_qformer_features.py Q-Former features (negative-result experiment)
├── extract_beats_stats_augmented.py 5×-augmented BEATs-stats (negative-result experiment)
├── train_context_classifier.py MLP head training, --variant {mean|stats|hybrid}, --all
├── cross_validate.py 5-fold stratified CV across {mean, stats, Q-Former}
├── cross_validate_augmented.py 5-fold group-aware CV with augmented training
├── sweep_features.py 80-config hyperparam sweep across all 5 variants
└── smoke_test_gpt_oss.py standalone GPT-OSS-20B load test (transformers path)
Nothing in NatureLM/ is modified — this is purely additive on top of upstream earthspecies/NatureLM-audio.
scripts/): Apache 2.0, same as upstreamIf you want commercial use of the trained cat classifier, contact Earth Species Project regarding their NC-licensed weights — the classifier head itself is small enough (~200K params) to retrain on different upstream features.
scripts/sara_recorder.py builds a per-cat corpus toward this — interactive recording + Scott's contextual labels (what was happening, interpretation, turn-taking flag). Saved to ~/sara-corpus/.For the actual research question — does a specific cat have a personal repertoire and turn-taking exchanges with their human? — the path is observing one cat closely over time, not more accuracy on a 21-cat dataset.
# List input devices (pick one if not using system default)
uv run python scripts/sara_recorder.py --list-devices
# Record interactively (ENTER to start/stop), prompts for context labels
uv run python scripts/sara_recorder.py
# Specify cat name and input device
uv run python scripts/sara_recorder.py --cat Sara --device 6
Saves ~/sara-corpus/{ISO-timestamp}/audio.wav + meta.json per clip. The metadata captures both context (what was happening, your interpretation) and turn-taking structure (was this an exchange? when did you acknowledge? did she follow up?) — because turn-taking patterns are the empirically important structural signal that pure single-clip classification misses entirely.
Once a corpus exists, the natural follow-on analyses are: (a) BEATs feature clustering of the recorded clips to see if observed contexts form acoustic clusters, (b) comparing turn-taking-exchange clips to non-exchange clips, (c) re-training the classifier with per-cat data added.
This project is a soft fork of earthspecies/NatureLM-audio. The audio-language foundation model, BEATs encoder fine-tune, and the entire bioacoustic pretraining stack are their work.
Python
99.9%
naturelm-cats is a soft fork of Earth Species Project's NatureLM-audio that adds a cat-vocalization workflow: a NatureLM-audio to GPT-OSS-20B bridge plus a CatMeows-trained context classifier for brushing, isolation, and waiting-for-food audio. The repository preserves the upstream NatureLM-audio code and documents the added bridge scripts, classifier experiments, licensing constraints, and limitations.
Extending NatureLM-audio (Earth Species Project) to cat vocalization understanding, with a more powerful reasoning LLM bridge.
A soft fork of earthspecies/NatureLM-audio that adds:
llama-server — getting structured behavioral reasoning on top of NatureLM's bioacoustic captions, running on a single consumer GPU (RTX 4070 Laptop, 8 GB).Both pieces work end-to-end against arbitrary cat audio today.
The original NatureLM-audio README is preserved at
README_UPSTREAM.md.
For LLM, search, and answer-engine context, see llms.txt. It summarizes the repository scope, canonical files, model/data licensing caveats, and citation guidance without changing model code, classifier scripts, datasets, weights, or benchmark claims.
audio (any cat sound, wav/mp3/flac/ogg)
│
├─► NatureLM-audio (CPU)
│ BEATs + Q-Former + Llama-3.1-8B + LoRA
│ → generic bioacoustic caption: "Domestic cats vocalizing."
│
└─► BEATs encoder + statistical pooling (mean+std+max) + trained MLP head (CPU)
→ cat-context softmax:
{brushing: 86.2%, isolation: 10.2%, waiting_for_food: 3.6%}
│
▼ merge both signals
GPT-OSS-20B Q4_K_M (RTX 4070, llama-server :9002)
→ structured behavioral analysis with confidence calibration
| Script | Inputs to GPT-OSS-20B | When to use |
|---|---|---|
scripts/bridge_caption.py | NatureLM caption (single generic query) | Simplest. Pre-classifier baseline. |
scripts/bridge_with_context.py | Caption + cat-context classifier probs | Default. 82.2% CV classifier grounding. |
scripts/bridge_multiquery.py | 3 NatureLM queries (acoustic / emotion / sentence) + classifier | Richest. GPT-OSS-20B can flag caption-classifier disagreements. |
The multi-query bridge runs NatureLM three times per clip (~3× the audio inference cost, ~15s warm) but gives GPT-OSS-20B enough independent signals to reason about which caption to trust when they disagree. Probe results in scripts/probe_naturelm_queries.py showed:
Generic caption:
"Domestic cats vocalizing."Classifier:brushing (86.2%), isolation (10.2%), waiting_for_food (3.6%)GPT-OSS-20B reasoning: Confident interpretation with explicit uncertainty calibration based on the classifier's softmax distribution. The reasoning is grounded in the top-predicted context but acknowledges the residual probability mass.
(Note: ESC-50 cat clips are out-of-distribution for the classifier — it was trained only on CatMeows' 3 contexts. The high brushing probability here is the classifier's best mapping of an unfamiliar context to its closest CatMeows class.)
NatureLM-audio is pretrained for general bioacoustics across many species. Replacing its Llama-3.1-8B head with a larger reasoner (GPT-OSS-20B) would require retraining the audio→LLM projection, which is expensive. Bridging is cheaper and modular:
On an 8 GB consumer GPU this works because GPT-OSS-20B is MoE: only ~3.6B params active per token, so llama.cpp -ngl 12 with Q4_K_M fits the always-on backbone on the GPU while expert weights stream from system RAM. We measured 32 tok/s generation on RTX 4070 Laptop.
Cat-context classifier on CatMeows, evaluated via 5-fold stratified cross-validation across all 276 clips, hyperparameters tuned per-variant via grid sweep:
| Feature variant | 5-fold CV accuracy | Best hparams |
|---|---|---|
| Hybrid: BEATs-stats + classical (2406-dim) | 82.2% | hidden=128, dropout=0.0, wd=1e-1 |
| BEATs-stats only (2304-dim, mean+std+max) | 81.5% | hidden=256, dropout=0.3, wd=1e-1 |
| BEATs-mean only (768-dim) | 76.5% | hidden=128, dropout=0.7, wd=1e-1 |
| Q-Former only (768-dim) | 73.9% | hidden=256, dropout=0.0, wd=1e-2 |
| Classical only (102-dim, MFCC+spectral+ZCR+RMS) | 72.5% | hidden=64, dropout=0.3, wd=1e-2 |
Chance = 33%. The Ludovico et al. 2020 paper reports 86% on the same dataset using hand-crafted features (MFCC + spectral centroid + ZCR + formants) + SVM, with full-dataset CV. Our 82.2% hybrid uses learned BEATs features + classical acoustic features + a small MLP head — within 4 points of the hand-crafted SVM baseline, despite a meaningfully different model class.
| Stage | Accuracy | Lift |
|---|---|---|
| Single 201/75 split (initial, misleading) | 65.3% | baseline |
| 5-fold CV, BEATs-mean, default hparams | 76.5% | +11.2 (single-split was unlucky) |
| BEATs-stats pooling (mean+std+max) | 78.6% | +2.1 |
| BEATs-stats, tuned hparams | 81.5% | +2.9 (regularization is the unlock) |
| Hybrid with classical features | 82.2% | +0.7 (marginal but free) |
Counterintuitive but reproducible: feeding the cat audio through NatureLM-audio's Q-Former (post-BEATs) and using the Q-Former output as classifier input gives 73.9%, ~5-8 points lower than raw BEATs features. Our interpretation: NatureLM-audio's Q-Former is designed as a tight bottleneck (1 query token per 333ms window) optimized for downstream Llama text generation; that bottleneck throws away acoustic detail needed for fine-grained cat-context discrimination. Statistical pooling on raw BEATs preserves more variability than the Q-Former bottleneck allows through.
Time-stretch (0.9-1.1×) + pitch-shift (±2 semitones) + light noise augmentation dropped CV from 78.6% to 75.4% with the BEATs-stats default hparams. Pitch-shift in particular seems to move meows out of the discriminative acoustic range. SEM also went up from ±1.5% to ±2.3%, indicating augmentation introduced fold-to-fold variance. Documented in scripts/extract_beats_stats_augmented.py for transparency.
Across folds, the most-confused pair is brushing ↔ isolation (both produce gentler, non-distressed meows). Food-context meows are most distinguishable (sharper onsets, more urgent envelope).
Prereqs:
meta-llama/Meta-Llama-3.1-8B-Instruct (gated)uv for Python env managementgit clone https://github.com/Scottcjn/naturelm-cats
cd naturelm-cats
uv sync --no-group gpu # or "uv sync" if you want CUDA torch
Download required models:
# GPT-OSS-20B GGUF (~11 GB)
hf download unsloth/gpt-oss-20b-GGUF gpt-oss-20b-Q4_K_M.gguf \
--local-dir ~/models/gpt-oss-20b-gguf
# NatureLM-audio head (~1.5 GB)
hf download EarthSpeciesProject/NatureLM-audio --local-dir ~/models/naturelm-audio
# Llama-3.1-8B base (NatureLM-audio's LoRA backbone, ~16 GB)
hf download meta-llama/Meta-Llama-3.1-8B-Instruct --exclude "original/*"
# CatMeows dataset (for training the classifier yourself, ~18 MB)
hf download oliveirabruno01/openfarm-catmeows --repo-type dataset \
--local-dir ~/datasets/cats/openfarm-catmeows
Start the LLM server (terminal 1):
~/llama.cpp/build-cuda/bin/llama-server \
--model ~/models/gpt-oss-20b-gguf/gpt-oss-20b-Q4_K_M.gguf \
--host 127.0.0.1 --port 9002 \
--n-gpu-layers 12 --ctx-size 4096 --threads 8
Run the bridge against any cat audio (terminal 2):
# Caption-only bridge (no trained classifier)
CUDA_VISIBLE_DEVICES="" uv run python scripts/bridge_caption.py path/to/cat.wav
# Bridge v2: classifier-grounded reasoning (default)
CUDA_VISIBLE_DEVICES="" uv run python scripts/bridge_with_context.py path/to/cat.wav
# Bridge v3: multi-query — runs 3 NatureLM queries, flags disagreements
CUDA_VISIBLE_DEVICES="" uv run python scripts/bridge_multiquery.py path/to/cat.wav
# Batch over many clips at once (writes /tmp/cat_batch_{results.jsonl,report.md})
CUDA_VISIBLE_DEVICES="" uv run python scripts/batch_bridge.py --source mixed --n-each 5
# Probe which NatureLM queries produce informative captions
CUDA_VISIBLE_DEVICES="" uv run python scripts/probe_naturelm_queries.py
CUDA_VISIBLE_DEVICES="" forces NatureLM-audio to CPU so it doesn't fight llama-server for VRAM. ~60s cold start (Llama-3.1-8B loads into 16 GB of system RAM), <10s warm.
# 1. Extract BEATs-stats + classical features for all 276 CatMeows clips
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_beats_stats_features.py
uv run python scripts/extract_classical_features.py
# Build the hybrid concat (saved as hybrid_features_{train,test}.npy)
uv run python -c "
import numpy as np
from pathlib import Path
D = Path.home() / 'datasets/cats'
for split in ['train', 'test']:
h = np.concatenate([np.load(D/f'stats_features_{split}.npy'),
np.load(D/f'classical_features_{split}.npy')], axis=1)
np.save(D/f'hybrid_features_{split}.npy', h.astype(np.float32))
"
# 2. Train final classifier on ALL data (no held-out — use CV results for accuracy estimate)
uv run python scripts/train_context_classifier.py --variant hybrid --all
# 3. (Optional) Confirm via 5-fold CV + full hyperparam sweep across all variants
uv run python scripts/cross_validate.py
uv run python scripts/sweep_features.py
Outputs ~/datasets/cats/context_classifier_hybrid_all.pt (used by bridge_with_context.py) plus per-variant cv_results.json and cv_results_augmented.json.
# BEATs-mean only — 76.5% CV (simpler baseline)
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_beats_features.py
uv run python scripts/train_context_classifier.py --variant mean --all
# BEATs-stats only — 81.5% CV (no classical features)
uv run python scripts/train_context_classifier.py --variant stats --all
# Q-Former features — 73.9% CV (negative result, documents bottleneck)
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_qformer_features.py
# Augmented BEATs-stats — 75.4% CV (negative result, off-the-shelf aug hurt)
CUDA_VISIBLE_DEVICES="" uv run python scripts/extract_beats_stats_augmented.py
uv run python scripts/cross_validate_augmented.py
scripts/
├── bridge_caption.py v1 bridge: NatureLM caption → GPT-OSS-20B
├── bridge_with_context.py v2 bridge: + hybrid classifier (82.2% CV)
├── bridge_multiquery.py v3 bridge: 3 NatureLM queries + classifier
├── batch_bridge.py batch-process many clips, save JSONL + Markdown
├── batch_bridge_multiquery.py batch-process clips with v3 multi-query
├── probe_naturelm_queries.py evaluate which NatureLM queries are informative
├── sara_recorder.py interactive per-cat corpus collection (turn-taking aware)
├── extract_beats_features.py BEATs mean-pool features (baseline)
├── extract_beats_stats_features.py BEATs mean+std+max stats features
├── extract_classical_features.py 102-dim MFCC+spectral+ZCR+RMS via librosa
├── extract_qformer_features.py Q-Former features (negative-result experiment)
├── extract_beats_stats_augmented.py 5×-augmented BEATs-stats (negative-result experiment)
├── train_context_classifier.py MLP head training, --variant {mean|stats|hybrid}, --all
├── cross_validate.py 5-fold stratified CV across {mean, stats, Q-Former}
├── cross_validate_augmented.py 5-fold group-aware CV with augmented training
├── sweep_features.py 80-config hyperparam sweep across all 5 variants
└── smoke_test_gpt_oss.py standalone GPT-OSS-20B load test (transformers path)
Nothing in NatureLM/ is modified — this is purely additive on top of upstream earthspecies/NatureLM-audio.
scripts/): Apache 2.0, same as upstreamIf you want commercial use of the trained cat classifier, contact Earth Species Project regarding their NC-licensed weights — the classifier head itself is small enough (~200K params) to retrain on different upstream features.
scripts/sara_recorder.py builds a per-cat corpus toward this — interactive recording + Scott's contextual labels (what was happening, interpretation, turn-taking flag). Saved to ~/sara-corpus/.For the actual research question — does a specific cat have a personal repertoire and turn-taking exchanges with their human? — the path is observing one cat closely over time, not more accuracy on a 21-cat dataset.
# List input devices (pick one if not using system default)
uv run python scripts/sara_recorder.py --list-devices
# Record interactively (ENTER to start/stop), prompts for context labels
uv run python scripts/sara_recorder.py
# Specify cat name and input device
uv run python scripts/sara_recorder.py --cat Sara --device 6
Saves ~/sara-corpus/{ISO-timestamp}/audio.wav + meta.json per clip. The metadata captures both context (what was happening, your interpretation) and turn-taking structure (was this an exchange? when did you acknowledge? did she follow up?) — because turn-taking patterns are the empirically important structural signal that pure single-clip classification misses entirely.
Once a corpus exists, the natural follow-on analyses are: (a) BEATs feature clustering of the recorded clips to see if observed contexts form acoustic clusters, (b) comparing turn-taking-exchange clips to non-exchange clips, (c) re-training the classifier with per-cat data added.
This project is a soft fork of earthspecies/NatureLM-audio. The audio-language foundation model, BEATs encoder fine-tune, and the entire bioacoustic pretraining stack are their work.
Python
99.9%