Closed at this problem framing: converged results beside pre-convergence ones, empty-result and competitiveness notice, viewer with unseen conditions
Browse files- .gitattributes +4 -0
- README.md +18 -5
- data.json +0 -0
- figures/fig1_recall_by_condition.dark.png +2 -2
- figures/fig1_recall_by_condition.light.png +2 -2
- figures/fig1_recall_by_condition.test_heldout.dark.png +0 -0
- figures/fig1_recall_by_condition.test_heldout.light.png +0 -0
- figures/fig1_recall_by_condition.test_heldout_v2.dark.png +0 -0
- figures/fig1_recall_by_condition.test_heldout_v2.light.png +0 -0
- figures/fig2_union_complementarity.dark.png +0 -0
- figures/fig2_union_complementarity.light.png +0 -0
- figures/fig2_union_complementarity.test_heldout.dark.png +0 -0
- figures/fig2_union_complementarity.test_heldout.light.png +0 -0
- figures/fig2_union_complementarity.test_heldout_v2.dark.png +0 -0
- figures/fig2_union_complementarity.test_heldout_v2.light.png +0 -0
- figures/fig3_recall_by_category.dark.png +0 -0
- figures/fig3_recall_by_category.light.png +0 -0
- figures/fig3_recall_by_category.test_heldout.dark.png +0 -0
- figures/fig3_recall_by_category.test_heldout.light.png +0 -0
- figures/fig3_recall_by_category.test_heldout_v2.dark.png +0 -0
- figures/fig3_recall_by_category.test_heldout_v2.light.png +0 -0
- figures/fig4_recall_vs_false_alarms.dark.png +0 -0
- figures/fig4_recall_vs_false_alarms.light.png +0 -0
- figures/fig4_recall_vs_false_alarms.test_heldout.dark.png +0 -0
- figures/fig4_recall_vs_false_alarms.test_heldout.light.png +0 -0
- figures/fig4_recall_vs_false_alarms.test_heldout_v2.dark.png +0 -0
- figures/fig4_recall_vs_false_alarms.test_heldout_v2.light.png +0 -0
- figures/pre-convergence/fig1_recall_by_condition.dark.png +3 -0
- figures/pre-convergence/fig1_recall_by_condition.light.png +3 -0
- figures/pre-convergence/fig1_recall_by_condition.test_heldout.dark.png +0 -0
- figures/pre-convergence/fig1_recall_by_condition.test_heldout.light.png +0 -0
- figures/pre-convergence/fig2_union_complementarity.dark.png +0 -0
- figures/pre-convergence/fig2_union_complementarity.light.png +0 -0
- figures/pre-convergence/fig2_union_complementarity.test_heldout.dark.png +0 -0
- figures/pre-convergence/fig2_union_complementarity.test_heldout.light.png +0 -0
- figures/pre-convergence/fig3_recall_by_category.dark.png +0 -0
- figures/pre-convergence/fig3_recall_by_category.light.png +0 -0
- figures/pre-convergence/fig3_recall_by_category.test_heldout.dark.png +0 -0
- figures/pre-convergence/fig3_recall_by_category.test_heldout.light.png +0 -0
- figures/pre-convergence/fig4_recall_vs_false_alarms.dark.png +0 -0
- figures/pre-convergence/fig4_recall_vs_false_alarms.light.png +0 -0
- figures/pre-convergence/fig4_recall_vs_false_alarms.test_heldout.dark.png +0 -0
- figures/pre-convergence/fig4_recall_vs_false_alarms.test_heldout.light.png +0 -0
- figures/val_curve.dark.png +3 -0
- figures/val_curve.light.png +3 -0
- index.html +145 -66
.gitattributes
CHANGED
|
@@ -35,3 +35,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
figures/fig1_recall_by_condition.dark.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
figures/fig1_recall_by_condition.light.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
figures/fig1_recall_by_condition.dark.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
figures/fig1_recall_by_condition.light.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
figures/pre-convergence/fig1_recall_by_condition.dark.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
figures/pre-convergence/fig1_recall_by_condition.light.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
figures/val_curve.dark.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
figures/val_curve.light.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -18,17 +18,30 @@ tags:
|
|
| 18 |
- redaction
|
| 19 |
- speech
|
| 20 |
- negative-results
|
|
|
|
| 21 |
---
|
| 22 |
|
| 23 |
# Bleep
|
| 24 |
|
| 25 |
**Find *where* sensitive speech is, without transcribing *what* was said.**
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
No audio is shipped — the corpus was rendered with macOS system voices, which
|
| 34 |
are licensed for use and not for republication. The viewer draws a downsampled
|
|
|
|
| 18 |
- redaction
|
| 19 |
- speech
|
| 20 |
- negative-results
|
| 21 |
+
- closed
|
| 22 |
---
|
| 23 |
|
| 24 |
# Bleep
|
| 25 |
|
| 26 |
**Find *where* sensitive speech is, without transcribing *what* was said.**
|
| 27 |
|
| 28 |
+
> **An empty result means nothing was flagged. It does not mean nothing is there.**
|
| 29 |
+
>
|
| 30 |
+
> This detector is not competitive with an ASR-based pipeline outside the
|
| 31 |
+
> conditions it was trained on. See the
|
| 32 |
+
> [model card](https://huggingface.co/NagaYu/bleep-0.09b).
|
| 33 |
+
|
| 34 |
+
**Status: closed at this problem framing.** A perfect transcript fed to the
|
| 35 |
+
same text rules scores 0.986, so the room above the ASR baseline (0.826) is
|
| 36 |
+
bounded by the ASR's word errors. Retrained to convergence on 2026-10-18,
|
| 37 |
+
Bleep reaches 0.903 at the baseline's false-alarm rate on the degradation types
|
| 38 |
+
it was trained on (0.156 before convergence); on unseen ones it raises 3.3–3.5×
|
| 39 |
+
the baseline's false alarms.
|
| 40 |
+
|
| 41 |
+
A results dashboard with a timeline viewer over real test utterances — from
|
| 42 |
+
conditions the model was trained on and from conditions it never saw — showing
|
| 43 |
+
ground truth against what each detector actually found, with the
|
| 44 |
+
pre-convergence model drawn as its own lane.
|
| 45 |
|
| 46 |
No audio is shipped — the corpus was rendered with macOS system voices, which
|
| 47 |
are licensed for use and not for republication. The viewer draws a downsampled
|
data.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
figures/fig1_recall_by_condition.dark.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
figures/fig1_recall_by_condition.light.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
figures/fig1_recall_by_condition.test_heldout.dark.png
CHANGED
|
|
figures/fig1_recall_by_condition.test_heldout.light.png
CHANGED
|
|
figures/fig1_recall_by_condition.test_heldout_v2.dark.png
ADDED
|
figures/fig1_recall_by_condition.test_heldout_v2.light.png
ADDED
|
figures/fig2_union_complementarity.dark.png
CHANGED
|
|
figures/fig2_union_complementarity.light.png
CHANGED
|
|
figures/fig2_union_complementarity.test_heldout.dark.png
CHANGED
|
|
figures/fig2_union_complementarity.test_heldout.light.png
CHANGED
|
|
figures/fig2_union_complementarity.test_heldout_v2.dark.png
ADDED
|
figures/fig2_union_complementarity.test_heldout_v2.light.png
ADDED
|
figures/fig3_recall_by_category.dark.png
CHANGED
|
|
figures/fig3_recall_by_category.light.png
CHANGED
|
|
figures/fig3_recall_by_category.test_heldout.dark.png
CHANGED
|
|
figures/fig3_recall_by_category.test_heldout.light.png
CHANGED
|
|
figures/fig3_recall_by_category.test_heldout_v2.dark.png
ADDED
|
figures/fig3_recall_by_category.test_heldout_v2.light.png
ADDED
|
figures/fig4_recall_vs_false_alarms.dark.png
CHANGED
|
|
figures/fig4_recall_vs_false_alarms.light.png
CHANGED
|
|
figures/fig4_recall_vs_false_alarms.test_heldout.dark.png
CHANGED
|
|
figures/fig4_recall_vs_false_alarms.test_heldout.light.png
CHANGED
|
|
figures/fig4_recall_vs_false_alarms.test_heldout_v2.dark.png
ADDED
|
figures/fig4_recall_vs_false_alarms.test_heldout_v2.light.png
ADDED
|
figures/pre-convergence/fig1_recall_by_condition.dark.png
ADDED
|
Git LFS Details
|
figures/pre-convergence/fig1_recall_by_condition.light.png
ADDED
|
Git LFS Details
|
figures/pre-convergence/fig1_recall_by_condition.test_heldout.dark.png
ADDED
|
figures/pre-convergence/fig1_recall_by_condition.test_heldout.light.png
ADDED
|
figures/pre-convergence/fig2_union_complementarity.dark.png
ADDED
|
figures/pre-convergence/fig2_union_complementarity.light.png
ADDED
|
figures/pre-convergence/fig2_union_complementarity.test_heldout.dark.png
ADDED
|
figures/pre-convergence/fig2_union_complementarity.test_heldout.light.png
ADDED
|
figures/pre-convergence/fig3_recall_by_category.dark.png
ADDED
|
figures/pre-convergence/fig3_recall_by_category.light.png
ADDED
|
figures/pre-convergence/fig3_recall_by_category.test_heldout.dark.png
ADDED
|
figures/pre-convergence/fig3_recall_by_category.test_heldout.light.png
ADDED
|
figures/pre-convergence/fig4_recall_vs_false_alarms.dark.png
ADDED
|
figures/pre-convergence/fig4_recall_vs_false_alarms.light.png
ADDED
|
figures/pre-convergence/fig4_recall_vs_false_alarms.test_heldout.dark.png
ADDED
|
figures/pre-convergence/fig4_recall_vs_false_alarms.test_heldout.light.png
ADDED
|
figures/val_curve.dark.png
ADDED
|
Git LFS Details
|
figures/val_curve.light.png
ADDED
|
Git LFS Details
|
index.html
CHANGED
|
@@ -46,6 +46,10 @@
|
|
| 46 |
border:1px solid var(--rule);border-radius:9px;text-decoration:none;
|
| 47 |
color:var(--ink);background:var(--panel);font-size:.92rem}
|
| 48 |
.links a:hover{border-color:var(--a)}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
.callout{background:var(--warn-bg);border-left:3px solid var(--warn-br);
|
| 50 |
border-radius:0 10px 10px 0;padding:18px 22px;margin:26px 0}
|
| 51 |
.callout h2{margin-top:0}
|
|
@@ -92,42 +96,63 @@
|
|
| 92 |
<a href="https://huggingface.co/datasets/NagaYu/bleep-spans">📊 Dataset</a>
|
| 93 |
</div>
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
<div class="callout">
|
| 96 |
-
<h2>
|
| 97 |
-
<p>
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
<div class="tablewrap"><table id="armTable"></table></div>
|
| 101 |
<p class="note" id="armNote"></p>
|
| 102 |
</div>
|
| 103 |
|
| 104 |
-
<h2>
|
| 105 |
-
<p
|
| 106 |
-
|
| 107 |
|
| 108 |
-
<
|
| 109 |
-
<p id="whyCond">…</p>
|
| 110 |
|
| 111 |
-
<h3>
|
| 112 |
-
<p
|
| 113 |
-
rules — scores 0.96–1.00 in every condition. So every baseline miss is an ASR
|
| 114 |
-
<em>word</em> error rather than a detector gap. That one arm turns “the
|
| 115 |
-
baseline failed” into “the baseline failed here, for this reason”.</p>
|
| 116 |
|
| 117 |
-
<h3>
|
| 118 |
<p>1500 steps, batch 4, ~1.8 epochs over 9.15 hours — 70 minutes on one
|
| 119 |
-
consumer GPU.
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
<h3>One directional hint</h3>
|
| 123 |
<p id="whyCat">…</p>
|
| 124 |
|
| 125 |
-
<h2>What
|
| 126 |
<p id="heldUp">…</p>
|
| 127 |
|
| 128 |
<h2>Timeline viewer</h2>
|
| 129 |
-
<p>Real utterances
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
<p class="note"><strong>No audio is shipped.</strong> These recordings were
|
| 132 |
rendered with the macOS system voices, which are licensed for use and not for
|
| 133 |
republication as a corpus. What you see is a downsampled amplitude envelope —
|
|
@@ -140,7 +165,8 @@
|
|
| 140 |
<div class="legend">
|
| 141 |
<span><i class="sw" style="background:var(--neutral)"></i>ground truth</span>
|
| 142 |
<span><i class="sw" style="background:var(--a)"></i>ASR + text PII</span>
|
| 143 |
-
<span><i class="sw" style="background:var(--c)"></i>Bleep</span>
|
|
|
|
| 144 |
<span><i class="sw" style="background:var(--d)"></i>union</span>
|
| 145 |
</div>
|
| 146 |
</div>
|
|
@@ -150,15 +176,16 @@
|
|
| 150 |
<img src="figures/fig1_recall_by_condition.light.png"
|
| 151 |
alt="Recall by degradation condition for each arm, training conditions"
|
| 152 |
id="figCondLight">
|
| 153 |
-
<figcaption>Training conditions. The subtitle is generated
|
| 154 |
-
the chart cannot assert a conclusion the numbers do not
|
| 155 |
-
|
| 156 |
</figure>
|
| 157 |
<figure>
|
| 158 |
-
<img src="figures/
|
| 159 |
-
alt="Recall
|
| 160 |
-
<figcaption>
|
| 161 |
-
|
|
|
|
| 162 |
</figure>
|
| 163 |
|
| 164 |
<h2>Recall by category</h2>
|
|
@@ -166,24 +193,32 @@
|
|
| 166 |
<img src="figures/fig3_recall_by_category.light.png"
|
| 167 |
alt="Recall by category of sensitive speech" id="figCatLight">
|
| 168 |
<figcaption>★ marks the failure modes reported for transcription-first
|
| 169 |
-
pipelines.
|
|
|
|
| 170 |
</figure>
|
| 171 |
|
| 172 |
<h2>What this is not</h2>
|
| 173 |
-
<p><strong>Not a redaction guarantee.</strong>
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
<p><strong>Not trained on real audio.</strong> Every recording is synthetic.
|
| 179 |
No real call, no clinical recording, nothing derived from either, at any
|
| 180 |
stage. That is also a limitation: TTS speech is far easier to transcribe than
|
| 181 |
conversation, which is part of why the baseline looks so strong here.</p>
|
|
|
|
|
|
|
| 182 |
|
| 183 |
<div class="foot">
|
| 184 |
<p id="foot">…</p>
|
| 185 |
<p>Apache-2.0. Negative results are cheap to hide and expensive to
|
| 186 |
-
rediscover.</p>
|
| 187 |
</div>
|
| 188 |
</div>
|
| 189 |
|
|
@@ -194,74 +229,116 @@ const el = (t, a = {}) => { const n = document.createElementNS(NS, t);
|
|
| 194 |
const f3 = v => (v === null || v === undefined || Number.isNaN(v)) ? "—" : v.toFixed(3);
|
| 195 |
|
| 196 |
fetch("data.json").then(r => r.json()).then(D => {
|
| 197 |
-
const S = D.summary.test,
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
const rows = [
|
| 201 |
["A — ASR (whisper-tiny.en) + text PII", arms.A, false],
|
| 202 |
["B — ASR (whisper-small.en) + text PII", arms.B, false],
|
| 203 |
-
["C — Bleep alone", arms.C, true],
|
| 204 |
-
["
|
|
|
|
|
|
|
| 205 |
["oracle — perfect transcript + same rules", arms.oracle, false],
|
| 206 |
].filter(r => r[1]);
|
| 207 |
const t = document.getElementById("armTable");
|
| 208 |
-
t.innerHTML = "<tr><th>arm</th><th style='text-align:right'>recall</th>"
|
| 209 |
+ "<th style='text-align:right'>false alarms/hour</th></tr>"
|
| 210 |
+ rows.map(([name, a, hero]) =>
|
| 211 |
`<tr class="${hero ? "hero" : ""}"><td>${name}</td>`
|
| 212 |
+ `<td class="num">${f3(a.recall)}</td>`
|
| 213 |
-
+ `<td class="num">${a.fa_per_hour
|
| 214 |
document.getElementById("armNote").textContent =
|
| 215 |
-
`${S.n_utterances} utterances (${S.audio_hours.toFixed(2)} h)
|
| 216 |
-
+ `
|
| 217 |
-
+ `
|
| 218 |
-
+ `
|
| 219 |
-
+ `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
// ---- data-derived prose ---------------------------------------------
|
| 222 |
-
const pc = S.per_condition, ph =
|
| 223 |
document.getElementById("whyCond").textContent =
|
| 224 |
`On the training-condition test split the ASR baseline is essentially flat: `
|
| 225 |
+ `${f3(pc.clean.A)} on clean audio and ${f3(pc.telephony_8k.A)} at 8 kHz — `
|
| 226 |
+ `i.e. better than clean. A band-limit plus G.711 is not a hard condition `
|
| 227 |
-
+ `for a modern ASR on hyper-articulated TTS speech. The collapse
|
| 228 |
+ `only in the harsher held-out conditions: `
|
| 229 |
+ `${f3(ph.overlap_0db_three_way.A)} at 0 dB three-way overlap and `
|
| 230 |
-
+ `${f3(ph.telephony_overlap_3db.A)} at narrowband-plus-overlap.
|
| 231 |
-
+ `
|
|
|
|
|
|
|
| 232 |
|
| 233 |
-
const cat = S.per_category;
|
| 234 |
-
const best = Object.entries(
|
| 235 |
.sort((a, b) => b[1].C - a[1].C).slice(0, 2);
|
| 236 |
document.getElementById("whyCat").textContent =
|
| 237 |
-
`Bleep's per-category recall
|
| 238 |
-
+ `Its best categories
|
| 239 |
k.replace(/_/g, " ").toLowerCase() + " (" + f3(v.C) + ")").join(" and ")}`
|
| 240 |
+ ` — the two with the most distinctive rhythm — against ~0.00 for run-on `
|
| 241 |
-
+ `digits, money and identifiers.
|
| 242 |
-
+ `
|
| 243 |
|
| 244 |
const sp = S.speed.bleep, asr = S.speed.asr_arms_rtf;
|
| 245 |
document.getElementById("heldUp").textContent =
|
| 246 |
`Speed: ${sp.rtf_median} real-time factor on CPU with the front end `
|
| 247 |
+ `included — ${sp.xrealtime_best}× real time, `
|
| 248 |
+ `${(asr.A / sp.rtf_median).toFixed(1)}× faster than the cheapest ASR arm `
|
| 249 |
-
+ `and ${(asr.B / sp.rtf_median).toFixed(0)}× faster than the better one
|
| 250 |
-
+ `
|
| 251 |
-
+ `
|
| 252 |
-
+ `
|
| 253 |
-
+ `
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
document.getElementById("foot").textContent =
|
| 256 |
-
`
|
| 257 |
-
+ `
|
|
|
|
| 258 |
|
| 259 |
// ---- timeline viewer -------------------------------------------------
|
| 260 |
const pick = document.getElementById("pick");
|
| 261 |
D.examples.forEach((e, i) => {
|
| 262 |
const o = document.createElement("option");
|
| 263 |
o.value = i;
|
| 264 |
-
o.textContent = `${e.
|
|
|
|
| 265 |
+ `(${e.truth.length} sensitive region${e.truth.length === 1 ? "" : "s"})`;
|
| 266 |
pick.appendChild(o);
|
| 267 |
});
|
|
@@ -270,6 +347,7 @@ fetch("data.json").then(r => r.json()).then(D => {
|
|
| 270 |
["ground truth", "truth", "var(--neutral)"],
|
| 271 |
["ASR + text PII", "asr", "var(--a)"],
|
| 272 |
["Bleep", "bleep", "var(--c)"],
|
|
|
|
| 273 |
["union", "union", "var(--d)"],
|
| 274 |
];
|
| 275 |
|
|
@@ -345,7 +423,8 @@ fetch("data.json").then(r => r.json()).then(D => {
|
|
| 345 |
// dark-mode figure swap
|
| 346 |
const swap = dark => {
|
| 347 |
[["figCondLight", "fig1_recall_by_condition"],
|
| 348 |
-
["
|
|
|
|
| 349 |
["figCatLight", "fig3_recall_by_category"]].forEach(([id, base]) => {
|
| 350 |
const n = document.getElementById(id);
|
| 351 |
if (n) n.src = `figures/${base}.${dark ? "dark" : "light"}.png`;
|
|
|
|
| 46 |
border:1px solid var(--rule);border-radius:9px;text-decoration:none;
|
| 47 |
color:var(--ink);background:var(--panel);font-size:.92rem}
|
| 48 |
.links a:hover{border-color:var(--a)}
|
| 49 |
+
.banner{border:2px solid var(--warn-br);background:var(--warn-bg);border-radius:12px;
|
| 50 |
+
padding:14px 18px;margin:18px 0 6px;font-size:1rem}
|
| 51 |
+
.banner p{margin:0 0 4px}
|
| 52 |
+
.banner p:last-child{margin:0}
|
| 53 |
.callout{background:var(--warn-bg);border-left:3px solid var(--warn-br);
|
| 54 |
border-radius:0 10px 10px 0;padding:18px 22px;margin:26px 0}
|
| 55 |
.callout h2{margin-top:0}
|
|
|
|
| 96 |
<a href="https://huggingface.co/datasets/NagaYu/bleep-spans">📊 Dataset</a>
|
| 97 |
</div>
|
| 98 |
|
| 99 |
+
<div class="banner" role="note">
|
| 100 |
+
<p><strong>An empty result means nothing was flagged. It does not mean nothing is there.</strong></p>
|
| 101 |
+
<p>This detector is not competitive with an ASR-based pipeline outside the
|
| 102 |
+
conditions it was trained on. See the
|
| 103 |
+
<a href="https://huggingface.co/NagaYu/bleep-0.09b">model card</a>.</p>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
<div class="callout">
|
| 107 |
+
<h2>STATUS: closed at this problem framing</h2>
|
| 108 |
+
<p><strong>The oracle arm — a perfect transcript fed to the same text rules —
|
| 109 |
+
scores <span id="oracleRecall">…</span>. <span id="missSplit">…</span> At this
|
| 110 |
+
problem framing the room above the baseline is bounded by that gap —
|
| 111 |
+
<span id="headroom">…</span> recall on the test split — and this is the
|
| 112 |
+
measurement that establishes it.</strong></p>
|
| 113 |
+
<p>Retrained to convergence on 2026-10-18. Pre-convergence numbers are kept
|
| 114 |
+
beside the new ones. These are final; there will not be a third training
|
| 115 |
+
run.</p>
|
| 116 |
<div class="tablewrap"><table id="armTable"></table></div>
|
| 117 |
<p class="note" id="armNote"></p>
|
| 118 |
</div>
|
| 119 |
|
| 120 |
+
<h2>Where it does not hold</h2>
|
| 121 |
+
<p id="heldoutText">…</p>
|
| 122 |
+
<div class="tablewrap"><table id="heldTable"></table></div>
|
| 123 |
|
| 124 |
+
<h2>Why it was negative before convergence</h2>
|
|
|
|
| 125 |
|
| 126 |
+
<h3>The training conditions were too mild</h3>
|
| 127 |
+
<p id="whyCond">…</p>
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
<h3>The model was badly undertrained</h3>
|
| 130 |
<p>1500 steps, batch 4, ~1.8 epochs over 9.15 hours — 70 minutes on one
|
| 131 |
+
consumer GPU. The validation loss recorded during that run (0.218, 0.200,
|
| 132 |
+
0.191, 0.203, 0.192, 0.183) was measured on freshly re-sampled crops, rose
|
| 133 |
+
once, and ended under a learning rate already annealed to 2% of its peak: a
|
| 134 |
+
stopped run, not a converged one. (This page previously said the loss was
|
| 135 |
+
"still falling monotonically"; that was not accurate.) The retrain ran to a
|
| 136 |
+
stopping rule fixed and hashed before it started, and stopped by that rule.</p>
|
| 137 |
+
<figure>
|
| 138 |
+
<img src="figures/val_curve.light.png" alt="Dev loss against training step for the retrain"
|
| 139 |
+
id="figValLight">
|
| 140 |
+
<figcaption>The retrain's dev loss on a fixed set of crops. The dashed line
|
| 141 |
+
is the pre-convergence checkpoint on the same crops.</figcaption>
|
| 142 |
+
</figure>
|
| 143 |
|
| 144 |
<h3>One directional hint</h3>
|
| 145 |
<p id="whyCat">…</p>
|
| 146 |
|
| 147 |
+
<h2>What is complete and load-bearing</h2>
|
| 148 |
<p id="heldUp">…</p>
|
| 149 |
|
| 150 |
<h2>Timeline viewer</h2>
|
| 151 |
+
<p>Real utterances: the first ones from <code>test</code> (conditions the
|
| 152 |
+
model was trained on) and from <code>test_heldout_v2</code> (conditions it
|
| 153 |
+
never saw). Ground truth against what each detector actually found, at the
|
| 154 |
+
operating points the benchmark reports. The pre-convergence model has its own
|
| 155 |
+
lane.</p>
|
| 156 |
<p class="note"><strong>No audio is shipped.</strong> These recordings were
|
| 157 |
rendered with the macOS system voices, which are licensed for use and not for
|
| 158 |
republication as a corpus. What you see is a downsampled amplitude envelope —
|
|
|
|
| 165 |
<div class="legend">
|
| 166 |
<span><i class="sw" style="background:var(--neutral)"></i>ground truth</span>
|
| 167 |
<span><i class="sw" style="background:var(--a)"></i>ASR + text PII</span>
|
| 168 |
+
<span><i class="sw" style="background:var(--c)"></i>Bleep (converged)</span>
|
| 169 |
+
<span><i class="sw" style="background:var(--b)"></i>Bleep (before convergence)</span>
|
| 170 |
<span><i class="sw" style="background:var(--d)"></i>union</span>
|
| 171 |
</div>
|
| 172 |
</div>
|
|
|
|
| 176 |
<img src="figures/fig1_recall_by_condition.light.png"
|
| 177 |
alt="Recall by degradation condition for each arm, training conditions"
|
| 178 |
id="figCondLight">
|
| 179 |
+
<figcaption>Training conditions, converged model. The subtitle is generated
|
| 180 |
+
from the data, so the chart cannot assert a conclusion the numbers do not
|
| 181 |
+
support.</figcaption>
|
| 182 |
</figure>
|
| 183 |
<figure>
|
| 184 |
+
<img src="figures/fig4_recall_vs_false_alarms.test_heldout_v2.light.png"
|
| 185 |
+
alt="Recall against false-alarm rate on unseen degradations" id="figFaLight">
|
| 186 |
+
<figcaption>Unseen conditions: the converged model's whole recall/false-alarm
|
| 187 |
+
curve sits to the right of the ASR baseline — no threshold reaches the
|
| 188 |
+
baseline's false-alarm rate.</figcaption>
|
| 189 |
</figure>
|
| 190 |
|
| 191 |
<h2>Recall by category</h2>
|
|
|
|
| 193 |
<img src="figures/fig3_recall_by_category.light.png"
|
| 194 |
alt="Recall by category of sensitive speech" id="figCatLight">
|
| 195 |
<figcaption>★ marks the failure modes reported for transcription-first
|
| 196 |
+
pipelines. Converged model; the pre-convergence figures are in
|
| 197 |
+
<code>figures/pre-convergence/</code>.</figcaption>
|
| 198 |
</figure>
|
| 199 |
|
| 200 |
<h2>What this is not</h2>
|
| 201 |
+
<p><strong>Not a redaction guarantee.</strong> Even on the conditions it was
|
| 202 |
+
trained on it misses about one region in ten; on conditions it was not, roughly
|
| 203 |
+
one in five to one in four, while raising several times the baseline's false
|
| 204 |
+
alarms. An empty result means “nothing was flagged”, never “nothing is
|
| 205 |
+
there”.</p>
|
| 206 |
+
<p><strong>Not a replacement for your ASR-based detector.</strong> It beats the
|
| 207 |
+
baseline only on the degradation types it was trained on. It is designed for
|
| 208 |
+
union mode, and the union is verified never to drop a region the existing
|
| 209 |
+
detector found — zero violations on every utterance of all three evaluation
|
| 210 |
+
splits.</p>
|
| 211 |
<p><strong>Not trained on real audio.</strong> Every recording is synthetic.
|
| 212 |
No real call, no clinical recording, nothing derived from either, at any
|
| 213 |
stage. That is also a limitation: TTS speech is far easier to transcribe than
|
| 214 |
conversation, which is part of why the baseline looks so strong here.</p>
|
| 215 |
+
<p><strong>English only.</strong> Japanese generators exist in the code but are
|
| 216 |
+
not benchmarked.</p>
|
| 217 |
|
| 218 |
<div class="foot">
|
| 219 |
<p id="foot">…</p>
|
| 220 |
<p>Apache-2.0. Negative results are cheap to hide and expensive to
|
| 221 |
+
rediscover; closed projects are worth closing in writing.</p>
|
| 222 |
</div>
|
| 223 |
</div>
|
| 224 |
|
|
|
|
| 229 |
const f3 = v => (v === null || v === undefined || Number.isNaN(v)) ? "—" : v.toFixed(3);
|
| 230 |
|
| 231 |
fetch("data.json").then(r => r.json()).then(D => {
|
| 232 |
+
const S = D.summary.test, B = D.summary_before.test;
|
| 233 |
+
const m = S.matched_fa, arms = S.arms, comp = S.complementarity;
|
| 234 |
+
const f1 = v => (v === null || v === undefined) ? "—" : v.toFixed(1);
|
| 235 |
+
|
| 236 |
+
// ---- status numbers (from the data, not typed) ------------------------
|
| 237 |
+
const oracle = arms.oracle.recall, baseA = arms.A.recall;
|
| 238 |
+
document.getElementById("oracleRecall").textContent = f3(oracle);
|
| 239 |
+
document.getElementById("headroom").textContent = f3(oracle - baseA);
|
| 240 |
+
const C = D.ceiling;
|
| 241 |
+
document.getElementById("missSplit").textContent =
|
| 242 |
+
`${C.baseline_misses - C.also_missed_by_oracle} of the baseline's `
|
| 243 |
+
+ `${C.baseline_misses} misses on the test split are ASR word errors, not `
|
| 244 |
+
+ `detector gaps (the oracle misses the other ${C.also_missed_by_oracle} too).`;
|
| 245 |
+
|
| 246 |
+
// ---- arm table: before and after convergence ---------------------------
|
| 247 |
const rows = [
|
| 248 |
["A — ASR (whisper-tiny.en) + text PII", arms.A, false],
|
| 249 |
["B — ASR (whisper-small.en) + text PII", arms.B, false],
|
| 250 |
+
["C — Bleep alone, converged", arms.C, true],
|
| 251 |
+
["C — Bleep alone, before convergence", B.arms.C, false],
|
| 252 |
+
["D — A ∪ Bleep, converged", arms.D, false],
|
| 253 |
+
["D — A ∪ Bleep, before convergence", B.arms.D, false],
|
| 254 |
["oracle — perfect transcript + same rules", arms.oracle, false],
|
| 255 |
].filter(r => r[1]);
|
| 256 |
const t = document.getElementById("armTable");
|
| 257 |
+
t.innerHTML = "<tr><th>arm (test split)</th><th style='text-align:right'>recall</th>"
|
| 258 |
+ "<th style='text-align:right'>false alarms/hour</th></tr>"
|
| 259 |
+ rows.map(([name, a, hero]) =>
|
| 260 |
`<tr class="${hero ? "hero" : ""}"><td>${name}</td>`
|
| 261 |
+ `<td class="num">${f3(a.recall)}</td>`
|
| 262 |
+
+ `<td class="num">${f1(a.fa_per_hour)}</td></tr>`).join("");
|
| 263 |
document.getElementById("armNote").textContent =
|
| 264 |
+
`${S.n_utterances} utterances (${S.audio_hours.toFixed(2)} h) from the eight `
|
| 265 |
+
+ `degradation conditions the model was trained on, speakers it never saw. `
|
| 266 |
+
+ `Bleep's threshold was calibrated on a disjoint split to arm A's measured `
|
| 267 |
+
+ `false-alarm rate of ${f1(m.arm_a_fa_per_hour_on_test)}/hour. Of the regions `
|
| 268 |
+
+ `arm A missed, the converged model found `
|
| 269 |
+
+ `${(comp.asr_misses_rescued_by_bleep * 100).toFixed(0)}% (before convergence: `
|
| 270 |
+
+ `${(B.complementarity.asr_misses_rescued_by_bleep * 100).toFixed(0)}%).`;
|
| 271 |
+
|
| 272 |
+
// ---- held-out: the false-alarm failure ---------------------------------
|
| 273 |
+
const H = D.summary.test_heldout, H2 = D.summary.test_heldout_v2;
|
| 274 |
+
const ratio = h => h.arms.C.fa_per_hour / h.matched_fa.arm_a_fa_per_hour_on_test;
|
| 275 |
+
document.getElementById("heldoutText").textContent =
|
| 276 |
+
`On degradation conditions it was not trained on, the threshold carried over `
|
| 277 |
+
+ `from the calibration split does not carry its false-alarm rate: the `
|
| 278 |
+
+ `converged model raises ${ratio(H).toFixed(1)}–${ratio(H2).toFixed(1)}× the `
|
| 279 |
+
+ `baseline's false alarms, and no threshold brings it down to the baseline's `
|
| 280 |
+
+ `rate. None of these recall comparisons is at a matched false-alarm rate.`;
|
| 281 |
+
const ht = document.getElementById("heldTable");
|
| 282 |
+
const hrow = (name, h) => `<tr><td>${name}</td>`
|
| 283 |
+
+ `<td class="num">${f3(h.arms.A.recall)} · ${f1(h.arms.A.fa_per_hour)}</td>`
|
| 284 |
+
+ `<td class="num">${f3(h.arms.C.recall)} · ${f1(h.arms.C.fa_per_hour)}</td></tr>`;
|
| 285 |
+
ht.innerHTML = "<tr><th>held-out split</th><th style='text-align:right'>ASR baseline: recall · FA/h</th>"
|
| 286 |
+
+ "<th style='text-align:right'>Bleep, converged: recall · FA/h</th></tr>"
|
| 287 |
+
+ hrow("test_heldout (2 of 5 conditions later trained)", H)
|
| 288 |
+
+ hrow("test_heldout_v2 (all 5 conditions unseen)", H2);
|
| 289 |
|
| 290 |
// ---- data-derived prose ---------------------------------------------
|
| 291 |
+
const pc = S.per_condition, ph = H.per_condition, ph2 = H2.per_condition;
|
| 292 |
document.getElementById("whyCond").textContent =
|
| 293 |
`On the training-condition test split the ASR baseline is essentially flat: `
|
| 294 |
+ `${f3(pc.clean.A)} on clean audio and ${f3(pc.telephony_8k.A)} at 8 kHz — `
|
| 295 |
+ `i.e. better than clean. A band-limit plus G.711 is not a hard condition `
|
| 296 |
+
+ `for a modern ASR on hyper-articulated TTS speech. The collapse appeared `
|
| 297 |
+ `only in the harsher held-out conditions: `
|
| 298 |
+ `${f3(ph.overlap_0db_three_way.A)} at 0 dB three-way overlap and `
|
| 299 |
+
+ `${f3(ph.telephony_overlap_3db.A)} at narrowband-plus-overlap. For the `
|
| 300 |
+
+ `retrain those two were moved into training; of five new unseen conditions `
|
| 301 |
+
+ `the baseline collapses only under overlap again (`
|
| 302 |
+
+ `${f3(ph2.overlap_minus3db.A)} at −3 dB).`;
|
| 303 |
|
| 304 |
+
const catB = B.per_category, cat = S.per_category;
|
| 305 |
+
const best = Object.entries(catB).filter(([, v]) => v.C !== null)
|
| 306 |
.sort((a, b) => b[1].C - a[1].C).slice(0, 2);
|
| 307 |
document.getElementById("whyCat").textContent =
|
| 308 |
+
`Before convergence, Bleep's per-category recall had the shape the acoustic `
|
| 309 |
+
+ `argument predicts. Its best categories were ${best.map(([k, v]) =>
|
| 310 |
k.replace(/_/g, " ").toLowerCase() + " (" + f3(v.C) + ")").join(" and ")}`
|
| 311 |
+ ` — the two with the most distinctive rhythm — against ~0.00 for run-on `
|
| 312 |
+
+ `digits, money and identifiers. After convergence those two reach `
|
| 313 |
+
+ `${best.map(([k]) => f3(cat[k].C)).join(" and ")}.`;
|
| 314 |
|
| 315 |
const sp = S.speed.bleep, asr = S.speed.asr_arms_rtf;
|
| 316 |
document.getElementById("heldUp").textContent =
|
| 317 |
`Speed: ${sp.rtf_median} real-time factor on CPU with the front end `
|
| 318 |
+ `included — ${sp.xrealtime_best}× real time, `
|
| 319 |
+ `${(asr.A / sp.rtf_median).toFixed(1)}× faster than the cheapest ASR arm `
|
| 320 |
+
+ `and ${(asr.B / sp.rtf_median).toFixed(0)}× faster than the better one; `
|
| 321 |
+
+ `on the Apple Neural Engine via Core ML, about 0.002 real-time factor. `
|
| 322 |
+
+ `${(sp.param_count / 1e6).toFixed(1)} M parameters. The no-content invariant `
|
| 323 |
+
+ `is enforced structurally by tests (an AST walk over the module graph); a `
|
| 324 |
+
+ `span carries only start, end, label, score and source. The union never `
|
| 325 |
+
+ `drops a region the existing detector found: zero violations on every `
|
| 326 |
+
+ `utterance of all three evaluation splits. Chunked inference is bit-exact `
|
| 327 |
+
+ `against whole-file inference, the receptive field is capped at 8.98 s on `
|
| 328 |
+
+ `purpose, and redaction writes a review plan rather than touching audio.`;
|
| 329 |
|
| 330 |
document.getElementById("foot").textContent =
|
| 331 |
+
`Converged benchmark generated ${D.generated_utc}; pre-convergence benchmark `
|
| 332 |
+
+ `${D.generated_utc_before}. Every number on this page is read from the `
|
| 333 |
+
+ `benchmarks' own JSON by the page itself.`;
|
| 334 |
|
| 335 |
// ---- timeline viewer -------------------------------------------------
|
| 336 |
const pick = document.getElementById("pick");
|
| 337 |
D.examples.forEach((e, i) => {
|
| 338 |
const o = document.createElement("option");
|
| 339 |
o.value = i;
|
| 340 |
+
o.textContent = `${e.split === "test" ? "trained" : "UNSEEN"} · `
|
| 341 |
+
+ `${e.condition.replace(/_/g, " ")} — ${e.duration.toFixed(1)}s `
|
| 342 |
+ `(${e.truth.length} sensitive region${e.truth.length === 1 ? "" : "s"})`;
|
| 343 |
pick.appendChild(o);
|
| 344 |
});
|
|
|
|
| 347 |
["ground truth", "truth", "var(--neutral)"],
|
| 348 |
["ASR + text PII", "asr", "var(--a)"],
|
| 349 |
["Bleep", "bleep", "var(--c)"],
|
| 350 |
+
["Bleep (before)", "bleep_before", "var(--b)"],
|
| 351 |
["union", "union", "var(--d)"],
|
| 352 |
];
|
| 353 |
|
|
|
|
| 423 |
// dark-mode figure swap
|
| 424 |
const swap = dark => {
|
| 425 |
[["figCondLight", "fig1_recall_by_condition"],
|
| 426 |
+
["figFaLight", "fig4_recall_vs_false_alarms.test_heldout_v2"],
|
| 427 |
+
["figValLight", "val_curve"],
|
| 428 |
["figCatLight", "fig3_recall_by_category"]].forEach(([id, base]) => {
|
| 429 |
const n = document.getElementById(id);
|
| 430 |
if (n) n.src = `figures/${base}.${dark ? "dark" : "light"}.png`;
|