From cf283570fa9f0c699e7ce02a2be0a28f201a0be0 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Sun, 4 Oct 2026 11:25:09 +0000 Subject: [PATCH 1/2] docs: the VAD head false-alarms on noise-only audio; Silero plus head fusion experiment Measured on synthetic LibriSpeech with added noise: on a speech-free file the Parakeet VAD head calls about 99 percent of the frames speech, and in a 30 s noise stretch inside a speech file it called 17.7 percent (Ultra) and 55 percent (Redux) of the frames speech, against 0 percent for Silero. Say so in docs/vad.md, docs/vad-benchmarks.md and the README, and prefer Silero as the always-on gate. Add the offline experiment that fuses Silero and the head, with the scripts and small result files under scripts/vad_bench/fusion/. Fusion is not implemented in parakeet.cpp; no code changed. Assisted-by: Claude:claude-sonnet-5-5 [Claude Code] --- README.md | 8 + docs/vad-benchmarks.md | 177 +++++++- docs/vad.md | 21 + scripts/vad_bench/fusion/README.md | 70 +++ scripts/vad_bench/fusion/analyze.py | 69 +++ scripts/vad_bench/fusion/collect.py | 27 ++ scripts/vad_bench/fusion/collect_gap.py | 27 ++ scripts/vad_bench/fusion/detail.py | 35 ++ scripts/vad_bench/fusion/fl.py | 59 +++ scripts/vad_bench/fusion/gap_results.md | 62 +++ scripts/vad_bench/fusion/gapeval.py | 51 +++ scripts/vad_bench/fusion/lib.py | 46 ++ scripts/vad_bench/fusion/mkcorpus.py | 29 ++ scripts/vad_bench/fusion/mkgap.py | 21 + scripts/vad_bench/fusion/pr_curves.py | 26 ++ scripts/vad_bench/fusion/pr_points.csv | 546 +++++++++++++++++++++++ scripts/vad_bench/fusion/prep_libri.py | 12 + scripts/vad_bench/fusion/report.py | 109 +++++ scripts/vad_bench/fusion/results.md | 325 ++++++++++++++ scripts/vad_bench/fusion/rules.py | 49 ++ scripts/vad_bench/fusion/run.py | 58 +++ scripts/vad_bench/fusion/ted.py | 23 + scripts/vad_bench/fusion/ted_results.txt | 16 + 23 files changed, 1865 insertions(+), 1 deletion(-) create mode 100644 scripts/vad_bench/fusion/README.md create mode 100644 scripts/vad_bench/fusion/analyze.py create mode 100644 scripts/vad_bench/fusion/collect.py create mode 100644 scripts/vad_bench/fusion/collect_gap.py create mode 100644 scripts/vad_bench/fusion/detail.py create mode 100644 scripts/vad_bench/fusion/fl.py create mode 100644 scripts/vad_bench/fusion/gap_results.md create mode 100644 scripts/vad_bench/fusion/gapeval.py create mode 100644 scripts/vad_bench/fusion/lib.py create mode 100644 scripts/vad_bench/fusion/mkcorpus.py create mode 100644 scripts/vad_bench/fusion/mkgap.py create mode 100644 scripts/vad_bench/fusion/pr_curves.py create mode 100644 scripts/vad_bench/fusion/pr_points.csv create mode 100644 scripts/vad_bench/fusion/prep_libri.py create mode 100644 scripts/vad_bench/fusion/report.py create mode 100644 scripts/vad_bench/fusion/results.md create mode 100644 scripts/vad_bench/fusion/rules.py create mode 100644 scripts/vad_bench/fusion/run.py create mode 100644 scripts/vad_bench/fusion/ted.py create mode 100644 scripts/vad_bench/fusion/ted_results.txt diff --git a/README.md b/README.md index 5621ca0..7fb3c20 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,14 @@ ternary ones. functions, as a stream (`parakeet_capi_vad_stream_*`), and as the cutter for any ASR model: `parakeet-cli transcribe --vad --vad-model silero.gguf`. See [`docs/vad.md`](docs/vad.md). - VAD-only slices of Ultra and Redux (6 to 10 MB, `redux-vad.gguf` and `ultra-vad-q8_0.gguf` in the same repo) hold just the head and its front end. They run `vad` and the `parakeet_capi_vad_*` calls and cannot transcribe. See [`docs/vad.md`](docs/vad.md). +- The head alone gives false alarms on audio without speech: on speech-free noise it calls about + 99 percent of the frames speech (Ultra 99.4, Redux 97.8), and over a 30 s noise stretch inside a + file with speech the false-alarm frame rate was 17.7 percent for Ultra and 55 percent for Redux, + against 0 percent for Silero (synthetic LibriSpeech with added noise). Prefer Silero as the + always-on gate or when the audio can have long non-speech stretches; use the head on audio known + to be mostly speech, or where its higher recall matters. An offline experiment that lets Silero + decide and the head move the edges (not implemented here) is in + [`docs/vad-benchmarks.md`](docs/vad-benchmarks.md#fusing-silero-and-the-head-offline-experiment). - Accuracy, speed and size of both detectors, with the method and the scripts to repeat them: [`docs/vad-benchmarks.md`](docs/vad-benchmarks.md). --- diff --git a/docs/vad-benchmarks.md b/docs/vad-benchmarks.md index ac02032..b84caa4 100644 --- a/docs/vad-benchmarks.md +++ b/docs/vad-benchmarks.md @@ -548,6 +548,173 @@ one command each when the machine is quiet. The accuracy numbers do not depend on load and were not re-run. +## The head on noise-only audio + +The Parakeet VAD head gives false alarms on audio that has no speech. Silero does not. +This was measured on synthetic data: LibriSpeech test-clean utterances with added white +or pink noise (see [the fusion experiment](#fusing-silero-and-the-head-offline-experiment) +for the corpus). The numbers are frame rates at threshold 0.5 with the default +segmenter settings (bridge 0.1 s, minimum speech 0.1 s, minimum pause 0.2 s, no padding). + +| Test | Silero | Ultra head (Q8_0) | Redux head (packed) | +| --- | ---: | ---: | ---: | +| Speech-free 30 s clips (96 clips: white and pink noise at 20, 10, 5 and 0 dB), share of frames called speech | 0.00% | 99.44% | 97.83% | +| A 30 s noise stretch inside a file that also has speech (96 files), share of frames in the stretch called speech | 0.0% | 17.7% | 55.2% | + +On a file with no speech at all, the heads call nearly every frame speech. The likely +reason is that the head normalises the features of each file on their own, so pure +noise is rescaled until it looks like speech (this is an explanation, not something the +experiment tested). When the file also has speech, the normalisation is set by the +speech, and the false alarms drop, but they do not go away. For the stretch inside a +speech file, the rate depends on the noise level: + +| Noise level (SNR) | Ultra head | Redux head | Silero | +| --- | ---: | ---: | ---: | +| 20 dB | 0.0% | 35.2% | 0.0% | +| 10 dB | 0.6% | 65.3% | 0.0% | +| 5 dB | 14.3% | 60.8% | 0.0% | +| 0 dB | 55.8% | 59.4% | 0.0% | + +For Ultra the false alarms grow with the noise level. For Redux they are already high at +20 dB. Each cell pools 12 white noise and 12 pink noise files. The numbers are in +`scripts/vad_bench/fusion/results.md` (noise-only clips) and `gap_results.md` (the +stretch inside a file). + +What this means: use Silero as the always-on gate, and whenever the audio can contain +long stretches without speech. Use the head on audio that is known to be mostly speech, +for example before the transcription of recorded talks, or where its higher recall +matters. + +Limits: synthetic noise (white and pink) added to read English speech. Babble, music, +room noise and other languages were not tested. The false alarms in a stretch were +measured only for a stretch of about 30 s. + +## Fusing Silero and the head (offline experiment) + +The head has the higher recall and Silero has the cleaner decisions. This experiment +asked whether combining them beats each alone. **Fusion is not implemented in +parakeet.cpp.** It is an offline experiment: the rules run in a Python port of the +segmenter (`scripts/vad_bench/fusion/`), on probabilities saved from Silero and from the +heads. No C++ code, API or option changed. + +### Setup + +- Corpus: rebuilt from LibriSpeech test-clean. 342 speech clips (38 sets of 5 + utterances, each set in 9 conditions: clean, and white and pink noise at 20, 10, 5 and + 0 dB SNR) and 96 noise-only 30 s clips (12 per noise type and level). +- Detectors: Silero ONNX 6.2.3 (run with onnxruntime), the Ultra Q8_0 head and the Redux + packed head, through `parakeet-cli vad --probabilities`. +- All probabilities go to a 10 ms grid by sample-and-hold. All rules use the same + post-processing: bridge 0.1 s, minimum speech 0.1 s, minimum pause 0.2 s, no padding. +- Tuned numbers use speaker-disjoint 4-fold cross-validation: the parameters are chosen on + three folds and scored on the fourth. All intervals are 95 percent bootstrap intervals + over clips. +- Score: frame level precision (P), recall (R) and F1 against the reference, pooled over + the 342 speech clips. The reference is the span of each utterance in the synthetic + clip, with an energy trim. + +### The two-stage rule + +Silero decides what is speech. The head only changes the edges: it moves the start of a +Silero region earlier by up to 160 to 320 ms and its end later by up to 160 ms, but only +where the head also says speech, and it fills a gap between two regions if the gap is +shorter than 300 to 600 ms and the head calls at least half of it speech. The default +setting is 160 ms before, 160 ms after, 300 ms fill; the best setting found on all the +clips was 320 ms before, 160 ms after, 600 ms fill. Where Silero finds no speech, the head can +never add any. This is why the rule has no false alarms on noise. + +### Results at the defaults (threshold 0.5) + +Pooled over 342 clips, percent. + +| System | P | R | F1 | +| --- | ---: | ---: | ---: | +| Silero | 99.6 | 85.6 | 92.1 | +| Ultra head | 98.6 | 89.4 | 93.8 | +| Redux head | 97.6 | 92.0 | 94.7 | +| OR of both (Redux) | 97.6 | 92.1 | 94.8 | +| AND of both (Redux) | 99.6 | 85.5 | 92.0 | +| Two-stage (Ultra) | 99.0 | 90.0 | 94.3 | +| Two-stage (Redux) | 98.9 | 91.2 | 94.9 | + +### Results after tuning (cross-validated) + +| System | P | R | F1 | F1 gain over the best single detector | +| --- | ---: | ---: | ---: | ---: | +| Silero, tuned threshold | 98.3 | 91.1 | 94.5 | | +| Ultra head, tuned threshold | 95.8 | 93.0 | 94.4 | | +| Redux head, tuned threshold | 96.9 | 92.8 | 94.8 | | +| Two-stage (Ultra) | 98.5 | 92.3 | 95.3 | +0.75 [+0.65, +0.86] | +| Two-stage (Redux) | 98.0 | 93.9 | 95.9 | +1.13 [+1.00, +1.28] | + +The gain is measured against the better of the two single detectors with their own tuned +threshold (Silero for Ultra, the Redux head for Redux). + +Recall when precision must stay at 99 percent or more (the threshold is chosen on the +training folds): + +| System | Recall at P of 99 percent or more | +| --- | ---: | +| Silero | 88.6 | +| Ultra head | 87.4 | +| Redux head | 89.4 | +| Two-stage (Ultra) | 90.6 | +| Two-stage (Redux) | 91.5 | + +### In plain language + +At the defaults on this corpus, for every 100 s of real speech Silero misses 14.4 s, +the Redux head 8.0 s and the two-stage rule 8.8 s (the Ultra head 10.6 s). For every +100 s that a detector calls speech, Silero is wrong for 0.4 s, the Redux head for 2.4 s +and the two-stage rule for 1.1 s (the Ultra head 1.4 s). The two-stage rule keeps most of +the head's extra recall and about two thirds of Silero's precision advantage over +the Redux head, and it never fires on noise-only audio. These are the default rows of the table +above, expressed in seconds; they are frame counts, not a statement about words. + +### Rules that did not help + +- **OR, max and mean of the two probabilities.** They inherit the head's false alarms on + noise. OR and max call 99.4 percent of the frames of the noise-only clips speech with + Ultra (97.8 percent with Redux), the same as the head alone. The mean at the default + weights is clean on noise-only clips (0.0 to 0.1 percent) but gained less on speech + (F1 93.4 for Redux against 94.9 for the two-stage rule). +- **AND and min.** No false alarms, but they are no better than Silero: recall 85.5 + against 85.6. +- **Switching between the two on an estimated noise level.** At the defaults it gave the + same false alarms on noise-only clips as the head alone (99.4 percent of the frames + with Ultra, 97.8 percent with Redux), so it did not remove the problem. +- **Logistic regression and gradient boosting on both probabilities and their neighbours.** + They scored the highest F1 on the synthetic clips (95.1 to 96.3 at threshold 0.5), but they + depend on the data they were fitted on. Fitted without noise-only clips, + boosting called 95 percent of the frames of noise-only clips speech with Ultra and 60 + percent with Redux, and the logistic regression 20 to 25 percent (Ultra) and 5 to 10 + percent (Redux). Adding noise-only clips to the training set brought those false alarms + down to under 0.3 percent, but the version fitted without them, applied to a real 600 s + talk, called about 90 percent of it speech, against 77.5 percent for Silero and 83 to + 84 percent for the heads (there was no reference for that talk, so this is a warning + sign and not a measured error). The two-stage rule called 84 percent. + +### Cost + +The two-stage rule needs both detectors to run. The cost is about the sum of the two. +From the speeds above (Silero about 660x real time and a head about 93x on one thread, +shared machine) both together run at about 80x real time. The rule itself is cheap +compared with the model runs, but it was not timed separately. + +### Caveats + +- Synthetic read speech with synthetic white and pink noise. Babble, music, + reverberation and non-English speech were not tested. +- Part of every tuned gain comes from the reference convention (it counts the pauses + inside an utterance as speech and uses an energy trim). A detector that bridges short + pauses gains from that. A gain of one F1 point is not a claim about real use. +- The word-time reference of the TED talks was lost, so the rules were not scored on + real talks. Only the share of speech per rule on one 600 s talk is reported. +- One corpus, one random seed for the bootstrap, one machine for the probabilities. + +Scripts, the small result files and how to reproduce: +[`scripts/vad_bench/fusion/`](../scripts/vad_bench/fusion/README.md). + ## When to use which This is limited to what the numbers above support. @@ -556,7 +723,11 @@ This is limited to what the numbers above support. MB, the command line tool needs about 60 MB, and it ran at about 660x real time on one thread on a shared machine. It gave frame level F1 of 91.7 to 94.4 on the synthetic data and 91.8 to 92.1 on the TED talks. -- **The Parakeet head when Ultra or Redux is already loaded.** The head costs no extra +- **Always-on gate, or audio with long stretches without speech.** Silero. The heads give + false alarms on noise-only audio (see + [the head on noise-only audio](#the-head-on-noise-only-audio)); Silero gave none. +- **The Parakeet head when Ultra or Redux is already loaded, on audio that is mostly + speech** (for example recorded talks before transcription). The head costs no extra model and its scores are close to Silero in the same tests: within about 1.5 F1 points on the synthetic clips at 5 dB and above (Ultra was 0.4 lower in white noise at 5 dB), 2 to 2.5 points higher on the TED talks, and 1.7 to 2.6 points higher at 0 dB pink @@ -575,6 +746,10 @@ This is limited to what the numbers above support. probabilities of parakeet.cpp match onnxruntime more closely (mean abs diff 0.00002, 2 flipped frames) than those of whisper.cpp do (0.00224, 309 flipped frames), but this did not change the segment scores. +- **Both together.** In an offline experiment, Silero deciding and the head only moving + the edges (the two-stage rule) gave 0.75 to 1.13 more F1 points than the best single + detector and no false alarms on noise. It is not implemented in parakeet.cpp. See + [the fusion experiment](#fusing-silero-and-the-head-offline-experiment). - **Not supported by these numbers:** any claim about GPUs, ARM, other languages, music or non speech noise, streaming latency, or the effect of the VAD on WER. diff --git a/docs/vad.md b/docs/vad.md index a8eda25..85367f9 100644 --- a/docs/vad.md +++ b/docs/vad.md @@ -15,6 +15,27 @@ transcription segments. The segmenter takes the frame period as a parameter Accuracy and speed numbers for both detectors, with the method and the scripts to repeat them, are in [vad-benchmarks.md](vad-benchmarks.md). +## Which detector to use + +The Parakeet VAD head alone gives false alarms on audio without speech. On a speech-free +file the head calls about 99 percent of the frames speech (Ultra 99.4 percent, Redux 97.8 +percent), because the features are normalised per file and pure noise is rescaled until it +looks like speech. Inside a file that has speech, over a 30 s stretch of noise at +threshold 0.5, the false-alarm frame rate was 17.7 percent for the Ultra head and 55 +percent for the Redux head, and it grows with the noise level. Silero showed 0 percent in +both tests. This was measured on synthetic data: LibriSpeech with added white and pink +noise, see [the experiment](vad-benchmarks.md#the-head-on-noise-only-audio). + +So prefer Silero as an always-on gate, or whenever the audio can contain long stretches +without speech. Use the head on audio that is known to be mostly speech (for example +before the transcription of recorded talks), or where its higher recall matters. + +An offline experiment also combined the two: Silero decides what is speech, and the head +only moves the edges. It scored 0.75 to 1.13 F1 points above the best single detector on +the synthetic clips and gave no false alarms on noise. It is not implemented in +parakeet.cpp. See +[Fusing Silero and the head](vad-benchmarks.md#fusing-silero-and-the-head-offline-experiment). + ## Standalone VAD API One call returns the speech regions of a clip as JSON, for either detector. Load diff --git a/scripts/vad_bench/fusion/README.md b/scripts/vad_bench/fusion/README.md new file mode 100644 index 0000000..3359307 --- /dev/null +++ b/scripts/vad_bench/fusion/README.md @@ -0,0 +1,70 @@ +# Silero plus head fusion experiment + +Offline experiment behind the section "Fusing Silero and the head (offline experiment)" +in [docs/vad-benchmarks.md](../../../docs/vad-benchmarks.md). It is not part of +parakeet.cpp: the fusion rules are a Python port of the segmenter, and no C++ code +implements them. + +No audio, no probability files and no pickles are committed. The four small result +files are: + +| File | What | +| --- | --- | +| `results.md` | All cross-validated tables for the Ultra head and the Redux head (precision, recall, F1 with bootstrap intervals, false alarms on noise-only clips, paired differences, noise-shift split, operating points at precision 99 percent or more) | +| `gap_results.md` | False alarms inside a 30 s noise stretch that sits in a file with speech. It is the printed output of `gapeval.py` with a longer heading | +| `pr_points.csv` | Points of the precision and recall frontiers (the data of the plot that `pr_curves.py` draws) | +| `ted_results.txt` | Output of `ted.py` on a 600 s talk: share of speech and agreement with Silero and with the head | + +## How it works + +Every script runs from one working directory (the "work directory") and reads and writes +files there with relative names: `data/`, `clips/`, `clips.json`, `gap.json`, +`probs.npz`, `probs_gap.npz`, `results.pkl`. All scripts need `numpy`, `soundfile`; +`run.py` and `gapeval.py` also need `scikit-learn`; `collect*.py` need `onnxruntime`; +`prep_libri.py` needs `datasets`; `pr_curves.py` needs `matplotlib`. Run them with +the work directory as the current directory, for example `python3 /path/to/fusion/run.py` +after copying or linking the scripts into it (the scripts import each other by name). + +The models are given by environment variables, read by `collect.py`, `collect_gap.py` +and `ted.py`: + +| Variable | Value | +| --- | --- | +| `SILERO_ONNX` | `silero_vad.onnx` of Silero VAD 6.2.3 | +| `PARAKEET_CLI` | built `parakeet-cli` (it must support `vad --probabilities`) | +| `ULTRA_GGUF` | ASR GGUF of `moondream/parakeet-ultra`, Q8_0 | +| `REDUX_GGUF` | ASR GGUF of `moondream/parakeet-redux`, packed (`keep`) | + +## Steps + +``` +python3 prep_libri.py # every 13th utterance of LibriSpeech test-clean -> data/libri.npy, data/spk.npy +python3 mkcorpus.py # 342 speech clips (38 sets of 5 utterances x 9 conditions) + 96 noise-only 30 s clips +python3 mkgap.py # 96 clips with a 30 s noise stretch between speech +python3 collect.py # per-frame probabilities of Silero and both heads -> probs.npz +python3 collect_gap.py # the same for the gap clips -> probs_gap.npz +python3 run.py # all rules, thresholds and the learned models -> results.pkl +python3 report.py # results.md (reads results.pkl) +python3 gapeval.py # prints the gap tables +python3 pr_curves.py # pr_curves.png and pr_points.csv +python3 detail.py # boundary error and missed segments -> detail.json +python3 ted.py talk.wav # speech share on one long talk (needs the clips and probs of the steps above) +``` + +The speaker folds are fixed by the seed in `mkcorpus.py`. `rules.py` holds the fusion +rules and the two-stage rule, `fl.py` the 10 ms grid, the post-processing and the +metrics, `analyze.py` the cross-validation and the bootstrap, `lib.py` the clip +builder and the noise. + +## Missing inputs + +- The TED talk used by `ted.py` is not part of this directory and is not fetched by + these scripts; `../fetch_data.py` fetches one TED-LIUM talk. The word-time reference of + the talk used in the main benchmark was lost, so the experiment could not score the + fusion rules against a TED reference. `ted.py` only reports how much of the talk each + rule calls speech and how well the rules agree with each other. +- `ted.py` fits its logistic regression on the synthetic clips, so it needs `clips.json`, + `clips/` and `probs.npz` from the steps above. +- `analyze.py` and `report.py` read `results.pkl` (about 50 MB), which is not committed. + Run `run.py` to make it. The random seeds are fixed, so a re-run should give the same tables + on the same probabilities, but this was not checked on a second machine. diff --git a/scripts/vad_bench/fusion/analyze.py b/scripts/vad_bench/fusion/analyze.py new file mode 100644 index 0000000..88a82c9 --- /dev/null +++ b/scripts/vad_bench/fusion/analyze.py @@ -0,0 +1,69 @@ +import pickle,json,sys,numpy as np +from fl import prf +R=pickle.load(open("results.pkl","rb")); CFG=R["cfg"]; LR=R["lr"]; M=R["meta"] +N=len(M["names"]); fold=np.array(M["fold"]); noz=np.array(M["noise_only"]); snr=np.array(M["snr_true"]); dur=np.array(M["dur"]) +sp=~noz +LEVELS=[("clean",99),("20 dB",20),("10 dB",10),("5 dB",5),("0 dB",0)] +rng=np.random.default_rng(0); B=2000 +def pooled(A,idx): c=A[idx,:4].sum(0); return prf(c) +def key(rule,p,h): return (rule,tuple(p),h,0.1) +def A_of(rule,p,h): return CFG[key(rule,p,"ultra" if rule=="silero" else h)] +HEADS=("ultra","redux") +# ---- candidate sets: dict name -> (list of params, array [ncfg,N,5]) ---- +def cand(rule,h): + hh="ultra" if rule=="silero" else h + ps=[k[1] for k in CFG if k[0]==rule and k[2]==hh and k[3]==0.1] + # keep insertion order + ps=[tuple(float(x) for x in p) for p in dict.fromkeys(ps)]; return ps,np.stack([CFG[key(rule,p,hh)] for p in ps]) +def lrcand(h,kind,split): o,c=LR[(h,kind,split)]; return [(float(t),) for t in __import__("rules").THR],o +def score(arr,idx,crit): + """arr [ncfg,N,5]; returns best cfg index on clips idx""" + c=arr[:,idx,:4].sum(1); tp,fp,fn=c[:,0],c[:,1],c[:,2] + P=tp/np.maximum(1,tp+fp);Rr=tp/np.maximum(1,tp+fn);F=2*P*Rr/np.maximum(1e-12,P+Rr) + if crit=="f1": return int(np.argmax(F)) + ok=P>=crit + return int(np.argmax(np.where(ok,Rr,-1+P*0)) if ok.any() else np.argmax(P)) +def crossfit(getarr,crit,mode="speaker"): + """returns (rows [N,5] with each clip from the cfg chosen without its own fold/shift side, chosen params per split)""" + rows=np.zeros((N,5)); chosen={} + if mode=="speaker": + for f in range(4): + ps,arr=getarr("f%d"%f); tr=np.flatnonzero(sp&(fold!=f)); te=np.flatnonzero(fold==f) + i=score(arr,tr,crit); rows[te]=arr[i][te]; chosen[f]=ps[i] + else: + ps,arr=getarr("shift"); tr=np.flatnonzero(sp&(snr>=10)); te=np.flatnonzero(snr<10) + i=score(arr,tr,crit); rows[te]=arr[i][te]; chosen["shift"]=ps[i] + return rows,chosen +def fixed(arr_ps,p): ps,arr=arr_ps; return arr[ps.index(tuple(p))] +def static(rule,h): + ps,arr=cand(rule,h); return lambda s:(ps,arr) +def lrget(h,kind): return lambda s:lrcand(h,kind,s) +# ---- bootstrap ---- +def boot_idx(idx): return rng.integers(0,len(idx),(B,len(idx))) +def ci(vals): return np.percentile(vals,[2.5,97.5]) +def prf_b(rows,idx,bi): + c=rows[idx][:,:4][bi].sum(1).astype(float) # B,4 + P=c[:,0]/np.maximum(1,c[:,0]+c[:,1]);Rr=c[:,0]/np.maximum(1,c[:,0]+c[:,2]);F=2*P*Rr/np.maximum(1e-12,P+Rr) + return P,Rr,F +def fmt(x,lo,hi): return f"{100*x:.1f} [{100*lo:.1f},{100*hi:.1f}]" +BI={} +def bidx(name,idx): + if name not in BI: BI[name]=(idx,rng.integers(0,len(idx),(B,len(idx)))) + return BI[name] +def level_idx(lv): return np.flatnonzero(sp&(snr==lv)) +def stats(rows): + """pooled + per level point estimates and CIs (paired resample indices fixed per index set)""" + out={} + for nm,idx in [("all",np.flatnonzero(sp))]+[(l,level_idx(v)) for l,v in LEVELS]: + idx,bi=bidx(nm,idx); P,Rr,F=prf_b(rows,idx,bi); p,r,f=pooled(rows,idx) + out[nm]=dict(P=p,R=r,F=f,Pci=ci(P),Rci=ci(Rr),Fci=ci(F),F_b=F) + return out +def fa(rows): + """false-alarm segments per hour and speech-frame FP rate on noise-only clips, per noise level""" + o={} + for l,v in LEVELS[1:]: + idx=np.flatnonzero(noz&(snr==v)); o[l]=(rows[idx,4].sum()/(dur[idx].sum()/3600), rows[idx,1].sum()/max(1,rows[idx,:4].sum())) + idx=np.flatnonzero(noz); o["all"]=(rows[idx,4].sum()/(dur[idx].sum()/3600), rows[idx,1].sum()/rows[idx,:4].sum()) + return o +if __name__=="__main__": + pass diff --git a/scripts/vad_bench/fusion/collect.py b/scripts/vad_bench/fusion/collect.py new file mode 100644 index 0000000..528721f --- /dev/null +++ b/scripts/vad_bench/fusion/collect.py @@ -0,0 +1,27 @@ +import json,os,subprocess,numpy as np,soundfile as sf,onnxruntime as ort +from multiprocessing import Pool +# Paths come from the environment: SILERO_ONNX (silero_vad.onnx, 6.2.3), PARAKEET_CLI (parakeet-cli), +# ULTRA_GGUF and REDUX_GGUF (the ASR GGUFs of the two heads). +M=os.environ["SILERO_ONNX"]; CLI=os.environ["PARAKEET_CLI"] +HEADS={"ultra":os.environ["ULTRA_GGUF"],"redux":os.environ["REDUX_GGUF"]} +meta=json.load(open("clips.json")) +def sil(y,sess): + n=(len(y)+511)//512; y=np.pad(y,(0,n*512-len(y))) + st=np.zeros((2,1,128),np.float32); ctx=np.zeros(64,np.float32); out=[] + for i in range(n): + x=np.concatenate([ctx,y[i*512:(i+1)*512]])[None].astype(np.float32) + o,st=sess.run(None,{"input":x,"state":st,"sr":np.array(16000,np.int64)}) + out.append(float(o[0,0])); ctx=x[0,-64:] + return np.array(out,np.float32) +def work(m): + so=ort.SessionOptions(); so.intra_op_num_threads=1; so.inter_op_num_threads=1 + sess=ort.InferenceSession(M,so,providers=["CPUExecutionProvider"]) + f=f"clips/{m['name']}.wav"; y=sf.read(f,dtype="float32")[0]; r={"silero":sil(y,sess)} + for k,g in HEADS.items(): + j=json.loads(subprocess.run([CLI,"vad","--model",g,"--input",f,"--probabilities","--threads","1"],capture_output=True,text=True,check=True).stdout) + r[k]=np.array(j["probabilities"],np.float32) + return m["name"],r +if __name__=="__main__": + with Pool(4) as p: res=p.map(work,meta,chunksize=4) + flat={f"{n}|{k}":v for n,r in res for k,v in r.items()} + np.savez_compressed("probs.npz",**flat); print("done",len(res)) diff --git a/scripts/vad_bench/fusion/collect_gap.py b/scripts/vad_bench/fusion/collect_gap.py new file mode 100644 index 0000000..b80ff58 --- /dev/null +++ b/scripts/vad_bench/fusion/collect_gap.py @@ -0,0 +1,27 @@ +import json,os,subprocess,numpy as np,soundfile as sf,onnxruntime as ort +from multiprocessing import Pool +# Paths come from the environment: SILERO_ONNX (silero_vad.onnx, 6.2.3), PARAKEET_CLI (parakeet-cli), +# ULTRA_GGUF and REDUX_GGUF (the ASR GGUFs of the two heads). +M=os.environ["SILERO_ONNX"]; CLI=os.environ["PARAKEET_CLI"] +HEADS={"ultra":os.environ["ULTRA_GGUF"],"redux":os.environ["REDUX_GGUF"]} +meta=json.load(open("gap.json")) +def sil(y,sess): + n=(len(y)+511)//512; y=np.pad(y,(0,n*512-len(y))) + st=np.zeros((2,1,128),np.float32); ctx=np.zeros(64,np.float32); out=[] + for i in range(n): + x=np.concatenate([ctx,y[i*512:(i+1)*512]])[None].astype(np.float32) + o,st=sess.run(None,{"input":x,"state":st,"sr":np.array(16000,np.int64)}) + out.append(float(o[0,0])); ctx=x[0,-64:] + return np.array(out,np.float32) +def work(m): + so=ort.SessionOptions(); so.intra_op_num_threads=1; so.inter_op_num_threads=1 + sess=ort.InferenceSession(M,so,providers=["CPUExecutionProvider"]) + f=f"clips/{m['name']}.wav"; y=sf.read(f,dtype="float32")[0]; r={"silero":sil(y,sess)} + for k,g in HEADS.items(): + j=json.loads(subprocess.run([CLI,"vad","--model",g,"--input",f,"--probabilities","--threads","1"],capture_output=True,text=True,check=True).stdout) + r[k]=np.array(j["probabilities"],np.float32) + return m["name"],r +if __name__=="__main__": + with Pool(4) as p: res=p.map(work,meta,chunksize=4) + flat={f"{n}|{k}":v for n,r in res for k,v in r.items()} + np.savez_compressed("probs_gap.npz",**flat); print("done",len(res)) diff --git a/scripts/vad_bench/fusion/detail.py b/scripts/vad_bench/fusion/detail.py new file mode 100644 index 0000000..6014283 --- /dev/null +++ b/scripts/vad_bench/fusion/detail.py @@ -0,0 +1,35 @@ +# Boundary error / missed segments / TED agreement for the cross-fitted systems +import json,numpy as np,soundfile as sf,subprocess +import run +from run import * +from analyze import crossfit,static,lrget,HEADS +folds=np.array([d["fold"] for d in D]); sp_idx=[i for i,d in enumerate(D) if not d["noise_only"]] +def build_systems(h): + S={} + for nm,(r,p) in {"Silero .5":("silero",(0.5,)),"head .5":("head",(0.5,)),"OR .5/.5":("or",(0.5,0.5)),"two-stage .5/.5/160/160/300":("two",(0.5,0.5,16,16,30))}.items(): + S[nm]=lambda d,f,r=r,p=p:raw_mask(r,p,d,h) + for r,nm in (("silero","Silero tuned"),("head","head tuned"),("or","OR tuned"),("and","AND tuned"),("mean","mean tuned"),("two","two-stage tuned")): + _,ch=crossfit(static(r,h),"f1"); S[nm]=lambda d,f,r=r,ch=ch:raw_mask(r,ch[f],d,h) + models={} + for kind in("lr6","hgb6","lr6nz","hgb6nz"): + base=kind.replace("nz",""); _,ch=crossfit(lrget(h,kind),"f1") + for f in range(4): + tr=[i for i in range(len(D)) if folds[i]!=f and (kind.endswith("nz") or not D[i]["noise_only"])] + X=np.concatenate([feats(D[i],h,base)[::10] for i in tr]); y=np.concatenate([D[i]["ref_mask"][::10] for i in tr]) + mdl=HistGradientBoostingClassifier(max_iter=120,learning_rate=0.1,random_state=0) if base=="hgb6" else LogisticRegression(C=1.0,max_iter=300) + models[(kind,f)]=mdl.fit(X,y) + S[f"{kind} tuned"]=lambda d,f,kind=kind,base=base,ch=ch:models[(kind,f)].predict_proba(feats(d,h,base))[:,1]>=ch[f][0] + return S +def metrics(fn,h): + st=[];en=[];miss=0;nref=0;nseg=0;cnt=np.zeros(4) + for i in sp_idx: + d=D[i]; m,regs=post(fn(d,d["fold"]),**POST_HEAD); a,b,ms=bnd(regs,d["ref"]); st+=a;en+=b;miss+=ms;nref+=len(d["ref"]);nseg+=len(regs); cnt+=counts(m,d["ref_mask"]) + q=lambda x:(np.median(x),np.percentile(x,90)) + return dict(start=q(st),end=q(en),miss=100*miss/nref,regions_per_ref=nseg/nref,PRF=[100*x for x in prf(cnt)]) +out={} +for h in HEADS: + out[h]={} + for nm,fn in build_systems(h).items(): + out[h][nm]=metrics(fn,h); o=out[h][nm] + print(f"{h:5s} {nm:30s} start {o['start'][0]:.0f}/{o['start'][1]:.0f} end {o['end'][0]:.0f}/{o['end'][1]:.0f} miss {o['miss']:.2f}% regions/ref {o['regions_per_ref']:.2f} PRF {o['PRF'][0]:.1f}/{o['PRF'][1]:.1f}/{o['PRF'][2]:.1f}",flush=True) +json.dump(out,open("detail.json","w"),default=float) diff --git a/scripts/vad_bench/fusion/fl.py b/scripts/vad_bench/fusion/fl.py new file mode 100644 index 0000000..f5f0050 --- /dev/null +++ b/scripts/vad_bench/fusion/fl.py @@ -0,0 +1,59 @@ +"""Fusion library: 10 ms grid, segmenter semantics, metrics.""" +import json,numpy as np,soundfile as sf +GR=0.01 +FS={"silero":0.032,"ultra":0.08,"redux":0.08} +def hold(p,fs,n): + """sample-and-hold: grid cell j (center (j+.5)*10ms) takes the value of the frame containing it; cells past the last frame get 0.""" + idx=((np.arange(n)+0.5)*GR/fs).astype(int) + out=np.zeros(n,np.float32); ok=idx0 and b1.0: miss+=1 # same convention as the earlier comparison: miss if a boundary is >1 s away + if ds<=1.0: st.append(ds*1000) + if de<=1.0: en.append(de*1000) + return st,en,miss +def load(name_filter=None): + meta=json.load(open("clips.json")); P=np.load("probs.npz") + D=[] + for m in meta: + n=int(round(m["dur"]/GR)); d=dict(m); d["n"]=n + d["ref_mask"]=np.zeros(n,bool) + for s,e in m["ref"]: d["ref_mask"][int(round(s/GR)):int(round(e/GR))]=True + for k in FS: d[k]=hold(P[f"{m['name']}|{k}"],FS[k],n) + y=sf.read(f"clips/{m['name']}.wav",dtype="float32")[0] + fr=len(y)//160; e2=(y[:fr*160].reshape(fr,160)**2).mean(1)+1e-12 + d["snr_est"]=10*np.log10(np.percentile(e2,90)/np.percentile(e2,10)) + D.append(d) + return D diff --git a/scripts/vad_bench/fusion/gap_results.md b/scripts/vad_bench/fusion/gap_results.md new file mode 100644 index 0000000..c488c2e --- /dev/null +++ b/scripts/vad_bench/fusion/gap_results.md @@ -0,0 +1,62 @@ +## ultra: FP frame rate inside embedded 30 s noise stretch % / false regions started in it (total over 96 clips: 12 per level x white,pink... 12 per level pooled over both noise types) +| system | 20 dB | 10 dB | 5 dB | 0 dB | all | +|---|---|---|---|---|---| +| Silero .5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| head .5 | 0.0% / 0 | 0.6% / 19 | 14.3% / 183 | 55.8% / 450 | 17.7% / 652 | +| OR .5/.5 | 0.0% / 0 | 0.6% / 19 | 14.3% / 183 | 55.8% / 450 | 17.7% / 652 | +| AND .5/.5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| mean .5/.5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| max .5 | 0.0% / 0 | 0.6% / 19 | 14.3% / 183 | 55.8% / 450 | 17.7% / 652 | +| min .5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| two-stage .5/.5/160/160/300 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| noise-switch .5/.5/T12 | 0.0% / 0 | 0.6% / 17 | 14.3% / 183 | 55.8% / 450 | 17.7% / 650 | +| Silero tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| head tuned | 0.0% / 0 | 16.7% / 223 | 58.0% / 356 | 97.4% / 59 | 43.0% / 638 | +| OR tuned | 0.0% / 0 | 0.6% / 20 | 14.3% / 183 | 55.8% / 449 | 17.7% / 652 | +| AND tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| max tuned | 0.0% / 0 | 11.9% / 205 | 50.9% / 359 | 95.6% / 88 | 39.6% / 652 | +| min tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| mean tuned | 0.0% / 0 | 2.9% / 66 | 24.3% / 218 | 66.0% / 385 | 23.3% / 669 | +| two-stage tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 1 | 0.0% / 0 | 0.0% / 1 | +| noise-switch tuned | 0.0% / 0 | 8.7% / 151 | 44.9% / 335 | 92.5% / 133 | 36.5% / 619 | +| lr2 @.5 | 0.0% / 0 | 0.0% / 0 | 0.8% / 6 | 2.1% / 11 | 0.7% / 17 | +| lr2 tuned | 0.0% / 0 | 0.0% / 0 | 0.4% / 4 | 1.3% / 9 | 0.4% / 13 | +| lr6 @.5 | 0.0% / 0 | 0.0% / 0 | 0.5% / 3 | 1.7% / 4 | 0.5% / 7 | +| lr6 tuned | 0.0% / 0 | 0.0% / 0 | 0.2% / 1 | 1.1% / 5 | 0.3% / 6 | +| hgb6 @.5 | 0.0% / 0 | 0.2% / 8 | 3.9% / 92 | 17.8% / 303 | 5.5% / 403 | +| hgb6 tuned | 0.0% / 0 | 0.2% / 8 | 3.9% / 92 | 17.8% / 303 | 5.5% / 403 | +| lr6nz @.5 | 0.0% / 0 | 0.0% / 0 | 0.1% / 0 | 0.2% / 1 | 0.1% / 1 | +| lr6nz tuned | 0.0% / 0 | 0.0% / 0 | 0.1% / 1 | 0.2% / 1 | 0.1% / 2 | +| hgb6nz @.5 | 0.5% / 15 | 2.9% / 61 | 3.6% / 75 | 1.2% / 35 | 2.0% / 186 | +| hgb6nz tuned | 0.6% / 16 | 3.2% / 68 | 4.4% / 84 | 1.2% / 38 | 2.4% / 206 | +## redux: FP frame rate inside embedded 30 s noise stretch % / false regions started in it (total over 96 clips: 12 per level x white,pink... 12 per level pooled over both noise types) +| system | 20 dB | 10 dB | 5 dB | 0 dB | all | +|---|---|---|---|---|---| +| Silero .5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| head .5 | 35.2% / 348 | 65.3% / 383 | 60.8% / 442 | 59.4% / 461 | 55.2% / 1634 | +| OR .5/.5 | 35.2% / 348 | 65.3% / 383 | 60.8% / 442 | 59.4% / 461 | 55.2% / 1634 | +| AND .5/.5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| mean .5/.5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| max .5 | 35.2% / 348 | 65.3% / 383 | 60.8% / 442 | 59.4% / 461 | 55.2% / 1634 | +| min .5 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| two-stage .5/.5/160/160/300 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| noise-switch .5/.5/T12 | 0.0% / 0 | 25.4% / 66 | 56.2% / 379 | 59.4% / 461 | 35.2% / 906 | +| Silero tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| head tuned | 45.6% / 352 | 79.1% / 280 | 76.0% / 345 | 74.0% / 363 | 68.7% / 1340 | +| OR tuned | 16.9% / 260 | 35.0% / 414 | 29.2% / 437 | 33.0% / 432 | 28.5% / 1543 | +| AND tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| max tuned | 45.6% / 352 | 79.1% / 280 | 76.0% / 345 | 74.0% / 363 | 68.7% / 1340 | +| min tuned | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | 0.0% / 0 | +| mean tuned | 17.2% / 262 | 35.5% / 416 | 30.1% / 438 | 34.0% / 433 | 29.2% / 1549 | +| two-stage tuned | 0.0% / 0 | 0.0% / 0 | 0.1% / 0 | 0.0% / 0 | 0.0% / 0 | +| noise-switch tuned | 35.2% / 348 | 65.3% / 383 | 60.8% / 442 | 59.4% / 461 | 55.2% / 1634 | +| lr2 @.5 | 0.4% / 1 | 0.7% / 4 | 0.9% / 6 | 1.3% / 7 | 0.8% / 18 | +| lr2 tuned | 0.1% / 0 | 0.2% / 2 | 0.5% / 3 | 0.8% / 6 | 0.4% / 11 | +| lr6 @.5 | 0.1% / 1 | 0.2% / 1 | 0.5% / 3 | 0.8% / 5 | 0.4% / 10 | +| lr6 tuned | 0.0% / 0 | 0.1% / 1 | 0.3% / 0 | 0.3% / 2 | 0.2% / 3 | +| hgb6 @.5 | 1.9% / 44 | 3.3% / 76 | 2.5% / 54 | 5.1% / 102 | 3.2% / 276 | +| hgb6 tuned | 2.2% / 51 | 3.4% / 77 | 2.6% / 58 | 5.4% / 105 | 3.4% / 291 | +| lr6nz @.5 | 0.0% / 0 | 0.0% / 0 | 0.1% / 0 | 0.1% / 0 | 0.1% / 0 | +| lr6nz tuned | 0.0% / 0 | 0.0% / 0 | 0.1% / 0 | 0.1% / 0 | 0.1% / 0 | +| hgb6nz @.5 | 0.9% / 15 | 0.2% / 8 | 0.4% / 8 | 0.4% / 11 | 0.5% / 42 | +| hgb6nz tuned | 1.0% / 15 | 0.2% / 8 | 0.4% / 8 | 0.4% / 12 | 0.5% / 43 | \ No newline at end of file diff --git a/scripts/vad_bench/fusion/gapeval.py b/scripts/vad_bench/fusion/gapeval.py new file mode 100644 index 0000000..687799d --- /dev/null +++ b/scripts/vad_bench/fusion/gapeval.py @@ -0,0 +1,51 @@ +# False alarms inside a 30 s speech-free noise stretch embedded in a file that also has speech +import json,numpy as np,soundfile as sf +import run +from run import * +from analyze import crossfit,static,lrget,HEADS +G=json.load(open("gap.json")); PG=np.load("probs_gap.npz"); GD=[] +for m in G: + n=int(round(m["dur"]/GR)); d=dict(m); d["n"]=n; d["ref_mask"]=np.zeros(n,bool) + for s,e in m["ref"]: d["ref_mask"][int(round(s/GR)):int(round(e/GR))]=True + for k in FS: d[k]=hold(PG[f"{m['name']}|{k}"],FS[k],n) + y=sf.read(f"clips/{m['name']}.wav",dtype="float32")[0]; fr=len(y)//160; e2=(y[:fr*160].reshape(fr,160)**2).mean(1)+1e-12 + d["snr_est"]=10*np.log10(np.percentile(e2,90)/np.percentile(e2,10)); d["snr_true"]=int(m["cond"].lstrip("whitepink")); GD.append(d) +folds=np.array([d["fold"] for d in D]); easy=None +def fa_stretch(mask,d): + m,regs=post(mask,**POST_HEAD); a,b=d["stretch"]; ia,ib=int(a/GR),int(b/GR) + fp=m[ia:ib].mean(); nseg=sum(1 for s,e in regs if a<=s=(thr if thr is not None else ch[f][0]) + res={} + for nm,fn in sysmask.items(): + L={}; + for d in GD: + fp,ns=fa_stretch(fn(d,d["fold"]),d); L.setdefault(d["snr_true"],[]).append((fp,ns)) + res[nm]={snr_:(float(np.mean([x[0] for x in v])),float(np.sum([x[1] for x in v]))) for snr_,v in L.items()} + allv=[x for v in L.values() for x in v]; res[nm]["all"]=(float(np.mean([x[0] for x in allv])),float(np.sum([x[1] for x in allv]))) + out[h]=res +json.dump(out,open("gap_results.json","w")) +for h,res in out.items(): + print(f"## {h}: embedded 30 s noise stretch ({len(GD)} clips = 12 per level x2 noise types... see below), FP frame rate in the stretch % / false regions started in stretch (total over clips)") + print("| system | 20 dB | 10 dB | 5 dB | 0 dB | all |"); print("|---|---|---|---|---|---|") + for nm,r in res.items(): print(f"| {nm} | "+" | ".join(f"{100*r[k][0]:.1f}% / {r[k][1]:.0f}" for k in (20,10,5,0,"all"))+" |") diff --git a/scripts/vad_bench/fusion/lib.py b/scripts/vad_bench/fusion/lib.py new file mode 100644 index 0000000..b27114c --- /dev/null +++ b/scripts/vad_bench/fusion/lib.py @@ -0,0 +1,46 @@ +import numpy as np +GR=0.01 # mask grid 10 ms +def mask(spans,n_sec): + m=np.zeros(int(round(n_sec/GR)),bool) + for s,e in spans: + m[int(round(s/GR)):int(round(e/GR))]=True + return m +def trim(u): + fr=len(u)//160; r=np.sqrt((u[:fr*160].reshape(fr,160)**2).mean(1)) + idx=np.where(r>r.max()*0.01)[0] + return u[idx[0]*160:(idx[-1]+1)*160] +def pink(n,rng): + X=np.fft.rfft(rng.standard_normal(n)); f=np.arange(len(X)); f[0]=1 + y=np.fft.irfft(X/np.sqrt(f),n); return y/y.std() +def build(utts,rng): + g=lambda a,b: rng.uniform(a,b) + parts=[];spans=[];t=g(0.5,1.5) + parts.append(rng.standard_normal(int(t*16000))*1e-3) + for i,u in enumerate(utts): + u=trim(u); spans.append((t,t+len(u)/16000)); parts.append(u); t+=len(u)/16000 + gap=g(0.3,2.0) if i1.0: miss+=1 + else: out.append(d*1000) + return out,miss + diff --git a/scripts/vad_bench/fusion/mkcorpus.py b/scripts/vad_bench/fusion/mkcorpus.py new file mode 100644 index 0000000..4c527d3 --- /dev/null +++ b/scripts/vad_bench/fusion/mkcorpus.py @@ -0,0 +1,29 @@ +# Build the fusion corpus: 4 speaker folds x 10 clips x 9 conditions (+ noise-only clips). Speakers never cross folds. +import json,os,numpy as np,soundfile as sf +from lib import * +libri=list(np.load("data/libri.npy",allow_pickle=True)); spk=np.load("data/spk.npy") +rng=np.random.default_rng(11) +sp_ids=rng.permutation(sorted(set(spk))); fold_of={s:i%4 for i,s in enumerate(sp_ids)} +sets=[] # (fold, utts) +for f in range(4): + idx=[i for i in range(len(libri)) if fold_of[spk[i]]==f]; idx=list(rng.permutation(idx)) + for k in range(min(10,len(idx)//5)): sets.append((f,[libri[i] for i in idx[k*5:(k+1)*5]])) +conds=[("clean",None)]+[(k,s) for k in("white","pink") for s in(20,10,5,0)] +os.makedirs("clips",exist_ok=True); meta=[]; Ps=[] +for ci,(kind,snr) in enumerate(conds): + for k,(f,us) in enumerate(sets): + y,spans=build(us,np.random.default_rng(1000+k)) + if ci==0: Ps.append(float(np.mean(np.concatenate([y[int(s*16000):int(e*16000)] for s,e in spans])**2))) + y=add_noise(y,spans,kind,snr,np.random.default_rng(5000+ci*100+k)) + name=f"{kind}{'' if snr is None else snr}_{k:02d}" + sf.write(f"clips/{name}.wav",y,16000,subtype="PCM_16") + meta.append(dict(name=name,cond=name.split("_")[0],fold=f,ref=spans,dur=len(y)/16000)) +# noise-only: 12 x 30 s per noise condition, level from a speech set's power (same SNR definition) +for ci,(kind,snr) in enumerate(conds[1:]): + for k in range(12): + r=np.random.default_rng(9000+ci*100+k); P=Ps[(k*3)%len(Ps)]; n=30*16000 + z=r.standard_normal(n) if kind=="white" else pink(n,r) + z=np.clip(z*np.sqrt(P/10**(snr/10)),-1,1).astype(np.float32) + name=f"nz-{kind}{snr}_{k:02d}"; sf.write(f"clips/{name}.wav",z,16000,subtype="PCM_16") + meta.append(dict(name=name,cond=f"nz-{kind}{snr}",fold=-1,ref=[],dur=30.0)) +json.dump(meta,open("clips.json","w")); print(len(meta),sum(m["dur"] for m in meta)) diff --git a/scripts/vad_bench/fusion/mkgap.py b/scripts/vad_bench/fusion/mkgap.py new file mode 100644 index 0000000..894e912 --- /dev/null +++ b/scripts/vad_bench/fusion/mkgap.py @@ -0,0 +1,21 @@ +# "Embedded noise stretch" clips: 2 utterances, 30 s of noise-only, 3 utterances; noise over the whole clip at the given SNR. +# Tests false alarms where the file also contains speech (the head normalises features per file). +import json,numpy as np,soundfile as sf +from lib import * +libri=list(np.load("data/libri.npy",allow_pickle=True)); spk=np.load("data/spk.npy") +rng=np.random.default_rng(11) +sp_ids=rng.permutation(sorted(set(spk))); fold_of={s:i%4 for i,s in enumerate(sp_ids)} +meta=[] +conds=[(k,s) for k in("white","pink") for s in(20,10,5,0)] +for ci,(kind,snr) in enumerate(conds): + for k in range(12): + f=k%4; idx=[i for i in range(len(libri)) if fold_of[spk[i]]==f]; r=np.random.default_rng(300+ci*50+k); idx=list(r.permutation(idx))[:5] + us=[libri[i] for i in idx] + y1,s1=build(us[:2],np.random.default_rng(400+k)); y2,s2=build(us[2:],np.random.default_rng(500+k)) + off=len(y1)/16000+30.0 + y=np.concatenate([y1,np.random.default_rng(600+k).standard_normal(30*16000)*1e-3,y2]).astype(np.float32) + spans=s1+[(a+off,b+off) for a,b in s2] + y=add_noise(y,spans,kind,snr,np.random.default_rng(7000+ci*100+k)) + name=f"gap-{kind}{snr}_{k:02d}"; sf.write(f"clips/{name}.wav",y,16000,subtype="PCM_16") + meta.append(dict(name=name,cond=f"{kind}{snr}",fold=f,ref=spans,dur=len(y)/16000,stretch=[s1[-1][1]+0.5,s2[0][0]+off-0.5])) +json.dump(meta,open("gap.json","w")); print(len(meta),sum(m["dur"] for m in meta)) diff --git a/scripts/vad_bench/fusion/pr_curves.py b/scripts/vad_bench/fusion/pr_curves.py new file mode 100644 index 0000000..da61234 --- /dev/null +++ b/scripts/vad_bench/fusion/pr_curves.py @@ -0,0 +1,26 @@ +from analyze import * +import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt +allidx=np.flatnonzero(sp); rows=[] +def pr(arr): c=arr[:,allidx,:4].sum(1); return c[:,0]/(c[:,0]+c[:,1]),c[:,0]/(c[:,0]+c[:,2]) +def front(P,Rr): + o=np.argsort(-Rr); best=-1; keep=[] + for i in o: + if P[i]>best+1e-9: keep.append(i); best=P[i] + return keep +fig,axs=plt.subplots(1,2,figsize=(13,5.5)) +for ax,h in zip(axs,HEADS): + for r_,lab,st in (("silero","Silero thr sweep","k-"),("head",f"{h} head thr sweep","r-"),("or","OR grid frontier","g--"),("and","AND grid frontier","c--"),("mean","mean (w .25/.5/.75) frontier","m--"),("max","max sweep","y:"),("two","two-stage frontier","b-")): + ps,arr=cand(r_,h); P,Rr=pr(arr); k=front(P,Rr); ax.plot(100*Rr[k],100*P[k],st,label=lab,lw=1.6) + for i in k: rows.append((h,r_,str(ps[i]),round(100*P[i],2),round(100*Rr[i],2))) + # LR out-of-fold curves + for kind,st in (("lr6","orange"),("hgb6","brown"),("lr6nz","olive")): + P=[];Rr=[] + for ti in range(17): + c=np.zeros(4) + for f in range(4): o,_=LR[(h,kind,"f%d"%f)]; te=np.flatnonzero(sp&(fold==f)); c+=o[ti][te,:4].sum(0) + P.append(c[0]/(c[0]+c[1])); Rr.append(c[0]/(c[0]+c[2])) + P=np.array(P);Rr=np.array(Rr); ax.plot(100*Rr,100*P,"-",color=st,label=f"{kind} out-of-fold thr sweep",lw=1.6) + for ti in range(17): rows.append((h,kind+"_oof",f"thr={0.1+0.05*ti:.2f}",round(100*P[ti],2),round(100*Rr[ti],2))) + ax.set_xlim(80,100); ax.set_ylim(93,100); ax.set_xlabel("frame recall %"); ax.set_ylabel("frame precision %"); ax.set_title(f"Silero + {h} head (342 synthetic speech clips)"); ax.grid(alpha=.3); ax.legend(fontsize=7,loc="lower left") +plt.tight_layout(); plt.savefig("pr_curves.png",dpi=130) +import csv; w=csv.writer(open("pr_points.csv","w")); w.writerow(["head","rule","params","precision_pct","recall_pct"]); w.writerows(rows) diff --git a/scripts/vad_bench/fusion/pr_points.csv b/scripts/vad_bench/fusion/pr_points.csv new file mode 100644 index 0000000..daee549 --- /dev/null +++ b/scripts/vad_bench/fusion/pr_points.csv @@ -0,0 +1,546 @@ +head,rule,params,precision_pct,recall_pct +ultra,silero,"(0.1,)",98.29,91.05 +ultra,silero,"(0.15,)",98.82,89.76 +ultra,silero,"(0.2,)",99.07,88.84 +ultra,silero,"(0.25,)",99.24,88.14 +ultra,silero,"(0.3,)",99.34,87.54 +ultra,silero,"(0.35,)",99.43,86.98 +ultra,silero,"(0.4,)",99.5,86.49 +ultra,silero,"(0.45,)",99.56,86.02 +ultra,silero,"(0.5,)",99.61,85.56 +ultra,silero,"(0.55,)",99.64,85.13 +ultra,silero,"(0.6,)",99.68,84.71 +ultra,silero,"(0.65,)",99.71,84.25 +ultra,silero,"(0.7,)",99.74,83.78 +ultra,silero,"(0.75,)",99.76,83.28 +ultra,silero,"(0.8,)",99.8,82.68 +ultra,silero,"(0.85,)",99.83,81.97 +ultra,silero,"(0.9,)",99.86,80.88 +ultra,head,"(0.1,)",91.92,96.24 +ultra,head,"(0.15,)",93.56,95.06 +ultra,head,"(0.2,)",94.79,94.09 +ultra,head,"(0.25,)",95.83,93.18 +ultra,head,"(0.3,)",96.65,92.33 +ultra,head,"(0.35,)",97.33,91.54 +ultra,head,"(0.4,)",97.85,90.79 +ultra,head,"(0.45,)",98.26,90.09 +ultra,head,"(0.5,)",98.58,89.4 +ultra,head,"(0.55,)",98.82,88.61 +ultra,head,"(0.6,)",99.0,87.76 +ultra,head,"(0.65,)",99.15,86.87 +ultra,head,"(0.7,)",99.28,85.91 +ultra,head,"(0.75,)",99.36,84.84 +ultra,head,"(0.8,)",99.47,83.46 +ultra,head,"(0.85,)",99.53,81.77 +ultra,head,"(0.9,)",99.6,79.05 +ultra,or,"(0.1, 0.1)",91.75,96.66 +ultra,or,"(0.2, 0.1)",91.89,96.37 +ultra,or,"(0.3, 0.1)",91.91,96.3 +ultra,or,"(0.4, 0.1)",91.92,96.27 +ultra,or,"(0.5, 0.1)",91.92,96.26 +ultra,or,"(0.1, 0.2)",94.33,95.18 +ultra,or,"(0.2, 0.2)",94.66,94.57 +ultra,or,"(0.3, 0.2)",94.75,94.34 +ultra,or,"(0.1, 0.3)",95.97,94.27 +ultra,or,"(0.1, 0.4)",97.01,93.55 +ultra,or,"(0.1, 0.5)",97.59,93.07 +ultra,or,"(0.1, 0.6)",97.9,92.64 +ultra,or,"(0.1, 0.7)",98.09,92.26 +ultra,or,"(0.1, 0.8)",98.18,91.92 +ultra,or,"(0.2, 0.5)",98.22,91.71 +ultra,or,"(0.1, 0.9)",98.23,91.54 +ultra,or,"(0.2, 0.6)",98.58,91.12 +ultra,or,"(0.2, 0.7)",98.81,90.55 +ultra,or,"(0.2, 0.8)",98.92,90.09 +ultra,or,"(0.3, 0.7)",99.03,89.68 +ultra,or,"(0.4, 0.7)",99.14,89.11 +ultra,or,"(0.3, 0.8)",99.16,89.09 +ultra,or,"(0.5, 0.7)",99.2,88.67 +ultra,or,"(0.4, 0.8)",99.27,88.43 +ultra,or,"(0.5, 0.8)",99.35,87.89 +ultra,or,"(0.4, 0.9)",99.37,87.6 +ultra,or,"(0.6, 0.8)",99.39,87.46 +ultra,or,"(0.7, 0.8)",99.42,87.05 +ultra,or,"(0.5, 0.9)",99.45,86.94 +ultra,or,"(0.6, 0.9)",99.5,86.37 +ultra,or,"(0.7, 0.9)",99.54,85.8 +ultra,or,"(0.8, 0.9)",99.57,85.21 +ultra,or,"(0.9, 0.9)",99.59,84.38 +ultra,and,"(0.1, 0.1)",98.52,90.51 +ultra,and,"(0.1, 0.2)",98.8,89.78 +ultra,and,"(0.1, 0.3)",99.02,88.93 +ultra,and,"(0.2, 0.1)",99.12,88.67 +ultra,and,"(0.2, 0.2)",99.22,88.23 +ultra,and,"(0.2, 0.3)",99.31,87.7 +ultra,and,"(0.3, 0.1)",99.36,87.45 +ultra,and,"(0.3, 0.2)",99.4,87.18 +ultra,and,"(0.3, 0.3)",99.45,86.82 +ultra,and,"(0.4, 0.1)",99.5,86.45 +ultra,and,"(0.4, 0.2)",99.52,86.28 +ultra,and,"(0.4, 0.3)",99.55,85.99 +ultra,and,"(0.4, 0.4)",99.57,85.58 +ultra,and,"(0.5, 0.1)",99.61,85.54 +ultra,and,"(0.5, 0.2)",99.62,85.41 +ultra,and,"(0.5, 0.3)",99.63,85.19 +ultra,and,"(0.5, 0.4)",99.65,84.85 +ultra,and,"(0.6, 0.1)",99.68,84.68 +ultra,and,"(0.6, 0.2)",99.68,84.6 +ultra,and,"(0.6, 0.3)",99.69,84.4 +ultra,and,"(0.6, 0.4)",99.7,84.12 +ultra,and,"(0.7, 0.1)",99.74,83.76 +ultra,and,"(0.7, 0.2)",99.74,83.69 +ultra,and,"(0.7, 0.3)",99.75,83.51 +ultra,and,"(0.7, 0.4)",99.75,83.29 +ultra,and,"(0.7, 0.5)",99.76,82.95 +ultra,and,"(0.8, 0.1)",99.8,82.66 +ultra,and,"(0.8, 0.2)",99.8,82.59 +ultra,and,"(0.8, 0.3)",99.8,82.43 +ultra,and,"(0.8, 0.4)",99.8,82.25 +ultra,and,"(0.8, 0.5)",99.81,81.95 +ultra,and,"(0.8, 0.6)",99.82,81.44 +ultra,and,"(0.9, 0.1)",99.86,80.88 +ultra,and,"(0.9, 0.2)",99.86,80.82 +ultra,and,"(0.9, 0.3)",99.86,80.7 +ultra,and,"(0.9, 0.4)",99.86,80.55 +ultra,and,"(0.9, 0.5)",99.86,80.31 +ultra,and,"(0.9, 0.6)",99.87,79.87 +ultra,and,"(0.9, 0.7)",99.87,79.23 +ultra,and,"(0.9, 0.8)",99.88,78.03 +ultra,and,"(0.9, 0.9)",99.89,75.26 +ultra,mean,"(0.25, 0.1)",92.66,95.97 +ultra,mean,"(0.5, 0.1)",93.82,95.43 +ultra,mean,"(0.25, 0.15)",94.41,94.7 +ultra,mean,"(0.75, 0.1)",96.13,94.28 +ultra,mean,"(0.5, 0.2)",97.3,92.75 +ultra,mean,"(0.75, 0.15)",97.91,92.49 +ultra,mean,"(0.5, 0.25)",98.11,91.7 +ultra,mean,"(0.25, 0.35)",98.21,91.04 +ultra,mean,"(0.75, 0.2)",98.66,91.02 +ultra,mean,"(0.5, 0.35)",98.97,89.92 +ultra,mean,"(0.75, 0.25)",99.05,89.63 +ultra,mean,"(0.5, 0.4)",99.18,89.1 +ultra,mean,"(0.75, 0.3)",99.24,88.49 +ultra,mean,"(0.5, 0.45)",99.31,88.16 +ultra,mean,"(0.75, 0.35)",99.35,87.73 +ultra,mean,"(0.75, 0.4)",99.44,87.14 +ultra,mean,"(0.5, 0.5)",99.45,87.12 +ultra,mean,"(0.75, 0.45)",99.51,86.57 +ultra,mean,"(0.5, 0.55)",99.53,86.27 +ultra,mean,"(0.75, 0.5)",99.56,86.06 +ultra,mean,"(0.75, 0.55)",99.61,85.54 +ultra,mean,"(0.75, 0.6)",99.66,85.03 +ultra,mean,"(0.75, 0.65)",99.69,84.52 +ultra,mean,"(0.5, 0.7)",99.69,83.96 +ultra,mean,"(0.75, 0.7)",99.73,83.93 +ultra,mean,"(0.75, 0.75)",99.76,83.31 +ultra,mean,"(0.75, 0.8)",99.79,82.48 +ultra,mean,"(0.75, 0.85)",99.83,81.38 +ultra,mean,"(0.75, 0.9)",99.86,79.61 +ultra,max,"(0.1,)",91.75,96.66 +ultra,max,"(0.15,)",93.44,95.47 +ultra,max,"(0.2,)",94.66,94.57 +ultra,max,"(0.25,)",95.73,93.73 +ultra,max,"(0.3,)",96.57,92.95 +ultra,max,"(0.35,)",97.26,92.23 +ultra,max,"(0.4,)",97.8,91.57 +ultra,max,"(0.45,)",98.2,91.04 +ultra,max,"(0.5,)",98.54,90.48 +ultra,max,"(0.55,)",98.78,89.9 +ultra,max,"(0.6,)",98.96,89.29 +ultra,max,"(0.65,)",99.11,88.66 +ultra,max,"(0.7,)",99.25,88.03 +ultra,max,"(0.75,)",99.33,87.39 +ultra,max,"(0.8,)",99.44,86.62 +ultra,max,"(0.85,)",99.51,85.7 +ultra,max,"(0.9,)",99.59,84.38 +ultra,two,"(0.3, 0.3, 48.0, 32.0, 60.0)",98.3,92.39 +ultra,two,"(0.3, 0.3, 48.0, 32.0, 30.0)",98.32,92.37 +ultra,two,"(0.3, 0.3, 48.0, 32.0, 15.0)",98.33,92.37 +ultra,two,"(0.3, 0.3, 32.0, 32.0, 60.0)",98.36,92.36 +ultra,two,"(0.3, 0.3, 48.0, 16.0, 60.0)",98.39,92.34 +ultra,two,"(0.3, 0.3, 32.0, 32.0, 0.0)",98.39,92.32 +ultra,two,"(0.3, 0.3, 32.0, 16.0, 60.0)",98.46,92.31 +ultra,two,"(0.3, 0.3, 32.0, 16.0, 30.0)",98.48,92.25 +ultra,two,"(0.3, 0.3, 32.0, 16.0, 0.0)",98.49,92.24 +ultra,two,"(0.3, 0.3, 48.0, 8.0, 60.0)",98.5,92.18 +ultra,two,"(0.3, 0.3, 16.0, 16.0, 60.0)",98.53,92.15 +ultra,two,"(0.3, 0.3, 32.0, 8.0, 60.0)",98.57,92.14 +ultra,two,"(0.3, 0.3, 32.0, 8.0, 30.0)",98.6,92.04 +ultra,two,"(0.3, 0.3, 32.0, 8.0, 0.0)",98.6,92.01 +ultra,two,"(0.3, 0.3, 16.0, 8.0, 60.0)",98.65,91.98 +ultra,two,"(0.3, 0.3, 16.0, 8.0, 30.0)",98.68,91.84 +ultra,two,"(0.3, 0.3, 16.0, 8.0, 15.0)",98.68,91.78 +ultra,two,"(0.5, 0.3, 16.0, 16.0, 60.0)",98.7,91.66 +ultra,two,"(0.5, 0.3, 16.0, 16.0, 30.0)",98.72,91.52 +ultra,two,"(0.5, 0.3, 48.0, 8.0, 60.0)",98.73,91.51 +ultra,two,"(0.3, 0.3, 8.0, 8.0, 60.0)",98.79,91.48 +ultra,two,"(0.5, 0.3, 32.0, 8.0, 60.0)",98.79,91.45 +ultra,two,"(0.5, 0.3, 32.0, 8.0, 30.0)",98.8,91.32 +ultra,two,"(0.5, 0.3, 16.0, 8.0, 60.0)",98.86,91.28 +ultra,two,"(0.5, 0.3, 16.0, 8.0, 30.0)",98.88,91.07 +ultra,two,"(0.5, 0.3, 16.0, 8.0, 15.0)",98.89,90.99 +ultra,two,"(0.3, 0.3, 16.0, 0.0, 60.0)",98.94,90.98 +ultra,two,"(0.3, 0.3, 16.0, 0.0, 30.0)",98.96,90.78 +ultra,two,"(0.5, 0.3, 8.0, 8.0, 60.0)",99.01,90.64 +ultra,two,"(0.3, 0.3, 8.0, 0.0, 60.0)",99.08,90.51 +ultra,two,"(0.3, 0.3, 8.0, 0.0, 30.0)",99.1,90.12 +ultra,two,"(0.5, 0.3, 32.0, 0.0, 60.0)",99.12,90.05 +ultra,two,"(0.7, 0.3, 8.0, 8.0, 60.0)",99.14,89.92 +ultra,two,"(0.5, 0.3, 16.0, 0.0, 60.0)",99.21,89.85 +ultra,two,"(0.5, 0.3, 16.0, 0.0, 30.0)",99.23,89.51 +ultra,two,"(0.5, 0.3, 8.0, 0.0, 60.0)",99.36,89.35 +ultra,two,"(0.5, 0.3, 8.0, 0.0, 30.0)",99.38,88.73 +ultra,two,"(0.7, 0.3, 8.0, 0.0, 60.0)",99.51,88.48 +ultra,two,"(0.5, 0.3, 0.0, 0.0, 60.0)",99.6,88.39 +ultra,two,"(0.7, 0.3, 0.0, 0.0, 60.0)",99.72,87.54 +ultra,two,"(0.7, 0.5, 0.0, 0.0, 60.0)",99.74,86.07 +ultra,two,"(0.7, 0.3, 0.0, 0.0, 30.0)",99.74,85.13 +ultra,lr6_oof,thr=0.10,84.63,99.84 +ultra,lr6_oof,thr=0.15,85.91,99.48 +ultra,lr6_oof,thr=0.20,87.46,99.0 +ultra,lr6_oof,thr=0.25,89.17,98.48 +ultra,lr6_oof,thr=0.30,90.88,97.93 +ultra,lr6_oof,thr=0.35,92.4,97.34 +ultra,lr6_oof,thr=0.40,93.79,96.71 +ultra,lr6_oof,thr=0.45,94.96,96.07 +ultra,lr6_oof,thr=0.50,95.93,95.36 +ultra,lr6_oof,thr=0.55,96.75,94.68 +ultra,lr6_oof,thr=0.60,97.44,93.93 +ultra,lr6_oof,thr=0.65,98.0,93.13 +ultra,lr6_oof,thr=0.70,98.44,92.3 +ultra,lr6_oof,thr=0.75,98.79,91.38 +ultra,lr6_oof,thr=0.80,99.08,90.29 +ultra,lr6_oof,thr=0.85,99.31,89.03 +ultra,lr6_oof,thr=0.90,99.53,87.43 +ultra,hgb6_oof,thr=0.10,84.13,99.99 +ultra,hgb6_oof,thr=0.15,86.31,99.68 +ultra,hgb6_oof,thr=0.20,89.79,98.93 +ultra,hgb6_oof,thr=0.25,91.97,98.3 +ultra,hgb6_oof,thr=0.30,93.7,97.7 +ultra,hgb6_oof,thr=0.35,94.9,97.07 +ultra,hgb6_oof,thr=0.40,95.74,96.65 +ultra,hgb6_oof,thr=0.45,96.26,96.24 +ultra,hgb6_oof,thr=0.50,96.72,95.83 +ultra,hgb6_oof,thr=0.55,97.1,95.33 +ultra,hgb6_oof,thr=0.60,97.42,94.84 +ultra,hgb6_oof,thr=0.65,97.74,94.33 +ultra,hgb6_oof,thr=0.70,98.06,93.73 +ultra,hgb6_oof,thr=0.75,98.44,92.78 +ultra,hgb6_oof,thr=0.80,98.81,91.65 +ultra,hgb6_oof,thr=0.85,99.15,90.39 +ultra,hgb6_oof,thr=0.90,99.43,88.99 +ultra,lr6nz_oof,thr=0.10,85.49,99.47 +ultra,lr6nz_oof,thr=0.15,86.93,99.04 +ultra,lr6nz_oof,thr=0.20,88.61,98.51 +ultra,lr6nz_oof,thr=0.25,90.39,97.87 +ultra,lr6nz_oof,thr=0.30,92.06,97.27 +ultra,lr6nz_oof,thr=0.35,93.52,96.63 +ultra,lr6nz_oof,thr=0.40,94.76,96.0 +ultra,lr6nz_oof,thr=0.45,95.78,95.33 +ultra,lr6nz_oof,thr=0.50,96.61,94.66 +ultra,lr6nz_oof,thr=0.55,97.3,94.03 +ultra,lr6nz_oof,thr=0.60,97.82,93.3 +ultra,lr6nz_oof,thr=0.65,98.21,92.59 +ultra,lr6nz_oof,thr=0.70,98.52,91.81 +ultra,lr6nz_oof,thr=0.75,98.81,90.97 +ultra,lr6nz_oof,thr=0.80,99.07,90.01 +ultra,lr6nz_oof,thr=0.85,99.29,88.88 +ultra,lr6nz_oof,thr=0.90,99.5,87.48 +redux,silero,"(0.1,)",98.29,91.05 +redux,silero,"(0.15,)",98.82,89.76 +redux,silero,"(0.2,)",99.07,88.84 +redux,silero,"(0.25,)",99.24,88.14 +redux,silero,"(0.3,)",99.34,87.54 +redux,silero,"(0.35,)",99.43,86.98 +redux,silero,"(0.4,)",99.5,86.49 +redux,silero,"(0.45,)",99.56,86.02 +redux,silero,"(0.5,)",99.61,85.56 +redux,silero,"(0.55,)",99.64,85.13 +redux,silero,"(0.6,)",99.68,84.71 +redux,silero,"(0.65,)",99.71,84.25 +redux,silero,"(0.7,)",99.74,83.78 +redux,silero,"(0.75,)",99.76,83.28 +redux,silero,"(0.8,)",99.8,82.68 +redux,silero,"(0.85,)",99.83,81.97 +redux,silero,"(0.9,)",99.86,80.88 +redux,head,"(0.1,)",89.05,97.92 +redux,head,"(0.15,)",90.17,97.26 +redux,head,"(0.2,)",91.33,96.64 +redux,head,"(0.25,)",92.49,96.04 +redux,head,"(0.3,)",93.63,95.26 +redux,head,"(0.35,)",94.82,94.45 +redux,head,"(0.4,)",95.86,93.64 +redux,head,"(0.45,)",96.85,92.81 +redux,head,"(0.5,)",97.58,92.05 +redux,head,"(0.55,)",98.16,91.33 +redux,head,"(0.6,)",98.63,90.72 +redux,head,"(0.65,)",98.93,90.08 +redux,head,"(0.7,)",99.08,89.48 +redux,head,"(0.75,)",99.22,88.79 +redux,head,"(0.8,)",99.32,88.02 +redux,head,"(0.85,)",99.43,87.1 +redux,head,"(0.9,)",99.56,85.84 +redux,or,"(0.1, 0.1)",89.02,98.05 +redux,or,"(0.2, 0.1)",89.05,97.95 +redux,or,"(0.3, 0.1)",89.05,97.93 +redux,or,"(0.4, 0.1)",89.05,97.93 +redux,or,"(0.5, 0.1)",89.05,97.93 +redux,or,"(0.1, 0.2)",91.14,96.97 +redux,or,"(0.2, 0.2)",91.29,96.73 +redux,or,"(0.3, 0.2)",91.32,96.68 +redux,or,"(0.4, 0.2)",91.33,96.66 +redux,or,"(0.5, 0.2)",91.33,96.65 +redux,or,"(0.1, 0.3)",93.25,95.91 +redux,or,"(0.2, 0.3)",93.53,95.48 +redux,or,"(0.3, 0.3)",93.6,95.35 +redux,or,"(0.4, 0.3)",93.62,95.3 +redux,or,"(0.5, 0.3)",93.63,95.27 +redux,or,"(0.6, 0.3)",93.63,95.27 +redux,or,"(0.7, 0.3)",93.63,95.26 +redux,or,"(0.8, 0.3)",93.63,95.26 +redux,or,"(0.9, 0.3)",93.63,95.26 +redux,or,"(0.1, 0.4)",95.3,94.8 +redux,or,"(0.2, 0.4)",95.72,94.09 +redux,or,"(0.3, 0.4)",95.81,93.84 +redux,or,"(0.1, 0.5)",96.8,93.76 +redux,or,"(0.1, 0.6)",97.71,93.04 +redux,or,"(0.1, 0.7)",98.05,92.53 +redux,or,"(0.1, 0.8)",98.17,92.1 +redux,or,"(0.2, 0.6)",98.35,91.77 +redux,or,"(0.3, 0.6)",98.52,91.27 +redux,or,"(0.2, 0.7)",98.73,91.04 +redux,or,"(0.2, 0.8)",98.89,90.42 +redux,or,"(0.3, 0.7)",98.93,90.37 +redux,or,"(0.4, 0.7)",99.01,90.02 +redux,or,"(0.5, 0.7)",99.05,89.81 +redux,or,"(0.6, 0.7)",99.06,89.69 +redux,or,"(0.7, 0.7)",99.07,89.62 +redux,or,"(0.8, 0.7)",99.08,89.58 +redux,or,"(0.9, 0.7)",99.08,89.54 +redux,or,"(0.3, 0.8)",99.11,89.54 +redux,or,"(0.4, 0.8)",99.21,89.04 +redux,or,"(0.3, 0.9)",99.24,88.77 +redux,or,"(0.5, 0.8)",99.26,88.71 +redux,or,"(0.6, 0.8)",99.29,88.49 +redux,or,"(0.7, 0.8)",99.3,88.35 +redux,or,"(0.8, 0.8)",99.31,88.25 +redux,or,"(0.9, 0.8)",99.31,88.18 +redux,or,"(0.4, 0.9)",99.37,88.07 +redux,or,"(0.5, 0.9)",99.44,87.56 +redux,or,"(0.6, 0.9)",99.48,87.15 +redux,or,"(0.7, 0.9)",99.51,86.84 +redux,or,"(0.8, 0.9)",99.53,86.61 +redux,or,"(0.9, 0.9)",99.55,86.39 +redux,and,"(0.1, 0.1)",98.36,90.88 +redux,and,"(0.1, 0.2)",98.54,90.59 +redux,and,"(0.1, 0.3)",98.73,90.2 +redux,and,"(0.1, 0.4)",98.91,89.64 +redux,and,"(0.1, 0.5)",99.1,89.15 +redux,and,"(0.2, 0.2)",99.13,88.67 +redux,and,"(0.1, 0.6)",99.25,88.54 +redux,and,"(0.2, 0.5)",99.31,87.98 +redux,and,"(0.1, 0.7)",99.36,87.84 +redux,and,"(0.2, 0.6)",99.37,87.59 +redux,and,"(0.3, 0.3)",99.38,87.4 +redux,and,"(0.3, 0.4)",99.4,87.26 +redux,and,"(0.3, 0.5)",99.43,87.12 +redux,and,"(0.2, 0.7)",99.43,87.07 +redux,and,"(0.1, 0.8)",99.45,86.87 +redux,and,"(0.3, 0.6)",99.46,86.87 +redux,and,"(0.4, 0.1)",99.5,86.48 +redux,and,"(0.3, 0.7)",99.5,86.47 +redux,and,"(0.4, 0.2)",99.5,86.47 +redux,and,"(0.4, 0.3)",99.51,86.44 +redux,and,"(0.4, 0.4)",99.52,86.38 +redux,and,"(0.4, 0.5)",99.53,86.29 +redux,and,"(0.4, 0.6)",99.55,86.12 +redux,and,"(0.3, 0.8)",99.56,85.86 +redux,and,"(0.4, 0.7)",99.57,85.85 +redux,and,"(0.5, 0.1)",99.61,85.56 +redux,and,"(0.5, 0.2)",99.61,85.55 +redux,and,"(0.5, 0.3)",99.61,85.53 +redux,and,"(0.5, 0.4)",99.61,85.5 +redux,and,"(0.5, 0.5)",99.62,85.46 +redux,and,"(0.5, 0.6)",99.63,85.36 +redux,and,"(0.5, 0.7)",99.64,85.16 +redux,and,"(0.2, 0.9)",99.64,84.84 +redux,and,"(0.5, 0.8)",99.67,84.79 +redux,and,"(0.6, 0.1)",99.68,84.7 +redux,and,"(0.6, 0.2)",99.68,84.7 +redux,and,"(0.6, 0.3)",99.68,84.69 +redux,and,"(0.6, 0.4)",99.68,84.67 +redux,and,"(0.6, 0.5)",99.68,84.65 +redux,and,"(0.6, 0.6)",99.69,84.58 +redux,and,"(0.6, 0.7)",99.69,84.44 +redux,and,"(0.6, 0.8)",99.71,84.15 +redux,and,"(0.5, 0.9)",99.73,83.81 +redux,and,"(0.7, 0.1)",99.74,83.78 +redux,and,"(0.7, 0.2)",99.74,83.78 +redux,and,"(0.7, 0.3)",99.74,83.78 +redux,and,"(0.7, 0.4)",99.74,83.76 +redux,and,"(0.7, 0.5)",99.74,83.75 +redux,and,"(0.7, 0.6)",99.74,83.71 +redux,and,"(0.7, 0.7)",99.74,83.61 +redux,and,"(0.7, 0.8)",99.76,83.4 +redux,and,"(0.7, 0.9)",99.78,82.69 +redux,and,"(0.8, 0.1)",99.8,82.68 +redux,and,"(0.8, 0.2)",99.8,82.67 +redux,and,"(0.8, 0.3)",99.8,82.67 +redux,and,"(0.8, 0.4)",99.8,82.66 +redux,and,"(0.8, 0.5)",99.8,82.66 +redux,and,"(0.8, 0.6)",99.8,82.63 +redux,and,"(0.8, 0.7)",99.8,82.56 +redux,and,"(0.8, 0.8)",99.81,82.41 +redux,and,"(0.8, 0.9)",99.82,81.84 +redux,and,"(0.9, 0.1)",99.86,80.88 +redux,and,"(0.9, 0.3)",99.86,80.88 +redux,and,"(0.9, 0.4)",99.86,80.87 +redux,and,"(0.9, 0.5)",99.86,80.87 +redux,and,"(0.9, 0.7)",99.86,80.81 +redux,and,"(0.9, 0.8)",99.86,80.71 +redux,and,"(0.9, 0.9)",99.87,80.27 +redux,mean,"(0.25, 0.1)",89.55,97.7 +redux,mean,"(0.5, 0.1)",90.61,97.22 +redux,mean,"(0.25, 0.15)",91.05,96.88 +redux,mean,"(0.25, 0.2)",92.59,96.09 +redux,mean,"(0.5, 0.15)",92.83,96.06 +redux,mean,"(0.75, 0.1)",93.73,95.89 +redux,mean,"(0.25, 0.25)",94.15,95.09 +redux,mean,"(0.5, 0.2)",95.1,94.74 +redux,mean,"(0.25, 0.3)",95.62,94.06 +redux,mean,"(0.75, 0.15)",97.11,93.64 +redux,mean,"(0.25, 0.4)",97.77,92.07 +redux,mean,"(0.5, 0.3)",98.12,92.04 +redux,mean,"(0.75, 0.2)",98.46,91.79 +redux,mean,"(0.25, 0.45)",98.48,91.26 +redux,mean,"(0.5, 0.35)",98.75,91.05 +redux,mean,"(0.25, 0.5)",98.87,90.56 +redux,mean,"(0.75, 0.25)",98.94,90.3 +redux,mean,"(0.5, 0.4)",99.04,90.17 +redux,mean,"(0.25, 0.55)",99.05,89.88 +redux,mean,"(0.5, 0.45)",99.2,89.27 +redux,mean,"(0.25, 0.65)",99.32,88.36 +redux,mean,"(0.5, 0.5)",99.34,88.2 +redux,mean,"(0.75, 0.4)",99.38,87.57 +redux,mean,"(0.25, 0.7)",99.43,87.52 +redux,mean,"(0.5, 0.55)",99.44,87.26 +redux,mean,"(0.75, 0.45)",99.46,86.98 +redux,mean,"(0.5, 0.6)",99.52,86.56 +redux,mean,"(0.75, 0.5)",99.52,86.43 +redux,mean,"(0.25, 0.75)",99.55,86.39 +redux,mean,"(0.5, 0.65)",99.58,85.94 +redux,mean,"(0.75, 0.6)",99.63,85.4 +redux,mean,"(0.5, 0.7)",99.64,85.31 +redux,mean,"(0.75, 0.65)",99.67,84.92 +redux,mean,"(0.5, 0.75)",99.69,84.65 +redux,mean,"(0.75, 0.7)",99.7,84.38 +redux,mean,"(0.25, 0.85)",99.71,84.23 +redux,mean,"(0.5, 0.8)",99.74,83.88 +redux,mean,"(0.75, 0.75)",99.74,83.82 +redux,mean,"(0.75, 0.8)",99.77,83.18 +redux,mean,"(0.5, 0.85)",99.78,83.03 +redux,mean,"(0.25, 0.9)",99.78,82.8 +redux,mean,"(0.75, 0.85)",99.81,82.4 +redux,mean,"(0.5, 0.9)",99.83,81.87 +redux,mean,"(0.75, 0.9)",99.85,81.32 +redux,max,"(0.1,)",89.02,98.05 +redux,max,"(0.15,)",90.14,97.36 +redux,max,"(0.2,)",91.29,96.73 +redux,max,"(0.25,)",92.45,96.14 +redux,max,"(0.3,)",93.6,95.35 +redux,max,"(0.35,)",94.8,94.54 +redux,max,"(0.4,)",95.84,93.73 +redux,max,"(0.45,)",96.84,92.9 +redux,max,"(0.5,)",97.56,92.12 +redux,max,"(0.55,)",98.15,91.42 +redux,max,"(0.6,)",98.63,90.83 +redux,max,"(0.65,)",98.92,90.21 +redux,max,"(0.7,)",99.07,89.62 +redux,max,"(0.75,)",99.21,88.97 +redux,max,"(0.8,)",99.31,88.25 +redux,max,"(0.85,)",99.42,87.44 +redux,max,"(0.9,)",99.55,86.39 +redux,two,"(0.3, 0.3, 48.0, 32.0, 60.0)",97.42,94.27 +redux,two,"(0.3, 0.3, 48.0, 32.0, 30.0)",97.57,94.18 +redux,two,"(0.3, 0.3, 32.0, 32.0, 60.0)",97.61,94.16 +redux,two,"(0.5, 0.3, 48.0, 32.0, 60.0)",97.63,94.09 +redux,two,"(0.3, 0.3, 32.0, 32.0, 30.0)",97.74,94.05 +redux,two,"(0.3, 0.3, 48.0, 16.0, 60.0)",97.8,94.05 +redux,two,"(0.5, 0.3, 32.0, 32.0, 60.0)",97.82,93.97 +redux,two,"(0.3, 0.3, 32.0, 16.0, 60.0)",97.99,93.94 +redux,two,"(0.5, 0.3, 48.0, 16.0, 60.0)",98.01,93.77 +redux,two,"(0.3, 0.3, 32.0, 16.0, 30.0)",98.08,93.75 +redux,two,"(0.3, 0.3, 32.0, 16.0, 0.0)",98.08,93.7 +redux,two,"(0.5, 0.3, 32.0, 16.0, 60.0)",98.2,93.64 +redux,two,"(0.3, 0.3, 32.0, 8.0, 60.0)",98.23,93.58 +redux,two,"(0.5, 0.3, 32.0, 16.0, 30.0)",98.28,93.38 +redux,two,"(0.7, 0.3, 32.0, 16.0, 60.0)",98.33,93.34 +redux,two,"(0.5, 0.3, 16.0, 16.0, 60.0)",98.4,93.3 +redux,two,"(0.3, 0.3, 16.0, 8.0, 60.0)",98.43,93.27 +redux,two,"(0.5, 0.3, 32.0, 8.0, 60.0)",98.51,92.98 +redux,two,"(0.7, 0.3, 16.0, 16.0, 60.0)",98.52,92.92 +redux,two,"(0.5, 0.3, 8.0, 16.0, 60.0)",98.58,92.7 +redux,two,"(0.3, 0.3, 8.0, 8.0, 60.0)",98.61,92.69 +redux,two,"(0.5, 0.3, 16.0, 8.0, 60.0)",98.7,92.63 +redux,two,"(0.7, 0.3, 32.0, 8.0, 60.0)",98.71,92.37 +redux,two,"(0.3, 0.3, 16.0, 0.0, 60.0)",98.86,92.07 +redux,two,"(0.7, 0.3, 16.0, 8.0, 60.0)",98.89,91.96 +redux,two,"(0.5, 0.3, 32.0, 0.0, 60.0)",98.97,91.57 +redux,two,"(0.3, 0.3, 8.0, 0.0, 60.0)",99.04,91.5 +redux,two,"(0.5, 0.3, 16.0, 0.0, 60.0)",99.16,91.17 +redux,two,"(0.5, 0.3, 8.0, 0.0, 60.0)",99.33,90.5 +redux,two,"(0.7, 0.3, 8.0, 0.0, 60.0)",99.49,89.7 +redux,two,"(0.5, 0.3, 0.0, 0.0, 60.0)",99.57,89.55 +redux,two,"(0.7, 0.3, 0.0, 0.0, 60.0)",99.69,88.71 +redux,two,"(0.7, 0.5, 0.0, 0.0, 60.0)",99.75,87.2 +redux,lr6_oof,thr=0.10,85.56,99.76 +redux,lr6_oof,thr=0.15,86.68,99.48 +redux,lr6_oof,thr=0.20,87.82,99.16 +redux,lr6_oof,thr=0.25,89.23,98.72 +redux,lr6_oof,thr=0.30,90.63,98.19 +redux,lr6_oof,thr=0.35,92.19,97.65 +redux,lr6_oof,thr=0.40,93.66,97.07 +redux,lr6_oof,thr=0.45,95.0,96.42 +redux,lr6_oof,thr=0.50,96.14,95.72 +redux,lr6_oof,thr=0.55,97.03,95.01 +redux,lr6_oof,thr=0.60,97.71,94.28 +redux,lr6_oof,thr=0.65,98.17,93.52 +redux,lr6_oof,thr=0.70,98.55,92.7 +redux,lr6_oof,thr=0.75,98.84,91.75 +redux,lr6_oof,thr=0.80,99.09,90.69 +redux,lr6_oof,thr=0.85,99.35,89.39 +redux,lr6_oof,thr=0.90,99.58,87.68 +redux,hgb6_oof,thr=0.10,84.83,99.95 +redux,hgb6_oof,thr=0.15,85.95,99.79 +redux,hgb6_oof,thr=0.20,89.35,99.02 +redux,hgb6_oof,thr=0.25,92.08,98.18 +redux,hgb6_oof,thr=0.30,93.68,97.58 +redux,hgb6_oof,thr=0.35,95.01,96.94 +redux,hgb6_oof,thr=0.40,95.98,96.39 +redux,hgb6_oof,thr=0.45,96.56,95.96 +redux,hgb6_oof,thr=0.50,96.98,95.61 +redux,hgb6_oof,thr=0.55,97.34,95.22 +redux,hgb6_oof,thr=0.60,97.66,94.82 +redux,hgb6_oof,thr=0.65,97.92,94.41 +redux,hgb6_oof,thr=0.70,98.18,93.86 +redux,hgb6_oof,thr=0.75,98.49,93.2 +redux,hgb6_oof,thr=0.80,98.84,92.17 +redux,hgb6_oof,thr=0.85,99.17,90.9 +redux,hgb6_oof,thr=0.90,99.48,89.45 +redux,lr6nz_oof,thr=0.10,85.96,99.43 +redux,lr6nz_oof,thr=0.15,87.6,98.98 +redux,lr6nz_oof,thr=0.20,89.65,98.41 +redux,lr6nz_oof,thr=0.25,91.45,97.84 +redux,lr6nz_oof,thr=0.30,93.03,97.3 +redux,lr6nz_oof,thr=0.35,94.36,96.7 +redux,lr6nz_oof,thr=0.40,95.39,96.1 +redux,lr6nz_oof,thr=0.45,96.21,95.48 +redux,lr6nz_oof,thr=0.50,96.9,94.9 +redux,lr6nz_oof,thr=0.55,97.46,94.29 +redux,lr6nz_oof,thr=0.60,97.88,93.68 +redux,lr6nz_oof,thr=0.65,98.24,93.04 +redux,lr6nz_oof,thr=0.70,98.53,92.33 +redux,lr6nz_oof,thr=0.75,98.78,91.57 +redux,lr6nz_oof,thr=0.80,99.04,90.62 +redux,lr6nz_oof,thr=0.85,99.26,89.52 +redux,lr6nz_oof,thr=0.90,99.49,88.1 diff --git a/scripts/vad_bench/fusion/prep_libri.py b/scripts/vad_bench/fusion/prep_libri.py new file mode 100644 index 0000000..3dafd8f --- /dev/null +++ b/scripts/vad_bench/fusion/prep_libri.py @@ -0,0 +1,12 @@ +import io, numpy as np, soundfile as sf, os +os.environ["HF_HOME"]=os.path.abspath("hf") +from datasets import load_dataset, Audio +ds=load_dataset("openslr/librispeech_asr","clean",split="test",streaming=True).cast_column("audio",Audio(decode=False)) +os.makedirs("data",exist_ok=True) +U=[];spk=[] +for i,ex in enumerate(ds): + if i%13: continue + y,sr=sf.read(io.BytesIO(ex["audio"]["bytes"]),dtype="float32"); assert sr==16000 + U.append(y);spk.append(int(ex["speaker_id"])) +np.save("data/libri.npy",np.array(U,dtype=object),allow_pickle=True); np.save("data/spk.npy",np.array(spk)) +print(len(U),len(set(spk)),flush=True) diff --git a/scripts/vad_bench/fusion/report.py b/scripts/vad_bench/fusion/report.py new file mode 100644 index 0000000..a2b0eec --- /dev/null +++ b/scripts/vad_bench/fusion/report.py @@ -0,0 +1,109 @@ +from analyze import * +import rules +out=[] +KL=(("lr2","5a logistic reg, 2 probs"),("lr6","5b logistic reg, + prev/next frame"),("hgb6","5c gradient boosting, + prev/next frame"),("lr6nz","5d as 5b, trained also on noise-only clips"),("hgb6nz","5e as 5c, trained also on noise-only clips")) +KINDS=[k for k,_ in KL] +def P_(s=""): out.append(s) +def crossfit_fixed(getarr,thr_idx_of): + rows=np.zeros((N,5)) + for f in range(4): + ps,arr=getarr("f%d"%f); te=np.flatnonzero(fold==f); rows[te]=arr[thr_idx_of(ps)][te] + return rows +def shiftfit(getarr,crit): + return crossfit(getarr,crit,"shift") +sysdefs={} # name -> (rows, note) +def build(h): + S={} + S["Silero thr .5 (unified post)"]=(A_of("silero",(0.5,),h),"fixed") + S["Silero thr .5 (native defaults: min speech .25, pause .1, pad .03)"]=(CFG[("silero",(0.5,),"ultra",0.25)],"fixed") + S[f"{h} head thr .5"]=(A_of("head",(0.5,),h),"fixed") + S["Silero tuned thr (CV)"]=crossfit(static("silero",h),"f1")[0],"cv" + S[f"{h} head tuned thr (CV)"]=crossfit(static("head",h),"f1")[0],"cv" + dflt=dict(OR=("or",(0.5,0.5)),AND=("and",(0.5,0.5)),MEAN=("mean",(0.5,0.5)),MAX=("max",(0.5,)),MIN=("min",(0.5,)),TWO=("two",(0.5,0.5,16,16,30)),SWITCH=("switch",(0.5,0.5,12))) + names=dict(OR="1 OR of decisions",AND="2 AND of decisions",MEAN="3 mean of probs (w=.5)",MAX="4a max of probs",MIN="4b min of probs",TWO="6 two-stage (Silero + head extend/fill)",SWITCH="7 noise-switch (estimated SNR)") + for k,(r,p) in dflt.items(): S[f"{names[k]} @ defaults {p}"]=(A_of(r,p,h),"fixed") + for kind,nm in KL: + S[f"{nm} @ thr .5"]=(crossfit_fixed(lrget(h,kind),lambda ps:ps.index((0.5,))),"cv-model") + for k in ("OR","AND","MAX","MIN","TWO","SWITCH"): + r=dflt[k][0]; S[f"{names[k]} tuned (CV)"]=crossfit(static(r,h),"f1")[0],"cv" + # mean: separate weights are in the same candidate set + S[f"{names['MEAN']} tuned w,thr (CV)"]=crossfit(static("mean",h),"f1")[0],"cv" + for kind,nm in KL: + S[f"{nm} tuned thr (CV)"]=crossfit(lrget(h,kind),"f1")[0],"cv" + S["7o oracle noise-switch (true SNR) tuned (CV)"]=crossfit(static("oracle",h),"f1")[0],"cv" + return S +def table(S,title): + P_(f"### {title}"); P_() + P_("pooled over 342 speech clips; point [95% bootstrap CI over clips], percent. Per-level columns: F1 with CI (white+pink pooled).") + P_() + P_("| system | P | R | F1 | F1 clean | F1 20 dB | F1 10 dB | F1 5 dB | F1 0 dB |"); P_("|---|---|---|---|---|---|---|---|---|") + for n,(rows,_) in S.items(): + s=stats(rows); a=s["all"] + P_(f"| {n} | {fmt(a['P'],*a['Pci'])} | {fmt(a['R'],*a['Rci'])} | {fmt(a['F'],*a['Fci'])} | "+" | ".join(fmt(s[l]['F'],*s[l]['Fci']) for l,_ in LEVELS)+" |") + P_() +def table_pr_levels(S,title,names): + P_(f"### {title}"); P_(); P_("| system | "+" | ".join(f"P/R {l}" for l,_ in LEVELS)+" |"); P_("|---|"+"---|"*len(LEVELS)) + for n in names: + s=stats(S[n][0]); P_(f"| {n} | "+" | ".join(f"{100*s[l]['P']:.1f}/{100*s[l]['R']:.1f}" for l,_ in LEVELS)+" |") + P_() +def fa_table(S,names,title): + P_(f"### {title}"); P_(); P_("noise-only clips (12 x 30 s per level, white+pink pooled over the levels shown). False-alarm segments per hour (frame false-positive rate in brackets).") + P_(); P_("| system | "+" | ".join(l for l,_ in LEVELS[1:])+" | all |"); P_("|---|"+"---|"*5) + for n in names: + f=fa(S[n][0]); P_(f"| {n} | "+" | ".join(f"{f[l][0]:.0f} ({100*f[l][1]:.2f}%)" for l,_ in LEVELS[1:])+f" | {f['all'][0]:.0f} ({100*f['all'][1]:.2f}%) |") + P_() +ALL={} +for h in HEADS: + S=build(h); ALL[h]=S + P_(f"## Head = {h}"); P_() + table(S,f"Cross-validated results ({h} head + Silero; speaker-disjoint 4-fold; 'tuned' = parameters chosen on the 3 training folds, scored on the held-out fold)") + tuned=[n for n in S if "tuned" in n or "@" in n or "head thr" in n or "Silero thr" in n] + fa_table(S,[n for n in S],"False alarms on noise-only clips") + # best fusion vs best single + singles=["Silero tuned thr (CV)",f"{h} head tuned thr (CV)"] + fus=[n for n in S if n not in singles and "thr .5 (" not in n and "head thr" not in n and "native" not in n and "oracle" not in n] + bs=max(singles,key=lambda n:stats(S[n][0])["all"]["F"]); + P_(f"**Best single (CV-tuned):** {bs}."); P_() + P_("| fusion (CV) | dF1 vs best single, pooled | dF1 5 dB | dF1 0 dB | dR pooled | dP pooled |"); P_("|---|---|---|---|---|---|") + b0=stats(S[bs][0]) + for n in fus: + s=stats(S[n][0]); d=s["all"]["F_b"]-b0["all"]["F_b"] + # recall / precision diff, paired + idx,bi=bidx("all",np.flatnonzero(sp)); Pb,Rb,_=prf_b(S[n][0],idx,bi); Pb0,Rb0,_=prf_b(S[bs][0],idx,bi) + l5=s["5 dB"]["F_b"]-b0["5 dB"]["F_b"]; l0=s["0 dB"]["F_b"]-b0["0 dB"]["F_b"] + f=lambda x,y: f"{100*x:+.2f} [{100*y[0]:+.2f},{100*y[1]:+.2f}]" + P_(f"| {n} | {f(s['all']['F']-b0['all']['F'],ci(d))} | {f(s['5 dB']['F']-b0['5 dB']['F'],ci(l5))} | {f(s['0 dB']['F']-b0['0 dB']['F'],ci(l0))} | {f(s['all']['R']-b0['all']['R'],ci(Rb-Rb0))} | {f(s['all']['P']-b0['all']['P'],ci(Pb-Pb0))} |") + P_() + # shift generalisation + P_(f"### Noise-shift split ({h}): parameters chosen on clean/20/10 dB clips only, scored on 5 and 0 dB clips"); P_() + P_("| system | P | R | F1 (5+0 dB clips) |"); P_("|---|---|---|---|") + def shiftrow(nm,getarr): + rows,ch=shiftfit(getarr,"f1"); idx=np.flatnonzero(sp&(snr<10)); ii=rng.integers(0,len(idx),(B,len(idx))); Pb,Rb,Fb=prf_b(rows,idx,ii); p,r,f=pooled(rows,idx) + P_(f"| {nm} {ch['shift']} | {fmt(p,*ci(Pb))} | {fmt(r,*ci(Rb))} | {fmt(f,*ci(Fb))} |") + for r_,nm in (("silero","Silero"),("head",h+" head"),("or","OR"),("and","AND"),("mean","mean"),("max","max"),("min","min"),("two","two-stage"),("switch","noise-switch")): + shiftrow(nm,static(r_,h)) + for kind in KINDS: shiftrow(kind,lrget(h,kind)) + P_() + # precision-constrained + P_(f"### Operating points with precision >= 99% ({h}): CV-selected maximum recall") + P_(); P_("| system | P | R | F1 | chosen params per fold |"); P_("|---|---|---|---|---|") + for r_,nm in (("silero","Silero"),("head",h+" head"),("or","OR"),("and","AND"),("mean","mean"),("max","max"),("min","min"),("two","two-stage"),("switch","noise-switch")): + rows,ch=crossfit(static(r_,h),0.99); s=stats(rows)["all"]; P_(f"| {nm} | {fmt(s['P'],*s['Pci'])} | {fmt(s['R'],*s['Rci'])} | {fmt(s['F'],*s['Fci'])} | {list(ch.values())} |") + for kind in KINDS: + rows,ch=crossfit(lrget(h,kind),0.99); s=stats(rows)["all"]; P_(f"| {kind} | {fmt(s['P'],*s['Pci'])} | {fmt(s['R'],*s['Rci'])} | {fmt(s['F'],*s['Fci'])} | thr {[c[0] for c in ch.values()]} |") + P_() + # in-sample frontier (descriptive) + P_(f"### In-sample best operating points ({h}), descriptive only (selected on all clips, optimistic)"); P_() + P_("| rule | best F1 params | P | R | F1 | max R at P>=99% params | P | R |"); P_("|---|---|---|---|---|---|---|---|") + allidx=np.flatnonzero(sp) + for r_ in("silero","head","or","and","mean","max","min","two","switch","oracle"): + ps,arr=cand(r_,h); i=score(arr,allidx,"f1"); j=score(arr,allidx,0.99) + a=prf(arr[i][allidx,:4].sum(0)); b=prf(arr[j][allidx,:4].sum(0)) + P_(f"| {r_} | {ps[i]} | {100*a[0]:.1f} | {100*a[1]:.1f} | {100*a[2]:.1f} | {ps[j]} | {100*b[0]:.1f} | {100*b[1]:.1f} |") + P_() + # LR coefs + P_(f"LR coefficients ({h}, fit on folds != 0; features lg(silero), lg(head)[, lg(silero prev/next), lg(head prev/next)]):") + for kind in("lr2","lr6","lr6nz"): P_(f"- {kind}: {LR[(h,kind,'f0')][1]}") + P_() +open("results.md","w").write("\n".join(out)); pickle.dump({h:{n:v[0] for n,v in S.items()} for h,S in ALL.items()},open("rows.pkl","wb")) +print("\n".join(out)[:200]) diff --git a/scripts/vad_bench/fusion/results.md b/scripts/vad_bench/fusion/results.md new file mode 100644 index 0000000..96d8c10 --- /dev/null +++ b/scripts/vad_bench/fusion/results.md @@ -0,0 +1,325 @@ +## Head = ultra + +### Cross-validated results (ultra head + Silero; speaker-disjoint 4-fold; 'tuned' = parameters chosen on the 3 training folds, scored on the held-out fold) + +pooled over 342 speech clips; point [95% bootstrap CI over clips], percent. Per-level columns: F1 with CI (white+pink pooled). + +| system | P | R | F1 | F1 clean | F1 20 dB | F1 10 dB | F1 5 dB | F1 0 dB | +|---|---|---|---|---|---|---|---|---| +| Silero thr .5 (unified post) | 99.6 [99.6,99.6] | 85.6 [84.9,86.2] | 92.1 [91.7,92.4] | 93.5 [92.5,94.4] | 92.8 [92.0,93.5] | 92.4 [91.6,93.1] | 91.9 [91.2,92.7] | 90.4 [89.4,91.2] | +| Silero thr .5 (native defaults: min speech .25, pause .1, pad .03) | 99.4 [99.3,99.4] | 86.7 [86.1,87.3] | 92.6 [92.3,92.9] | 93.9 [92.9,94.7] | 93.3 [92.6,93.9] | 93.0 [92.3,93.6] | 92.5 [91.9,93.2] | 90.9 [90.1,91.8] | +| ultra head thr .5 | 98.6 [98.3,98.8] | 89.4 [88.8,90.0] | 93.8 [93.5,94.1] | 95.0 [94.2,95.7] | 94.2 [93.6,94.8] | 93.6 [93.0,94.2] | 93.3 [92.6,94.0] | 93.3 [92.7,93.9] | +| Silero tuned thr (CV) | 98.3 [98.1,98.4] | 91.1 [90.5,91.6] | 94.5 [94.2,94.8] | 94.9 [93.9,95.6] | 94.4 [93.8,95.0] | 94.6 [94.0,95.2] | 94.6 [94.0,95.2] | 94.3 [93.7,94.8] | +| ultra head tuned thr (CV) | 95.8 [95.2,96.3] | 93.0 [92.4,93.5] | 94.4 [94.1,94.7] | 95.6 [94.8,96.3] | 95.1 [94.6,95.6] | 94.9 [94.3,95.4] | 94.6 [94.0,95.1] | 92.5 [91.9,93.1] | +| 1 OR of decisions @ defaults (0.5, 0.5) | 98.5 [98.3,98.7] | 90.5 [89.9,91.0] | 94.3 [94.1,94.6] | 95.1 [94.3,95.8] | 94.6 [94.0,95.2] | 94.3 [93.7,94.9] | 94.2 [93.5,94.7] | 93.9 [93.3,94.5] | +| 2 AND of decisions @ defaults (0.5, 0.5) | 99.7 [99.6,99.7] | 84.4 [83.7,85.1] | 91.4 [91.0,91.8] | 93.3 [92.2,94.2] | 92.3 [91.6,93.0] | 91.6 [90.8,92.4] | 91.1 [90.2,91.8] | 89.7 [88.7,90.6] | +| 3 mean of probs (w=.5) @ defaults (0.5, 0.5) | 99.4 [99.4,99.5] | 87.1 [86.5,87.7] | 92.9 [92.5,93.2] | 94.2 [93.2,95.0] | 93.5 [92.8,94.1] | 93.0 [92.3,93.7] | 92.6 [91.9,93.3] | 91.7 [90.9,92.5] | +| 4a max of probs @ defaults (0.5,) | 98.5 [98.3,98.7] | 90.5 [89.9,91.0] | 94.3 [94.1,94.6] | 95.1 [94.3,95.8] | 94.6 [94.0,95.2] | 94.3 [93.7,94.9] | 94.2 [93.5,94.7] | 93.9 [93.3,94.5] | +| 4b min of probs @ defaults (0.5,) | 99.7 [99.6,99.7] | 84.4 [83.7,85.1] | 91.4 [91.0,91.8] | 93.3 [92.2,94.2] | 92.3 [91.6,93.0] | 91.6 [90.8,92.4] | 91.1 [90.2,91.8] | 89.7 [88.7,90.6] | +| 6 two-stage (Silero + head extend/fill) @ defaults (0.5, 0.5, 16, 16, 30) | 99.0 [99.0,99.1] | 90.0 [89.4,90.5] | 94.3 [94.0,94.6] | 95.1 [94.3,95.8] | 94.6 [94.0,95.1] | 94.3 [93.7,94.8] | 94.1 [93.4,94.6] | 93.8 [93.0,94.5] | +| 7 noise-switch (estimated SNR) @ defaults (0.5, 0.5, 12) | 98.9 [98.7,99.1] | 87.5 [86.8,88.1] | 92.8 [92.5,93.2] | 93.5 [92.5,94.4] | 92.8 [92.0,93.5] | 92.4 [91.6,93.1] | 92.6 [91.9,93.3] | 93.3 [92.7,93.9] | +| 5a logistic reg, 2 probs @ thr .5 | 95.7 [95.2,96.1] | 94.6 [94.1,95.1] | 95.1 [94.9,95.4] | 95.8 [95.1,96.5] | 95.5 [95.0,96.1] | 95.6 [95.1,96.1] | 95.2 [94.7,95.7] | 93.9 [93.4,94.4] | +| 5b logistic reg, + prev/next frame @ thr .5 | 95.9 [95.5,96.3] | 95.4 [94.9,95.8] | 95.6 [95.4,95.9] | 96.1 [95.3,96.7] | 96.0 [95.5,96.5] | 96.1 [95.7,96.5] | 95.8 [95.4,96.2] | 94.4 [93.9,94.9] | +| 5c gradient boosting, + prev/next frame @ thr .5 | 96.7 [96.5,96.9] | 95.8 [95.4,96.2] | 96.3 [96.1,96.4] | 97.0 [96.5,97.4] | 96.6 [96.2,96.9] | 96.4 [96.0,96.7] | 96.2 [95.8,96.5] | 95.6 [95.2,96.0] | +| 5d as 5b, trained also on noise-only clips @ thr .5 | 96.6 [96.4,96.9] | 94.7 [94.2,95.1] | 95.6 [95.4,95.8] | 96.0 [95.2,96.7] | 95.9 [95.3,96.4] | 95.8 [95.3,96.2] | 95.6 [95.2,96.0] | 95.0 [94.5,95.5] | +| 5e as 5c, trained also on noise-only clips @ thr .5 | 96.8 [96.6,97.0] | 95.6 [95.1,95.9] | 96.2 [96.0,96.4] | 96.9 [96.4,97.3] | 96.5 [96.1,96.9] | 96.3 [95.9,96.7] | 96.1 [95.7,96.5] | 95.4 [94.9,95.9] | +| 1 OR of decisions tuned (CV) | 97.6 [97.3,97.8] | 93.1 [92.6,93.5] | 95.3 [95.0,95.5] | 95.6 [94.7,96.2] | 95.1 [94.6,95.7] | 95.4 [94.8,95.9] | 95.4 [94.9,95.9] | 95.0 [94.6,95.5] | +| 2 AND of decisions tuned (CV) | 98.5 [98.4,98.7] | 90.5 [89.9,91.0] | 94.3 [94.1,94.6] | 94.7 [93.8,95.5] | 94.2 [93.6,94.8] | 94.3 [93.6,94.8] | 94.4 [93.8,95.0] | 94.3 [93.7,94.8] | +| 4a max of probs tuned (CV) | 96.1 [95.6,96.7] | 93.3 [92.8,93.8] | 94.7 [94.4,95.0] | 95.7 [94.9,96.3] | 95.3 [94.8,95.8] | 95.2 [94.6,95.7] | 94.9 [94.4,95.4] | 93.0 [92.4,93.6] | +| 4b min of probs tuned (CV) | 98.5 [98.4,98.7] | 90.5 [89.9,91.0] | 94.3 [94.1,94.6] | 94.7 [93.8,95.5] | 94.2 [93.6,94.8] | 94.3 [93.6,94.8] | 94.4 [93.8,95.0] | 94.3 [93.7,94.8] | +| 6 two-stage (Silero + head extend/fill) tuned (CV) | 98.5 [98.3,98.6] | 92.3 [91.8,92.8] | 95.3 [95.0,95.5] | 95.6 [94.8,96.3] | 95.2 [94.7,95.7] | 95.1 [94.5,95.6] | 95.1 [94.6,95.6] | 95.6 [95.0,96.1] | +| 7 noise-switch (estimated SNR) tuned (CV) | 96.7 [96.2,97.2] | 92.1 [91.6,92.6] | 94.4 [94.1,94.6] | 94.8 [93.9,95.5] | 95.0 [94.4,95.5] | 94.6 [94.0,95.2] | 94.5 [94.0,95.1] | 93.1 [92.5,93.7] | +| 3 mean of probs (w=.5) tuned w,thr (CV) | 96.6 [96.1,97.0] | 93.7 [93.2,94.2] | 95.1 [94.8,95.4] | 95.7 [94.9,96.4] | 95.4 [94.8,95.9] | 95.4 [94.8,95.9] | 95.4 [94.8,95.8] | 94.1 [93.5,94.6] | +| 5a logistic reg, 2 probs tuned thr (CV) | 96.7 [96.4,97.0] | 93.7 [93.2,94.2] | 95.2 [94.9,95.4] | 95.7 [94.8,96.3] | 95.4 [94.8,95.9] | 95.4 [94.8,95.9] | 95.4 [94.9,95.9] | 94.4 [93.9,94.9] | +| 5b logistic reg, + prev/next frame tuned thr (CV) | 96.7 [96.4,97.1] | 94.7 [94.2,95.1] | 95.7 [95.5,95.9] | 96.0 [95.2,96.6] | 95.8 [95.3,96.3] | 96.0 [95.5,96.4] | 96.0 [95.5,96.4] | 94.9 [94.4,95.4] | +| 5c gradient boosting, + prev/next frame tuned thr (CV) | 96.7 [96.5,96.9] | 95.8 [95.4,96.2] | 96.3 [96.1,96.4] | 97.0 [96.5,97.4] | 96.6 [96.2,96.9] | 96.4 [96.0,96.7] | 96.2 [95.8,96.5] | 95.6 [95.2,96.0] | +| 5d as 5b, trained also on noise-only clips tuned thr (CV) | 96.8 [96.6,97.1] | 94.6 [94.1,95.0] | 95.7 [95.5,95.9] | 96.1 [95.3,96.7] | 95.9 [95.3,96.4] | 95.9 [95.4,96.3] | 95.7 [95.3,96.1] | 95.1 [94.6,95.5] | +| 5e as 5c, trained also on noise-only clips tuned thr (CV) | 96.7 [96.5,96.9] | 95.7 [95.3,96.1] | 96.2 [96.0,96.4] | 97.0 [96.5,97.3] | 96.5 [96.2,96.9] | 96.3 [95.9,96.7] | 96.2 [95.8,96.5] | 95.4 [94.9,95.9] | +| 7o oracle noise-switch (true SNR) tuned (CV) | 96.9 [96.4,97.4] | 91.1 [90.6,91.7] | 93.9 [93.6,94.2] | 94.0 [93.0,94.9] | 93.4 [92.7,94.0] | 94.6 [94.0,95.2] | 94.5 [94.0,95.1] | 93.1 [92.5,93.7] | + +### False alarms on noise-only clips + +noise-only clips (12 x 30 s per level, white+pink pooled over the levels shown). False-alarm segments per hour (frame false-positive rate in brackets). + +| system | 20 dB | 10 dB | 5 dB | 0 dB | all | +|---|---|---|---|---|---| +| Silero thr .5 (unified post) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| Silero thr .5 (native defaults: min speech .25, pause .1, pad .03) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| ultra head thr .5 | 130 (99.33%) | 120 (99.56%) | 135 (99.32%) | 120 (99.53%) | 126 (99.44%) | +| Silero tuned thr (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| ultra head tuned thr (CV) | 120 (99.73%) | 120 (99.81%) | 120 (99.81%) | 120 (99.78%) | 120 (99.78%) | +| 1 OR of decisions @ defaults (0.5, 0.5) | 130 (99.33%) | 120 (99.56%) | 135 (99.32%) | 120 (99.53%) | 126 (99.44%) | +| 2 AND of decisions @ defaults (0.5, 0.5) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 3 mean of probs (w=.5) @ defaults (0.5, 0.5) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 4a max of probs @ defaults (0.5,) | 130 (99.33%) | 120 (99.56%) | 135 (99.32%) | 120 (99.53%) | 126 (99.44%) | +| 4b min of probs @ defaults (0.5,) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 6 two-stage (Silero + head extend/fill) @ defaults (0.5, 0.5, 16, 16, 30) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 7 noise-switch (estimated SNR) @ defaults (0.5, 0.5, 12) | 130 (99.33%) | 120 (99.56%) | 135 (99.32%) | 120 (99.53%) | 126 (99.44%) | +| 5a logistic reg, 2 probs @ thr .5 | 1000 (18.21%) | 1265 (35.14%) | 720 (27.86%) | 465 (17.62%) | 862 (24.71%) | +| 5b logistic reg, + prev/next frame @ thr .5 | 800 (13.37%) | 1075 (27.54%) | 700 (23.74%) | 365 (15.73%) | 735 (20.09%) | +| 5c gradient boosting, + prev/next frame @ thr .5 | 750 (93.20%) | 535 (95.29%) | 450 (96.32%) | 475 (95.98%) | 552 (95.19%) | +| 5d as 5b, trained also on noise-only clips @ thr .5 | 5 (0.01%) | 5 (0.03%) | 15 (0.13%) | 15 (0.09%) | 10 (0.06%) | +| 5e as 5c, trained also on noise-only clips @ thr .5 | 55 (0.27%) | 45 (0.16%) | 60 (0.38%) | 45 (0.20%) | 51 (0.25%) | +| 1 OR of decisions tuned (CV) | 130 (99.33%) | 120 (99.56%) | 135 (99.32%) | 120 (99.53%) | 126 (99.44%) | +| 2 AND of decisions tuned (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 4a max of probs tuned (CV) | 120 (99.70%) | 120 (99.79%) | 120 (99.79%) | 120 (99.76%) | 120 (99.76%) | +| 4b min of probs tuned (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 6 two-stage (Silero + head extend/fill) tuned (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 7 noise-switch (estimated SNR) tuned (CV) | 120 (99.70%) | 120 (99.73%) | 120 (99.78%) | 120 (99.74%) | 120 (99.74%) | +| 3 mean of probs (w=.5) tuned w,thr (CV) | 145 (99.36%) | 140 (99.51%) | 145 (99.46%) | 125 (99.60%) | 139 (99.48%) | +| 5a logistic reg, 2 probs tuned thr (CV) | 755 (9.41%) | 1235 (22.73%) | 840 (19.75%) | 415 (13.41%) | 811 (16.32%) | +| 5b logistic reg, + prev/next frame tuned thr (CV) | 520 (7.06%) | 970 (18.34%) | 655 (16.74%) | 375 (11.34%) | 630 (13.37%) | +| 5c gradient boosting, + prev/next frame tuned thr (CV) | 750 (93.20%) | 535 (95.29%) | 450 (96.32%) | 475 (95.98%) | 552 (95.19%) | +| 5d as 5b, trained also on noise-only clips tuned thr (CV) | 5 (0.01%) | 5 (0.02%) | 10 (0.09%) | 10 (0.05%) | 8 (0.04%) | +| 5e as 5c, trained also on noise-only clips tuned thr (CV) | 55 (0.27%) | 55 (0.21%) | 65 (0.41%) | 50 (0.25%) | 56 (0.28%) | +| 7o oracle noise-switch (true SNR) tuned (CV) | 0 (0.00%) | 120 (99.73%) | 120 (99.78%) | 120 (99.74%) | 90 (74.81%) | + +**Best single (CV-tuned):** Silero tuned thr (CV). + +| fusion (CV) | dF1 vs best single, pooled | dF1 5 dB | dF1 0 dB | dR pooled | dP pooled | +|---|---|---|---|---|---| +| 1 OR of decisions @ defaults (0.5, 0.5) | -0.20 [-0.33,-0.06] | -0.47 [-0.71,-0.23] | -0.39 [-0.82,+0.06] | -0.57 [-0.83,-0.31] | +0.25 [+0.04,+0.44] | +| 2 AND of decisions @ defaults (0.5, 0.5) | -3.12 [-3.34,-2.90] | -3.57 [-4.03,-3.14] | -4.61 [-5.19,-4.05] | -6.62 [-6.99,-6.25] | +1.37 [+1.25,+1.50] | +| 3 mean of probs (w=.5) @ defaults (0.5, 0.5) | -1.66 [-1.82,-1.50] | -2.01 [-2.31,-1.71] | -2.59 [-3.01,-2.16] | -3.93 [-4.20,-3.67] | +1.16 [+1.04,+1.28] | +| 4a max of probs @ defaults (0.5,) | -0.20 [-0.33,-0.06] | -0.47 [-0.71,-0.23] | -0.39 [-0.82,+0.06] | -0.57 [-0.83,-0.31] | +0.25 [+0.04,+0.44] | +| 4b min of probs @ defaults (0.5,) | -3.12 [-3.34,-2.90] | -3.57 [-4.03,-3.14] | -4.61 [-5.19,-4.05] | -6.62 [-6.99,-6.25] | +1.37 [+1.25,+1.50] | +| 6 two-stage (Silero + head extend/fill) @ defaults (0.5, 0.5, 16, 16, 30) | -0.25 [-0.37,-0.14] | -0.56 [-0.80,-0.32] | -0.51 [-0.87,-0.17] | -1.09 [-1.30,-0.87] | +0.74 [+0.63,+0.86] | +| 7 noise-switch (estimated SNR) @ defaults (0.5, 0.5, 12) | -1.69 [-1.86,-1.51] | -2.02 [-2.39,-1.63] | -0.99 [-1.51,-0.45] | -3.59 [-3.92,-3.26] | +0.64 [+0.43,+0.84] | +| 5a logistic reg, 2 probs @ thr .5 | +0.61 [+0.42,+0.79] | +0.59 [+0.21,+0.96] | -0.36 [-0.96,+0.26] | +3.58 [+3.31,+3.86] | -2.63 [-2.94,-2.31] | +| 5b logistic reg, + prev/next frame @ thr .5 | +1.11 [+0.92,+1.29] | +1.20 [+0.83,+1.56] | +0.15 [-0.41,+0.74] | +4.30 [+4.05,+4.56] | -2.36 [-2.64,-2.09] | +| 5c gradient boosting, + prev/next frame @ thr .5 | +1.74 [+1.59,+1.90] | +1.54 [+1.27,+1.82] | +1.34 [+1.00,+1.68] | +4.77 [+4.54,+5.02] | -1.57 [-1.71,-1.44] | +| 5d as 5b, trained also on noise-only clips @ thr .5 | +1.09 [+0.96,+1.23] | +0.99 [+0.68,+1.31] | +0.72 [+0.37,+1.09] | +3.61 [+3.42,+3.80] | -1.68 [-1.82,-1.55] | +| 5e as 5c, trained also on noise-only clips @ thr .5 | +1.64 [+1.51,+1.79] | +1.50 [+1.24,+1.77] | +1.12 [+0.86,+1.38] | +4.51 [+4.28,+4.75] | -1.50 [-1.62,-1.38] | +| 1 OR of decisions tuned (CV) | +0.74 [+0.65,+0.84] | +0.81 [+0.69,+0.93] | +0.74 [+0.37,+1.13] | +2.02 [+1.85,+2.22] | -0.70 [-0.89,-0.54] | +| 2 AND of decisions tuned (CV) | -0.19 [-0.23,-0.15] | -0.21 [-0.31,-0.12] | -0.01 [-0.05,+0.04] | -0.55 [-0.62,-0.47] | +0.23 [+0.19,+0.28] | +| 4a max of probs tuned (CV) | +0.16 [-0.02,+0.32] | +0.31 [+0.02,+0.59] | -1.28 [-1.81,-0.77] | +2.24 [+1.93,+2.58] | -2.16 [-2.63,-1.68] | +| 4b min of probs tuned (CV) | -0.19 [-0.23,-0.15] | -0.21 [-0.31,-0.12] | -0.01 [-0.05,+0.04] | -0.55 [-0.62,-0.47] | +0.23 [+0.19,+0.28] | +| 6 two-stage (Silero + head extend/fill) tuned (CV) | +0.75 [+0.65,+0.86] | +0.52 [+0.34,+0.71] | +1.26 [+0.95,+1.58] | +1.26 [+1.06,+1.46] | +0.17 [+0.08,+0.28] | +| 7 noise-switch (estimated SNR) tuned (CV) | -0.18 [-0.35,-0.02] | -0.08 [-0.38,+0.22] | -1.17 [-1.69,-0.68] | +1.06 [+0.70,+1.43] | -1.58 [-2.03,-1.13] | +| 3 mean of probs (w=.5) tuned w,thr (CV) | +0.59 [+0.43,+0.72] | +0.75 [+0.50,+1.00] | -0.24 [-0.73,+0.23] | +2.68 [+2.40,+2.96] | -1.74 [-2.10,-1.40] | +| 5a logistic reg, 2 probs tuned thr (CV) | +0.66 [+0.52,+0.79] | +0.78 [+0.56,+1.00] | +0.11 [-0.37,+0.59] | +2.68 [+2.45,+2.91] | -1.59 [-1.83,-1.35] | +| 5b logistic reg, + prev/next frame tuned thr (CV) | +1.17 [+1.02,+1.31] | +1.33 [+1.07,+1.61] | +0.60 [+0.13,+1.10] | +3.63 [+3.41,+3.86] | -1.54 [-1.76,-1.32] | +| 5c gradient boosting, + prev/next frame tuned thr (CV) | +1.74 [+1.59,+1.90] | +1.54 [+1.27,+1.82] | +1.34 [+1.00,+1.68] | +4.77 [+4.54,+5.02] | -1.57 [-1.71,-1.44] | +| 5d as 5b, trained also on noise-only clips tuned thr (CV) | +1.16 [+1.03,+1.28] | +1.08 [+0.77,+1.39] | +0.80 [+0.46,+1.15] | +3.52 [+3.33,+3.71] | -1.45 [-1.58,-1.34] | +| 5e as 5c, trained also on noise-only clips tuned thr (CV) | +1.68 [+1.53,+1.83] | +1.54 [+1.27,+1.84] | +1.13 [+0.84,+1.42] | +4.67 [+4.43,+4.90] | -1.59 [-1.70,-1.47] | + +### Noise-shift split (ultra): parameters chosen on clean/20/10 dB clips only, scored on 5 and 0 dB clips + +| system | P | R | F1 (5+0 dB clips) | +|---|---|---|---| +| Silero (0.1,) | 97.6 [97.4,97.9] | 91.5 [90.6,92.3] | 94.5 [94.0,94.9] | +| ultra head (0.1,) | 86.5 [85.5,87.3] | 98.9 [98.5,99.2] | 92.2 [91.8,92.7] | +| OR (0.1, 0.1) | 86.3 [85.4,87.2] | 99.2 [98.9,99.4] | 92.3 [91.8,92.8] | +| AND (0.1, 0.1) | 97.9 [97.6,98.1] | 91.1 [90.2,91.9] | 94.3 [93.9,94.8] | +| mean (0.5, 0.1) | 89.3 [88.3,90.2] | 97.7 [97.2,98.2] | 93.3 [92.8,93.8] | +| max (0.1,) | 86.3 [85.4,87.2] | 99.2 [98.9,99.4] | 92.3 [91.8,92.8] | +| min (0.1,) | 97.9 [97.6,98.1] | 91.1 [90.2,91.9] | 94.3 [93.9,94.8] | +| two-stage (0.3, 0.3, 48.0, 16.0, 60.0) | 98.0 [97.7,98.2] | 92.8 [91.9,93.5] | 95.3 [94.9,95.7] | +| noise-switch (0.3, 0.3, 40.0) | 94.2 [93.2,95.1] | 93.4 [92.6,94.2] | 93.8 [93.4,94.2] | +| lr2 (0.5,) | 89.8 [89.0,90.5] | 97.8 [97.5,98.2] | 93.6 [93.2,94.0] | +| lr6 (0.45,) | 88.7 [87.8,89.5] | 98.8 [98.5,99.0] | 93.4 [93.0,93.9] | +| hgb6 (0.45,) | 91.1 [90.2,91.9] | 98.1 [97.7,98.4] | 94.4 [94.0,94.9] | +| lr6nz (0.5,) | 95.2 [94.7,95.5] | 95.5 [95.0,96.1] | 95.4 [95.0,95.6] | +| hgb6nz (0.45,) | 92.9 [92.3,93.4] | 96.9 [96.4,97.3] | 94.8 [94.4,95.2] | + +### Operating points with precision >= 99% (ultra): CV-selected maximum recall + +| system | P | R | F1 | chosen params per fold | +|---|---|---|---|---| +| Silero | 99.1 [99.0,99.2] | 88.6 [88.0,89.2] | 93.6 [93.2,93.9] | [(0.2,), (0.2,), (0.2,), (0.25,)] | +| ultra head | 99.0 [98.8,99.1] | 87.4 [86.8,88.1] | 92.9 [92.5,93.2] | [(0.55,), (0.6,), (0.65,), (0.65,)] | +| OR | 98.9 [98.7,99.0] | 89.7 [89.1,90.2] | 94.0 [93.7,94.3] | [(0.3, 0.6), (0.2, 0.8), (0.4, 0.7), (0.3, 0.8)] | +| AND | 99.1 [99.0,99.1] | 88.9 [88.3,89.4] | 93.7 [93.4,94.0] | [(0.1, 0.3), (0.1, 0.3), (0.2, 0.1), (0.2, 0.1)] | +| mean | 99.0 [98.9,99.1] | 89.5 [88.9,90.0] | 94.0 [93.7,94.3] | [(0.5, 0.35), (0.5, 0.35), (0.5, 0.4), (0.5, 0.4)] | +| max | 99.0 [98.8,99.1] | 88.8 [88.2,89.4] | 93.6 [93.3,93.9] | [(0.55,), (0.65,), (0.65,), (0.7,)] | +| min | 99.1 [99.0,99.2] | 88.7 [88.0,89.2] | 93.6 [93.3,93.9] | [(0.15,), (0.15,), (0.2,), (0.2,)] | +| two-stage | 99.0 [98.9,99.1] | 90.6 [90.0,91.1] | 94.6 [94.3,94.8] | [(0.3, 0.3, 16.0, 0.0, 60.0), (0.5, 0.3, 16.0, 8.0, 30.0), (0.3, 0.3, 8.0, 0.0, 60.0), (0.5, 0.3, 32.0, 0.0, 60.0)] | +| noise-switch | 99.0 [98.8,99.2] | 88.2 [87.5,88.7] | 93.3 [92.9,93.6] | [(0.3, 0.5, 16.0), (0.3, 0.5, 8.0), (0.3, 0.5, 8.0), (0.3, 0.5, 8.0)] | +| lr2 | 99.1 [99.1,99.2] | 89.1 [88.5,89.6] | 93.8 [93.5,94.1] | thr [0.8, 0.8, 0.8, 0.8] | +| lr6 | 99.1 [99.0,99.2] | 90.3 [89.7,90.8] | 94.5 [94.2,94.8] | thr [0.8, 0.8, 0.8, 0.8] | +| hgb6 | 99.1 [99.0,99.2] | 91.0 [90.5,91.5] | 94.9 [94.6,95.1] | thr [0.85, 0.85, 0.8, 0.8] | +| lr6nz | 99.1 [99.0,99.2] | 90.0 [89.4,90.6] | 94.3 [94.0,94.6] | thr [0.8, 0.8, 0.8, 0.8] | +| hgb6nz | 99.0 [98.9,99.1] | 91.3 [90.7,91.8] | 95.0 [94.7,95.2] | thr [0.8, 0.85, 0.8, 0.8] | + +### In-sample best operating points (ultra), descriptive only (selected on all clips, optimistic) + +| rule | best F1 params | P | R | F1 | max R at P>=99% params | P | R | +|---|---|---|---|---|---|---|---| +| silero | (0.1,) | 98.3 | 91.1 | 94.5 | (0.2,) | 99.1 | 88.8 | +| head | (0.25,) | 95.8 | 93.2 | 94.5 | (0.6,) | 99.0 | 87.8 | +| or | (0.1, 0.5) | 97.6 | 93.1 | 95.3 | (0.3, 0.7) | 99.0 | 89.7 | +| and | (0.1, 0.1) | 98.5 | 90.5 | 94.3 | (0.1, 0.3) | 99.0 | 88.9 | +| mean | (0.75, 0.1) | 96.1 | 94.3 | 95.2 | (0.75, 0.25) | 99.1 | 89.6 | +| max | (0.3,) | 96.6 | 93.0 | 94.7 | (0.65,) | 99.1 | 88.7 | +| min | (0.1,) | 98.5 | 90.5 | 94.3 | (0.2,) | 99.2 | 88.2 | +| two | (0.3, 0.3, 32.0, 16.0, 60.0) | 98.5 | 92.3 | 95.3 | (0.5, 0.3, 8.0, 8.0, 60.0) | 99.0 | 90.6 | +| switch | (0.3, 0.3, 40.0) | 96.7 | 92.1 | 94.4 | (0.3, 0.5, 8.0) | 99.2 | 87.8 | +| oracle | (0.3, 0.3, 10.0) | 96.9 | 91.1 | 93.9 | (0.3, 0.7, 5.0) | 99.4 | 86.3 | + +LR coefficients (ultra, fit on folds != 0; features lg(silero), lg(head)[, lg(silero prev/next), lg(head prev/next)]): +- lr2: ([0.494, 0.361], 2.1583826054377893) +- lr6: ([0.074, -0.41, 0.119, 0.368, 0.279, 0.533], 2.2670968869481705) +- lr6nz: ([-0.023, -0.518, 0.392, 0.604, 0.087, 0.484], 2.7718436816992393) + +## Head = redux + +### Cross-validated results (redux head + Silero; speaker-disjoint 4-fold; 'tuned' = parameters chosen on the 3 training folds, scored on the held-out fold) + +pooled over 342 speech clips; point [95% bootstrap CI over clips], percent. Per-level columns: F1 with CI (white+pink pooled). + +| system | P | R | F1 | F1 clean | F1 20 dB | F1 10 dB | F1 5 dB | F1 0 dB | +|---|---|---|---|---|---|---|---|---| +| Silero thr .5 (unified post) | 99.6 [99.6,99.6] | 85.6 [84.9,86.2] | 92.1 [91.7,92.4] | 93.5 [92.5,94.4] | 92.8 [92.0,93.5] | 92.4 [91.6,93.1] | 91.9 [91.2,92.7] | 90.4 [89.4,91.2] | +| Silero thr .5 (native defaults: min speech .25, pause .1, pad .03) | 99.4 [99.3,99.4] | 86.7 [86.1,87.3] | 92.6 [92.3,92.9] | 93.9 [92.9,94.7] | 93.3 [92.6,93.9] | 93.0 [92.3,93.6] | 92.5 [91.9,93.2] | 90.9 [90.1,91.8] | +| redux head thr .5 | 97.6 [97.3,97.8] | 92.0 [91.6,92.5] | 94.7 [94.5,95.0] | 95.4 [94.6,96.0] | 95.1 [94.5,95.6] | 94.8 [94.2,95.3] | 94.6 [94.1,95.1] | 94.1 [93.6,94.6] | +| Silero tuned thr (CV) | 98.3 [98.1,98.4] | 91.1 [90.5,91.6] | 94.5 [94.2,94.8] | 94.9 [93.9,95.6] | 94.4 [93.8,95.0] | 94.6 [94.0,95.2] | 94.6 [94.0,95.2] | 94.3 [93.7,94.8] | +| redux head tuned thr (CV) | 96.9 [96.5,97.2] | 92.8 [92.4,93.2] | 94.8 [94.5,95.0] | 95.5 [94.7,96.1] | 95.2 [94.6,95.7] | 94.9 [94.3,95.4] | 94.6 [94.0,95.1] | 94.2 [93.7,94.6] | +| 1 OR of decisions @ defaults (0.5, 0.5) | 97.6 [97.3,97.8] | 92.1 [91.7,92.5] | 94.8 [94.5,95.0] | 95.4 [94.6,96.1] | 95.1 [94.6,95.6] | 94.8 [94.2,95.3] | 94.7 [94.1,95.2] | 94.2 [93.7,94.6] | +| 2 AND of decisions @ defaults (0.5, 0.5) | 99.6 [99.6,99.6] | 85.5 [84.8,86.1] | 92.0 [91.6,92.4] | 93.4 [92.4,94.3] | 92.7 [92.0,93.4] | 92.3 [91.6,93.0] | 91.9 [91.1,92.6] | 90.3 [89.4,91.2] | +| 3 mean of probs (w=.5) @ defaults (0.5, 0.5) | 99.3 [99.3,99.4] | 88.2 [87.6,88.8] | 93.4 [93.1,93.7] | 94.4 [93.5,95.2] | 93.8 [93.1,94.4] | 93.6 [92.9,94.2] | 93.3 [92.6,93.9] | 92.6 [91.9,93.3] | +| 4a max of probs @ defaults (0.5,) | 97.6 [97.3,97.8] | 92.1 [91.7,92.5] | 94.8 [94.5,95.0] | 95.4 [94.6,96.1] | 95.1 [94.6,95.6] | 94.8 [94.2,95.3] | 94.7 [94.1,95.2] | 94.2 [93.7,94.6] | +| 4b min of probs @ defaults (0.5,) | 99.6 [99.6,99.6] | 85.5 [84.8,86.1] | 92.0 [91.6,92.4] | 93.4 [92.4,94.3] | 92.7 [92.0,93.4] | 92.3 [91.6,93.0] | 91.9 [91.1,92.6] | 90.3 [89.4,91.2] | +| 6 two-stage (Silero + head extend/fill) @ defaults (0.5, 0.5, 16, 16, 30) | 98.9 [98.8,98.9] | 91.2 [90.7,91.7] | 94.9 [94.6,95.1] | 95.4 [94.6,96.1] | 95.1 [94.5,95.6] | 95.0 [94.5,95.5] | 94.9 [94.3,95.4] | 94.3 [93.6,94.8] | +| 7 noise-switch (estimated SNR) @ defaults (0.5, 0.5, 12) | 98.4 [98.1,98.6] | 88.5 [87.9,89.1] | 93.2 [92.8,93.5] | 93.5 [92.5,94.4] | 92.8 [92.0,93.5] | 92.4 [91.6,93.1] | 93.2 [92.4,93.9] | 94.1 [93.6,94.6] | +| 5a logistic reg, 2 probs @ thr .5 | 95.3 [94.9,95.7] | 95.0 [94.6,95.4] | 95.1 [94.9,95.4] | 96.0 [95.2,96.6] | 95.6 [95.1,96.1] | 95.3 [94.8,95.8] | 95.0 [94.6,95.4] | 94.3 [93.8,94.7] | +| 5b logistic reg, + prev/next frame @ thr .5 | 96.1 [95.8,96.4] | 95.7 [95.3,96.1] | 95.9 [95.7,96.1] | 96.5 [95.7,97.1] | 96.2 [95.7,96.7] | 96.1 [95.7,96.5] | 95.9 [95.5,96.2] | 95.2 [94.8,95.6] | +| 5c gradient boosting, + prev/next frame @ thr .5 | 97.0 [96.8,97.2] | 95.6 [95.2,96.0] | 96.3 [96.1,96.5] | 96.8 [96.2,97.3] | 96.5 [96.1,96.9] | 96.3 [95.9,96.7] | 96.2 [95.8,96.6] | 95.9 [95.5,96.3] | +| 5d as 5b, trained also on noise-only clips @ thr .5 | 96.9 [96.7,97.1] | 94.9 [94.4,95.3] | 95.9 [95.7,96.1] | 96.3 [95.5,97.0] | 96.1 [95.6,96.6] | 96.0 [95.6,96.4] | 95.9 [95.5,96.2] | 95.4 [94.9,95.8] | +| 5e as 5c, trained also on noise-only clips @ thr .5 | 97.0 [96.8,97.2] | 95.4 [95.0,95.8] | 96.2 [96.0,96.4] | 96.9 [96.3,97.4] | 96.5 [96.1,96.8] | 96.3 [95.9,96.7] | 96.1 [95.7,96.5] | 95.6 [95.1,96.0] | +| 1 OR of decisions tuned (CV) | 97.7 [97.5,97.9] | 93.0 [92.5,93.5] | 95.3 [95.1,95.5] | 95.6 [94.7,96.3] | 95.2 [94.6,95.7] | 95.3 [94.7,95.8] | 95.4 [94.8,95.8] | 95.3 [94.9,95.6] | +| 2 AND of decisions tuned (CV) | 98.4 [98.2,98.5] | 90.9 [90.3,91.4] | 94.5 [94.2,94.7] | 94.7 [93.8,95.5] | 94.3 [93.7,94.9] | 94.6 [93.9,95.1] | 94.6 [94.0,95.1] | 94.3 [93.7,94.8] | +| 4a max of probs tuned (CV) | 96.8 [96.5,97.2] | 92.9 [92.5,93.3] | 94.8 [94.6,95.1] | 95.6 [94.8,96.2] | 95.2 [94.7,95.7] | 94.9 [94.4,95.5] | 94.6 [94.0,95.1] | 94.2 [93.7,94.7] | +| 4b min of probs tuned (CV) | 98.4 [98.2,98.5] | 90.9 [90.3,91.4] | 94.5 [94.2,94.7] | 94.7 [93.8,95.5] | 94.3 [93.7,94.9] | 94.6 [93.9,95.1] | 94.6 [94.0,95.1] | 94.3 [93.7,94.8] | +| 6 two-stage (Silero + head extend/fill) tuned (CV) | 98.0 [97.8,98.1] | 93.9 [93.5,94.3] | 95.9 [95.7,96.1] | 95.9 [95.1,96.5] | 95.6 [95.1,96.0] | 95.8 [95.4,96.3] | 96.1 [95.8,96.5] | 96.1 [95.7,96.5] | +| 7 noise-switch (estimated SNR) tuned (CV) | 97.6 [97.3,97.9] | 91.9 [91.4,92.3] | 94.7 [94.4,94.9] | 94.7 [93.8,95.4] | 95.1 [94.5,95.6] | 94.8 [94.2,95.3] | 94.6 [94.1,95.1] | 94.1 [93.6,94.6] | +| 3 mean of probs (w=.5) tuned w,thr (CV) | 97.1 [96.8,97.4] | 93.6 [93.2,94.1] | 95.3 [95.1,95.6] | 95.7 [94.9,96.4] | 95.3 [94.8,95.8] | 95.3 [94.8,95.8] | 95.3 [94.9,95.8] | 95.2 [94.7,95.6] | +| 5a logistic reg, 2 probs tuned thr (CV) | 96.6 [96.3,96.9] | 94.0 [93.5,94.4] | 95.3 [95.0,95.5] | 95.7 [94.9,96.4] | 95.4 [94.8,95.9] | 95.4 [94.8,95.9] | 95.3 [94.9,95.7] | 94.8 [94.3,95.2] | +| 5b logistic reg, + prev/next frame tuned thr (CV) | 97.0 [96.8,97.3] | 95.0 [94.6,95.4] | 96.0 [95.8,96.2] | 96.4 [95.6,97.0] | 96.1 [95.6,96.5] | 96.2 [95.8,96.6] | 96.0 [95.6,96.4] | 95.5 [95.1,95.9] | +| 5c gradient boosting, + prev/next frame tuned thr (CV) | 96.9 [96.7,97.1] | 95.7 [95.3,96.0] | 96.3 [96.1,96.5] | 96.8 [96.3,97.3] | 96.5 [96.1,96.9] | 96.3 [95.9,96.7] | 96.2 [95.8,96.5] | 95.9 [95.5,96.2] | +| 5d as 5b, trained also on noise-only clips tuned thr (CV) | 96.9 [96.7,97.1] | 94.9 [94.4,95.3] | 95.9 [95.7,96.1] | 96.3 [95.5,97.0] | 96.1 [95.6,96.6] | 96.0 [95.6,96.4] | 95.9 [95.5,96.2] | 95.4 [94.9,95.8] | +| 5e as 5c, trained also on noise-only clips tuned thr (CV) | 96.9 [96.7,97.1] | 95.5 [95.1,95.9] | 96.2 [96.0,96.4] | 96.9 [96.3,97.4] | 96.5 [96.1,96.8] | 96.3 [95.9,96.7] | 96.1 [95.7,96.5] | 95.6 [95.1,96.0] | +| 7o oracle noise-switch (true SNR) tuned (CV) | 97.8 [97.5,98.0] | 90.9 [90.4,91.4] | 94.2 [93.9,94.5] | 94.0 [93.0,94.9] | 93.4 [92.7,94.0] | 94.8 [94.2,95.3] | 94.6 [94.1,95.1] | 94.1 [93.6,94.6] | + +### False alarms on noise-only clips + +noise-only clips (12 x 30 s per level, white+pink pooled over the levels shown). False-alarm segments per hour (frame false-positive rate in brackets). + +| system | 20 dB | 10 dB | 5 dB | 0 dB | all | +|---|---|---|---|---|---| +| Silero thr .5 (unified post) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| Silero thr .5 (native defaults: min speech .25, pause .1, pad .03) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| redux head thr .5 | 485 (95.43%) | 195 (98.52%) | 170 (98.70%) | 170 (98.68%) | 255 (97.83%) | +| Silero tuned thr (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| redux head tuned thr (CV) | 255 (97.76%) | 135 (99.01%) | 135 (99.03%) | 135 (99.03%) | 165 (98.71%) | +| 1 OR of decisions @ defaults (0.5, 0.5) | 485 (95.43%) | 195 (98.52%) | 170 (98.70%) | 170 (98.68%) | 255 (97.83%) | +| 2 AND of decisions @ defaults (0.5, 0.5) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 3 mean of probs (w=.5) @ defaults (0.5, 0.5) | 0 (0.00%) | 5 (0.03%) | 5 (0.04%) | 10 (0.08%) | 5 (0.04%) | +| 4a max of probs @ defaults (0.5,) | 485 (95.43%) | 195 (98.52%) | 170 (98.70%) | 170 (98.68%) | 255 (97.83%) | +| 4b min of probs @ defaults (0.5,) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 6 two-stage (Silero + head extend/fill) @ defaults (0.5, 0.5, 16, 16, 30) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 7 noise-switch (estimated SNR) @ defaults (0.5, 0.5, 12) | 485 (95.43%) | 195 (98.52%) | 170 (98.70%) | 170 (98.68%) | 255 (97.83%) | +| 5a logistic reg, 2 probs @ thr .5 | 370 (2.73%) | 960 (13.42%) | 730 (14.70%) | 475 (10.07%) | 634 (10.23%) | +| 5b logistic reg, + prev/next frame @ thr .5 | 110 (0.78%) | 525 (5.73%) | 495 (8.33%) | 300 (6.14%) | 358 (5.24%) | +| 5c gradient boosting, + prev/next frame @ thr .5 | 2160 (39.14%) | 2005 (62.50%) | 1960 (67.37%) | 1800 (70.39%) | 1981 (59.85%) | +| 5d as 5b, trained also on noise-only clips @ thr .5 | 0 (0.00%) | 5 (0.02%) | 10 (0.09%) | 10 (0.07%) | 6 (0.05%) | +| 5e as 5c, trained also on noise-only clips @ thr .5 | 0 (0.00%) | 25 (0.11%) | 35 (0.24%) | 15 (0.14%) | 19 (0.12%) | +| 1 OR of decisions tuned (CV) | 1415 (82.51%) | 645 (94.09%) | 515 (94.99%) | 490 (95.38%) | 766 (91.74%) | +| 2 AND of decisions tuned (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 4a max of probs tuned (CV) | 255 (97.76%) | 135 (99.01%) | 135 (99.03%) | 135 (99.03%) | 165 (98.71%) | +| 4b min of probs tuned (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 6 two-stage (Silero + head extend/fill) tuned (CV) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | 0 (0.00%) | +| 7 noise-switch (estimated SNR) tuned (CV) | 485 (95.43%) | 195 (98.52%) | 170 (98.70%) | 170 (98.68%) | 255 (97.83%) | +| 3 mean of probs (w=.5) tuned w,thr (CV) | 1350 (83.67%) | 555 (94.87%) | 475 (95.49%) | 460 (95.73%) | 710 (92.44%) | +| 5a logistic reg, 2 probs tuned thr (CV) | 115 (0.67%) | 645 (5.86%) | 620 (7.87%) | 385 (5.58%) | 441 (4.99%) | +| 5b logistic reg, + prev/next frame tuned thr (CV) | 30 (0.13%) | 275 (2.27%) | 350 (3.73%) | 235 (3.02%) | 222 (2.29%) | +| 5c gradient boosting, + prev/next frame tuned thr (CV) | 2160 (39.64%) | 1985 (62.81%) | 1950 (67.77%) | 1805 (70.68%) | 1975 (60.23%) | +| 5d as 5b, trained also on noise-only clips tuned thr (CV) | 0 (0.00%) | 5 (0.02%) | 10 (0.09%) | 10 (0.07%) | 6 (0.05%) | +| 5e as 5c, trained also on noise-only clips tuned thr (CV) | 5 (0.02%) | 25 (0.11%) | 35 (0.25%) | 15 (0.14%) | 20 (0.13%) | +| 7o oracle noise-switch (true SNR) tuned (CV) | 0 (0.00%) | 195 (98.52%) | 170 (98.70%) | 170 (98.68%) | 134 (73.98%) | + +**Best single (CV-tuned):** redux head tuned thr (CV). + +| fusion (CV) | dF1 vs best single, pooled | dF1 5 dB | dF1 0 dB | dR pooled | dP pooled | +|---|---|---|---|---|---| +| 1 OR of decisions @ defaults (0.5, 0.5) | -0.02 [-0.08,+0.04] | +0.11 [-0.03,+0.27] | -0.01 [-0.17,+0.16] | -0.69 [-0.79,-0.60] | +0.71 [+0.61,+0.83] | +| 2 AND of decisions @ defaults (0.5, 0.5) | -2.79 [-3.02,-2.56] | -2.67 [-3.15,-2.20] | -3.87 [-4.57,-3.19] | -7.36 [-7.82,-6.90] | +2.77 [+2.43,+3.13] | +| 3 mean of probs (w=.5) @ defaults (0.5, 0.5) | -1.35 [-1.53,-1.16] | -1.25 [-1.66,-0.82] | -1.53 [-2.06,-1.01] | -4.61 [-4.95,-4.28] | +2.49 [+2.17,+2.85] | +| 4a max of probs @ defaults (0.5,) | -0.02 [-0.08,+0.04] | +0.11 [-0.03,+0.27] | -0.01 [-0.17,+0.16] | -0.69 [-0.79,-0.60] | +0.71 [+0.61,+0.83] | +| 4b min of probs @ defaults (0.5,) | -2.79 [-3.02,-2.56] | -2.67 [-3.15,-2.20] | -3.87 [-4.57,-3.19] | -7.36 [-7.82,-6.90] | +2.77 [+2.43,+3.13] | +| 6 two-stage (Silero + head extend/fill) @ defaults (0.5, 0.5, 16, 16, 30) | +0.09 [-0.04,+0.22] | +0.33 [+0.03,+0.66] | +0.09 [-0.36,+0.54] | -1.60 [-1.88,-1.35] | +2.00 [+1.69,+2.34] | +| 7 noise-switch (estimated SNR) @ defaults (0.5, 0.5, 12) | -1.62 [-1.81,-1.42] | -1.35 [-1.89,-0.84] | -0.04 [-0.21,+0.12] | -4.32 [-4.70,-3.95] | +1.52 [+1.34,+1.74] | +| 5a logistic reg, 2 probs @ thr .5 | +0.36 [+0.22,+0.49] | +0.42 [+0.05,+0.80] | +0.13 [-0.32,+0.57] | +2.17 [+1.95,+2.38] | -1.54 [-1.80,-1.28] | +| 5b logistic reg, + prev/next frame @ thr .5 | +1.14 [+1.01,+1.28] | +1.34 [+0.98,+1.70] | +1.05 [+0.63,+1.45] | +2.90 [+2.71,+3.09] | -0.71 [-0.94,-0.44] | +| 5c gradient boosting, + prev/next frame @ thr .5 | +1.50 [+1.37,+1.64] | +1.63 [+1.31,+1.98] | +1.73 [+1.40,+2.08] | +2.79 [+2.59,+2.98] | +0.13 [-0.16,+0.44] | +| 5d as 5b, trained also on noise-only clips @ thr .5 | +1.10 [+0.96,+1.25] | +1.29 [+0.98,+1.63] | +1.20 [+0.77,+1.62] | +2.09 [+1.84,+2.33] | +0.05 [-0.24,+0.38] | +| 5e as 5c, trained also on noise-only clips @ thr .5 | +1.42 [+1.29,+1.57] | +1.57 [+1.26,+1.90] | +1.40 [+1.03,+1.77] | +2.59 [+2.34,+2.82] | +0.18 [-0.11,+0.51] | +| 1 OR of decisions tuned (CV) | +0.53 [+0.42,+0.65] | +0.80 [+0.52,+1.09] | +1.09 [+0.84,+1.37] | +0.23 [+0.03,+0.41] | +0.85 [+0.63,+1.11] | +| 2 AND of decisions tuned (CV) | -0.32 [-0.46,-0.16] | +0.05 [-0.29,+0.42] | +0.11 [-0.32,+0.54] | -1.93 [-2.23,-1.65] | +1.50 [+1.22,+1.84] | +| 4a max of probs tuned (CV) | +0.04 [+0.03,+0.05] | +0.04 [+0.02,+0.06] | +0.04 [+0.01,+0.06] | +0.09 [+0.06,+0.11] | -0.02 [-0.02,-0.01] | +| 4b min of probs tuned (CV) | -0.32 [-0.46,-0.16] | +0.05 [-0.29,+0.42] | +0.11 [-0.32,+0.54] | -1.93 [-2.23,-1.65] | +1.50 [+1.22,+1.84] | +| 6 two-stage (Silero + head extend/fill) tuned (CV) | +1.13 [+1.00,+1.28] | +1.56 [+1.27,+1.89] | +1.94 [+1.56,+2.34] | +1.12 [+0.94,+1.29] | +1.14 [+0.88,+1.44] | +| 7 noise-switch (estimated SNR) tuned (CV) | -0.14 [-0.21,-0.06] | +0.07 [-0.07,+0.24] | -0.04 [-0.21,+0.12] | -0.94 [-1.06,-0.82] | +0.75 [+0.65,+0.88] | +| 3 mean of probs (w=.5) tuned w,thr (CV) | +0.55 [+0.47,+0.64] | +0.78 [+0.59,+0.97] | +0.99 [+0.75,+1.23] | +0.82 [+0.67,+0.97] | +0.25 [+0.11,+0.42] | +| 5a logistic reg, 2 probs tuned thr (CV) | +0.48 [+0.36,+0.61] | +0.75 [+0.46,+1.04] | +0.63 [+0.23,+1.02] | +1.14 [+0.94,+1.33] | -0.23 [-0.46,+0.04] | +| 5b logistic reg, + prev/next frame tuned thr (CV) | +1.21 [+1.10,+1.34] | +1.48 [+1.19,+1.79] | +1.35 [+1.01,+1.69] | +2.19 [+2.00,+2.37] | +0.17 [-0.07,+0.46] | +| 5c gradient boosting, + prev/next frame tuned thr (CV) | +1.51 [+1.38,+1.66] | +1.63 [+1.30,+1.98] | +1.72 [+1.40,+2.06] | +2.89 [+2.69,+3.08] | +0.05 [-0.24,+0.36] | +| 5d as 5b, trained also on noise-only clips tuned thr (CV) | +1.10 [+0.96,+1.25] | +1.29 [+0.98,+1.63] | +1.20 [+0.77,+1.62] | +2.09 [+1.84,+2.33] | +0.05 [-0.24,+0.38] | +| 5e as 5c, trained also on noise-only clips tuned thr (CV) | +1.42 [+1.28,+1.56] | +1.55 [+1.24,+1.87] | +1.39 [+1.00,+1.75] | +2.71 [+2.46,+2.94] | +0.05 [-0.24,+0.37] | + +### Noise-shift split (redux): parameters chosen on clean/20/10 dB clips only, scored on 5 and 0 dB clips + +| system | P | R | F1 (5+0 dB clips) | +|---|---|---|---| +| Silero (0.1,) | 97.6 [97.4,97.9] | 91.5 [90.6,92.2] | 94.5 [94.0,94.8] | +| redux head (0.3,) | 90.0 [89.0,90.9] | 97.2 [96.8,97.5] | 93.4 [93.0,93.8] | +| OR (0.1, 0.4) | 92.8 [92.0,93.4] | 96.3 [95.9,96.7] | 94.5 [94.1,94.9] | +| AND (0.1, 0.1) | 97.7 [97.4,97.9] | 91.4 [90.5,92.2] | 94.4 [94.0,94.8] | +| mean (0.75, 0.1) | 90.2 [89.4,91.0] | 97.9 [97.6,98.2] | 93.9 [93.5,94.3] | +| max (0.3,) | 89.9 [88.9,90.7] | 97.3 [96.9,97.6] | 93.5 [93.0,93.9] | +| min (0.1,) | 97.7 [97.4,97.9] | 91.4 [90.6,92.2] | 94.4 [94.0,94.8] | +| two-stage (0.3, 0.3, 32.0, 16.0, 60.0) | 97.5 [97.2,97.7] | 94.8 [94.3,95.3] | 96.1 [95.9,96.4] | +| noise-switch (0.3, 0.3, 40.0) | 90.0 [89.0,90.8] | 97.2 [96.8,97.5] | 93.4 [93.0,93.8] | +| lr2 (0.55,) | 92.2 [91.6,92.7] | 96.9 [96.6,97.2] | 94.5 [94.2,94.8] | +| lr6 (0.55,) | 93.7 [93.2,94.2] | 97.2 [96.9,97.5] | 95.4 [95.1,95.7] | +| hgb6 (0.45,) | 94.9 [94.4,95.3] | 96.8 [96.3,97.2] | 95.8 [95.6,96.1] | +| lr6nz (0.45,) | 94.4 [94.0,94.8] | 96.4 [95.9,96.8] | 95.4 [95.1,95.7] | +| hgb6nz (0.5,) | 95.6 [95.2,96.0] | 95.9 [95.3,96.4] | 95.8 [95.5,96.0] | + +### Operating points with precision >= 99% (redux): CV-selected maximum recall + +| system | P | R | F1 | chosen params per fold | +|---|---|---|---|---| +| Silero | 99.1 [99.0,99.2] | 88.6 [88.0,89.2] | 93.6 [93.2,93.9] | [(0.2,), (0.2,), (0.2,), (0.25,)] | +| redux head | 99.1 [99.0,99.1] | 89.4 [88.9,89.9] | 94.0 [93.7,94.3] | [(0.65,), (0.7,), (0.7,), (0.75,)] | +| OR | 98.9 [98.8,99.0] | 89.9 [89.3,90.4] | 94.2 [93.9,94.5] | [(0.3, 0.7), (0.2, 0.8), (0.5, 0.7), (0.4, 0.8)] | +| AND | 99.0 [99.0,99.1] | 89.2 [88.6,89.7] | 93.9 [93.6,94.2] | [(0.1, 0.4), (0.1, 0.5), (0.1, 0.5), (0.2, 0.2)] | +| mean | 98.9 [98.8,99.0] | 90.0 [89.4,90.5] | 94.3 [94.0,94.5] | [(0.25, 0.5), (0.75, 0.25), (0.25, 0.55), (0.5, 0.45)] | +| max | 99.0 [98.9,99.1] | 89.6 [89.0,90.1] | 94.1 [93.8,94.3] | [(0.65,), (0.7,), (0.7,), (0.75,)] | +| min | 99.1 [99.1,99.2] | 88.7 [88.1,89.2] | 93.6 [93.3,93.9] | [(0.2,), (0.2,), (0.2,), (0.2,)] | +| two-stage | 99.0 [98.9,99.1] | 91.5 [90.9,92.0] | 95.1 [94.8,95.3] | [(0.5, 0.3, 32.0, 0.0, 60.0), (0.7, 0.3, 16.0, 8.0, 60.0), (0.5, 0.3, 16.0, 0.0, 60.0), (0.5, 0.3, 16.0, 0.0, 60.0)] | +| noise-switch | 99.1 [99.0,99.2] | 89.3 [88.8,89.8] | 94.0 [93.7,94.2] | [(0.3, 0.7, 40.0), (0.3, 0.7, 40.0), (0.3, 0.7, 40.0), (0.5, 0.7, 28.0)] | +| lr2 | 99.1 [99.1,99.2] | 89.4 [88.9,90.0] | 94.0 [93.7,94.3] | thr [0.75, 0.8, 0.8, 0.75] | +| lr6 | 99.1 [99.0,99.2] | 90.7 [90.1,91.2] | 94.7 [94.4,95.0] | thr [0.8, 0.8, 0.8, 0.8] | +| hgb6 | 99.0 [98.9,99.1] | 91.9 [91.4,92.4] | 95.3 [95.0,95.6] | thr [0.8, 0.85, 0.8, 0.8] | +| lr6nz | 99.0 [99.0,99.1] | 90.6 [90.0,91.2] | 94.6 [94.3,94.9] | thr [0.8, 0.8, 0.8, 0.8] | +| hgb6nz | 99.0 [98.9,99.1] | 91.9 [91.3,92.3] | 95.3 [95.0,95.5] | thr [0.8, 0.85, 0.8, 0.8] | + +### In-sample best operating points (redux), descriptive only (selected on all clips, optimistic) + +| rule | best F1 params | P | R | F1 | max R at P>=99% params | P | R | +|---|---|---|---|---|---|---|---| +| silero | (0.1,) | 98.3 | 91.1 | 94.5 | (0.2,) | 99.1 | 88.8 | +| head | (0.45,) | 96.9 | 92.8 | 94.8 | (0.7,) | 99.1 | 89.5 | +| or | (0.1, 0.6) | 97.7 | 93.0 | 95.3 | (0.4, 0.7) | 99.0 | 90.0 | +| and | (0.1, 0.1) | 98.4 | 90.9 | 94.5 | (0.1, 0.5) | 99.1 | 89.2 | +| mean | (0.75, 0.15) | 97.1 | 93.6 | 95.3 | (0.5, 0.4) | 99.0 | 90.2 | +| max | (0.45,) | 96.8 | 92.9 | 94.8 | (0.7,) | 99.1 | 89.6 | +| min | (0.1,) | 98.4 | 90.9 | 94.5 | (0.2,) | 99.1 | 88.7 | +| two | (0.3, 0.3, 32.0, 16.0, 60.0) | 98.0 | 93.9 | 95.9 | (0.3, 0.3, 8.0, 0.0, 60.0) | 99.0 | 91.5 | +| switch | (0.3, 0.5, 40.0) | 97.6 | 91.9 | 94.7 | (0.3, 0.7, 40.0) | 99.1 | 89.4 | +| oracle | (0.3, 0.5, 10.0) | 97.8 | 90.9 | 94.2 | (0.3, 0.7, 10.0) | 99.2 | 88.9 | + +LR coefficients (redux, fit on folds != 0; features lg(silero), lg(head)[, lg(silero prev/next), lg(head prev/next)]): +- lr2: ([0.457, 0.322], 1.7936439121924497) +- lr6: ([0.108, -0.552, 0.096, 0.31, 0.371, 0.545], 1.7755007987974973) +- lr6nz: ([0.026, -0.684, 0.353, 0.543, 0.25, 0.56], 2.5562606732085627) diff --git a/scripts/vad_bench/fusion/rules.py b/scripts/vad_bench/fusion/rules.py new file mode 100644 index 0000000..d21490e --- /dev/null +++ b/scripts/vad_bench/fusion/rules.py @@ -0,0 +1,49 @@ +import numpy as np +from fl import * +THR=[round(x,2) for x in np.arange(0.1,0.91,0.05)] +THR9=[round(x,1) for x in np.arange(0.1,0.91,0.1)] +def dilate(m,pre,post): + """grow each run: `pre` cells earlier, `post` cells later""" + if pre==0 and post==0: return m + s,e=runs(m); out=m.copy(); n=len(m) + for a,b in zip(s,e): out[max(0,a-pre):a]=True; out[b:min(n,b+post)]=True + return out +def two_stage(S,H,pre,post,Y): + """Silero decides; head extends boundaries by up to pre/post cells (only where head says speech, contiguous to the silero run) + and fills silero gaps shorter than Y cells when the head calls >=50% of the gap speech.""" + out=S.copy(); n=len(S) + if pre or post: + s,e=runs(S) + for a,b in zip(s,e): + # walk outward while head is speech, max pre/post cells + i=a + while i>0 and a-i
0 and b=0.5: out[a:b]=True
+    return out
+def raw_mask(rule,p,d,h):
+    s=d["silero"];hh=d[h]
+    if rule=="silero": return s>=p[0]
+    if rule=="head": return hh>=p[0]
+    if rule=="or": return (s>=p[0])|(hh>=p[1])
+    if rule=="and": return (s>=p[0])&(hh>=p[1])
+    if rule=="mean": w,t=p; return (w*s+(1-w)*hh)>=t
+    if rule=="max": return np.maximum(s,hh)>=p[0]
+    if rule=="min": return np.minimum(s,hh)>=p[0]
+    if rule=="two": ts,th,pre,post,Y=p; return two_stage(s>=ts,hh>=th,pre,post,Y)
+    if rule=="switch": ts,th,T=p; return (hh>=th) if d["snr_est"]=ts)
+    if rule=="oracle": ts,th,T=p; return (hh>=th) if d["snr_true"]<=T else (s>=ts)
+    raise ValueError(rule)
+def candidates(rule):
+    if rule in("silero","head","max","min"): return [(t,) for t in THR]
+    if rule in("or","and"): return [(a,b) for a in THR9 for b in THR9]
+    if rule=="mean": return [(w,t) for w in(0.25,0.5,0.75) for t in THR]
+    if rule=="two": return [(ts,th,pre,post,Y) for ts in(.3,.5,.7) for th in(.3,.5,.7) for pre in(0,8,16,32,48) for post in(0,8,16,32) for Y in(0,15,30,60)]
+    if rule=="switch": return [(ts,th,T) for ts in(.3,.5,.7) for th in(.3,.5,.7) for T in(8,12,16,20,24,28,32,40)]
+    if rule=="oracle": return [(ts,th,T) for ts in(.3,.5,.7) for th in(.3,.5,.7) for T in(5,10)]
diff --git a/scripts/vad_bench/fusion/run.py b/scripts/vad_bench/fusion/run.py
new file mode 100644
index 0000000..2eccdae
--- /dev/null
+++ b/scripts/vad_bench/fusion/run.py
@@ -0,0 +1,58 @@
+import sys,pickle,time,numpy as np
+from multiprocessing import Pool
+from fl import *
+from rules import *
+from sklearn.linear_model import LogisticRegression
+from sklearn.ensemble import HistGradientBoostingClassifier
+D=load()
+for i,d in enumerate(D):
+    c=d["cond"].replace("nz-",""); d["snr_true"]=99 if c=="clean" else int(c.lstrip("whitepink"))
+    d["noise_only"]=d["name"].startswith("nz-")
+    k=int(d["name"].split("_")[1]); d["fold"]=k%4 if d["noise_only"] else d["fold"]
+N=len(D)
+def ev(mask,d,pk):
+    m,regs=post(mask,**pk)
+    c=counts(m,d["ref_mask"]); return np.append(c,len(regs))
+def eval_cfg(a):
+    rule,p,h,pk=a
+    return np.array([ev(raw_mask(rule,p,d,h),d,pk) for d in D])
+def lg(x): x=np.clip(x,1e-4,1-1e-4); return np.log(x/(1-x))
+def feats(d,h,kind):
+    s=d["silero"];hh=d[h]
+    if kind=="lr2": return np.stack([lg(s),lg(hh)],1)
+    def sh(x,k): return np.concatenate([np.full(k,x[0]),x[:-k]]) if k>0 else np.concatenate([x[-k:],np.full(-k,x[-1])])
+    return np.stack([lg(s),lg(hh),lg(sh(s,3)),lg(sh(s,-3)),lg(sh(hh,8)),lg(sh(hh,-8))],1)
+def lr_split(a):
+    h,kind,sname,train=a
+    nz=kind.endswith("nz"); kind=kind[:-2] if nz else kind
+    if not nz: train=[i for i in train if not D[i]["noise_only"]]
+    X=np.concatenate([feats(D[i],h,kind)[::10] for i in train]); y=np.concatenate([D[i]["ref_mask"][::10] for i in train])
+    mdl=HistGradientBoostingClassifier(max_iter=120,learning_rate=0.1,random_state=0) if kind=="hgb6" else LogisticRegression(C=1.0,max_iter=300)
+    mdl.fit(X,y)
+    P=[mdl.predict_proba(feats(d,h,kind))[:,1] for d in D]
+    out=np.zeros((len(THR),N,5))
+    for ti,t in enumerate(THR):
+        for i,d in enumerate(D): out[ti,i]=ev(P[i]>=t,d,POST_HEAD)
+    coef=getattr(mdl,"coef_",None)
+    return (h,kind+("nz" if nz else ""),sname),out,(None if coef is None else (coef[0].round(3).tolist(),float(mdl.intercept_[0])))
+if __name__=="__main__":
+    t0=time.time(); res={}
+    jobs=[]
+    for h in("ultra","redux"):
+        for rule in("head","or","and","mean","max","min","two","switch","oracle"):
+            for p in candidates(rule): jobs.append((rule,p,h,POST_HEAD))
+    for p in candidates("silero"): jobs.append(("silero",p,"ultra",POST_HEAD))
+    # native default post for singles at thr 0.5
+    jobs.append(("silero",(0.5,),"ultra",POST_SIL))
+    print(len(jobs),"jobs",flush=True)
+    with Pool(5) as pool:
+        outs=pool.map(eval_cfg,jobs,chunksize=4)
+    res["cfg"]={ (j[0],j[1],j[2],j[3]["min_speech"]):o for j,o in zip(jobs,outs)}
+    print("rules done",time.time()-t0,flush=True)
+    folds=np.array([d["fold"] for d in D]); easy=np.array([d["snr_true"]>=10 for d in D])
+    splits=[(f"f{f}",[i for i in range(N) if folds[i]!=f]) for f in range(4)]+[("shift",[i for i in range(N) if easy[i]])]
+    lj=[(h,k,sn,tr) for h in("ultra","redux") for k in("lr2","lr6","hgb6","lr6nz","hgb6nz") for sn,tr in splits]
+    with Pool(5) as pool: lo=pool.map(lr_split,lj,chunksize=1)
+    res["lr"]={k:(o,c) for k,o,c in lo}
+    res["meta"]=dict(names=[d["name"] for d in D],cond=[d["cond"] for d in D],fold=folds.tolist(),snr_true=[d["snr_true"] for d in D],noise_only=[d["noise_only"] for d in D],snr_est=[d["snr_est"] for d in D],dur=[d["dur"] for d in D])
+    pickle.dump(res,open("results.pkl","wb")); print("done",time.time()-t0)
diff --git a/scripts/vad_bench/fusion/ted.py b/scripts/vad_bench/fusion/ted.py
new file mode 100644
index 0000000..ecd6e3b
--- /dev/null
+++ b/scripts/vad_bench/fusion/ted.py
@@ -0,0 +1,23 @@
+import sys,json,numpy as np,soundfile as sf,subprocess
+import collect
+from run import *
+f=sys.argv[1]   # 16 kHz mono wav of one long talk
+import onnxruntime as ort
+so=ort.SessionOptions(); so.intra_op_num_threads=1; so.inter_op_num_threads=1
+sess=ort.InferenceSession(collect.M,so,providers=["CPUExecutionProvider"])
+y=sf.read(f,dtype="float32")[0]; n=int(round(len(y)/16000/GR))
+d=dict(n=n,silero=hold(collect.sil(y,sess),0.032,n),snr_est=0.0)
+for k,g in collect.HEADS.items():
+    j=json.loads(subprocess.run([collect.CLI,"vad","--model",g,"--input",f,"--probabilities","--threads","1"],capture_output=True,text=True,check=True).stdout)
+    d[k]=hold(np.array(j["probabilities"],np.float32),0.08,n)
+tr=[i for i,x in enumerate(D) if not x["noise_only"]]
+def agree(a,b):
+    c=counts(a,b); tp,fp,fn,tn=c; P,R,F=prf(c); po=(tp+tn)/c.sum(); pe=((tp+fp)*(tp+fn)+(fn+tn)*(fp+tn))/c.sum()**2; return F,(po-pe)/(1-pe)
+for h in("ultra","redux"):
+    X=np.concatenate([feats(D[i],h,"lr6")[::10] for i in tr]); yy=np.concatenate([D[i]["ref_mask"][::10] for i in tr])
+    mdl=LogisticRegression(C=1.0,max_iter=300).fit(X,yy)
+    ms={"Silero .5":raw_mask("silero",(0.5,),d,h),f"{h} head .5":raw_mask("head",(0.5,),d,h),"OR":raw_mask("or",(0.5,0.5),d,h),"AND":raw_mask("and",(0.5,0.5),d,h),"mean .5":raw_mask("mean",(0.5,0.5),d,h),"two-stage .5/.5/160/160/300":raw_mask("two",(0.5,0.5,16,16,30),d,h),"LR+ctx @.5 (fit on synthetic)":mdl.predict_proba(feats(d,h,"lr6"))[:,1]>=0.5}
+    ms={k:post(v,**POST_HEAD)[0] for k,v in ms.items()}
+    print(f"## TED talk {len(y)/16000:.0f} s, head={h}: speech fraction and agreement (F1/kappa) with Silero and with the head")
+    for k,v in ms.items():
+        a=agree(v,ms["Silero .5"]); b=agree(v,ms[f"{h} head .5"]); print(f"| {k} | {100*v.mean():.1f}% | vs Silero {100*a[0]:.1f}/{a[1]:.3f} | vs head {100*b[0]:.1f}/{b[1]:.3f} |")
diff --git a/scripts/vad_bench/fusion/ted_results.txt b/scripts/vad_bench/fusion/ted_results.txt
new file mode 100644
index 0000000..dc9259b
--- /dev/null
+++ b/scripts/vad_bench/fusion/ted_results.txt
@@ -0,0 +1,16 @@
+## TED talk 600 s, head=ultra: speech fraction and agreement (F1/kappa) with Silero and with the head
+| Silero .5 | 77.5% | vs Silero 100.0/1.000 | vs head 95.6/0.777 |
+| ultra head .5 | 83.4% | vs Silero 95.6/0.777 | vs head 100.0/1.000 |
+| OR | 84.1% | vs Silero 95.9/0.788 | vs head 99.6/0.975 |
+| AND | 76.5% | vs Silero 99.4/0.973 | vs head 95.7/0.785 |
+| mean .5 | 79.7% | vs Silero 98.2/0.915 | vs head 97.3/0.855 |
+| two-stage .5/.5/160/160/300 | 83.8% | vs Silero 96.1/0.799 | vs head 99.4/0.964 |
+| LR+ctx @.5 (fit on synthetic) | 90.5% | vs Silero 92.2/0.531 | vs head 95.6/0.664 |
+## TED talk 600 s, head=redux: speech fraction and agreement (F1/kappa) with Silero and with the head
+| Silero .5 | 77.5% | vs Silero 100.0/1.000 | vs head 95.5/0.769 |
+| redux head .5 | 84.3% | vs Silero 95.5/0.769 | vs head 100.0/1.000 |
+| OR | 84.5% | vs Silero 95.6/0.772 | vs head 99.9/0.991 |
+| AND | 77.2% | vs Silero 99.8/0.991 | vs head 95.6/0.772 |
+| mean .5 | 80.8% | vs Silero 97.7/0.888 | vs head 97.8/0.876 |
+| two-stage .5/.5/160/160/300 | 84.1% | vs Silero 95.9/0.787 | vs head 99.6/0.976 |
+| LR+ctx @.5 (fit on synthetic) | 90.2% | vs Silero 92.4/0.543 | vs head 96.3/0.710 |

From 799e40d85f7f14728de399569ab8664a6c021692 Mon Sep 17 00:00:00 2001
From: Ettore Di Giacinto 
Date: Sun, 4 Oct 2026 12:28:51 +0000
Subject: [PATCH 2/2] docs: explain why the VAD head fires on noise, with the
 study and mitigations

A reference built from Hugging Face transformers matches the CLI, so the
false alarms are a property of the model: the head scores each frame by
its level and texture relative to the file. Document the verified
mechanism, the noise types and levels that trigger it, the cost in
transcribe --vad, the mitigations tested and the limits. Add the study's
scripts and small result files under scripts/vad_bench/noise_dive.

Assisted-by: Claude:claude-sonnet-5-5 [Claude Code]
---
 docs/vad-benchmarks.md                        | 253 ++++++++++++++++--
 docs/vad.md                                   |  39 ++-
 scripts/vad_bench/noise_dive/README.md        |  30 +++
 scripts/vad_bench/noise_dive/a2_variants.py   |  28 ++
 scripts/vad_bench/noise_dive/a_faith.py       |  23 ++
 scripts/vad_bench/noise_dive/b10_DS.py        |  34 +++
 scripts/vad_bench/noise_dive/b1_amp.py        |  35 +++
 scripts/vad_bench/noise_dive/b2_signals.py    |  31 +++
 scripts/vad_bench/noise_dive/b3_norm.py       |  56 ++++
 scripts/vad_bench/noise_dive/b3_prep.py       |  12 +
 scripts/vad_bench/noise_dive/b4_regress.py    |  63 +++++
 scripts/vad_bench/noise_dive/b4b_probe.py     |  36 +++
 scripts/vad_bench/noise_dive/b5_logits.py     |  53 ++++
 scripts/vad_bench/noise_dive/b6_types.py      |  41 +++
 scripts/vad_bench/noise_dive/b7_fraction.py   |  31 +++
 scripts/vad_bench/noise_dive/b8_delta.py      |  31 +++
 scripts/vad_bench/noise_dive/b9_gapD.py       |  13 +
 scripts/vad_bench/noise_dive/cli_check.py     |  15 ++
 scripts/vad_bench/noise_dive/corpus.py        |  36 +++
 scripts/vad_bench/noise_dive/d_analyze.py     |  26 ++
 scripts/vad_bench/noise_dive/d_mit.py         |  19 ++
 scripts/vad_bench/noise_dive/d_prep.py        |  21 ++
 scripts/vad_bench/noise_dive/d_probs.py       |  14 +
 scripts/vad_bench/noise_dive/d_run.py         |  17 ++
 scripts/vad_bench/noise_dive/e2_talk.py       |  24 ++
 scripts/vad_bench/noise_dive/e_build.py       |  41 +++
 scripts/vad_bench/noise_dive/e_eval.py        |  60 +++++
 scripts/vad_bench/noise_dive/fig_D_S.png      | Bin 0 -> 64253 bytes
 scripts/vad_bench/noise_dive/fig_energy.py    |  29 ++
 .../vad_bench/noise_dive/fig_logit_hist.png   | Bin 0 -> 61067 bytes
 .../vad_bench/noise_dive/fig_offset_scale.png | Bin 0 -> 46320 bytes
 .../noise_dive/fig_types_vs_level.png         | Bin 0 -> 136616 bytes
 scripts/vad_bench/noise_dive/res_A.md         |  26 ++
 .../vad_bench/noise_dive/res_A2_variants.txt  |  12 +
 scripts/vad_bench/noise_dive/res_B1.txt       |  25 ++
 scripts/vad_bench/noise_dive/res_B10.txt      |  10 +
 scripts/vad_bench/noise_dive/res_B2.md        |  23 ++
 scripts/vad_bench/noise_dive/res_B3.txt       |  34 +++
 scripts/vad_bench/noise_dive/res_B4.txt       |  11 +
 scripts/vad_bench/noise_dive/res_B4b.txt      |  21 ++
 scripts/vad_bench/noise_dive/res_B5.txt       |  34 +++
 scripts/vad_bench/noise_dive/res_B6.txt       |  27 ++
 scripts/vad_bench/noise_dive/res_B7.md        |  33 +++
 scripts/vad_bench/noise_dive/res_B8.txt       |  14 +
 scripts/vad_bench/noise_dive/res_D.md         |  39 +++
 scripts/vad_bench/noise_dive/res_D_mit.md     |  27 ++
 scripts/vad_bench/noise_dive/res_D_probs.md   |  28 ++
 scripts/vad_bench/noise_dive/res_D_silero.txt |   5 +
 scripts/vad_bench/noise_dive/res_E.md         |  44 +++
 scripts/vad_bench/noise_dive/res_E2_talk.txt  |  15 ++
 .../vad_bench/noise_dive/res_cli_check.txt    |  17 ++
 .../vad_bench/noise_dive/res_energy_corr.txt  |   8 +
 scripts/vad_bench/noise_dive/sig.py           |  38 +++
 scripts/vad_bench/noise_dive/vadlib.py        |  94 +++++++
 54 files changed, 1665 insertions(+), 31 deletions(-)
 create mode 100644 scripts/vad_bench/noise_dive/README.md
 create mode 100644 scripts/vad_bench/noise_dive/a2_variants.py
 create mode 100644 scripts/vad_bench/noise_dive/a_faith.py
 create mode 100644 scripts/vad_bench/noise_dive/b10_DS.py
 create mode 100644 scripts/vad_bench/noise_dive/b1_amp.py
 create mode 100644 scripts/vad_bench/noise_dive/b2_signals.py
 create mode 100644 scripts/vad_bench/noise_dive/b3_norm.py
 create mode 100644 scripts/vad_bench/noise_dive/b3_prep.py
 create mode 100644 scripts/vad_bench/noise_dive/b4_regress.py
 create mode 100644 scripts/vad_bench/noise_dive/b4b_probe.py
 create mode 100644 scripts/vad_bench/noise_dive/b5_logits.py
 create mode 100644 scripts/vad_bench/noise_dive/b6_types.py
 create mode 100644 scripts/vad_bench/noise_dive/b7_fraction.py
 create mode 100644 scripts/vad_bench/noise_dive/b8_delta.py
 create mode 100644 scripts/vad_bench/noise_dive/b9_gapD.py
 create mode 100644 scripts/vad_bench/noise_dive/cli_check.py
 create mode 100644 scripts/vad_bench/noise_dive/corpus.py
 create mode 100644 scripts/vad_bench/noise_dive/d_analyze.py
 create mode 100644 scripts/vad_bench/noise_dive/d_mit.py
 create mode 100644 scripts/vad_bench/noise_dive/d_prep.py
 create mode 100644 scripts/vad_bench/noise_dive/d_probs.py
 create mode 100644 scripts/vad_bench/noise_dive/d_run.py
 create mode 100644 scripts/vad_bench/noise_dive/e2_talk.py
 create mode 100644 scripts/vad_bench/noise_dive/e_build.py
 create mode 100644 scripts/vad_bench/noise_dive/e_eval.py
 create mode 100644 scripts/vad_bench/noise_dive/fig_D_S.png
 create mode 100644 scripts/vad_bench/noise_dive/fig_energy.py
 create mode 100644 scripts/vad_bench/noise_dive/fig_logit_hist.png
 create mode 100644 scripts/vad_bench/noise_dive/fig_offset_scale.png
 create mode 100644 scripts/vad_bench/noise_dive/fig_types_vs_level.png
 create mode 100644 scripts/vad_bench/noise_dive/res_A.md
 create mode 100644 scripts/vad_bench/noise_dive/res_A2_variants.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B1.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B10.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B2.md
 create mode 100644 scripts/vad_bench/noise_dive/res_B3.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B4.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B4b.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B5.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B6.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_B7.md
 create mode 100644 scripts/vad_bench/noise_dive/res_B8.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_D.md
 create mode 100644 scripts/vad_bench/noise_dive/res_D_mit.md
 create mode 100644 scripts/vad_bench/noise_dive/res_D_probs.md
 create mode 100644 scripts/vad_bench/noise_dive/res_D_silero.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_E.md
 create mode 100644 scripts/vad_bench/noise_dive/res_E2_talk.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_cli_check.txt
 create mode 100644 scripts/vad_bench/noise_dive/res_energy_corr.txt
 create mode 100644 scripts/vad_bench/noise_dive/sig.py
 create mode 100644 scripts/vad_bench/noise_dive/vadlib.py

diff --git a/docs/vad-benchmarks.md b/docs/vad-benchmarks.md
index b84caa4..fd91aa5 100644
--- a/docs/vad-benchmarks.md
+++ b/docs/vad-benchmarks.md
@@ -551,22 +551,19 @@ The accuracy numbers do not depend on load and were not re-run.
 ## The head on noise-only audio
 
 The Parakeet VAD head gives false alarms on audio that has no speech. Silero does not.
-This was measured on synthetic data: LibriSpeech test-clean utterances with added white
-or pink noise (see [the fusion experiment](#fusing-silero-and-the-head-offline-experiment)
-for the corpus). The numbers are frame rates at threshold 0.5 with the default
-segmenter settings (bridge 0.1 s, minimum speech 0.1 s, minimum pause 0.2 s, no padding).
+This was first measured on synthetic data: LibriSpeech test-clean utterances with added
+white or pink noise (see [the fusion experiment](#fusing-silero-and-the-head-offline-experiment)
+for the corpus). A follow-up root-cause study (below) then looked at why, at other noise
+types and levels, and at what it costs in `transcribe --vad`. The numbers in the first
+two tables are frame rates at threshold 0.5 with the default segmenter settings (bridge
+0.1 s, minimum speech 0.1 s, minimum pause 0.2 s, no padding).
 
 | Test | Silero | Ultra head (Q8_0) | Redux head (packed) |
 | --- | ---: | ---: | ---: |
 | Speech-free 30 s clips (96 clips: white and pink noise at 20, 10, 5 and 0 dB), share of frames called speech | 0.00% | 99.44% | 97.83% |
 | A 30 s noise stretch inside a file that also has speech (96 files), share of frames in the stretch called speech | 0.0% | 17.7% | 55.2% |
 
-On a file with no speech at all, the heads call nearly every frame speech. The likely
-reason is that the head normalises the features of each file on their own, so pure
-noise is rescaled until it looks like speech (this is an explanation, not something the
-experiment tested). When the file also has speech, the normalisation is set by the
-speech, and the false alarms drop, but they do not go away. For the stretch inside a
-speech file, the rate depends on the noise level:
+For the stretch inside a speech file, the rate depends on the noise level:
 
 | Noise level (SNR) | Ultra head | Redux head | Silero |
 | --- | ---: | ---: | ---: |
@@ -575,19 +572,232 @@ speech file, the rate depends on the noise level:
 | 5 dB | 14.3% | 60.8% | 0.0% |
 | 0 dB | 55.8% | 59.4% | 0.0% |
 
-For Ultra the false alarms grow with the noise level. For Redux they are already high at
-20 dB. Each cell pools 12 white noise and 12 pink noise files. The numbers are in
+Each cell pools 12 white noise and 12 pink noise files. The numbers are in
 `scripts/vad_bench/fusion/results.md` (noise-only clips) and `gap_results.md` (the
 stretch inside a file).
 
-What this means: use Silero as the always-on gate, and whenever the audio can contain
-long stretches without speech. Use the head on audio that is known to be mostly speech,
-for example before the transcription of recorded talks, or where its higher recall
-matters.
+### Root-cause study
 
-Limits: synthetic noise (white and pink) added to read English speech. Babble, music,
-room noise and other languages were not tested. The false alarms in a stretch were
-measured only for a stretch of about 30 s.
+Verdict: this is not a bug in parakeet.cpp. It is a property of the model. Scripts and
+result files: [`scripts/vad_bench/noise_dive/`](../scripts/vad_bench/noise_dive/README.md).
+
+**Our implementation is faithful.** An independent reference was built from Hugging Face
+transformers (feature extractor and subsampler) and the head structure documented in
+[ternary.md](ternary.md). On 8 signals it matches `parakeet-cli vad --probabilities` to a
+maximum probability difference of 1.1e-4 to 2.6e-4 on F16 files (Redux and Ultra F16),
+and up to 1.1e-2 for the Ultra Q8_0 file (quantisation), and the speech
+fractions agree (`res_A.md`). On the 12 stretch files checked, the CLI and the reference
+agree on the false-alarm rate to within 0.5 points (`res_cli_check.txt`). The one real
+difference is digital silence (see Proposed behaviour).
+
+#### Why
+
+The head judges each 80 ms frame by its level and texture relative to the average of the
+file it is in. Steady loud noise, or a file that is only noise, therefore sits at a logit
+of about +1.5 to +2 (probability 0.8 to 0.9). Speech sits at +5 to +12 and pauses at -3
+to -8:
+
+| Logit (median, 5th to 95th percentile) | Redux | Ultra |
+| --- | ---: | ---: |
+| Speech in clean clips | +8.0 (-3.1 to +12.7) | +7.0 (-2.7 to +13.2) |
+| Pauses in clean clips (digital -60 dBFS dither) | -8.1 (-11.6 to -5.5) | -3.2 (-5.0 to -1.8) |
+| Noise-only file at -23 dBFS | +1.5 (+0.2 to +3.0) | +1.7 (+0.5 to +3.1) |
+| Noise-only file at -50 dBFS | +0.5 (-0.6 to +1.8) | +1.3 (+0.1 to +2.6) |
+
+![Logit histograms](../scripts/vad_bench/noise_dive/fig_logit_hist.png)
+
+What the study found about the cause:
+
+- The per-file normalisation of the features is one input, not the whole cause. With a
+  fixed normalisation (mean and standard deviation taken from the speech clips), noise-only
+  files at -60 and -50 dBFS stop firing (Redux 0.0 percent, Ultra 0.0 to 49 percent
+  depending on the statistics), but at -30 and -23 dBFS they still do (Redux 78 and 97
+  percent with one set of statistics). A noise stretch inside a speech file does not go away
+  either (`res_B3.txt`).
+- Level alone does not explain the logit on noise. A held-out regression of the logit on
+  six frame features (level, absolute log level, flux, flatness, 2 to 8 Hz modulation,
+  tilt) gives R2 of 0.01 to 0.03 on white, pink and speech-shaped noise (about 0.2 on
+  brown noise) and 0.74 to 0.76 on speech (`res_B4.txt`). Inside a speech file the logit
+  follows the mean level of the stretch relative to the file (correlation 0.80 for Redux
+  and 0.84 for Ultra with the median logit, `res_B8.txt`) and its temporal variation
+  (`res_B10.txt`).
+- The share of noise in the file is not monotonic. For white noise at -15 dB re the talk,
+  Ultra calls 100 percent of a 5 s stretch speech, 23 percent of a 60 s stretch and 3
+  percent of a 90 s stretch (`res_B7.md`).
+- Amplitude invariance does not hold. Above about -40 dBFS RMS the output is flat.
+  Below it the output falls off (white noise alone in a file: Redux 94 percent speech
+  frames at -40 dBFS, 22 at -60, 0 at -80; Ultra 99, 85, 7; `res_B1.txt`), because of the
+  2^-24 guard inside the log of the mel features. That attribution is from the study
+  notes; no result file isolates the guard.
+- Moondream's cards say the head exists so that recordings can be cut at pauses into
+  segments of at most 30 s. What it was trained on is not stated. Nothing in the cards
+  claims robustness to noise or music. Moondream's own runtime was not available, so the
+  head wiring is inferred from the documentation and is consistent with the behaviour
+  (a residual variant of the context block breaks Ultra: clean speech F1 81.0 instead of
+  95.0, `res_A2_variants.txt`).
+
+#### What the head does with different noise
+
+Each signal is the only content of a 30 s file (375 frames), share of frames called
+speech at threshold 0.5 (`res_B2.md`):
+
+| Signal | Redux | Ultra |
+| --- | ---: | ---: |
+| Digital silence | 98.7% | 99.7% |
+| Constant tone (1 kHz, -20 dBFS) | 0.0% | 98.9% |
+| Sine sweep 50 to 7500 Hz | 5.9% | 45.1% |
+| Clicks, 3 per second | 3.5% | 46.9% |
+| Clicks, 10 per second | 33.1% | 94.4% |
+| Hum 50 Hz and harmonics, -25 dBFS | 0.0% | 0.0% |
+| Music-like chords, -20 dBFS | 23.5% | 54.9% |
+| Brown noise, -25 dBFS | 28.3% | 19.2% |
+| White noise, -25 dBFS | 98.7% | 99.2% |
+| Pink noise, -25 dBFS | 97.3% | 98.4% |
+| Speech (LibriSpeech, for comparison) | 91.5% | 88.5% |
+
+Redux and Ultra differ by stimulus, and neither is simply worse. Redux reacts to the
+texture of flat-spectrum noise (white, pink, speech-shaped) and reacts much less to
+tones, hum, sparse clicks and music. Ultra reacts to level, and also to clicks, sweeps
+and music, but it stops firing on white and pink noise sooner once the noise is below
+the speech. A pure tone fires Ultra only when it is alone in a file (98.9 percent above);
+inside a speech file it gave 0 percent (next table).
+
+#### How far below the speech the noise must be
+
+In a speech file, with a 30 s stretch of a given noise type, share of stretch frames
+called speech by the level of the stretch below the speech (6 clips per cell,
+`res_B6.txt`):
+
+| Stretch type | Redux 10 dB | Redux 20 dB | Redux 30 dB | Ultra 10 dB | Ultra 20 dB | Ultra 30 dB |
+| --- | ---: | ---: | ---: | ---: | ---: | ---: |
+| White | 79 | 53 | 0 | 0 | 0 | 0 |
+| Pink | 63 | 33 | 0 | 0 | 0 | 0 |
+| Speech-shaped | 48 | 43 | 1 | 46 | 0 | 0 |
+| Clicks, 10 per second | 16 | 9 | 0 | 64 | 22 | 1 |
+| Clicks, 3 per second | 1 | 1 | 0 | 26 | 9 | 0 |
+| Music-like | 16 | 10 | 1 | 44 | 8 | 0 |
+| Brown noise, 50 Hz hum, 1 kHz sine | 0 | 0 | 0 | 0 | 0 | 0 |
+
+![Stretch false alarms against level](../scripts/vad_bench/noise_dive/fig_types_vs_level.png)
+
+- Brown noise, hum and a pure tone never trigger the head inside a speech file, for
+  either model.
+- Redux needs the white or pink stretch to be 30 dB below the speech before it stops
+  (it is still 33 to 53 percent at 20 dB). Ultra stops at 10 dB for white and pink, 20 dB
+  for speech-shaped noise, and 30 dB for clicks and music.
+- Alone in a file, white noise stops being called speech below about -55 dBFS for Redux
+  and about -70 dBFS for Ultra (`res_B1.txt`; the grid has 10 dB steps: Redux 22 percent
+  at -60 and 8 at -70, Ultra 42 at -70 and 7 at -80).
+
+This corrects the earlier statement that false alarms grow with the noise level. That is
+true for Ultra (stretch in a speech file, white and pink pooled: 0 percent at 20 dB SNR, 14
+at 5 dB, 56 at 0 dB). It is not true for Redux, which is already at 35 percent at 20 dB
+and stays at 59 to 65 percent from 10 dB to 0 dB. And for both models the false-alarm
+rate is not monotonic in the share of the file that is noise (`res_B7.md`).
+
+#### The user-visible cost in `transcribe --vad`
+
+A 60 s block of inserted audio (white, pink, clicks, music; at -35, -20 and -5 dB
+relative to the RMS of the talk) was put at 111.7 to 171.7 s of a 300 s excerpt of a real
+talk. Words that start inside the block are hallucinations, because the block has no
+speech (`res_D.md`, 24 runs per model):
+
+| Case | Hallucinated words in the block |
+| --- | ---: |
+| Without VAD, all 24 runs (12 per model) | 0 |
+| `--vad`, Redux | 0 in 11 files, 2 for white noise at -20 dB |
+| `--vad`, Ultra | 0 in 9 files, 15, 12 and 3 for music at -35, -20 and -5 dB |
+| `--vad`, Silero (Ultra decoder, music at -35 dB, the only Silero file transcribed) | 20 |
+
+Example text: "I don't know, I don't know, I don't know" for Ultra on music at -35 dB. The
+Silero case kept an 18 s segment of music because its 30 s segment ended 18 s into the
+block (`res_D_silero.txt`).
+
+Per-frame head calls inside the inserted block (`res_D_probs.md`): Redux at most 12
+percent in any file; Ultra 68 percent for white at -5 dB and 38 percent for music and
+clicks at -5 dB. At -35 dB the head called 0 percent of the block speech in 5 of the 8 files.
+Even so, the segments the head produced covered 24 to 60 s of the 60 s block (Redux 24
+to 60 s, Ultra 54 to 60 s) and Silero about 18 s.
+
+The cause is the segmenter, not the head. With a 60 s block the head's segments still
+covered 54 to 60 s of it in nearly every file, even where the head called 0 percent of the
+block speech (Ultra white and music at -35 dB). A hard 30 s cut can land just before
+speech resumes, so a piece with a little speech at its edge is kept and decoded. On the
+two Silero files checked (white at -20 dB, music at -35 dB) Silero dropped about 42 s of the
+block. This segmenter behaviour deserves its own look. It is not fixed.
+
+So the user-visible cost is small: wasted decoding of segments that are noise, and rarely
+a hallucinated phrase. With `--vad` the talk words outside the block differed from the
+run without VAD by at most 6 words in about 814 (`res_D.md`). The head does well on real speech: on a 1168 s talk it called speech 97.0
+percent (Redux) and 96.3 percent (Ultra) of the time, where Silero called 93.4 percent,
+and the head and Silero agree with an F1 of 98.0 to 98.3 (`res_E2_talk.txt`). On
+synthetic clips with pauses its frame F1 is 95.4 (Redux) and 95.0 (Ultra), and 94.7 and
+93.6 with added white and pink noise (`res_E.md`).
+
+#### Mitigations we tested
+
+Offline, on a Python reimplementation of the segmenter on a 10 ms grid, with the speech
+clips and noise of the fusion corpus (`res_E.md`). F1 is the frame F1 on speech; the
+noisy set is white and pink noise at 20 to 0 dB. False alarms are the share of frames
+called speech in noise-only files and in a 30 s stretch embedded in speech. Lower is
+better for false alarms.
+
+| Rule | Redux F1 clean / noisy | Redux false alarms, noise-only / stretch | Ultra F1 clean / noisy | Ultra false alarms, noise-only / stretch |
+| --- | --- | --- | --- | --- |
+| Head, threshold 0.5 (default) | 95.4 / 94.7 | 97.8 / 55.2 | 95.0 / 93.6 | 99.4 / 17.3 |
+| Head, threshold 0.7 | 94.8 / 93.9 | 76.5 / 10.1 | 94.2 / 91.8 | 94.2 / 3.6 |
+| Head, threshold 0.9 | 93.9 / 92.0 | 16.3 / 0.1 | 92.4 / 87.5 | 25.8 / 0.0 |
+| Head, threshold 0.97 | 92.5 / 89.4 | 1.0 / 0.0 | 90.0 / 80.6 | 0.5 / 0.0 |
+| Head 0.5, minimum speech 1.0 s | 94.6 / 94.1 | 96.6 / 29.0 | 93.8 / 91.9 | 99.4 / 6.9 |
+| Head 0.5, energy gate (frame at least 25 dB below the file P95) | 92.1 / 94.3 | 97.8 / 53.2 | 92.0 / 93.3 | 99.4 / 17.3 |
+| Head 0.5, keep a run only if its median logit is at least 2.5 (p about 0.92) | 95.4 / 94.7 | 0.1 / 0.3 | 94.5 / 90.3 | 0.0 / 0.0 |
+| Silero 0.5 AND head 0.5 | 93.4 / 91.8 | 0.0 / 0.0 | 93.3 / 91.2 | 0.0 / 0.0 |
+| Two-stage (Silero decides, head moves the edges) | 95.4 / 94.8 | 0.0 / 0.0 | 95.1 / 94.2 | 0.0 / 0.0 |
+| Silero 0.5 alone, for reference | 93.9 / 92.4 | 0.0 / 0.0 | 93.9 / 92.4 | 0.0 / 0.0 |
+
+In plain language:
+
+- Raising the threshold trades recall for fewer false alarms and still leaves noise-only
+  files firing until about 0.97, where speech F1 has fallen by 3 to 5 points (clean) and
+  up to 13 points (Ultra, noisy). At 0.9 most false alarms in a speech file go away, at a
+  cost of 1.5 to 2.6 F1 points on clean speech.
+- The energy gate and a longer minimum speech do not help: the gate removes speech
+  (clean F1 86.1 to 94.6, depending on the gate) and leaves noise-only files at 97.8 and 99.4 percent. A minimum speech of
+  1.0 s helps only for the embedded stretch (Ultra 17.3 to 6.9) and costs about 0.8 to 1.2
+  clean F1 points.
+- The median-logit run gate is the best cheap option. It drops almost all false alarms
+  and costs nothing for Redux on clean speech (95.4) and 0.5 F1 points for Ultra (3.3 on the
+  noisy set). With the run threshold at 3.5 (not in the table) Redux clean F1 is 94.9 and
+  Ultra 93.6. It is not validated on WER and is not
+  implemented. On the 1168 s talk it removed 0.2 to 0.3 percent of the head's speech
+  frames at 2.5 (`res_E2_talk.txt`). In the inserted block test at most 18 percent of the
+  block frames remained, and 0 in 21 of 24 files (`res_D_mit.md`).
+- The two-stage rule needs Silero. It removes the false alarms and keeps the F1 of the head.
+  See [the fusion experiment](#fusing-silero-and-the-head-offline-experiment).
+
+#### Proposed behaviour
+
+The defaults stay as they are. One candidate fix, not in this PR: a guard for digital
+silence or a constant tone. Our CLI calls 98.7 percent (Redux) and 99.7 percent (Ultra)
+of the frames of a silent file speech, because it accumulates the normalisation in
+double and gets exactly zero features. The reference with the same double accumulation
+gives a speech probability of 0.76 to 0.86 per frame on the same file (`res_B2.md`
+column "mean p"). The Hugging Face float32 normalisation leaves rounding noise instead and
+gives a probability of about 0; that float32 run is not among the committed result
+files.
+
+#### Limits of this study
+
+- Moondream's runtime was not available. The head wiring is inferred from the
+  documentation and is consistent with the behaviour, but not checked against it.
+- The noise is synthetic: LibriSpeech with generated white, pink, brown, speech-shaped,
+  click, hum, tone and music-like signals, plus one real talk.
+- The mitigation numbers come from a Python reimplementation of the segmenter on a 10 ms
+  grid, not from the C++ code.
+- The hallucination counts come from one talk and one insertion point. Only one Silero
+  file was transcribed.
+- The black-box check of the head against the released runtime was skipped (it needs
+  CUDA or Metal).
 
 ## Fusing Silero and the head (offline experiment)
 
@@ -724,7 +934,7 @@ This is limited to what the numbers above support.
   one thread on a shared machine. It gave frame level F1 of 91.7 to 94.4 on the
   synthetic data and 91.8 to 92.1 on the TED talks.
 - **Always-on gate, or audio with long stretches without speech.** Silero. The heads give
-  false alarms on noise-only audio (see
+  false alarms on noise-only audio, and the effect depends on the noise type and level (see
   [the head on noise-only audio](#the-head-on-noise-only-audio)); Silero gave none.
 - **The Parakeet head when Ultra or Redux is already loaded, on audio that is mostly
   speech** (for example recorded talks before transcription). The head costs no extra
@@ -751,7 +961,7 @@ This is limited to what the numbers above support.
   detector and no false alarms on noise. It is not implemented in parakeet.cpp. See
   [the fusion experiment](#fusing-silero-and-the-head-offline-experiment).
 - **Not supported by these numbers:** any claim about GPUs, ARM, other languages,
-  music or non speech noise, streaming latency, or the effect of the VAD on WER.
+  music or non speech noise beyond the synthetic signals of [the noise study](#root-cause-study), streaming latency, or the effect of the VAD on WER.
 
 ## How to reproduce
 
@@ -766,6 +976,7 @@ are committed. In short:
 4. Silero parity: `make_clips.py`, build `wvad.cpp` against whisper.cpp,
    `silero_collect.py`, `silero_analyze.py`; speed with `speed_silero.py`.
 5. Long talks: `longform_b1.sh`.
+6. Noise root-cause study: [`noise_dive/`](../scripts/vad_bench/noise_dive/README.md).
 
 Small result files of the runs on this page (tables, per run timings and load logs)
 are in `scripts/vad_bench/results/`. The raw per clip predictions are not committed;
diff --git a/docs/vad.md b/docs/vad.md
index 85367f9..5388a08 100644
--- a/docs/vad.md
+++ b/docs/vad.md
@@ -19,16 +19,35 @@ repeat them, are in [vad-benchmarks.md](vad-benchmarks.md).
 
 The Parakeet VAD head alone gives false alarms on audio without speech. On a speech-free
 file the head calls about 99 percent of the frames speech (Ultra 99.4 percent, Redux 97.8
-percent), because the features are normalised per file and pure noise is rescaled until it
-looks like speech. Inside a file that has speech, over a 30 s stretch of noise at
-threshold 0.5, the false-alarm frame rate was 17.7 percent for the Ultra head and 55
-percent for the Redux head, and it grows with the noise level. Silero showed 0 percent in
-both tests. This was measured on synthetic data: LibriSpeech with added white and pink
-noise, see [the experiment](vad-benchmarks.md#the-head-on-noise-only-audio).
-
-So prefer Silero as an always-on gate, or whenever the audio can contain long stretches
-without speech. Use the head on audio that is known to be mostly speech (for example
-before the transcription of recorded talks), or where its higher recall matters.
+percent). Inside a file that has speech, over a 30 s stretch of noise at threshold 0.5,
+the false-alarm frame rate was 17.7 percent for the Ultra head and 55 percent for the
+Redux head. Silero showed 0 percent in both tests. This was measured on synthetic data:
+LibriSpeech with added white and pink noise, see
+[the experiment](vad-benchmarks.md#the-head-on-noise-only-audio).
+
+This is not a bug in parakeet.cpp. An independent reference built from Hugging Face
+transformers and the documented head structure matches `parakeet-cli vad --probabilities`
+to a maximum probability difference of 2e-4 on F16 files. It is a property of the model.
+The head judges each 80 ms frame by its level and texture relative to the average of the
+file it is in. Steady loud noise, or a file that is only noise, sits at a logit of about
++1.5 to +2 (probability 0.8 to 0.9). Speech sits at +5 to +12 and pauses at -3 to -8. The
+cards of the model say the head exists so that recordings can be cut at pauses into
+segments of at most 30 s. What it was trained on is not stated. It is not a noise or
+music rejector.
+
+So use Silero, or Silero first with the head extending the boundaries, whenever long
+stretches without speech are possible (always-on gates, calls, recordings with music or
+noise). Use the head alone on audio that is known to be mostly speech (for example
+before the transcription of recorded talks), or where its higher recall matters. If you
+use the head alone and want fewer false alarms, raising the threshold to 0.9 removes most
+noise false alarms at a cost of 1.5 to 2.6 F1 points on speech. Details, the noise types
+that do and do not trigger it, and the mitigations we tested are in
+[the benchmark page](vad-benchmarks.md#the-head-on-noise-only-audio).
+
+For `transcribe --vad` the user-visible cost is small: some wasted decoding, and rarely a
+hallucinated phrase. The 30 s hard cut of the segmenter can also keep a large part of a
+long noise gap, even where the head called none of it speech. This is a segmenter
+behaviour, not a head false alarm, and it has not been fixed.
 
 An offline experiment also combined the two: Silero decides what is speech, and the head
 only moves the edges. It scored 0.75 to 1.13 F1 points above the best single detector on
diff --git a/scripts/vad_bench/noise_dive/README.md b/scripts/vad_bench/noise_dive/README.md
new file mode 100644
index 0000000..d2fe3ce
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/README.md
@@ -0,0 +1,30 @@
+# Why the Parakeet VAD head fires on noise
+
+Scripts and small results of the root-cause study described in
+[../../../docs/vad-benchmarks.md](../../../docs/vad-benchmarks.md#the-head-on-noise-only-audio).
+No audio, model, `.npz` or `.pkl` file is committed. A re-run regenerates them.
+
+## Inputs (set as environment variables)
+
+| Variable | Meaning |
+| --- | --- |
+| `FUSION_DIR` | `scripts/vad_bench/fusion` with `data/libri.npy` and `data/spk.npy` built by `prep_libri.py` (default `../fusion`) |
+| `NOISE_DIVE_WORK` | working directory with `ref/` (reference weights as `_weights.npz`), `hfmeta/` (`.config.json`) and the CLI build |
+| `PARAKEET_CLI` | path to `parakeet-cli` |
+| `GGUF_ROOT` | directory with `gguf/` and `vad-card/` GGUF files |
+| `SILERO_ONNX` | path to `silero_vad.onnx` (used by `e_build.py`, `e2_talk.py`) |
+
+Run the scripts from this directory. Python packages: numpy, torch, soundfile,
+transformers (with the Parakeet classes), onnxruntime, matplotlib.
+
+## Files
+
+- `vadlib.py`: independent reference of the head path built on Hugging Face transformers.
+  `sig.py` and `corpus.py`: signal generators and the speech clips.
+- `a_faith.py`, `cli_check.py`, `a2_variants.py`: the reference against the CLI; head variants (`res_A*`, `res_cli_check.txt`).
+- `b1` to `b10`: amplitude, signal types, normalisation, regression, level and share of noise (`res_B*`).
+- `d_prep.py`, `d_run.py`, `d_analyze.py`, `d_probs.py`, `d_mit.py`: the 60 s block in a real talk with `transcribe --vad` (`res_D*`).
+  `res_D_silero.txt` holds the Silero segment check, done by hand from its segment file.
+- `e_build.py`, `e_eval.py`, `e2_talk.py`: the mitigations (`res_E*`).
+- `fig_*.png`: four figures (logit histograms, false alarms against level, level and variation of the stretch, offset and scale probe).
+- The `res_*.md` and `res_*.txt` files are the results quoted in the docs.
diff --git a/scripts/vad_bench/noise_dive/a2_variants.py b/scripts/vad_bench/noise_dive/a2_variants.py
new file mode 100644
index 0000000..1f2fe04
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/a2_variants.py
@@ -0,0 +1,28 @@
+"""Does an alternative head wiring (the documented debug variants) change the noise behaviour? (rules out 'wrong activation/residual' as the cause)"""
+import numpy as np,torch,json
+from vadlib import *; from sig import *; import corpus
+F=torch.nn.functional
+def head(r,s,act_proj,res,act_ctx):
+    a=lambda k,x: F.silu(x) if k=='silu' else F.relu(x)
+    h=a(act_proj,s@r.pw.T+r.pb); c=F.conv1d(h.T[None],r.cw,r.cb,padding=2)[0].T
+    if res: c=c+h
+    return (a(act_ctx,c)@r.ow+r.ob).numpy()
+V=[('silu','silu',False),('relu','relu',False),('silu','silu',True),('relu','relu',True)]
+cl=corpus.clean_clips()[:20]; gaps=[g for g in corpus.gap_clips() if g['snr'] in(5,0)][:12:3]+[g for g in corpus.gap_clips() if g['snr'] in(5,0)][48:60:3]
+rng=np.random.default_rng(5); N=30*SR
+nz=[at_db(white(N,rng),-23),at_db(colored(N,rng,1),-23),at_db(white(N,rng),-40),at_db(colored(N,rng,1),-40)]
+for m in ['redux','ultra']:
+    r=ref(m); print(f'\n{m}: variant (proj act, ctx act, ctx residual) | clean speech F1 % | speech frames in noise-only -23 dBFS % | -40 dBFS % | embedded 5/0 dB stretch FP %')
+    sub_cl=[(r.sub_out(norm_mel(raw_logmel(y))),sp) for y,sp in cl]
+    sub_nz=[r.sub_out(norm_mel(raw_logmel(x))) for x in nz]
+    sub_g=[(r.sub_out(norm_mel(raw_logmel(g['y']))),g) for g in gaps]
+    for ap,ac,res in V:
+        c=np.zeros(3)
+        for s,sp in sub_cl:
+            z=head(r,s,ap,res,ac); p=z>=0; ref_m=frame_mask(sp,len(p)); c+=[(p&ref_m).sum(),(p&~ref_m).sum(),(~p&ref_m).sum()]
+        P=c[0]/(c[0]+c[1]); R=c[0]/(c[0]+c[2])
+        n23=np.mean([(head(r,s,ap,res,ac)>=0).mean() for s in sub_nz[:2]]); n40=np.mean([(head(r,s,ap,res,ac)>=0).mean() for s in sub_nz[2:]])
+        fps=[]
+        for s,g in sub_g:
+            z=head(r,s,ap,res,ac); t=(np.arange(len(z))+.5)*.08; fps.append((z[(t>=g['stretch'][0])&(t=0).mean())
+        print(f'  ({ap},{ac},res={res}) | F1 {100*2*P*R/(P+R):.1f} | {100*n23:.0f} | {100*n40:.0f} | {100*np.mean(fps):.0f}',flush=True)
diff --git a/scripts/vad_bench/noise_dive/a_faith.py b/scripts/vad_bench/noise_dive/a_faith.py
new file mode 100644
index 0000000..398303f
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/a_faith.py
@@ -0,0 +1,23 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+import sys,numpy as np,json
+from vadlib import *; from sig import *
+os.makedirs('wav',exist_ok=True)
+lib=list(np.load(FUSION+'/data/libri.npy',allow_pickle=True)); r=np.random.default_rng(0)
+N=30*SR
+sp=np.concatenate(lib[10:16]).astype(np.float32)[:N]
+sp2=np.concatenate(lib[40:46]).astype(np.float32)[:N]
+spec=ltass(lib[:60])
+sigs={'speech_a':sp,'speech_b':sp2,'white':at_db(white(N,r),-25),'pink':at_db(colored(N,r,1),-25),'brown':at_db(colored(N,r,2),-25),
+ 'speechshaped':at_db(shaped(N,r,spec),-25),'silence':np.zeros(N,np.float32),'dither_1e-4':(r.standard_normal(N)*1e-4).astype(np.float32)}
+rows=[]
+for k,x in sigs.items():
+    wr(f'wav/{k}.wav',x)
+    for m,g in [('redux','redux'),('ultra','ultra'),('ultra','ultra-f16')]:
+        p=ref(m).probs(sf.read(f'wav/{k}.wav',dtype='float32')[0]); c=cli_probs(g,f'wav/{k}.wav'); n=min(len(p),len(c)); e=np.abs(p[:n]-c[:n])
+        rows.append(dict(sig=k,model=g,n=n,maxdiff=float(e.max()),meandiff=float(e.mean()),ref_speech_frac=float((p>=.5).mean()),cli_speech_frac=float((c>=.5).mean()),ref_mean_p=float(p.mean())))
+        pass
+json.dump(rows,open('res_A.json','w'),indent=1)
+
+print('| signal | model file | frames | max abs diff | mean abs diff | speech frac ref | speech frac CLI |\n|---|---|---|---|---|---|---|')
+for r in rows: print(f"| {r['sig']} | {r['model']} | {r['n']} | {r['maxdiff']:.2e} | {r['meandiff']:.2e} | {100*r['ref_speech_frac']:.1f}% | {100*r['cli_speech_frac']:.1f}% |")
diff --git a/scripts/vad_bench/noise_dive/b10_DS.py b/scripts/vad_bench/noise_dive/b10_DS.py
new file mode 100644
index 0000000..f66e547
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b10_DS.py
@@ -0,0 +1,34 @@
+"""Two-variable predictor of the head's response on a noise stretch: D (mean normalised level of the stretch) and S (temporal std of the
+normalised log-mel inside the stretch, mean over bins = fluctuation scale s of the feature-space probe). Pool talk+inserted-noise clips (b8) and the fusion gap clips."""
+import numpy as np,soundfile as sf,json
+from vadlib import *; from sig import *; import corpus
+from sklearn.linear_model import LinearRegression
+import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
+pts=[]
+def add(y,a,b,src,kind):
+    mn=norm_mel(raw_logmel(y)).numpy(); seg=mn[int((a+1)*100):int((b-1)*100)]; D=float(seg.mean()); S=float(seg.std(0).mean())
+    for m in ['redux','ultra']:
+        z=ref(m).logits(y); t=(np.arange(len(z))+.5)*.08; st=(t>=a+1)&(t=0).mean()),med=float(np.median(z[st]))))
+for g in corpus.gap_clips():
+    if g['k']<6: add(g['y'],g['stretch'][0],g['stretch'][1],'gap',g['kind'])
+x=sf.read('talk/JaneMcGonigal-merged.wav',dtype='float32')[0][60*SR:300*SR]; rdb=20*np.log10(rms(x)); r=np.random.default_rng(12)
+for kind in ['white','pink']:
+    for rel in [-35,-25,-15,-5]:
+        for L in [10,30,60]:
+            s0=int(r.integers(0,100))*SR; sp=120-L; a=sp//2
+            n=at_db(white(L*SR,r) if kind=='white' else colored(L*SR,r,1),rdb+rel)
+            y=np.concatenate([x[s0:s0+a*SR],n,x[s0+a*SR:s0+sp*SR]]).astype(np.float32); add(y,a,a+L,'talk+insert',kind)
+json.dump(pts,open('res_B10.json','w'))
+for m in ['redux','ultra']:
+    P=[p for p in pts if p['m']==m]; X=np.array([[p['D'],p['S']] for p in P]); M=np.clip(np.array([p['med'] for p in P]),-10,10)
+    for nm,cols in [('D only',[0]),('S only',[1]),('D and S',[0,1])]:
+        lr=LinearRegression().fit(X[:,cols],M); print(m,nm,'R2 of median logit: %.2f'%lr.score(X[:,cols],M),'coef',lr.coef_.round(2))
+    for src in ['gap','talk+insert']:
+        Q=[p for p in P if p['src']==src]; print(f'   {src:12s} n={len(Q)} mean D {np.mean([p["D"] for p in Q]):+.2f} mean S {np.mean([p["S"] for p in Q]):.2f} FA {100*np.mean([p["fa"] for p in Q]):.0f}% median logit {np.mean([p["med"] for p in Q]):+.2f}')
+fig,axs=plt.subplots(1,2,figsize=(12,4.2),sharey=True)
+for ax,m in zip(axs,['redux','ultra']):
+    for src,c in [('gap','C3'),('talk+insert','C0')]:
+        Q=[p for p in pts if p['m']==m and p['src']==src]; sc=ax.scatter([p['D'] for p in Q],[p['S'] for p in Q],c=[p['med'] for p in Q],cmap='coolwarm',vmin=-6,vmax=3,marker='o' if src=='gap' else 's',s=30,edgecolors='k',linewidths=.4,label=src)
+    ax.set_xlabel('D: mean normalised level of the stretch'); ax.set_title(f'{m}: colour = median logit in the stretch'); ax.legend(fontsize=8)
+axs[0].set_ylabel('S: temporal std of normalised features in the stretch'); plt.colorbar(sc,ax=axs.ravel().tolist()); plt.savefig('fig_D_S.png',dpi=110,bbox_inches='tight')
diff --git a/scripts/vad_bench/noise_dive/b1_amp.py b/scripts/vad_bench/noise_dive/b1_amp.py
new file mode 100644
index 0000000..1669ea8
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b1_amp.py
@@ -0,0 +1,35 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+import numpy as np,json
+from vadlib import *; from sig import *
+N=30*SR; r=np.random.default_rng(1)
+lib=list(np.load(FUSION+'/data/libri.npy',allow_pickle=True)); spec=ltass(lib[:60])
+base={'white':white(N,r),'pink':colored(N,r,1),'brown':colored(N,r,2),'speechshaped':shaped(N,r,spec)}
+sp=np.concatenate(lib[10:16]).astype(np.float32)[:N]
+base['speech']=sp
+levels=list(range(-110,1,10))  # dBFS rms
+res={}
+print('| signal | '+' | '.join(f'{l}' for l in levels)+' |')
+for k,b in base.items():
+    row=[]
+    for l in levels:
+        x=at_db(b,l); x=np.clip(x,-1,1)   # float samples; clip like a wav would
+        row.append({m:dict(sp=float((ref(m).probs(x)>=.5).mean()),mp=float(ref(m).probs(x).mean())) for m in ['redux','ultra']})
+    res[k]=row
+    for m in ['redux','ultra']:
+        print(f'| {k} {m} | '+' | '.join(f'{100*c[m]["sp"]:.0f}' for c in row)+' |',flush=True)
+# exact invariance test: pure gain on top of fixed noise (float arithmetic, no clipping), -80..0 dB, and x1000
+b=at_db(base['white'],-20)
+print('\nPure gain on white@-20dBFS (speech-frame %, max |logit change| vs 0 dB gain)')
+ref0={m:ref(m).logits(b) for m in['redux','ultra']}
+g={}
+for gdb in [-80,-70,-60,-50,-40,-30,-20,-10,0]:
+    x=(b*10**(gdb/20)).astype(np.float32)
+    for m in ['redux','ultra']:
+        z=ref(m).logits(x); g[(gdb,m)]=(float((z>=0).mean()),float(np.abs(z-ref0[m]).max()),float(np.abs(z-ref0[m]).mean()))
+    print(gdb,{m:tuple(round(v,3) for v in g[(gdb,m)]) for m in['redux','ultra']})
+x=(at_db(base['white'],-80)*1000).astype(np.float32)   # 1000x
+b2=at_db(base['white'],-80)
+for m in ['redux','ultra']:
+    z=ref(m).logits(x); z0=ref(m).logits(b2); print('x1000 of -80dBFS noise',m,'speech%',100*(z>=0).mean(),'(base %',100*(z0>=0).mean(),') max|dz| to base',np.abs(z-z0).max())
+json.dump(res,open('res_B1.json','w'))
diff --git a/scripts/vad_bench/noise_dive/b2_signals.py b/scripts/vad_bench/noise_dive/b2_signals.py
new file mode 100644
index 0000000..95fbba2
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b2_signals.py
@@ -0,0 +1,31 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+import numpy as np,json
+from vadlib import *; from sig import *
+N=30*SR; r=np.random.default_rng(3)
+lib=list(np.load(FUSION+'/data/libri.npy',allow_pickle=True))
+S={}
+S['digital silence']=np.zeros(N,np.float32)
+S['DC offset 0.1']=np.full(N,0.1,np.float32)
+for f in [100,440,1000,3000]: S[f'sine {f} Hz -20 dBFS']=sine(N,f)
+S['sine 1 kHz -60 dBFS']=sine(N,1000,-60)
+S['sine sweep 50-7500 Hz (30 s)']=sweep(N)
+S['sine sweep -50 dBFS']=sweep(N,db=-50)
+S['clicks 3/s -12 dBFS']=clicks(N,r,3,-12)
+S['clicks 10/s -12 dBFS']=clicks(N,r,10,-12)
+S['clicks 1/s -12 dBFS']=clicks(N,r,1,-12)
+S['hum 50 Hz+harmonics -25 dBFS']=hum(N,50,-25)
+S['hum 60 Hz+harmonics -25 dBFS']=hum(N,60,-25)
+S['hum 50 Hz + white -60 dBFS']=hum(N,50,-25)+at_db(white(N,r),-60)
+S['music-like chords -20 dBFS']=music(N,r)
+S['white -25 dBFS']=at_db(white(N,r),-25); S['pink -25 dBFS']=at_db(colored(N,r,1),-25); S['brown -25 dBFS']=at_db(colored(N,r,2),-25)
+S['white -70 dBFS']=at_db(white(N,r),-70)
+S['speech (LibriSpeech x6)']=np.concatenate(lib[10:16]).astype(np.float32)[:N]
+rows=[]; Z={}
+print('| signal | Redux speech frames % | Redux mean p | Ultra speech frames % | Ultra mean p |\n|---|---|---|---|---|')
+for k,x in S.items():
+    cell=[]
+    for m in ['redux','ultra']:
+        z=ref(m).logits(x.astype(np.float32)); Z[f'{k}|{m}']=z; p=sig(z); cell+= [f'{100*(p>=.5).mean():.1f}',f'{p.mean():.2f}']
+    print(f'| {k} | '+' | '.join(cell)+' |',flush=True)
+np.savez('res_B2_logits.npz',**{k.replace('|','@'):v for k,v in Z.items()})
diff --git a/scripts/vad_bench/noise_dive/b3_norm.py b/scripts/vad_bench/noise_dive/b3_norm.py
new file mode 100644
index 0000000..20ea098
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b3_norm.py
@@ -0,0 +1,56 @@
+import numpy as np,json,torch
+from vadlib import *; from sig import *; import corpus
+S=np.load('fixed_stats.npz'); T=lambda a: torch.tensor(a)
+variants={'file':(None,None),'fixedA':(T(S['aM']),T(S['aS'])),'fixedB':(T(S['bM']),T(S['bS']))}
+models=['redux','ultra']
+cl=corpus.clean_clips(); gaps=corpus.gap_clips()
+R={}
+def f1(p,r):
+    n=min(len(p),len(r)); p=p[:n]; r=r[:n]; tp=(p&r).sum(); fp=(p&~r).sum(); fn=(~p&r).sum()
+    P=tp/max(1,tp+fp); Rc=tp/max(1,tp+fn); return P,Rc,2*P*Rc/max(1e-9,P+Rc)
+# 1. clean speech clips
+for v,(mu,sd) in variants.items():
+    for m in models:
+        c=np.zeros(3)
+        for y,spans in cl:
+            z=logits_blocks(m,y,mu,sd) if mu is None else ref(m).logits(y,mu,sd)
+            p=z>=0; r=frame_mask(spans,len(p)); n=len(p)
+            c+=[(p&r).sum(),(p&~r).sum(),(~p&r).sum()]
+        P=c[0]/(c[0]+c[1]); Rc=c[0]/(c[0]+c[2]); R[f'clean|{v}|{m}']=dict(P=P,R=Rc,F1=2*P*Rc/(P+Rc))
+        print('clean',v,m,R[f'clean|{v}|{m}'],flush=True)
+# 2. noise-only 30 s at absolute levels
+rng=np.random.default_rng(5); N=30*SR
+for kind in ['white','pink']:
+    for lvl in [-60,-50,-40,-30,-23,-15]:
+        fr={v:{m:[] for m in models} for v in variants}
+        for k in range(6):
+            b=at_db(white(N,rng) if kind=='white' else colored(N,rng,1),lvl)
+            for v,(mu,sd) in variants.items():
+                for m in models: fr[v][m].append((ref(m).logits(b,mu,sd)>=0).mean())
+        for v in variants:
+            for m in models: R[f'nz|{kind}|{lvl}|{v}|{m}']=float(np.mean(fr[v][m]))
+        print('noise-only',kind,lvl,{v:{m:round(100*np.mean(fr[v][m]),1) for m in models} for v in variants},flush=True)
+# 3. embedded stretch
+agg={}
+for g in gaps:
+    y=g['y']; ref_m=None
+    for v,(mu,sd) in variants.items():
+        for m in models:
+            z=logits_blocks(m,y,mu,sd) if mu is None else ref(m).logits(y,mu,sd); p=z>=0
+            n=len(p); t=(np.arange(n)+.5)*.08; st=(t>=g['stretch'][0])&(t=0).mean()),r2_lin6=r2te,r2_gbm6=gbr2,r2_spec128=rdr2,r2_energy_only=e1,corr=corr,coef=lr.coef_.tolist(),logit_mean=float(Z.mean()),logit_std=float(Z.std()))
+        print(f'{m:6s} {kn:22s} speech {100*(Z>=0).mean():5.1f}%  logit mean {Z.mean():+.2f} sd {Z.std():.2f} | held-out R2: 6-feature linear {r2te:+.2f}, 6-feature GBM {gbr2:+.2f}, energy only {e1:+.2f}, 128-bin window-mel ridge {rdr2:+.2f} | corr '+' '.join(f'{c:+.2f}' for c in corr)+f' | dominant {dom}',flush=True)
+print('features:',names)
+json.dump(res,open('res_B4.json','w'),indent=1)
diff --git a/scripts/vad_bench/noise_dive/b4b_probe.py b/scripts/vad_bench/noise_dive/b4b_probe.py
new file mode 100644
index 0000000..e0e16df
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b4b_probe.py
@@ -0,0 +1,36 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+"""Feature-space probe: feed the head (via the real subsampler) normalised-mel inputs that we construct directly."""
+import numpy as np,json
+from vadlib import *; from sig import *
+import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
+rng=np.random.default_rng(8); N=30*SR; T=3000
+lib=list(np.load(FUSION+'/data/libri.npy',allow_pickle=True))
+sp=np.concatenate(lib[10:16]).astype(np.float32)[:N]
+res={}
+mel_sp=norm_mel(raw_logmel(sp))                      # speech as the net sees it
+mel_wn=norm_mel(raw_logmel(at_db(white(N,rng),-25)))
+iid=torch.tensor(rng.standard_normal((T,128)).astype(np.float32))   # iid N(0,1) features, no structure at all
+print('A) iid N(0,1) normalised features (no structure) -> speech frames %')
+for m in ['redux','ultra']:
+    r=ref(m); print(' ',m,100*(r.head_logits(r.sub_out(iid))>=0).float().mean().item(),'% ; mean logit',r.head_logits(r.sub_out(iid)).mean().item(), '; all-zero features logit',r.head_logits(r.sub_out(torch.zeros(T,128))).mean().item())
+print('B) offset d (in per-file std units) added to every bin and frame; scale s on the fluctuation (white noise normalised mel)')
+ds=np.arange(-3,3.01,0.5); ss=[0.25,0.5,1.0,1.5,2.0]
+fig,ax=plt.subplots(1,2,figsize=(11,4))
+for mi,m in enumerate(['redux','ultra']):
+    r=ref(m); Mi=np.zeros((len(ss),len(ds)))
+    for i,s in enumerate(ss):
+        for j,d in enumerate(ds):
+            x=mel_wn*s+d; Mi[i,j]=(r.head_logits(r.sub_out(x))>=0).float().mean().item()
+    res[f'offset_scale|{m}']=Mi.tolist()
+    im=ax[mi].imshow(100*Mi,origin='lower',aspect='auto',extent=[ds[0]-.25,ds[-1]+.25,-0.5,len(ss)-.5],vmin=0,vmax=100,cmap='viridis')
+    ax[mi].set_yticks(range(len(ss))); ax[mi].set_yticklabels(ss); ax[mi].set_xlabel('offset d (std units of the file)'); ax[mi].set_ylabel('fluctuation scale s'); ax[mi].set_title(f'{m}: % frames speech, white-noise features*s+d')
+    plt.colorbar(im,ax=ax[mi])
+    print(m); print('   s\\d '+' '.join(f'{d:5.1f}' for d in ds))
+    for i,s in enumerate(ss): print(f'  {s:4.2f} '+' '.join(f'{100*v:5.0f}' for v in Mi[i]))
+plt.tight_layout(); plt.savefig('fig_offset_scale.png',dpi=110); plt.close()
+# C) speech file: shift the whole normalised mel by d
+print('C) real speech normalised mel shifted by d: speech frames % (of frames)')
+for m in ['redux','ultra']:
+    r=ref(m); print(' ',m,[ (d,round(100*(r.head_logits(r.sub_out(mel_sp+d))>=0).float().mean().item())) for d in [-2,-1,-0.5,0,0.5,1,2]])
+json.dump(res,open('res_B4b.json','w'))
diff --git a/scripts/vad_bench/noise_dive/b5_logits.py b/scripts/vad_bench/noise_dive/b5_logits.py
new file mode 100644
index 0000000..dc8b02b
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b5_logits.py
@@ -0,0 +1,53 @@
+import numpy as np,json
+from vadlib import *; from sig import *; import corpus
+from sklearn.metrics import roc_auc_score, roc_curve
+import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
+models=['redux','ultra']; cl=corpus.clean_clips(); gaps=corpus.gap_clips()
+rng=np.random.default_rng(5); N=30*SR
+D={m:dict(speech_clean=[],pause_clean=[],speech_noisy={},stretch={},nz={}) for m in models}
+for m in models:
+    for y,spans in cl:
+        z=logits_blocks(m,y); sp=frame_mask(spans,len(z))
+        # pause frames: >=0.3 s from any speech (gap between utterances, digital -60 dBFS dither)
+        t=(np.arange(len(z))+.5)*.08; far=np.ones(len(z),bool)
+        for a,b in spans: far&=~((t>a-0.3)&(t=g['stretch'][0])&(t=0).mean():.0f}% / {100*(b>=0).mean():.0f}% |')
+        out[f'{m}|{nm}']=dict(auc=auc,thr=float(thr),tpr=tpr,fpr=fpr,thrF1=float(thF))
+json.dump(out,open('res_B5.json','w'),indent=1)
+# logit percentiles
+print('\nlogit percentiles (5,25,50,75,95):')
+for m in models:
+    d=D[m]
+    for nm,z in [('speech clean',cat(d['speech_clean'])),('pause (digital -60 dBFS dither) in clean clips',cat(d['pause_clean'])),('noise-only -23 dBFS',d['nz'][-23]),('noise-only -40 dBFS',d['nz'][-40]),('noise-only -50 dBFS',d['nz'][-50]),('embedded stretch 20 dB',cat(d['stretch'][20])),('embedded stretch 5 dB',cat(d['stretch'][5])),('embedded stretch 0 dB',cat(d['stretch'][0]))]:
+        print(f'{m:6s} {nm:48s}',np.percentile(z,[5,25,50,75,95]).round(2))
+fig,axs=plt.subplots(1,2,figsize=(12,4),sharey=True)
+for ax,m in zip(axs,models):
+    d=D[m]; bins=np.linspace(-20,25,90)
+    for nm,z,c in [('speech (clean clips)',cat(d['speech_clean']),'C0'),('pauses in clean clips',cat(d['pause_clean']),'C1'),('noise-only -23 dBFS',d['nz'][-23],'C3'),('noise-only -50 dBFS',d['nz'][-50],'C2'),('embedded stretch 5 dB',cat(d['stretch'][5]),'C4')]:
+        ax.hist(np.clip(z,-20,25),bins=bins,density=True,histtype='step',label=nm,color=c,lw=1.6)
+    ax.axvline(0,color='k',ls=':'); ax.set_title(m+': pre-sigmoid logit (0 = default threshold 0.5)'); ax.set_xlabel('logit'); ax.legend(fontsize=8)
+plt.tight_layout(); plt.savefig('fig_logit_hist.png',dpi=110)
diff --git a/scripts/vad_bench/noise_dive/b6_types.py b/scripts/vad_bench/noise_dive/b6_types.py
new file mode 100644
index 0000000..1200a03
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b6_types.py
@@ -0,0 +1,41 @@
+import numpy as np,json
+from vadlib import *; from sig import *; import corpus
+import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
+models=['redux','ultra']; fold_of=corpus.folds(); libri=corpus.libri; spk=corpus.spk
+spec=ltass(libri[:60])
+def mk(k, noise_fn, snr, whole):
+    f=k%4; idx=[i for i in range(len(libri)) if fold_of[spk[i]]==f]; r=np.random.default_rng(900+k); idx=list(r.permutation(idx))[:5]
+    us=[libri[i] for i in idx]
+    y1,s1=corpus.FL.build(us[:2],np.random.default_rng(400+k)); y2,s2=corpus.FL.build(us[2:],np.random.default_rng(500+k))
+    off=len(y1)/16000+30.0
+    y=np.concatenate([y1,np.random.default_rng(600+k).standard_normal(30*16000)*1e-3,y2]).astype(np.float32)
+    spans=s1+[(a+off,b+off) for a,b in s2]
+    P=np.mean(np.concatenate([y[int(a*SR):int(b*SR)] for a,b in spans])**2)
+    st=(s1[-1][1]+0.5,s2[0][0]+off-0.5); a,b=int(st[0]*SR),int(st[1]*SR)
+    nr=np.random.default_rng(77+k)
+    if whole:
+        n=noise_fn(len(y),nr); n=n/ (rms(n)+1e-12)*np.sqrt(P/10**(snr/10)); y=y+n.astype(np.float32)
+    else:
+        n=noise_fn(b-a,nr); n=n/(rms(n)+1e-12)*np.sqrt(P/10**(snr/10)); y=y.copy(); y[a:b]+=n.astype(np.float32)
+    return np.clip(y,-1,1).astype(np.float32),st
+types={'white':(lambda n,r:white(n,r),True),'pink':(lambda n,r:colored(n,r,1),True),'brown':(lambda n,r:colored(n,r,2),True),'speech-shaped':(lambda n,r:shaped(n,r,spec),True),
+ 'hum 50 Hz':(lambda n,r:hum(n,50,-10),False),'sine 1 kHz':(lambda n,r:sine(n,1000,-10),False),'clicks 3/s':(lambda n,r:clicks(n,r,3,-10),False),'clicks 10/s':(lambda n,r:clicks(n,r,10,-10),False),'music-like':(lambda n,r:music(n,r,-10),False)}
+snrs=[-10,0,10,20,30,40]
+R={}
+print('Embedded-stretch false-alarm frame % at threshold 0.5 ; rows=type, cols=SNR re speech (dB) ; 6 clips per cell')
+for m in models:
+    print(f'\n{m}:'); print('| type | '+' | '.join(f'{s} dB' for s in snrs)+' |'); print('|---|'+'---|'*len(snrs))
+    for tn,(fn,whole) in types.items():
+        row=[]
+        for s in snrs:
+            fps=[];mz=[]
+            for k in range(6):
+                y,st=mk(k,fn,s,whole); z=ref(m).logits(y); t=(np.arange(len(z))+.5)*.08; msk=(t>=st[0])&(t=0).mean()); mz.append(np.median(z[msk]))
+            R[f'{m}|{tn}|{s}']=(float(np.mean(fps)),float(np.mean(mz))); row.append(f'{100*np.mean(fps):.0f}')
+        print(f'| {tn} | '+' | '.join(row)+' |',flush=True)
+json.dump(R,open('res_B6.json','w'))
+fig,axs=plt.subplots(1,2,figsize=(12,4),sharey=True)
+for ax,m in zip(axs,models):
+    for tn in types: ax.plot(snrs,[100*R[f'{m}|{tn}|{s}'][0] for s in snrs],marker='o',label=tn,ls='-' if types[tn][1] else '--')
+    ax.set_title(f'{m}: stretch false alarms vs level of the stretch below speech'); ax.set_xlabel('stretch level re speech power (dB below speech)'); ax.set_ylabel('% stretch frames called speech'); ax.legend(fontsize=7)
+plt.tight_layout(); plt.savefig('fig_types_vs_level.png',dpi=110)
diff --git a/scripts/vad_bench/noise_dive/b7_fraction.py b/scripts/vad_bench/noise_dive/b7_fraction.py
new file mode 100644
index 0000000..303d7fc
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b7_fraction.py
@@ -0,0 +1,31 @@
+"""Mechanism: share of the normalisation window that is noise. 120 s clip = (120-L) s of a real talk + L s of noise in the middle."""
+import numpy as np,soundfile as sf,json
+from vadlib import *; from sig import *
+import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
+x=sf.read('talk/JaneMcGonigal-merged.wav',dtype='float32')[0][60*SR:300*SR]
+rdb=20*np.log10(rms(x)); r=np.random.default_rng(2)
+Ls=[5,10,20,30,45,60,90]; rels=[-35,-25,-15,-5]; R={}
+fig,axs=plt.subplots(2,2,figsize=(11,7),sharey=True)
+for mi,m in enumerate(['redux','ultra']):
+    for ki,kind in enumerate(['white','pink']):
+        ax=axs[mi][ki]
+        for rel in rels:
+            ys=[]
+            for L in Ls:
+                fp=[]
+                for rep in range(3):
+                    s0=int(r.integers(0,100))*SR; sp=120-L; a=sp//2
+                    pre=x[s0:s0+a*SR]; post=x[s0+a*SR:s0+sp*SR]
+                    n=at_db(white(L*SR,r) if kind=='white' else colored(L*SR,r,1),rdb+rel)
+                    y=np.concatenate([pre,n,post]).astype(np.float32)
+                    z=ref(m).logits(y); t=(np.arange(len(z))+.5)*.08; st=(t>=a+1)&(t=0).mean())
+                ys.append(100*np.mean(fp)); R[f'{m}|{kind}|{rel}|{L}']=ys[-1]
+            ax.plot([100*L/120 for L in Ls],ys,marker='o',label=f'noise {rel} dB re talk RMS')
+        ax.set_title(f'{m}, {kind} noise stretch'); ax.set_xlabel('noise share of the 120 s normalisation window (%)'); ax.set_ylabel('% stretch frames called speech'); ax.grid(alpha=.3)
+axs[0][0].legend(fontsize=8); plt.tight_layout(); plt.savefig('fig_noise_fraction.png',dpi=110)
+print('talk rms dBFS %.1f'%rdb)
+for m in ['redux','ultra']:
+    for kind in ['white','pink']:
+        print(f'\n{m} {kind}: stretch FA % ; rows noise level re talk RMS, cols noise share of window (L s of 120)'); print('| level | '+' | '.join(f'{L} s ({100*L//120}%)' for L in Ls)+' |'); print('|---|'+'---|'*len(Ls))
+        for rel in rels: print(f'| {rel} dB | '+' | '.join(f'{R[f"{m}|{kind}|{rel}|{L}"]:.0f}' for L in Ls)+' |')
+json.dump(R,open('res_B7.json','w'))
diff --git a/scripts/vad_bench/noise_dive/b8_delta.py b/scripts/vad_bench/noise_dive/b8_delta.py
new file mode 100644
index 0000000..dfdbe3c
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/b8_delta.py
@@ -0,0 +1,31 @@
+"""Unifying predictor: normalised level of the stretch, D = mean over stretch frames and mel bins of the per-file-normalised log-mel
+(0 = the file's average level, -1 = one per-bin std below it). Relate D to the head's decision on the stretch."""
+import numpy as np,soundfile as sf,json
+from vadlib import *; from sig import *
+import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
+x=sf.read('talk/JaneMcGonigal-merged.wav',dtype='float32')[0][60*SR:300*SR]; rdb=20*np.log10(rms(x)); r=np.random.default_rng(12)
+pts=[]
+for kind in ['white','pink','speech-shaped','music']:
+    for rel in [-35,-30,-25,-20,-15,-10,-5]:
+        for L in [10,30,60]:
+            for rep in range(2):
+                s0=int(r.integers(0,100))*SR; sp=120-L; a=sp//2
+                n=at_db(white(L*SR,r) if kind=='white' else colored(L*SR,r,1) if kind=='pink' else shaped(L*SR,r,ltass([x[:SR*60]])) if kind=='speech-shaped' else music(L*SR,r,0),rdb+rel)
+                y=np.concatenate([x[s0:s0+a*SR],n,x[s0+a*SR:s0+sp*SR]]).astype(np.float32)
+                mn=norm_mel(raw_logmel(y)); fr0=int((a+1)*100); fr1=int((a+L-1)*100); D=float(mn[fr0:fr1].mean())
+                for m in ['redux','ultra']:
+                    z=ref(m).logits(y); t=(np.arange(len(z))+.5)*.08; st=(t>=a+1)&(t=0).mean()),med=float(np.median(z[st]))))
+json.dump(pts,open('res_B8.json','w'))
+fig,axs=plt.subplots(1,2,figsize=(12,4.2),sharey=True)
+for ax,m in zip(axs,['redux','ultra']):
+    for kind,c in zip(['white','pink','speech-shaped','music'],['C0','C1','C3','C2']):
+        P=[p for p in pts if p['m']==m and p['kind']==kind]; ax.scatter([p['D'] for p in P],[p['med'] for p in P],s=18,c=c,label=kind,alpha=.7)
+    ax.axhline(0,color='k',ls=':'); ax.set_xlabel('normalised level of the stretch D (std units; 0 = file average)'); ax.set_title(f'{m}: median logit in a noise stretch vs D'); ax.legend(fontsize=8); ax.grid(alpha=.3)
+axs[0].set_ylabel('median logit in stretch'); plt.tight_layout(); plt.savefig('fig_logit_vs_delta.png',dpi=110)
+for m in ['redux','ultra']:
+    P=[p for p in pts if p['m']==m]; D=np.array([p['D'] for p in P]); M=np.array([p['med'] for p in P]); F=np.array([p['fa'] for p in P])
+    print(m,'corr(D, median logit) %.2f ; corr(D, FA) %.2f'%(np.corrcoef(D,M)[0,1],np.corrcoef(D,F)[0,1]))
+    for lo,hi in [(-9,-2),(-2,-1.5),(-1.5,-1),(-1,-.5),(-.5,0),(0,.5),(.5,9)]:
+        s=(D>=lo)&(D=a)&(t=0).mean()),med=float(np.median(z[st]))))
+json.dump(rows,open('res_B9.json','w'))
+for m in ['redux','ultra']:
+    print(m)
+    for snr in [20,10,5,0]:
+        P=[r for r in rows if r['m']==m and r['snr']==snr]; print(f'  SNR {snr:2d}: mean D {np.mean([r["D"] for r in P]):+.2f}  FA {100*np.mean([r["fa"] for r in P]):.0f}%  median logit {np.mean([r["med"] for r in P]):+.2f}')
diff --git a/scripts/vad_bench/noise_dive/cli_check.py b/scripts/vad_bench/noise_dive/cli_check.py
new file mode 100644
index 0000000..7c0c63b
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/cli_check.py
@@ -0,0 +1,15 @@
+"""Cross-check with the shipped parakeet-cli on a few rebuilt clips: gap clips (embedded stretch) and amplitude."""
+import numpy as np,soundfile as sf,subprocess,json,os
+from vadlib import *; from sig import *; import corpus
+os.makedirs('wav',exist_ok=True); gaps=corpus.gap_clips(); res=[]
+for g in gaps:
+    if g['k'] not in (0,1) or g['snr'] not in (20,5,0) or g['kind']!='white': continue
+    wr(f"wav/{g['name']}.wav",g['y']); n=g['name']
+    for m,gg in [('redux','redux'),('ultra','ultra')]:
+        c=cli_probs(gg,f'wav/{n}.wav'); z=ref(m).logits(sf.read(f'wav/{n}.wav',dtype='float32')[0]); p=sig(z)
+        t=(np.arange(len(c))+.5)*.08; st=(t>=g['stretch'][0])&(t=.5).mean(),100*(p[:len(c)][st]>=.5).mean(),np.abs(c-p[:len(c)]).max()),flush=True)
+r=np.random.default_rng(1); b=white(30*SR,r)
+for lv in [-20,-50,-60,-70,-80]:
+    wr(f'wav/amp_{lv}.wav',at_db(b,lv))
+    print('amp',lv,{m:round(100*(cli_probs(m,f'wav/amp_{lv}.wav')>=.5).mean(),1) for m in ['redux','ultra']},flush=True)
diff --git a/scripts/vad_bench/noise_dive/corpus.py b/scripts/vad_bench/noise_dive/corpus.py
new file mode 100644
index 0000000..fc62b10
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/corpus.py
@@ -0,0 +1,36 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+"""Rebuild fusion/ clips in memory with the same generators and seeds (fusion/lib.py is imported read-only)."""
+import sys,json,numpy as np
+sys.path.insert(0,FUSION)
+import lib as FL
+libri=list(np.load(FUSION+"/data/libri.npy",allow_pickle=True)); spk=np.load(FUSION+"/data/spk.npy")
+def folds():
+    rng=np.random.default_rng(11); sp_ids=rng.permutation(sorted(set(spk))); return {s:i%4 for i,s in enumerate(sp_ids)}
+def speech_sets():
+    fold_of=folds(); rng=np.random.default_rng(11); rng.permutation(sorted(set(spk)))
+    sets=[]
+    for f in range(4):
+        idx=[i for i in range(len(libri)) if fold_of[spk[i]]==f]; idx=list(rng.permutation(idx))
+        for k in range(min(10,len(idx)//5)): sets.append((f,[libri[i] for i in idx[k*5:(k+1)*5]]))
+    return sets
+def clean_clips():
+    out=[]
+    for k,(f,us) in enumerate(speech_sets()):
+        y,spans=FL.build(us,np.random.default_rng(1000+k)); out.append((y,spans))
+    return out
+def gap_clips():
+    """12 per (white|pink) x (20,10,5,0 dB): 2 utts, 30 s noise stretch, 3 utts; noise over the whole clip at the SNR (speech power)."""
+    fold_of=folds(); meta=[]
+    for ci,(kind,snr) in enumerate([(k,s) for k in("white","pink") for s in(20,10,5,0)]):
+        for k in range(12):
+            f=k%4; idx=[i for i in range(len(libri)) if fold_of[spk[i]]==f]; r=np.random.default_rng(300+ci*50+k); idx=list(r.permutation(idx))[:5]
+            us=[libri[i] for i in idx]
+            y1,s1=FL.build(us[:2],np.random.default_rng(400+k)); y2,s2=FL.build(us[2:],np.random.default_rng(500+k))
+            off=len(y1)/16000+30.0
+            y=np.concatenate([y1,np.random.default_rng(600+k).standard_normal(30*16000)*1e-3,y2]).astype(np.float32)
+            spans=s1+[(a+off,b+off) for a,b in s2]
+            clean=y.copy()
+            y=FL.add_noise(y,spans,kind,snr,np.random.default_rng(7000+ci*100+k))
+            meta.append(dict(name=f"gap-{kind}{snr}_{k:02d}",kind=kind,snr=snr,k=k,y=y,clean=clean,spans=spans,stretch=(s1[-1][1]+0.5,s2[0][0]+off-0.5)))
+    return meta
diff --git a/scripts/vad_bench/noise_dive/d_analyze.py b/scripts/vad_bench/noise_dive/d_analyze.py
new file mode 100644
index 0000000..c1fec1f
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/d_analyze.py
@@ -0,0 +1,26 @@
+import json,os
+meta=json.load(open('talk/d/meta.json')); a,b=meta['ins']
+def load(p):
+    try: return json.load(open(p))
+    except Exception: return None
+def words_in(d,lo,hi): return [w for w in d['words'] if lo<=w['start']a and s['start']0: print(r['model'],r['file'],'| novad:',r['sample0'][:90],'| vad:',r['sample1'][:90])
diff --git a/scripts/vad_bench/noise_dive/d_mit.py b/scripts/vad_bench/noise_dive/d_mit.py
new file mode 100644
index 0000000..90669dd
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/d_mit.py
@@ -0,0 +1,19 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+import numpy as np,json,soundfile as sf,sys
+sys.path.insert(0,FUSION); import fl
+from vadlib import *
+meta=json.load(open('talk/d/meta.json')); a,b=meta['ins']
+def runconf(z,q):
+    keep=np.zeros(len(z),bool)
+    for i,j in zip(*fl.runs(z>=0)):
+        if np.median(z[i:j])>=q: keep[i:j]=True
+    return keep
+print('insert (60 s) frames called speech %, by mitigation (block-normalised logits, shipped segmenter smoothing not applied)\n| file | model | thr 0.5 | thr 0.7 | thr 0.9 | runconf 2.5 | talk speech frames kept (outside insert, vs thr 0.5) runconf 2.5 |\n|---|---|---|---|---|---|---|')
+for f,tn,rel in meta['files']:
+    if f=='base': continue
+    x=sf.read(f'talk/d/{f}.wav',dtype='float32')[0]
+    for m in ['redux','ultra']:
+        z=logits_blocks(m,x); t=(np.arange(len(z))+.5)*.08; st=(t>=a+1)&(tb+1)
+        rc=runconf(z,2.5); base=z>=0
+        print(f'| {f} | {m} | {100*base[st].mean():.0f} | {100*(z[st]>=np.log(.7/.3)).mean():.0f} | {100*(z[st]>=np.log(.9/.1)).mean():.0f} | {100*rc[st].mean():.0f} | {100*rc[out].sum()/base[out].sum():.1f}% |',flush=True)
diff --git a/scripts/vad_bench/noise_dive/d_prep.py b/scripts/vad_bench/noise_dive/d_prep.py
new file mode 100644
index 0000000..97e6914
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/d_prep.py
@@ -0,0 +1,21 @@
+import numpy as np,soundfile as sf,json
+from sig import *
+x,sr=sf.read('talk/JaneMcGonigal-merged.wav',dtype='float32'); assert sr==16000
+ex=x[60*SR:300*SR]                                  # 4 min excerpt
+# find the quietest 1 s window between 100 and 140 s of the excerpt to cut at (a natural pause)
+fr=len(ex)//1600; e=10*np.log10((ex[:fr*1600].reshape(fr,1600)**2).mean(1)+1e-12)
+c=[(e[i:i+10].mean(),i) for i in range(1000,1400)]; _,i0=min(c); cut=(i0+5)*1600     # centre of that window
+speech_rms_db=20*np.log10(rms(ex)); print('excerpt rms dBFS',speech_rms_db,'cut at',cut/SR,'s')
+r=np.random.default_rng(4); L=60*SR
+gens={'white':lambda: white(L,r),'pink':lambda: colored(L,r,1),'clicks':lambda: clicks(L,r,3,0),'music':lambda: music(L,r,0)}
+meta={'cut':cut/SR,'ins':[cut/SR,cut/SR+60],'rms_db':speech_rms_db,'files':[]}
+sf.write('talk/d/base.wav',ex,16000,subtype='PCM_16'); meta['files'].append(('base',None,None))
+for tn,g in gens.items():
+    n0=g()
+    for rel in [-35,-20,-5]:
+        n=at_db(n0,speech_rms_db+rel) if tn!='clicks' else (n0/ (np.abs(n0).max()+1e-9) * 10**((speech_rms_db+rel+14)/20)).astype(np.float32)  # clicks: set RMS ~ rel via crest ~ calibrate below
+        if tn=='clicks': n=at_db(n0,speech_rms_db+rel)
+        y=np.concatenate([ex[:cut],np.clip(n,-1,1),ex[cut:]]).astype(np.float32)
+        nm=f'{tn}_{rel}'; sf.write(f'talk/d/{nm}.wav',y,16000,subtype='PCM_16'); meta['files'].append((nm,tn,rel))
+json.dump(meta,open('talk/d/meta.json','w'))
+print(len(meta['files']),'files')
diff --git a/scripts/vad_bench/noise_dive/d_probs.py b/scripts/vad_bench/noise_dive/d_probs.py
new file mode 100644
index 0000000..3025c77
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/d_probs.py
@@ -0,0 +1,14 @@
+import numpy as np,json,torch,soundfile as sf
+from vadlib import *
+S=np.load('fixed_stats.npz'); fa=(torch.tensor(S['aM']),torch.tensor(S['aS']))
+meta=json.load(open('talk/d/meta.json')); a,b=meta['ins']; res={}
+print('| file | model | insert frames called speech %: block-120 s norm (shipped) | whole-file norm | fixed norm A | median logit (shipped) |\n|---|---|---|---|---|---|')
+for f,tn,rel in meta['files']:
+    x=sf.read(f'talk/d/{f}.wav',dtype='float32')[0]
+    for m in ['redux','ultra']:
+        zb=logits_blocks(m,x); zf=ref(m).logits(x); zx=ref(m).logits(x,*fa)
+        t=(np.arange(len(zb))+.5)*.08; st=(t>=a+1)&(t=a-30)&(t=0).mean()),whole=float((zf[:len(zb)][st]>=0).mean()),fixed=float((zx[:len(zb)][st]>=0).mean()),med=float(np.median(zb[st])))
+        r=res[f'{f}|{m}']; print(f'| {f} | {m} | {100*r["block"]:.0f} | {100*r["whole"]:.0f} | {100*r["fixed"]:.0f} | {r["med"]:+.1f} |',flush=True)
+json.dump(res,open('res_D_probs.json','w'))
diff --git a/scripts/vad_bench/noise_dive/d_run.py b/scripts/vad_bench/noise_dive/d_run.py
new file mode 100644
index 0000000..7a39b7b
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/d_run.py
@@ -0,0 +1,17 @@
+import json,subprocess,sys,os,time
+CLI=os.environ.get('PARAKEET_CLI','build/examples/cli/parakeet-cli'); S=os.environ.get('GGUF_ROOT','..')
+M={'ultra':S+'/gguf/ultra-q8_0.gguf','redux':S+'/gguf/redux-keep.gguf'}
+meta=json.load(open('talk/d/meta.json')); os.makedirs('talk/out',exist_ok=True)
+files=[f[0] for f in meta['files']]
+for m in ['ultra','redux']:
+    for f in files:
+        wav=f'talk/d/{f}.wav'
+        o=f'talk/out/{f}.{m}.segments.json'
+        if not os.path.exists(o):
+            r=subprocess.run([CLI,'vad','--model',M[m],'--input',wav,'--mode','segments','--threads','4'],capture_output=True,text=True); open(o,'w').write(r.stdout)
+        for vad in [1,0]:
+            o=f'talk/out/{f}.{m}.vad{vad}.json'
+            if os.path.exists(o): continue
+            t=time.time()
+            r=subprocess.run([CLI,'transcribe','--model',M[m],'--input',wav,'--json','--timestamps','--threads','4']+(['--vad'] if vad else []),capture_output=True,text=True)
+            open(o,'w').write(r.stdout); print(m,f,vad,round(time.time()-t,1),r.returncode,len(r.stdout),flush=True)
diff --git a/scripts/vad_bench/noise_dive/e2_talk.py b/scripts/vad_bench/noise_dive/e2_talk.py
new file mode 100644
index 0000000..7f1f468
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/e2_talk.py
@@ -0,0 +1,24 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+import numpy as np,soundfile as sf,sys,onnxruntime as ort,os
+sys.path.insert(0,FUSION); import fl
+from vadlib import *
+x=sf.read('talk/JaneMcGonigal-merged.wav',dtype='float32')[0]
+so=ort.SessionOptions(); so.intra_op_num_threads=2
+sess=ort.InferenceSession(os.environ['SILERO_ONNX'],so,providers=["CPUExecutionProvider"])
+y=np.pad(x,(0,(-len(x))%512)); st=np.zeros((2,1,128),np.float32); ctx=np.zeros(64,np.float32); sil=[]
+for i in range(len(y)//512):
+    xx=np.concatenate([ctx,y[i*512:(i+1)*512]])[None].astype(np.float32); o,st=sess.run(None,{"input":xx,"state":st,"sr":np.array(16000,np.int64)}); sil.append(float(o[0,0])); ctx=xx[0,-64:]
+sil=np.array(sil); n=int(len(x)/160)
+S,_=fl.post(fl.hold(sil,0.032,n)>=0.5,**fl.POST_SIL)
+print('talk %.0f s; Silero speech time %.1f%%'%(len(x)/16000,100*S.mean()))
+for m in ['redux','ultra']:
+    z=logits_blocks(m,x); base,_=fl.post(fl.hold(sig(z),0.08,n)>=0.5,**fl.POST_HEAD)
+    def rc(q):
+        keep=np.zeros(len(z),bool)
+        for i,j in zip(*fl.runs(z>=0)):
+            if np.median(z[i:j])>=q: keep[i:j]=True
+        return fl.post(fl.hold(keep.astype(np.float32),0.08,n)>=0.5,**fl.POST_HEAD)[0]
+    print(f'\n{m}: speech time % | agreement with Silero (F1 of mask vs Silero mask) | frames removed vs head 0.5 (% of its speech frames)')
+    for nm,mk in [('head 0.5',base),('head 0.7',fl.post(fl.hold(sig(z),0.08,n)>=0.7,**fl.POST_HEAD)[0]),('head 0.9',fl.post(fl.hold(sig(z),0.08,n)>=0.9,**fl.POST_HEAD)[0]),('runconf 2.5',rc(2.5)),('runconf 3.5',rc(3.5))]:
+        P,R,F=fl.prf(fl.counts(mk,S)); print(f'  {nm:12s} {100*mk.mean():5.1f}% | F1 vs Silero {100*F:.1f} (P {100*P:.1f} R {100*R:.1f}) | removed {100*((base&~mk).sum()/base.sum()):.2f}%')
diff --git a/scripts/vad_bench/noise_dive/e_build.py b/scripts/vad_bench/noise_dive/e_build.py
new file mode 100644
index 0000000..e617e6e
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/e_build.py
@@ -0,0 +1,41 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+"""Rebuild the fusion synthetic set (same generators and seeds as fusion/mkcorpus.py + mkgap.py) and compute per-clip:
+head logits (redux, ultra F16; CPU reference validated against parakeet-cli), Silero probabilities (ONNX), and 10 ms frame energy."""
+import numpy as np,json,pickle,sys,os
+from vadlib import *; import corpus; import onnxruntime as ort
+FL=corpus.FL; libri=corpus.libri; spk=corpus.spk
+sets=corpus.speech_sets()
+conds=[("clean",None)]+[(k,s) for k in("white","pink") for s in(20,10,5,0)]
+Ps=[]; clips=[]
+q=lambda y: (np.round(np.clip(y,-1,1)*32767)/32767).astype(np.float32)   # PCM_16 round trip as in the original wavs
+for ci,(kind,snr) in enumerate(conds):
+    for k,(f,us) in enumerate(sets):
+        y,spans=FL.build(us,np.random.default_rng(1000+k))
+        if ci==0: Ps.append(float(np.mean(np.concatenate([y[int(s*16000):int(e*16000)] for s,e in spans])**2)))
+        y=FL.add_noise(y,spans,kind,snr,np.random.default_rng(5000+ci*100+k))
+        clips.append(dict(name=f"{kind}{'' if snr is None else snr}_{k:02d}",cond=f"{kind}{'' if snr is None else snr}",kind='speech',spans=spans,y=q(y)))
+for ci,(kind,snr) in enumerate(conds[1:]):
+    for k in range(12):
+        r=np.random.default_rng(9000+ci*100+k); P=Ps[(k*3)%len(Ps)]; n=30*16000
+        z=r.standard_normal(n) if kind=="white" else FL.pink(n,r)
+        z=np.clip(z*np.sqrt(P/10**(snr/10)),-1,1).astype(np.float32)
+        clips.append(dict(name=f"nz-{kind}{snr}_{k:02d}",cond=f"nz-{kind}{snr}",kind='noise',spans=[],y=q(z)))
+for g in corpus.gap_clips():
+    clips.append(dict(name=g['name'],cond=f"gap-{g['kind']}{g['snr']}",kind='gap',spans=g['spans'],stretch=g['stretch'],y=q(g['y'])))
+print(len(clips),'clips',sum(len(c['y']) for c in clips)/16000,'s',flush=True)
+so=ort.SessionOptions(); so.intra_op_num_threads=2; so.inter_op_num_threads=1
+sess=ort.InferenceSession(os.environ['SILERO_ONNX'],so,providers=["CPUExecutionProvider"])
+def silero(y):
+    n=(len(y)+511)//512; y=np.pad(y,(0,n*512-len(y))); st=np.zeros((2,1,128),np.float32); ctx=np.zeros(64,np.float32); out=[]
+    for i in range(n):
+        x=np.concatenate([ctx,y[i*512:(i+1)*512]])[None].astype(np.float32)
+        o,st=sess.run(None,{"input":x,"state":st,"sr":np.array(16000,np.int64)}); out.append(float(o[0,0])); ctx=x[0,-64:]
+    return np.array(out,np.float32)
+for i,c in enumerate(clips):
+    y=c['y']; c['z']={m:logits_blocks(m,y).astype(np.float32) for m in['redux','ultra']}
+    c['sil']=silero(y)
+    fr=len(y)//160; c['e10']=10*np.log10((y[:fr*160].reshape(fr,160).astype(np.float64)**2).mean(1)+1e-12)   # dBFS^2 per 10 ms
+    del c['y']
+    if i%40==0: print(i,flush=True)
+pickle.dump(clips,open('e_set.pkl','wb'))
diff --git a/scripts/vad_bench/noise_dive/e_eval.py b/scripts/vad_bench/noise_dive/e_eval.py
new file mode 100644
index 0000000..b6af397
--- /dev/null
+++ b/scripts/vad_bench/noise_dive/e_eval.py
@@ -0,0 +1,60 @@
+import os
+FUSION=os.environ.get('FUSION_DIR','../fusion')  # scripts/vad_bench/fusion with data/ built by prep_libri.py
+import numpy as np,pickle,json,sys
+sys.path.insert(0,FUSION)
+import fl, rules
+from vadlib import sig
+GR=fl.GR
+clips=pickle.load(open('e_set.pkl','rb'))
+def grid(c):
+    n=int(round(len(c['e10'])))   # 10 ms cells
+    return n
+def refmask(c,n):
+    m=np.zeros(n,bool)
+    for s,e in c['spans']: m[int(round(s/GR)):int(round(e/GR))]=True
+    return m
+def stretch_mask(c,n):
+    m=np.zeros(n,bool)
+    if 'stretch' in c: a,b=c['stretch']; m[int(round(a/GR)):int(round(b/GR))]=True
+    return m
+def e80(c):
+    p=10**(c['e10']/10); k=np.ones(8)/8; return 10*np.log10(np.convolve(p,k,'same')+1e-12)
+def run_rule(rule,c,model):
+    n=len(c['e10']); H=fl.hold(sig(c['z'][model]),0.08,n); S=fl.hold(c['sil'],0.032,n)
+    kind=rule[0]
+    if kind=='head': m=H>=rule[1]; post=fl.POST_HEAD
+    elif kind=='head_ms': m=H>=0.5; post=dict(fl.POST_HEAD,min_speech=rule[1])
+    elif kind=='gate':   # head thr + relative energy gate
+        m=H>=rule[1]; e=e80(c); ref_lvl=np.percentile(c['e10'],95); m=m&(e>=ref_lvl-rule[2]); post=fl.POST_HEAD
+    elif kind=='runconf':   # keep a p>=0.5 run only when its median logit is >= Q (noise plateau sits near +1.5, speech runs near +7)
+        z=c['z'][model]; keep=np.zeros(len(z),bool); a=fl.runs(z>=0)
+        for i,j in zip(*a):
+            if np.median(z[i:j])>=rule[1]: keep[i:j]=True
+        m=fl.hold(keep.astype(np.float32),0.08,n)>=0.5; post=fl.POST_HEAD
+    elif kind=='silero': m=S>=0.5; post=fl.POST_SIL
+    elif kind=='and': m=(S>=0.5)&(H>=rule[1]); post=fl.POST_HEAD
+    elif kind=='two': m=rules.two_stage(S>=0.5,H>=0.5,16,16,30) if False else rules.two_stage(S>=0.5,H>=0.5,rule[1],rule[2],rule[3]); post=fl.POST_HEAD
+    return fl.post(m,**post)
+R=[('head',0.5),('head',0.7),('head',0.9),('head',0.97),('head_ms',0.25),('head_ms',0.5),('head_ms',1.0),
+   ('gate',0.5,15),('gate',0.5,25),('gate',0.5,35),('gate',0.9,25),
+   ('runconf',2.5),('runconf',3.5),('runconf',5.0),('and',0.5),('and',0.9),('two',16,16,30),('silero',)]
+def name(r):
+    k=r[0]; g=lambda i: r[i] if len(r)>i else ""
+    return {'head':lambda:f'head thr {g(1)}','head_ms':lambda:f'head thr 0.5, min_speech {g(1)} s','gate':lambda:f'head thr {g(1)} + energy gate (frame >= file P95 - {g(2)} dB)','and':lambda:f'Silero 0.5 AND head {g(1)}','two':lambda:'two-stage (Silero + head extend/fill, fusion rule 6)','silero':lambda:'Silero 0.5 alone (reference, its own post)','runconf':lambda:f'head thr 0.5, keep run only if median logit >= {g(1)}'}[k]()
+out={}
+for model in ['redux','ultra']:
+    print(f'\n### {model}\n| rule | speech F1 clean | speech F1 noisy (white+pink 20..0 dB) | noise-only FA frames % | embedded-stretch FA frames % | noise-only FA regions per clip |\n|---|---|---|---|---|---|')
+    for r in R:
+        cc={'clean':np.zeros(4),'noisy':np.zeros(4)}; fa_nz=[0,0]; fa_st=[0,0]; regs=[]
+        for c in clips:
+            n=len(c['e10']); m,rg=run_rule(r,c,model)
+            if c['kind']=='speech':
+                key='clean' if c['cond']=='clean' else 'noisy'; cc[key]+=fl.counts(m,refmask(c,n))
+            elif c['kind']=='noise': fa_nz[0]+=m.sum(); fa_nz[1]+=n; regs.append(len(rg))
+            else:
+                st=stretch_mask(c,n); fa_st[0]+=(m&st).sum(); fa_st[1]+=st.sum()
+        f=lambda k: 100*fl.prf(cc[k])[2]; P=lambda k:100*fl.prf(cc[k])[0]; Rc=lambda k:100*fl.prf(cc[k])[1]
+        out[f'{model}|{name(r)}']=dict(f1_clean=f('clean'),f1_noisy=f('noisy'),R_clean=Rc('clean'),P_clean=P('clean'),fa_noise=100*fa_nz[0]/fa_nz[1],fa_stretch=100*fa_st[0]/fa_st[1],regions=float(np.mean(regs)))
+        o=out[f'{model}|{name(r)}']
+        print(f'| {name(r)} | {o["f1_clean"]:.1f} (P {o["P_clean"]:.1f}, R {o["R_clean"]:.1f}) | {o["f1_noisy"]:.1f} | {o["fa_noise"]:.1f} | {o["fa_stretch"]:.1f} | {o["regions"]:.1f} |',flush=True)
+json.dump(out,open('res_E.json','w'),indent=1)
diff --git a/scripts/vad_bench/noise_dive/fig_D_S.png b/scripts/vad_bench/noise_dive/fig_D_S.png
new file mode 100644
index 0000000000000000000000000000000000000000..8103002f3077f1c90c62d8610fb1fb453b6af1b6
GIT binary patch
literal 64253
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�N
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
e@BhEPzuE1wR=o*2?k&N=n*8 else None
+        if e is None: n=len(c['e10'])//8; z=z[:n]; e=c['e10'][:n*8].reshape(n,8)
+        e80=10*np.log10((10**(e/10)).mean(1)+1e-12); rel=e80-np.percentile(c['e10'],95)
+        t=(np.arange(n)+.5)*.08
+        if c['kind']=='speech':
+            sp=frame_mask(c['spans'],n); X['speech frames (clean+noisy speech clips)'].append(rel[sp]); Y['speech frames (clean+noisy speech clips)'].append(z[sp])
+            X['non-speech frames in speech clips (gaps)'].append(rel[~sp]); Y['non-speech frames in speech clips (gaps)'].append(z[~sp])
+        elif c['kind']=='noise': X['noise-only files'].append(rel); Y['noise-only files'].append(z)
+        else:
+            a,b=c['stretch']; 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

o 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

GtTDj7wNARw0>A-JDn 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 speech frames % + redux 15.999999642372131 % ; mean logit -1.504223108291626 ; all-zero features logit 1.7988790273666382 + ultra 58.399999141693115 % ; mean logit 0.17368166148662567 ; all-zero features logit 1.143395185470581 +B) offset d (in per-file std units) added to every bin and frame; scale s on the fluctuation (white noise normalised mel) +redux + s\d -3.0 -2.5 -2.0 -1.5 -1.0 -0.5 0.0 0.5 1.0 1.5 2.0 2.5 3.0 + 0.25 0 0 0 0 0 0 0 1 76 83 90 98 100 + 0.50 0 0 0 0 1 45 61 83 99 98 99 99 100 + 1.00 0 0 0 0 1 10 99 99 97 97 99 99 99 + 1.50 0 0 1 14 34 39 47 58 96 99 99 99 99 + 2.00 66 76 81 66 35 6 4 11 60 96 99 99 99 +ultra + s\d -3.0 -2.5 -2.0 -1.5 -1.0 -0.5 0.0 0.5 1.0 1.5 2.0 2.5 3.0 + 0.25 0 0 0 0 0 0 0 77 61 14 38 49 60 + 0.50 0 0 0 0 0 0 14 94 82 43 20 22 42 + 1.00 0 0 0 0 6 81 99 97 66 18 5 11 23 + 1.50 0 1 6 30 77 95 94 66 14 8 20 33 51 + 2.00 1 2 10 30 49 47 20 3 1 11 38 58 64 +C) real speech normalised mel shifted by d: speech frames % (of frames) + redux [(-2, 55), (-1, 74), (-0.5, 90), (0, 91), (0.5, 92), (1, 91), (2, 98)] + ultra [(-2, 1), (-1, 44), (-0.5, 75), (0, 89), (0.5, 92), (1, 94), (2, 91)] diff --git a/scripts/vad_bench/noise_dive/res_B5.txt b/scripts/vad_bench/noise_dive/res_B5.txt new file mode 100644 index 0000000..dfee3cf --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_B5.txt @@ -0,0 +1,34 @@ +| model | comparison | AUC | Youden-best logit thr (p) | TPR / FPR at best | F1-best logit thr (p) | speech/noise at default 0 | +|---|---|---|---|---|---|---| +| redux | speech (clean clips) vs pauses in the same clips | 0.990 | -3.36 (0.03) | 0.95 / 0.00 | -5.90 (0.00) | 92% / 0% | +| redux | speech vs embedded noise stretch, SNR 20 dB | 0.948 | +1.72 (0.85) | 0.89 / 0.00 | +1.58 (0.83) | 93% / 33% | +| redux | speech vs embedded noise stretch, SNR 10 dB | 0.931 | +1.82 (0.86) | 0.87 / 0.01 | +1.71 (0.85) | 94% / 60% | +| redux | speech vs embedded noise stretch, SNR 5 dB | 0.947 | +1.71 (0.85) | 0.89 / 0.01 | +1.55 (0.82) | 95% / 56% | +| redux | speech vs embedded noise stretch, SNR 0 dB | 0.943 | +1.67 (0.84) | 0.86 / 0.02 | +1.63 (0.84) | 95% / 55% | +| redux | speech (clean) vs noise-only files -50..-15 dBFS pooled | 0.896 | +3.35 (0.97) | 0.85 / 0.02 | +3.14 (0.96) | 92% / 93% | +| redux | speech (clean) vs noise-only file at -23 dBFS (speech-level) | 0.892 | +3.53 (0.97) | 0.85 / 0.01 | -13.09 (0.00) | 92% / 97% | +| ultra | speech (clean clips) vs pauses in the same clips | 0.961 | -0.89 (0.29) | 0.92 / 0.00 | -1.86 (0.13) | 91% / 0% | +| ultra | speech vs embedded noise stretch, SNR 20 dB | 0.982 | -1.85 (0.14) | 0.94 / 0.00 | -2.01 (0.12) | 91% / 0% | +| ultra | speech vs embedded noise stretch, SNR 10 dB | 0.958 | +0.06 (0.51) | 0.89 / 0.01 | -0.08 (0.48) | 89% / 1% | +| ultra | speech vs embedded noise stretch, SNR 5 dB | 0.956 | +1.10 (0.75) | 0.87 / 0.02 | +0.81 (0.69) | 92% / 13% | +| ultra | speech vs embedded noise stretch, SNR 0 dB | 0.941 | +1.40 (0.80) | 0.85 / 0.03 | +1.29 (0.78) | 95% / 48% | +| ultra | speech (clean) vs noise-only files -50..-15 dBFS pooled | 0.868 | +3.51 (0.97) | 0.81 / 0.01 | +3.01 (0.95) | 91% / 98% | +| ultra | speech (clean) vs noise-only file at -23 dBFS (speech-level) | 0.865 | +3.59 (0.97) | 0.80 / 0.01 | -7.21 (0.00) | 91% / 99% | + +logit percentiles (5,25,50,75,95): +redux speech clean [-3.13 5.52 7.97 10.01 12.67] +redux pause (digital -60 dBFS dither) in clean clips [-11.63 -9. -8.12 -7.26 -5.49] +redux noise-only -23 dBFS [0.16 0.89 1.45 2.03 2.99] +redux noise-only -40 dBFS [0.03 0.65 1.19 1.77 2.6 ] +redux noise-only -50 dBFS [-0.61 0.02 0.49 0.98 1.8 ] +redux embedded stretch 20 dB [-2.01 -1. -0.35 0.19 0.91] +redux embedded stretch 5 dB [-0.84 -0.28 0.08 0.47 1.12] +redux embedded stretch 0 dB [-0.87 -0.33 0.08 0.53 1.26] +ultra speech clean [-2.72 4.52 7.04 9.25 13.15] +ultra pause (digital -60 dBFS dither) in clean clips [-4.99 -4.23 -3.23 -2.65 -1.76] +ultra noise-only -23 dBFS [0.5 1.23 1.74 2.27 3.08] +ultra noise-only -40 dBFS [0.37 1.09 1.57 2.08 2.86] +ultra noise-only -50 dBFS [0.05 0.76 1.25 1.78 2.55] +ultra embedded stretch 20 dB [-4.67 -4.06 -3.62 -3.17 -2.5 ] +ultra embedded stretch 5 dB [-2.41 -1.65 -1.08 -0.43 0.58] +ultra embedded stretch 0 dB [-1.3 -0.58 -0.04 0.48 1.24] diff --git a/scripts/vad_bench/noise_dive/res_B6.txt b/scripts/vad_bench/noise_dive/res_B6.txt new file mode 100644 index 0000000..1540e0f --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_B6.txt @@ -0,0 +1,27 @@ +Embedded-stretch false-alarm frame % at threshold 0.5 ; rows=type, cols=SNR re speech (dB) ; 6 clips per cell + +redux: +| type | -10 dB | 0 dB | 10 dB | 20 dB | 30 dB | 40 dB | +|---|---|---|---|---|---|---| +| white | 93 | 68 | 79 | 53 | 0 | 0 | +| pink | 93 | 61 | 63 | 33 | 0 | 0 | +| brown | 0 | 0 | 0 | 0 | 0 | 0 | +| speech-shaped | 99 | 89 | 48 | 43 | 1 | 0 | +| hum 50 Hz | 0 | 0 | 0 | 0 | 0 | 0 | +| sine 1 kHz | 0 | 0 | 0 | 0 | 0 | 0 | +| clicks 3/s | 2 | 1 | 1 | 1 | 0 | 0 | +| clicks 10/s | 15 | 14 | 16 | 9 | 0 | 0 | +| music-like | 30 | 23 | 16 | 10 | 1 | 0 | + +ultra: +| type | -10 dB | 0 dB | 10 dB | 20 dB | 30 dB | 40 dB | +|---|---|---|---|---|---|---| +| white | 97 | 44 | 0 | 0 | 0 | 0 | +| pink | 96 | 55 | 0 | 0 | 0 | 0 | +| brown | 0 | 0 | 0 | 0 | 0 | 0 | +| speech-shaped | 100 | 99 | 46 | 0 | 0 | 0 | +| hum 50 Hz | 0 | 0 | 0 | 0 | 0 | 0 | +| sine 1 kHz | 0 | 0 | 0 | 0 | 0 | 0 | +| clicks 3/s | 46 | 38 | 26 | 9 | 0 | 0 | +| clicks 10/s | 90 | 84 | 64 | 22 | 1 | 0 | +| music-like | 55 | 53 | 44 | 8 | 0 | 0 | diff --git a/scripts/vad_bench/noise_dive/res_B7.md b/scripts/vad_bench/noise_dive/res_B7.md new file mode 100644 index 0000000..57044be --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_B7.md @@ -0,0 +1,33 @@ +talk rms dBFS -20.4 + +redux white: stretch FA % ; rows noise level re talk RMS, cols noise share of window (L s of 120) +| level | 5 s (4%) | 10 s (8%) | 20 s (16%) | 30 s (25%) | 45 s (37%) | 60 s (50%) | 90 s (75%) | +|---|---|---|---|---|---|---|---| +| -35 dB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| -25 dB | 0 | 0 | 0 | 0 | 0 | 0 | 8 | +| -15 dB | 83 | 70 | 21 | 8 | 1 | 0 | 18 | +| -5 dB | 22 | 12 | 6 | 3 | 1 | 1 | 17 | + +redux pink: stretch FA % ; rows noise level re talk RMS, cols noise share of window (L s of 120) +| level | 5 s (4%) | 10 s (8%) | 20 s (16%) | 30 s (25%) | 45 s (37%) | 60 s (50%) | 90 s (75%) | +|---|---|---|---|---|---|---|---| +| -35 dB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| -25 dB | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| -15 dB | 0 | 0 | 0 | 0 | 0 | 0 | 12 | +| -5 dB | 6 | 10 | 1 | 1 | 1 | 5 | 32 | + +ultra white: stretch FA % ; rows noise level re talk RMS, cols noise share of window (L s of 120) +| level | 5 s (4%) | 10 s (8%) | 20 s (16%) | 30 s (25%) | 45 s (37%) | 60 s (50%) | 90 s (75%) | +|---|---|---|---|---|---|---|---| +| -35 dB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| -25 dB | 85 | 98 | 87 | 26 | 1 | 0 | 0 | +| -15 dB | 100 | 100 | 100 | 98 | 62 | 23 | 3 | +| -5 dB | 100 | 100 | 94 | 76 | 60 | 62 | 64 | + +ultra pink: stretch FA % ; rows noise level re talk RMS, cols noise share of window (L s of 120) +| level | 5 s (4%) | 10 s (8%) | 20 s (16%) | 30 s (25%) | 45 s (37%) | 60 s (50%) | 90 s (75%) | +|---|---|---|---|---|---|---|---| +| -35 dB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| -25 dB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| -15 dB | 57 | 42 | 24 | 8 | 2 | 0 | 1 | +| -5 dB | 35 | 38 | 43 | 29 | 18 | 18 | 45 | diff --git a/scripts/vad_bench/noise_dive/res_B8.txt b/scripts/vad_bench/noise_dive/res_B8.txt new file mode 100644 index 0000000..b298dd9 --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_B8.txt @@ -0,0 +1,14 @@ +redux corr(D, median logit) 0.80 ; corr(D, FA) 0.45 + D in [-2,-1.5): n= 10 FA mean 0.0% median logit -5.68 + D in [-1.5,-1): n= 28 FA mean 0.0% median logit -6.59 + D in [-1,-0.5): n= 55 FA mean 0.8% median logit -4.64 + D in [-0.5,0): n= 32 FA mean 1.5% median logit -2.16 + D in [0,0.5): n= 31 FA mean 14.1% median logit -0.95 + D in [0.5,9): n= 12 FA mean 21.9% median logit -0.51 +ultra corr(D, median logit) 0.84 ; corr(D, FA) 0.64 + D in [-2,-1.5): n= 10 FA mean 0.0% median logit -5.27 + D in [-1.5,-1): n= 28 FA mean 1.6% median logit -4.67 + D in [-1,-0.5): n= 55 FA mean 6.2% median logit -3.91 + D in [-0.5,0): n= 32 FA mean 19.2% median logit -1.79 + D in [0,0.5): n= 31 FA mean 46.9% median logit -0.16 + D in [0.5,9): n= 12 FA mean 83.1% median logit +1.17 diff --git a/scripts/vad_bench/noise_dive/res_D.md b/scripts/vad_bench/noise_dive/res_D.md new file mode 100644 index 0000000..408c4d9 --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_D.md @@ -0,0 +1,39 @@ +Inserted 60 s block at 111.7-171.7 s of a 300 s talk excerpt. Words whose start lies inside the block = hallucinated (the inserted audio has no speech). + +### ultra (talk excerpt alone: 811 words without VAD, 809 with VAD) +| inserted audio | level re talk RMS | words in block, no VAD | words in block, --vad | talk words outside block, no VAD / --vad (base 811) | VAD segments touching block (s covered / count) | +|---|---|---|---|---|---| +| white | -35 dB | 0 | 0 | 811 / 812 | 54 s / 2 | +| white | -20 dB | 0 | 0 | 811 / 809 | 60 s / 4 | +| white | -5 dB | 0 | 0 | 811 / 809 | 60 s / 4 | +| pink | -35 dB | 0 | 0 | 811 / 812 | 54 s / 2 | +| pink | -20 dB | 0 | 0 | 811 / 808 | 54 s / 3 | +| pink | -5 dB | 0 | 0 | 811 / 809 | 60 s / 4 | +| clicks | -35 dB | 0 | 0 | 811 / 810 | 60 s / 4 | +| clicks | -20 dB | 0 | 0 | 811 / 810 | 60 s / 4 | +| clicks | -5 dB | 0 | 0 | 811 / 810 | 60 s / 4 | +| music | -35 dB | 0 | 15 | 811 / 813 | 54 s / 2 | +| music | -20 dB | 0 | 12 | 811 / 812 | 54 s / 2 | +| music | -5 dB | 0 | 3 | 811 / 809 | 60 s / 4 | + +### redux (talk excerpt alone: 814 words without VAD, 812 with VAD) +| inserted audio | level re talk RMS | words in block, no VAD | words in block, --vad | talk words outside block, no VAD / --vad (base 814) | VAD segments touching block (s covered / count) | +|---|---|---|---|---|---| +| white | -35 dB | 0 | 0 | 814 / 810 | 24 s / 2 | +| white | -20 dB | 0 | 2 | 815 / 811 | 54 s / 2 | +| white | -5 dB | 0 | 0 | 814 / 808 | 60 s / 4 | +| pink | -35 dB | 0 | 0 | 814 / 810 | 24 s / 2 | +| pink | -20 dB | 0 | 0 | 815 / 811 | 54 s / 2 | +| pink | -5 dB | 0 | 0 | 813 / 814 | 60 s / 5 | +| clicks | -35 dB | 0 | 0 | 813 / 809 | 60 s / 3 | +| clicks | -20 dB | 0 | 0 | 814 / 810 | 60 s / 4 | +| clicks | -5 dB | 0 | 0 | 815 / 813 | 60 s / 4 | +| music | -35 dB | 0 | 0 | 814 / 813 | 60 s / 4 | +| music | -20 dB | 0 | 0 | 814 / 810 | 60 s / 4 | +| music | -5 dB | 0 | 0 | 815 / 813 | 60 s / 4 | + +example hallucinated text: +ultra music_-35 | novad: | vad: I don't know, I don't know, I don't know, I don't know, +ultra music_-20 | novad: | vad: I don't know if you're not sure if you're not a mistake. +ultra music_-5 | novad: | vad: I don't know. +redux white_-20 | novad: | vad: you're a diff --git a/scripts/vad_bench/noise_dive/res_D_mit.md b/scripts/vad_bench/noise_dive/res_D_mit.md new file mode 100644 index 0000000..5a1c81f --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_D_mit.md @@ -0,0 +1,27 @@ +insert (60 s) frames called speech %, by mitigation (block-normalised logits, shipped segmenter smoothing not applied) +| file | model | thr 0.5 | thr 0.7 | thr 0.9 | runconf 2.5 | talk speech frames kept (outside insert, vs thr 0.5) runconf 2.5 | +|---|---|---|---|---|---|---| +| white_-35 | redux | 0 | 0 | 0 | 0 | 99.9% | +| white_-35 | ultra | 0 | 0 | 0 | 0 | 100.0% | +| white_-20 | redux | 0 | 0 | 0 | 0 | 99.9% | +| white_-20 | ultra | 27 | 13 | 4 | 0 | 99.9% | +| white_-5 | redux | 3 | 0 | 0 | 0 | 99.9% | +| white_-5 | ultra | 68 | 22 | 5 | 0 | 99.9% | +| pink_-35 | redux | 0 | 0 | 0 | 0 | 99.9% | +| pink_-35 | ultra | 0 | 0 | 0 | 0 | 100.0% | +| pink_-20 | redux | 0 | 0 | 0 | 0 | 99.9% | +| pink_-20 | ultra | 0 | 0 | 0 | 0 | 100.0% | +| pink_-5 | redux | 5 | 0 | 0 | 0 | 99.9% | +| pink_-5 | ultra | 23 | 1 | 0 | 0 | 100.0% | +| clicks_-35 | redux | 1 | 1 | 0 | 0 | 99.8% | +| clicks_-35 | ultra | 6 | 2 | 0 | 0 | 100.0% | +| clicks_-20 | redux | 5 | 2 | 0 | 0 | 99.8% | +| clicks_-20 | ultra | 25 | 18 | 8 | 3 | 100.0% | +| clicks_-5 | redux | 3 | 1 | 0 | 0 | 99.9% | +| clicks_-5 | ultra | 38 | 30 | 20 | 18 | 100.0% | +| music_-35 | redux | 12 | 2 | 0 | 0 | 99.8% | +| music_-35 | ultra | 0 | 0 | 0 | 0 | 100.0% | +| music_-20 | redux | 3 | 0 | 0 | 0 | 99.8% | +| music_-20 | ultra | 0 | 0 | 0 | 0 | 100.0% | +| music_-5 | redux | 2 | 0 | 0 | 0 | 99.8% | +| music_-5 | ultra | 38 | 27 | 10 | 7 | 100.0% | diff --git a/scripts/vad_bench/noise_dive/res_D_probs.md b/scripts/vad_bench/noise_dive/res_D_probs.md new file mode 100644 index 0000000..db33199 --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_D_probs.md @@ -0,0 +1,28 @@ +| file | model | insert frames called speech %: block-120 s norm (shipped) | whole-file norm | fixed norm A | median logit (shipped) | +|---|---|---|---|---|---| +| base | redux | 99 | 100 | 100 | +6.6 | +| base | ultra | 99 | 99 | 99 | +7.0 | +| white_-35 | redux | 0 | 0 | 0 | -8.0 | +| white_-35 | ultra | 0 | 0 | 0 | -3.5 | +| white_-20 | redux | 0 | 2 | 0 | -2.1 | +| white_-20 | ultra | 27 | 100 | 13 | -0.4 | +| white_-5 | redux | 3 | 3 | 96 | -1.0 | +| white_-5 | ultra | 68 | 87 | 100 | +0.3 | +| pink_-35 | redux | 0 | 0 | 0 | -9.4 | +| pink_-35 | ultra | 0 | 0 | 0 | -5.0 | +| pink_-20 | redux | 0 | 0 | 0 | -3.3 | +| pink_-20 | ultra | 0 | 0 | 0 | -1.9 | +| pink_-5 | redux | 5 | 2 | 24 | -1.0 | +| pink_-5 | ultra | 23 | 26 | 73 | -0.4 | +| clicks_-35 | redux | 1 | 0 | 0 | -6.7 | +| clicks_-35 | ultra | 6 | 2 | 0 | -3.6 | +| clicks_-20 | redux | 5 | 2 | 1 | -5.5 | +| clicks_-20 | ultra | 25 | 19 | 14 | -2.2 | +| clicks_-5 | redux | 3 | 2 | 1 | -5.8 | +| clicks_-5 | ultra | 38 | 32 | 27 | -0.9 | +| music_-35 | redux | 12 | 0 | 0 | -2.8 | +| music_-35 | ultra | 0 | 0 | 0 | -5.3 | +| music_-20 | redux | 3 | 0 | 0 | -3.2 | +| music_-20 | ultra | 0 | 0 | 0 | -3.8 | +| music_-5 | redux | 2 | 0 | 0 | -3.6 | +| music_-5 | ultra | 38 | 32 | 43 | -1.3 | diff --git a/scripts/vad_bench/noise_dive/res_D_silero.txt b/scripts/vad_bench/noise_dive/res_D_silero.txt new file mode 100644 index 0000000..3b532db --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_D_silero.txt @@ -0,0 +1,5 @@ +Silero VAD on the 300 s talk excerpt with a 60 s inserted block at 111.7 to 171.7 s. +Segments from the Silero run (files white_-20 and music_-35; the other files were not run with Silero): + segment 99.968 to 129.952 s is the only one touching the block, so 18.25 s of the block are kept + and 41.75 s are dropped. +Transcription with the Ultra model and the Silero segments, music at -35 dB: 20 words start inside the block. diff --git a/scripts/vad_bench/noise_dive/res_E.md b/scripts/vad_bench/noise_dive/res_E.md new file mode 100644 index 0000000..f855f0f --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_E.md @@ -0,0 +1,44 @@ + +### redux +| rule | speech F1 clean | speech F1 noisy (white+pink 20..0 dB) | noise-only FA frames % | embedded-stretch FA frames % | noise-only FA regions per clip | +|---|---|---|---|---|---| +| head thr 0.5 | 95.4 (P 98.6, R 92.3) | 94.7 | 97.8 | 55.2 | 2.1 | +| head thr 0.7 | 94.8 (P 98.9, R 91.0) | 93.9 | 76.5 | 10.1 | 13.8 | +| head thr 0.9 | 93.9 (P 99.3, R 89.1) | 92.0 | 16.3 | 0.1 | 12.9 | +| head thr 0.97 | 92.5 (P 99.5, R 86.4) | 89.4 | 1.0 | 0.0 | 1.1 | +| head thr 0.5, min_speech 0.25 s | 95.4 (P 98.7, R 92.3) | 94.7 | 97.7 | 51.7 | 2.2 | +| head thr 0.5, min_speech 0.5 s | 95.4 (P 98.7, R 92.3) | 94.9 | 97.4 | 43.3 | 2.2 | +| head thr 0.5, min_speech 1.0 s | 94.6 (P 98.7, R 90.8) | 94.1 | 96.6 | 29.0 | 2.2 | +| head thr 0.5 + energy gate (frame >= file P95 - 15 dB) | 86.1 (P 100.0, R 75.6) | 91.7 | 97.8 | 40.5 | 2.1 | +| head thr 0.5 + energy gate (frame >= file P95 - 25 dB) | 92.1 (P 99.9, R 85.4) | 94.3 | 97.8 | 53.2 | 2.1 | +| head thr 0.5 + energy gate (frame >= file P95 - 35 dB) | 94.6 (P 99.7, R 90.1) | 94.7 | 97.8 | 55.2 | 2.1 | +| head thr 0.9 + energy gate (frame >= file P95 - 25 dB) | 91.4 (P 99.9, R 84.1) | 91.8 | 16.3 | 0.1 | 12.9 | +| head thr 0.5, keep run only if median logit >= 2.5 | 95.4 (P 98.7, R 92.3) | 94.7 | 0.1 | 0.3 | 0.0 | +| head thr 0.5, keep run only if median logit >= 3.5 | 94.9 (P 98.7, R 91.4) | 93.7 | 0.0 | 0.2 | 0.0 | +| head thr 0.5, keep run only if median logit >= 5.0 | 93.5 (P 98.7, R 88.8) | 90.2 | 0.0 | 0.1 | 0.0 | +| Silero 0.5 AND head 0.5 | 93.4 (P 99.4, R 88.2) | 91.8 | 0.0 | 0.0 | 0.0 | +| Silero 0.5 AND head 0.9 | 93.0 (P 99.5, R 87.4) | 90.8 | 0.0 | 0.0 | 0.0 | +| two-stage (Silero + head extend/fill, fusion rule 6) | 95.4 (P 98.6, R 92.4) | 94.8 | 0.0 | 0.0 | 0.0 | +| Silero 0.5 alone (reference, its own post) | 93.9 (P 99.1, R 89.2) | 92.4 | 0.0 | 0.0 | 0.0 | + +### ultra +| rule | speech F1 clean | speech F1 noisy (white+pink 20..0 dB) | noise-only FA frames % | embedded-stretch FA frames % | noise-only FA regions per clip | +|---|---|---|---|---|---| +| head thr 0.5 | 95.0 (P 98.5, R 91.7) | 93.6 | 99.4 | 17.3 | 1.1 | +| head thr 0.7 | 94.2 (P 98.8, R 90.0) | 91.8 | 94.2 | 3.6 | 5.1 | +| head thr 0.9 | 92.4 (P 99.2, R 86.5) | 87.5 | 25.8 | 0.0 | 18.2 | +| head thr 0.97 | 90.0 (P 99.5, R 82.1) | 80.6 | 0.5 | 0.0 | 0.7 | +| head thr 0.5, min_speech 0.25 s | 95.0 (P 98.5, R 91.7) | 93.6 | 99.4 | 15.4 | 1.1 | +| head thr 0.5, min_speech 0.5 s | 95.0 (P 98.5, R 91.7) | 93.5 | 99.4 | 11.9 | 1.1 | +| head thr 0.5, min_speech 1.0 s | 93.8 (P 98.6, R 89.6) | 91.9 | 99.4 | 6.9 | 1.1 | +| head thr 0.5 + energy gate (frame >= file P95 - 15 dB) | 86.0 (P 100.0, R 75.4) | 90.9 | 99.4 | 17.3 | 1.1 | +| head thr 0.5 + energy gate (frame >= file P95 - 25 dB) | 92.0 (P 99.9, R 85.2) | 93.3 | 99.4 | 17.3 | 1.1 | +| head thr 0.5 + energy gate (frame >= file P95 - 35 dB) | 94.4 (P 99.7, R 89.6) | 93.6 | 99.4 | 17.3 | 1.1 | +| head thr 0.9 + energy gate (frame >= file P95 - 25 dB) | 90.4 (P 99.9, R 82.5) | 87.3 | 25.8 | 0.0 | 18.2 | +| head thr 0.5, keep run only if median logit >= 2.5 | 94.5 (P 98.5, R 90.8) | 90.3 | 0.0 | 0.0 | 0.0 | +| head thr 0.5, keep run only if median logit >= 3.5 | 93.6 (P 98.6, R 89.0) | 85.3 | 0.0 | 0.0 | 0.0 | +| head thr 0.5, keep run only if median logit >= 5.0 | 89.5 (P 98.6, R 82.0) | 67.2 | 0.0 | 0.0 | 0.0 | +| Silero 0.5 AND head 0.5 | 93.3 (P 99.4, R 87.8) | 91.2 | 0.0 | 0.0 | 0.0 | +| Silero 0.5 AND head 0.9 | 91.7 (P 99.5, R 85.0) | 86.7 | 0.0 | 0.0 | 0.0 | +| two-stage (Silero + head extend/fill, fusion rule 6) | 95.1 (P 98.5, R 92.0) | 94.2 | 0.0 | 0.0 | 0.0 | +| Silero 0.5 alone (reference, its own post) | 93.9 (P 99.1, R 89.2) | 92.4 | 0.0 | 0.0 | 0.0 | diff --git a/scripts/vad_bench/noise_dive/res_E2_talk.txt b/scripts/vad_bench/noise_dive/res_E2_talk.txt new file mode 100644 index 0000000..0269f54 --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_E2_talk.txt @@ -0,0 +1,15 @@ +talk 1168 s; Silero speech time 93.4% + +redux: speech time % | agreement with Silero (F1 of mask vs Silero mask) | frames removed vs head 0.5 (% of its speech frames) + head 0.5 97.0% | F1 vs Silero 98.0 (P 96.2 R 99.9) | removed 0.00% + head 0.7 95.9% | F1 vs Silero 98.4 (P 97.1 R 99.7) | removed 1.05% + head 0.9 94.1% | F1 vs Silero 98.5 (P 98.1 R 98.8) | removed 2.93% + runconf 2.5 96.7% | F1 vs Silero 98.1 (P 96.5 R 99.9) | removed 0.29% + runconf 3.5 96.5% | F1 vs Silero 98.1 (P 96.5 R 99.7) | removed 0.50% + +ultra: speech time % | agreement with Silero (F1 of mask vs Silero mask) | frames removed vs head 0.5 (% of its speech frames) + head 0.5 96.3% | F1 vs Silero 98.3 (P 96.8 R 99.8) | removed 0.00% + head 0.7 95.1% | F1 vs Silero 98.6 (P 97.7 R 99.5) | removed 1.24% + head 0.9 92.9% | F1 vs Silero 98.4 (P 98.6 R 98.1) | removed 3.57% + runconf 2.5 96.1% | F1 vs Silero 98.3 (P 97.0 R 99.8) | removed 0.22% + runconf 3.5 96.0% | F1 vs Silero 98.3 (P 97.0 R 99.7) | removed 0.28% diff --git a/scripts/vad_bench/noise_dive/res_cli_check.txt b/scripts/vad_bench/noise_dive/res_cli_check.txt new file mode 100644 index 0000000..2d9c357 --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_cli_check.txt @@ -0,0 +1,17 @@ +gap-white20_00 redux stretch FP pct CLI 42.5 ref 42.5 | max|dp| 0.0003 +gap-white20_00 ultra stretch FP pct CLI 0.0 ref 0.0 | max|dp| 0.0075 +gap-white20_01 redux stretch FP pct CLI 57.4 ref 57.4 | max|dp| 0.0003 +gap-white20_01 ultra stretch FP pct CLI 0.0 ref 0.0 | max|dp| 0.0071 +gap-white5_00 redux stretch FP pct CLI 71.5 ref 71.5 | max|dp| 0.0002 +gap-white5_00 ultra stretch FP pct CLI 2.3 ref 2.0 | max|dp| 0.0119 +gap-white5_01 redux stretch FP pct CLI 56.4 ref 56.4 | max|dp| 0.0369 +gap-white5_01 ultra stretch FP pct CLI 0.8 ref 0.8 | max|dp| 0.0324 +gap-white0_00 redux stretch FP pct CLI 50.0 ref 50.0 | max|dp| 0.0067 +gap-white0_00 ultra stretch FP pct CLI 32.0 ref 31.5 | max|dp| 0.0167 +gap-white0_01 redux stretch FP pct CLI 40.8 ref 40.8 | max|dp| 0.0002 +gap-white0_01 ultra stretch FP pct CLI 19.4 ref 18.9 | max|dp| 0.0086 +amp -20 {'redux': np.float64(97.6), 'ultra': np.float64(98.9)} +amp -50 {'redux': np.float64(69.6), 'ultra': np.float64(97.9)} +amp -60 {'redux': np.float64(22.4), 'ultra': np.float64(85.1)} +amp -70 {'redux': np.float64(8.0), 'ultra': np.float64(42.9)} +amp -80 {'redux': np.float64(0.0), 'ultra': np.float64(6.9)} diff --git a/scripts/vad_bench/noise_dive/res_energy_corr.txt b/scripts/vad_bench/noise_dive/res_energy_corr.txt new file mode 100644 index 0000000..a0652ac --- /dev/null +++ b/scripts/vad_bench/noise_dive/res_energy_corr.txt @@ -0,0 +1,8 @@ +('redux', 'speech frames (clean+noisy speech clips)') corr(rel energy, logit) 0.46 over 152550 frames +('redux', 'non-speech frames in speech clips (gaps)') corr(rel energy, logit) 0.71 over 28764 frames +('redux', 'noise-only files') corr(rel energy, logit) -0.02 over 36000 frames +('redux', 'embedded noise stretch') corr(rel energy, logit) 0.31 over 37043 frames +('ultra', 'speech frames (clean+noisy speech clips)') corr(rel energy, logit) 0.40 over 152550 frames +('ultra', 'non-speech frames in speech clips (gaps)') corr(rel energy, logit) 0.43 over 28764 frames +('ultra', 'noise-only files') corr(rel energy, logit) 0.05 over 36000 frames +('ultra', 'embedded noise stretch') corr(rel energy, logit) 0.84 over 37043 frames diff --git a/scripts/vad_bench/noise_dive/sig.py b/scripts/vad_bench/noise_dive/sig.py new file mode 100644 index 0000000..cff91e0 --- /dev/null +++ b/scripts/vad_bench/noise_dive/sig.py @@ -0,0 +1,38 @@ +import numpy as np +SR=16000 +def rms(x): return float(np.sqrt(np.mean(np.square(x,dtype=np.float64)))) +def at_db(x,db): # set RMS to db dBFS + return (x/ (rms(x)+1e-12) * 10**(db/20)).astype(np.float32) +def white(n,r): return r.standard_normal(n) +def colored(n,r,alpha): # 1/f^alpha power spectrum (alpha=1 pink, 2 brown) + X=np.fft.rfft(r.standard_normal(n)); f=np.arange(len(X),dtype=float); f[0]=1 + y=np.fft.irfft(X/f**(alpha/2),n); return y +def shaped(n,r,spec): # noise with a given long-term magnitude spectrum (len n//2+1 interpolated) + X=np.fft.rfft(r.standard_normal(n)); f=np.linspace(0,1,len(X)); sp=np.interp(f,np.linspace(0,1,len(spec)),spec) + return np.fft.irfft(X*sp,n) +def ltass(utts,nfft=1024): + acc=np.zeros(nfft//2+1) + for u in utts: + for i in range(0,len(u)-nfft,nfft//2): + acc+=np.abs(np.fft.rfft(u[i:i+nfft]*np.hanning(nfft)))**2 + return np.sqrt(acc/acc.sum()) +def sine(n,f,db=-20): t=np.arange(n)/SR; return at_db(np.sin(2*np.pi*f*t),db) +def sweep(n,f0=50,f1=7500,db=-20): + t=np.arange(n)/SR; T=n/SR; ph=2*np.pi*(f0*t+(f1-f0)*t**2/(2*T)); return at_db(np.sin(ph),db) +def clicks(n,r,rate=3.0,db=-12,decay_ms=8): # claps/coughs-like: decaying noise bursts at Poisson times + y=np.zeros(n); L=int(SR*decay_ms/1000*6) + env=np.exp(-np.arange(L)/(SR*decay_ms/1000)) + for t in np.cumsum(r.exponential(1/rate,int(n/SR*rate*2))): + i=int(t*SR) + if i+L>=n: break + y[i:i+L]+=r.standard_normal(L)*env + return at_db(y,db)*1.0 +def hum(n,f=50,db=-25): + t=np.arange(n)/SR; y=sum(np.sin(2*np.pi*f*k*t)/k for k in (1,2,3,4,5)); return at_db(y,db) +def music(n,r,db=-20): # chords of harmonic tones, changing every 0.5 s + y=np.zeros(n); seg=SR//2; roots=[220,247,262,294,330,349,392] + for i in range(0,n,seg): + f0=r.choice(roots)*r.choice([0.5,1,2]); L=min(seg,n-i); t=np.arange(L)/SR + v=sum(np.sin(2*np.pi*f0*m*t)/m for m in (1,2,3,4,5)) + sum(np.sin(2*np.pi*f0*1.26*m*t)/m for m in (1,2,3)) + env=np.minimum(1,np.minimum(t/0.02,(L/SR-t)/0.05)); y[i:i+L]=v*env + return at_db(y,db) diff --git a/scripts/vad_bench/noise_dive/vadlib.py b/scripts/vad_bench/noise_dive/vadlib.py new file mode 100644 index 0000000..54a281b --- /dev/null +++ b/scripts/vad_bench/noise_dive/vadlib.py @@ -0,0 +1,94 @@ +"""Independent reference of the Moondream Parakeet VAD head path, built on HF transformers +(ParakeetFeatureExtractor for log-mel, ParakeetEncoderSubsamplingConv2D for the 8x subsampler) +plus the head structure documented in parakeet.cpp docs/ternary.md. Weights come from the HF safetensors.""" +import json, os, subprocess, numpy as np, torch, soundfile as sf +torch.set_num_threads(4) +from transformers.models.parakeet.configuration_parakeet import ParakeetEncoderConfig +from transformers.models.parakeet.modeling_parakeet import ParakeetEncoderSubsamplingConv2D +from transformers.models.parakeet.feature_extraction_parakeet import ParakeetFeatureExtractor +HERE = os.environ.get("NOISE_DIVE_WORK", os.path.dirname(os.path.abspath(__file__))) # holds build/, ref/, hfmeta/ +CLI = os.environ.get("PARAKEET_CLI", f"{HERE}/build/examples/cli/parakeet-cli") +SCR = os.environ.get("GGUF_ROOT", os.path.dirname(HERE)) # holds gguf/ and vad-card/ +GG = {'redux': f"{SCR}/vad-card/redux-vad.gguf", 'ultra': f"{SCR}/vad-card/ultra-vad-q8_0.gguf", 'ultra-f16': f"{SCR}/gguf/ultra-f16.gguf", 'redux-keep': f"{SCR}/gguf/redux-keep.gguf"} +FE = ParakeetFeatureExtractor(feature_size=128) +EPS = 1e-5 + +def raw_logmel(x): + """[T,128] un-normalised log-mel of a float32 mono 16 kHz array (HF code path).""" + w = torch.tensor(np.asarray(x, np.float32))[None, :] + pre = torch.cat([w[:, :1], w[:, 1:] - FE.preemphasis * w[:, :-1]], 1) + return FE._torch_extract_fbank_features(pre)[0] + +def norm_mel(m, mean=None, std=None, f64=True): + """per-feature (over time) mean/std normalisation as HF/NeMo (HF masks the last STFT frame: stats over n_samples//160 + frames); or fixed mean/std (len-128 tensors). f64=True accumulates in double like parakeet.cpp; HF uses float32.""" + n = m.shape[0] - 1 + d = m.double() if f64 else m + if mean is None: + mv = d[:n] + mean = mv.sum(0) / n + std = torch.sqrt(((mv - mean) ** 2).sum(0) / (n - 1)) + else: + mean, std = mean.to(d.dtype), std.to(d.dtype) + out = ((d - mean) / (std + EPS)).float() + return out[:n] # the masked last STFT frame is dropped (parakeet.cpp keeps n_samples//160 frames) + +class Ref: + def __init__(self, name): + self.name = name + W = np.load(f"{HERE}/ref/{name}_weights.npz") + cfg = ParakeetEncoderConfig(**{k: v for k, v in json.load(open(f"{HERE}/hfmeta/{name}.config.json"))['encoder_config'].items() if k != 'model_type'}) + self.sub = ParakeetEncoderSubsamplingConv2D(cfg).eval() + sd = {k[len('encoder.subsampling.'):]: torch.tensor(v.astype(np.float32)) for k, v in W.items() if k.startswith('encoder.subsampling.')} + self.sub.load_state_dict(sd) + g = lambda k: torch.tensor(W[k].astype(np.float32)) + self.pw, self.pb = g('vad_head.proj.weight')[:, :, 0], g('vad_head.proj.bias') + self.cw, self.cb = g('vad_head.ctx.weight'), g('vad_head.ctx.bias') + self.ow, self.ob = g('vad_head.out.weight')[0, :, 0], g('vad_head.out.bias')[0] + @torch.no_grad() + def sub_out(self, mel_norm): + return self.sub(mel_norm[None])[0] # [T/8,1024] + @torch.no_grad() + def head_logits(self, s): + h = torch.nn.functional.silu(s @ self.pw.T + self.pb) # [T,128] + c = torch.nn.functional.conv1d(h.T[None], self.cw, self.cb, padding=2)[0].T + c = torch.nn.functional.silu(c) + return c @ self.ow + self.ob # logits [T] + def logits(self, x, mean=None, std=None): + return self.head_logits(self.sub_out(norm_mel(raw_logmel(x), mean, std))).numpy() + def probs(self, x, **kw): + return 1 / (1 + np.exp(-self.logits(x, **kw))) + def feats(self, x, mean=None, std=None): + return self.sub_out(norm_mel(raw_logmel(x), mean, std)).numpy() + +_refs = {} +def ref(name): + if name not in _refs: _refs[name] = Ref(name) + return _refs[name] + +def cli_probs(name, wav, thr=None): + out = subprocess.run([CLI, 'vad', '--model', GG[name], '--input', wav, '--probabilities', '--threads', '4'], capture_output=True, text=True) + j = json.loads(out.stdout) + return np.array(j['probabilities'], np.float32) + +def wr(path, x): + sf.write(path, np.clip(x, -1, 1).astype(np.float32), 16000, subtype='FLOAT') + +BLOCK = 120 * 16000 +def logits_blocks(name, x, mean=None, std=None, block=BLOCK, tail=5 * 16000): + """logits with parakeet.cpp's 120 s blocks (own mel normalisation per block), or fixed mean/std if given.""" + r = ref(name); out = []; pos = 0; fs = 1280 + while True: + ln = min(block, len(x) - pos) + if len(x) - pos - ln < tail: ln = len(x) - pos + z = r.logits(x[pos:pos + ln], mean, std) + last = pos + ln >= len(x) + if not last: z = z[:block // fs] + out.append(z); pos += ln + if pos >= len(x): break + return np.concatenate(out) +def sig(z): return 1 / (1 + np.exp(-z)) +def frame_mask(spans, n, fs=0.08): + t = (np.arange(n) + 0.5) * fs; m = np.zeros(n, bool) + for a, b in spans: m |= (t >= a) & (t < b) + return m

p)_7+M8apS$0d7O%0G2)@{kW?|W%~sO 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 CFn?bF literal 0 HcmV?d00001 diff --git a/scripts/vad_bench/noise_dive/fig_offset_scale.png b/scripts/vad_bench/noise_dive/fig_offset_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..92527e7603a1490549d2596dc170720af553421a GIT binary patch literal 46320 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 N5o3+xhtF91e*plM;KKj_ literal 0 HcmV?d00001 diff --git a/scripts/vad_bench/noise_dive/fig_types_vs_level.png b/scripts/vad_bench/noise_dive/fig_types_vs_level.png new file mode 100644 index 0000000000000000000000000000000000000000..c03acc6482955495fb46486b92f8c7f427a11a6b GIT binary patch literal 136616 zcmcG$WmuK%)-}B75Trwp6c84zB8ZfPAR$O2NJvR{w+c!KA|(w9C?VYq(jXurAuTMC zkXAtIo7cVX_j&j69LINjzrG)PdsA1O=RD^eW6UvExW-*YQep;T6beOpM@e22g~H=T zp|ENR@!>nSYl$Y{A8~gDeRnMNZ2Eq0HZ zT9lR5uP3AT@Pxumg=3v!cz;(yqK|30d4Kk0i>R8 z$i^aAkj3$TehNQ$Pokqi`QLvKhUO;vpFbyLL(9>`|JSdRCu9>V|JScS@6*8J{I6e^ zw|xKj)qg#}n=9J4?)=yN?_K!+{q{RJyYp>0jlRE?znb|EKYsFLdwsG3&BM#fdoG4q zQdUL=S zqW9>msE7|27nhZdEj!}U(NJ6hZ~gtf=K3o&KK?1((89t(TReJITkm_( zFXLwa@MmN!1>#Zc1viV+(k@E)?BK$UlvGp-H7*QgNPc{)7*iltPG)lV?s;r%Y;rof zcOzHgdxwS;;kDkpeOp**w3E47@AWkV%`GjIF7DAjkbG5#>2|2L zy?XlmIU%w*=mg!`D1}JcHuxzX9v+%sE_p8Kj0vu(q^@2v;@TTjur?d}s>?=Mzu%~(qNF4zCl4MN7$8n6^V@fX=hXSzwwr#_YyRg?!kag5UUQng z7)i^I9zVT&H!V0RiH=#qTO(hr!0MxK5o1vqpGSJ+V6e1!`ax_Sn9TX zHz6@w)yU`~@|De(JT!mO@w@ts2Oe3(%9?iCIkKT*($=bf?jHFxqR%HhZs8C_`Y54uUw-0MRsIb(_eCjxwa%w@= z$-uzit0#%9(kJ9Oj}LcY6YF%mrk|Z>dJJy}QC;|p-crP8#{w4We_gqHb$h9oik_aHyTgFOX|7qp-TfNumg&QX?fhh9 zWQrCR94Lb%RiC6QDk>`a_wTb`zfKGHd^Gx=V)X0RSU7tFI23%*!6;qnmE#^CMt?VOk7-+kCx6)N;99j#JCAN zru)g;x8|{S$IZvb{u2HNFBxxgN=h=pnH~Q6RpI3C@48>*6%~*U@Esg}QJUcNDREGC zbab%7@BI1mM?+6ful~WE@xmfBm9@b>yl9vb$K%Mzb2*wk?d|O^6B6(g6&0Vtxf&ci z&&9 z^78NXvAvtSN3Y`I>b7|>iG)z#M-u07JQI2ssGeHt3tHabe3mzUSsr9bH=xFMB9 z>!hoj*uEwXS=P+V4B6VMDiOUvI4s}4e>b(YC3bUj3pJPa`xC|Jc2!8|rCqv%lM~sc zOP4|&sHmtYsHkul85!YdCVAie{Q0xeojW0Maa!ScaG7Z5nFtE;O} zys|c468rLH@Q+WaIPWvo?qxXG{XGqea&>c~xOkDo𝔗|B|>k1KeAAmmFo}h<_~l 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