Spaces:
Running
Running
FonBench — classement statique, copie de Kimyayd/FonBench
Browse files- README.md +47 -4
- app.js +463 -0
- index.html +174 -17
README.md
CHANGED
|
@@ -1,10 +1,53 @@
|
|
| 1 |
---
|
| 2 |
title: FonBench
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: yellow
|
| 6 |
sdk: static
|
| 7 |
-
pinned:
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: FonBench
|
| 3 |
+
emoji: 🗣️
|
| 4 |
+
colorFrom: indigo
|
| 5 |
colorTo: yellow
|
| 6 |
sdk: static
|
| 7 |
+
pinned: true
|
| 8 |
+
license: apache-2.0
|
| 9 |
+
short_description: The public speech-recognition leaderboard for Fon
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# FonBench 🇧🇯
|
| 13 |
+
|
| 14 |
+
The public speech-recognition leaderboard for **Fon**, a tonal language
|
| 15 |
+
spoken by over two million people in Benin.
|
| 16 |
+
|
| 17 |
+
This page carries the leaderboard itself: rankings, queue and submission
|
| 18 |
+
form. It reads its data live from the same database as the evaluation
|
| 19 |
+
service, so nothing here is a stale copy.
|
| 20 |
+
|
| 21 |
+
Evaluation runs elsewhere — a static Space cannot execute Python. Models
|
| 22 |
+
submitted here are picked up by the evaluator at
|
| 23 |
+
[Kimyayd/FonBench](https://huggingface.co/spaces/Kimyayd/FonBench), which
|
| 24 |
+
holds the GPU and the read access to the private test set.
|
| 25 |
+
|
| 26 |
+
## Metrics
|
| 27 |
+
|
| 28 |
+
Fon is written with tones (á, ɔ́, ě…) that change the meaning of words, but
|
| 29 |
+
corpora don't follow the same convention — some mark no tone at all. A raw
|
| 30 |
+
WER is therefore not comparable from one corpus to the next.
|
| 31 |
+
|
| 32 |
+
- **WER_seg** — word errors with tones stripped. Comparable everywhere.
|
| 33 |
+
- **WER_ton** — errors on tone marks alone. Not computed when the corpus
|
| 34 |
+
doesn't annotate tones, so the figure is never misleading.
|
| 35 |
+
- **T-WER** = `WER_seg + 2 × WER_ton` — the headline metric.
|
| 36 |
+
- **RTFx** — seconds of audio per second of compute. Higher is faster;
|
| 37 |
+
hardware-dependent, so only compare at equal hardware.
|
| 38 |
+
|
| 39 |
+
## Test set
|
| 40 |
+
|
| 41 |
+
2,555 utterances, 4.98 hours, 45 speakers, **not published** — a test set
|
| 42 |
+
that circulates stops being a test set. Its 45 speakers are strictly
|
| 43 |
+
disjoint from the 471 training speakers, verified, zero in common. It is
|
| 44 |
+
not secret: request access to `JMLdata/fon-test-v1` and you can recompute
|
| 45 |
+
any row yourself.
|
| 46 |
+
|
| 47 |
+
## Verify any number
|
| 48 |
+
|
| 49 |
+
Scoring code, a standalone evaluator and one script per evaluated model:
|
| 50 |
+
[github.com/Izzoudine/EvalScripts](https://github.com/Izzoudine/EvalScripts).
|
| 51 |
+
Expect agreement within ±0.0002 — CTC padding depends on batch composition,
|
| 52 |
+
and we would rather document that than round the published figures to three
|
| 53 |
+
decimals.
|
app.js
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* FonBench — version statique du classement.
|
| 2 |
+
*
|
| 3 |
+
* Un Space statique ne peut pas exécuter de Python : cette copie affiche le
|
| 4 |
+
* classement, la file et le formulaire, mais l'évaluation reste sur le Space
|
| 5 |
+
* Gradio qui détient le GPU et l'accès au corpus privé. Les données sont lues
|
| 6 |
+
* en direct depuis la même base : rien ici n'est une copie figée.
|
| 7 |
+
*
|
| 8 |
+
* La clé ci-dessous est publique par conception — c'est la Row Level Security
|
| 9 |
+
* qui décide de ce qui est lisible, pas le secret de la clé.
|
| 10 |
+
*/
|
| 11 |
+
const SUPABASE = "https://cqdimvcnmhrsdcoobkmd.supabase.co/rest/v1";
|
| 12 |
+
const ANON = "sb_publishable_MapYll-_Y0hNoLYOfaDR3w_tsTEHyiz";
|
| 13 |
+
const H = { apikey: ANON };
|
| 14 |
+
|
| 15 |
+
const EVALUATEUR = "https://huggingface.co/spaces/Kimyayd/FonBench";
|
| 16 |
+
|
| 17 |
+
const TYPES = { base: "🌍 General", maison: "🔧 FonBench", tiers: "👥 Community" };
|
| 18 |
+
// « Fine-tuned » réunit les deux origines : c'est la question la plus
|
| 19 |
+
// fréquente — quels modèles ont vu du fongbe.
|
| 20 |
+
const VUES = [
|
| 21 |
+
["All models", null],
|
| 22 |
+
["🎯 Fine-tuned on Fon", ["maison", "tiers"]],
|
| 23 |
+
["🔧 By FonBench", ["maison"]],
|
| 24 |
+
["👥 By the community", ["tiers"]],
|
| 25 |
+
["🌍 General-purpose", ["base"]],
|
| 26 |
+
];
|
| 27 |
+
|
| 28 |
+
const ONGLETS = [
|
| 29 |
+
["leaderboard", "Leaderboard"], ["queue", "Queue"],
|
| 30 |
+
["submit", "Submit a model"], ["finetuning", "Fine-tuning"], ["about", "About"],
|
| 31 |
+
];
|
| 32 |
+
|
| 33 |
+
let etat = { vue: 0, benchmarks: [], resultats: [], tri: null, sens: 1 };
|
| 34 |
+
|
| 35 |
+
/* --- accès aux données ------------------------------------------------ */
|
| 36 |
+
|
| 37 |
+
async function lire(chemin) {
|
| 38 |
+
const r = await fetch(`${SUPABASE}/${chemin}`, { headers: H });
|
| 39 |
+
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
|
| 40 |
+
return r.json();
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/* --- mise en forme ----------------------------------------------------- */
|
| 44 |
+
|
| 45 |
+
const pct = (x) => (x === null || x === undefined ? "—" : (x * 100).toFixed(1));
|
| 46 |
+
|
| 47 |
+
function taille(n) {
|
| 48 |
+
if (!n) return "—";
|
| 49 |
+
return n >= 1e9 ? `${(n / 1e9).toFixed(1)}B` : `${Math.round(n / 1e6)}M`;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/* La colonne model_kind a d'abord valu « fongbe » ou « base » avant de passer
|
| 53 |
+
* à trois valeurs. On retombe sur le préfixe du dépôt le cas échéant, sinon
|
| 54 |
+
* une base à l'ancien schéma ferait disparaître des lignes entières. */
|
| 55 |
+
function categorie(r) {
|
| 56 |
+
const k = r.model_kind;
|
| 57 |
+
if (["base", "maison", "tiers"].includes(k)) return k;
|
| 58 |
+
if (k === "fongbe") return r.model_id.startsWith("fonbench/") ? "maison" : "tiers";
|
| 59 |
+
return "base";
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
function lienModele(id, checkpoint) {
|
| 63 |
+
if (!id) return "—";
|
| 64 |
+
const url = `https://huggingface.co/${id}`;
|
| 65 |
+
const base = `<a href="${url}" target="_blank" rel="noopener">${id}</a>`;
|
| 66 |
+
return checkpoint ? `${base}<br><small>↳ ${checkpoint}</small>` : base;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
const esc = (s) =>
|
| 70 |
+
String(s).replace(/[&<>"]/g, (c) =>
|
| 71 |
+
({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
| 72 |
+
|
| 73 |
+
/* --- classement -------------------------------------------------------- */
|
| 74 |
+
|
| 75 |
+
const COLONNES = [
|
| 76 |
+
["#", null], ["Model", null], ["Type", null],
|
| 77 |
+
["T-WER", "twer"], ["WER_seg", "wer_notone"], ["WER_ton", "wer_ton"],
|
| 78 |
+
["WER", "wer"], ["CER", "cer"], ["MER", "mer"], ["WIL", "wil"],
|
| 79 |
+
["RTFx", "rtfx"], ["Size", "model_params"],
|
| 80 |
+
["Base model", null], ["Trained on", null],
|
| 81 |
+
["Architecture", null], ["Decoder", null],
|
| 82 |
+
];
|
| 83 |
+
|
| 84 |
+
function entraineSur(r) {
|
| 85 |
+
if (!r.train_data) return "not declared";
|
| 86 |
+
if (r.train_data === "aucun fongbe") return "no Fon data";
|
| 87 |
+
return r.train_hours ? `${r.train_data} · ${+r.train_hours}h` : r.train_data;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
function dessinerClassement() {
|
| 91 |
+
const rows = etat.resultats;
|
| 92 |
+
const bench = etat.benchmarks.find((b) => b.id === document.getElementById("bench").value) || {};
|
| 93 |
+
|
| 94 |
+
// Renseigne les listes d'architectures et de décodeurs à partir des données.
|
| 95 |
+
for (const [id, champ] of [["arch", "architecture"], ["dec", "decoder_type"]]) {
|
| 96 |
+
const sel = document.getElementById(id);
|
| 97 |
+
const vals = [...new Set(rows.map((r) => r[champ]).filter(Boolean))].sort();
|
| 98 |
+
const courant = sel.value;
|
| 99 |
+
sel.innerHTML = '<option value="">all</option>' +
|
| 100 |
+
vals.map((v) => `<option${v === courant ? " selected" : ""}>${esc(v)}</option>`).join("");
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
let sel = rows.slice();
|
| 104 |
+
const vises = VUES[etat.vue][1];
|
| 105 |
+
if (vises) sel = sel.filter((r) => vises.includes(categorie(r)));
|
| 106 |
+
const a = document.getElementById("arch").value;
|
| 107 |
+
const d = document.getElementById("dec").value;
|
| 108 |
+
if (a) sel = sel.filter((r) => r.architecture === a);
|
| 109 |
+
if (d) sel = sel.filter((r) => r.decoder_type === d);
|
| 110 |
+
if (document.getElementById("contam").checked)
|
| 111 |
+
sel = sel.filter((r) => !r.contamination_flag);
|
| 112 |
+
|
| 113 |
+
// Le corpus annote-t-il les tons ? Si oui le T-WER fait foi, sinon le
|
| 114 |
+
// WER_seg : comparer des WER bruts entre conventions tonales différentes
|
| 115 |
+
// n'aurait aucun sens.
|
| 116 |
+
const tonal = rows.some((r) => r.twer !== null && r.twer !== undefined);
|
| 117 |
+
const metrique = tonal ? "twer" : "wer_notone";
|
| 118 |
+
const grand = 9e9;
|
| 119 |
+
|
| 120 |
+
if (etat.tri) {
|
| 121 |
+
sel.sort((x, y) => etat.sens * ((x[etat.tri] ?? grand) - (y[etat.tri] ?? grand)));
|
| 122 |
+
} else {
|
| 123 |
+
const mode = document.getElementById("sort").value;
|
| 124 |
+
if (mode === "speed") sel.sort((x, y) => (y.rtfx ?? 0) - (x.rtfx ?? 0));
|
| 125 |
+
else if (mode === "size") sel.sort((x, y) => (x.model_params ?? 0) - (y.model_params ?? 0));
|
| 126 |
+
else sel.sort((x, y) => (x[metrique] ?? grand) - (y[metrique] ?? grand));
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
const medaille = { 1: "🥇", 2: "🥈", 3: "🥉" };
|
| 130 |
+
const corps = sel.map((r, i) => {
|
| 131 |
+
const ck = (r.model_revision || "").split(":");
|
| 132 |
+
const nom = lienModele(r.model_id, ck.length > 1 ? esc(ck.slice(1).join(":")) : null)
|
| 133 |
+
+ (r.contamination_flag ? " ⚠️" : "");
|
| 134 |
+
return `<tr>
|
| 135 |
+
<td class="num">${i + 1} ${medaille[i + 1] || ""}</td>
|
| 136 |
+
<td>${nom}</td><td>${TYPES[categorie(r)]}</td>
|
| 137 |
+
<td class="num">${pct(r.twer)}</td><td class="num">${pct(r.wer_notone)}</td>
|
| 138 |
+
<td class="num">${pct(r.wer_ton)}</td><td class="num">${pct(r.wer)}</td>
|
| 139 |
+
<td class="num">${pct(r.cer)}</td><td class="num">${pct(r.mer)}</td>
|
| 140 |
+
<td class="num">${pct(r.wil)}</td>
|
| 141 |
+
<td class="num">${r.rtfx ? (+r.rtfx).toFixed(1) + "×" : "—"}</td>
|
| 142 |
+
<td class="num">${taille(r.model_params)}</td>
|
| 143 |
+
<td>${lienModele(r.base_model, null)}</td>
|
| 144 |
+
<td>${esc(entraineSur(r))}</td>
|
| 145 |
+
<td>${esc(r.architecture || "—")}</td><td>${esc(r.decoder_type || "—")}</td>
|
| 146 |
+
</tr>`;
|
| 147 |
+
}).join("");
|
| 148 |
+
|
| 149 |
+
document.getElementById("board").innerHTML =
|
| 150 |
+
`<thead><tr>${COLONNES.map(([t, c], i) =>
|
| 151 |
+
`<th data-col="${c || ""}" title="${c ? "Trier" : ""}">${t}${
|
| 152 |
+
etat.tri === c && c ? (etat.sens > 0 ? " ▲" : " ▼") : ""}</th>`).join("")}
|
| 153 |
+
</tr></thead><tbody>${corps || '<tr><td colspan="16">Aucun modèle.</td></tr>'}</tbody>`;
|
| 154 |
+
|
| 155 |
+
document.querySelectorAll("#board th[data-col]").forEach((th) => {
|
| 156 |
+
const c = th.dataset.col;
|
| 157 |
+
if (!c) return;
|
| 158 |
+
th.onclick = () => {
|
| 159 |
+
etat.sens = etat.tri === c ? -etat.sens : 1;
|
| 160 |
+
etat.tri = c;
|
| 161 |
+
dessinerClassement();
|
| 162 |
+
};
|
| 163 |
+
});
|
| 164 |
+
|
| 165 |
+
const compte = { maison: 0, tiers: 0, base: 0 };
|
| 166 |
+
sel.forEach((r) => compte[categorie(r)]++);
|
| 167 |
+
const drapeau = `<svg viewBox="0 0 30 20" width="20" height="13"
|
| 168 |
+
style="border-radius:2px"><rect width="30" height="20" fill="#FCD116"/>
|
| 169 |
+
<rect y="10" width="30" height="10" fill="#E8112D"/>
|
| 170 |
+
<rect width="12" height="20" fill="#008751"/></svg>`;
|
| 171 |
+
document.getElementById("meta").innerHTML =
|
| 172 |
+
`<span>${drapeau} <b>${esc(bench.name || "")}</b></span>
|
| 173 |
+
<span><b>${bench.num_utterances ?? "?"}</b> utterances</span>
|
| 174 |
+
<span><b>${bench.duration_hours ?? "?"}</b> hours</span>
|
| 175 |
+
${bench.is_private ? '<span class="badge">private test set</span>' : ""}
|
| 176 |
+
<span>ranked by <b>${tonal ? "T-WER" : "WER_seg"}</b></span>
|
| 177 |
+
<span><b>${sel.length}</b> of ${rows.length} models shown</span>
|
| 178 |
+
<span>🔧 <b>${compte.maison}</b> fine-tuned by FonBench · 👥
|
| 179 |
+
<b>${compte.tiers}</b> fine-tuned by the community · 🌍
|
| 180 |
+
<b>${compte.base}</b> general-purpose</span>`;
|
| 181 |
+
|
| 182 |
+
document.getElementById("legend").innerHTML =
|
| 183 |
+
"All error rates in <b>%</b>, lower is better — except <b>RTFx</b>, where " +
|
| 184 |
+
"higher means faster. <b>⚠️</b> marks a model likely trained on this test " +
|
| 185 |
+
"set: its score is not comparable. Click a column header to sort.";
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
async function chargerClassement() {
|
| 189 |
+
const id = document.getElementById("bench").value;
|
| 190 |
+
try {
|
| 191 |
+
etat.resultats = await lire(
|
| 192 |
+
`results?is_hidden=eq.false&benchmark_id=eq.${encodeURIComponent(id)}&select=*`);
|
| 193 |
+
dessinerClassement();
|
| 194 |
+
} catch (e) {
|
| 195 |
+
document.getElementById("board").innerHTML =
|
| 196 |
+
`<tbody><tr><td>Database unreachable: ${esc(e.message)}</td></tr></tbody>`;
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/* --- file d'attente ---------------------------------------------------- */
|
| 201 |
+
|
| 202 |
+
async function chargerFile() {
|
| 203 |
+
const etats = {
|
| 204 |
+
pending: "⏳ pending", running: "⚙️ running", done: "✅ done",
|
| 205 |
+
failed: "❌ failed", rejected: "🚫 rejected",
|
| 206 |
+
};
|
| 207 |
+
try {
|
| 208 |
+
const rows = await lire("public_queue?select=*&order=created_at.desc&limit=50");
|
| 209 |
+
const corps = rows.map((r) => {
|
| 210 |
+
const t = r.progress_total || 0, f = r.progress_done || 0;
|
| 211 |
+
let av = t ? `${f}/${t}` : "—";
|
| 212 |
+
if (t && r.status === "running") av += ` (${Math.floor((f * 100) / t)}%)`;
|
| 213 |
+
return `<tr><td>${esc(r.model_id)}</td><td>${etats[r.status] || esc(r.status)}</td>
|
| 214 |
+
<td class="num">${av}</td><td>${esc((r.error_message || "").slice(0, 120))}</td>
|
| 215 |
+
<td>${esc((r.created_at || "").slice(0, 10))}</td></tr>`;
|
| 216 |
+
}).join("");
|
| 217 |
+
document.getElementById("queue").innerHTML =
|
| 218 |
+
`<thead><tr><th>Model</th><th>Status</th><th>Progress</th><th>Details</th>
|
| 219 |
+
<th>Submitted</th></tr></thead>
|
| 220 |
+
<tbody>${corps || '<tr><td colspan="5">Queue empty.</td></tr>'}</tbody>`;
|
| 221 |
+
} catch (e) {
|
| 222 |
+
document.getElementById("queue").innerHTML =
|
| 223 |
+
`<tbody><tr><td>Database unreachable: ${esc(e.message)}</td></tr></tbody>`;
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
/* --- soumission -------------------------------------------------------- */
|
| 228 |
+
|
| 229 |
+
async function soumettre(ev) {
|
| 230 |
+
ev.preventDefault();
|
| 231 |
+
const sortie = document.getElementById("submit-out");
|
| 232 |
+
const val = (id) => document.getElementById(id).value.trim();
|
| 233 |
+
const modele = val("f-model");
|
| 234 |
+
const bouts = modele.split("/");
|
| 235 |
+
if (bouts.length !== 2 || !bouts[0] || !bouts[1]) {
|
| 236 |
+
sortie.innerHTML = '<div class="msg"><b>❌ Invalid format</b><br>The identifier ' +
|
| 237 |
+
"must look like <code>organisation/name</code>, exactly as it appears in " +
|
| 238 |
+
"the model URL on Hugging Face.</div>";
|
| 239 |
+
return;
|
| 240 |
+
}
|
| 241 |
+
const corps = { model_id: modele, benchmark_id: val("f-bench") };
|
| 242 |
+
for (const [champ, id] of [["hf_username", "f-user"], ["contact", "f-contact"],
|
| 243 |
+
["note", "f-note"], ["train_data", "f-data"],
|
| 244 |
+
["base_model", "f-base"]]) {
|
| 245 |
+
if (val(id)) corps[champ] = val(id);
|
| 246 |
+
}
|
| 247 |
+
if (val("f-hours")) corps.train_hours = parseFloat(val("f-hours"));
|
| 248 |
+
|
| 249 |
+
try {
|
| 250 |
+
const r = await fetch(`${SUPABASE}/public_requests`, {
|
| 251 |
+
method: "POST",
|
| 252 |
+
headers: { ...H, "Content-Type": "application/json", Prefer: "return=minimal" },
|
| 253 |
+
body: JSON.stringify(corps),
|
| 254 |
+
});
|
| 255 |
+
if (!r.ok) {
|
| 256 |
+
// La base applique elle-même les garde-fous (doublon, débit, file
|
| 257 |
+
// pleine) et renvoie un message déjà rédigé.
|
| 258 |
+
let detail = await r.text();
|
| 259 |
+
try { detail = JSON.parse(detail).message || detail; } catch (_) {}
|
| 260 |
+
sortie.innerHTML = `<div class="msg"><b>❌ Submission rejected</b><br>${esc(detail)}</div>`;
|
| 261 |
+
return;
|
| 262 |
+
}
|
| 263 |
+
sortie.innerHTML = `<div class="msg"><b>✅ <code>${esc(modele)}</code> is queued</b><br>
|
| 264 |
+
Evaluation runs in slices on the GPU of
|
| 265 |
+
<a href="${EVALUATEUR}" target="_blank" rel="noopener">Kimyayd/FonBench</a>.
|
| 266 |
+
Depending on the available quota, expect anywhere from a few minutes to a
|
| 267 |
+
few hours. The score will appear in the leaderboard once computed — follow
|
| 268 |
+
progress in the <b>Queue</b> tab.</div>`;
|
| 269 |
+
chargerFile();
|
| 270 |
+
} catch (e) {
|
| 271 |
+
sortie.innerHTML = `<div class="msg"><b>❌ Could not submit</b><br>${esc(e.message)}</div>`;
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/* --- textes longs ------------------------------------------------------ */
|
| 276 |
+
|
| 277 |
+
const NOTE_METRIQUES = `
|
| 278 |
+
<h3>Reading the table</h3>
|
| 279 |
+
<p>Fon is a <b>tonal</b> language: tones are written with diacritics (á, ɔ́, ě…)
|
| 280 |
+
and change the meaning of words. But Fon corpora don't follow the same
|
| 281 |
+
convention — some mark no tone at all. A raw WER therefore isn't comparable
|
| 282 |
+
from one corpus to the next. Hence three families of measures.</p>
|
| 283 |
+
<table>
|
| 284 |
+
<tr><th>Metric</th><th>What it tells you</th></tr>
|
| 285 |
+
<tr><td><b>WER_seg</b></td><td>Word errors with <b>tones stripped</b>. Measures
|
| 286 |
+
phonetic accuracy and stays comparable across every corpus.</td></tr>
|
| 287 |
+
<tr><td><b>WER_ton</b></td><td>Errors on tone marks alone. Shown as “—” when the
|
| 288 |
+
corpus doesn't annotate tones, so the figure is never misleading.</td></tr>
|
| 289 |
+
<tr><td><b>T-WER</b></td><td><code>WER_seg + 2 × WER_ton</code>. The headline
|
| 290 |
+
metric: it penalises tone mistakes twice over.</td></tr>
|
| 291 |
+
<tr><td>WER, CER</td><td>Word and character errors, tones included.</td></tr>
|
| 292 |
+
<tr><td>MER, WIL</td><td><i>Match error rate</i> and <i>word information lost</i>
|
| 293 |
+
— more robust when a model produces many insertions.</td></tr>
|
| 294 |
+
<tr><td>RTFx</td><td>Seconds of audio per second of compute. <b>Higher is
|
| 295 |
+
faster.</b></td></tr>
|
| 296 |
+
</table>
|
| 297 |
+
<p><b>Model types.</b> 🌍 <i>General-purpose</i> models were not built for Fon:
|
| 298 |
+
some are multilingual systems that do cover it among a thousand other languages
|
| 299 |
+
(the MMS family ships a Fon adapter), others are multilingual without Fon, and
|
| 300 |
+
others again are monolingual systems for English or French. They are not
|
| 301 |
+
<i>base</i> models in the pretrained sense — <code>wav2vec2-large-960h-lv60-self</code>
|
| 302 |
+
is a finished English recogniser, not a starting point. The <b>Trained on</b>
|
| 303 |
+
column says exactly what each one saw. 🔧 <i>FonBench</i> and 👥 <i>Community</i>
|
| 304 |
+
models were fine-tuned on Fon.</p>
|
| 305 |
+
<p>Truly <i>base</i> models — raw pretrained checkpoints such as
|
| 306 |
+
<code>facebook/wav2vec2-large-xlsr-53</code> — cannot appear here at all: without
|
| 307 |
+
a CTC head or a vocabulary they transcribe nothing. They show up only in the
|
| 308 |
+
<b>Base model</b> column, as the starting point of the models fine-tuned from
|
| 309 |
+
them.</p>
|
| 310 |
+
<p><b>Speed.</b> RTFx depends on the hardware, recorded with each score. Only
|
| 311 |
+
compare speeds at equal hardware.</p>`;
|
| 312 |
+
|
| 313 |
+
const FINE_TUNING = `
|
| 314 |
+
<h3>The FonBench fine-tuning runs</h3>
|
| 315 |
+
<p>Four pretrained models were fine-tuned on Fon under strictly identical
|
| 316 |
+
conditions, so that any gap between them comes from the starting model alone.</p>
|
| 317 |
+
<p><b>The data.</b> The training corpus holds 44,225 utterances, of which
|
| 318 |
+
<b>13,716 were kept — 30.00 hours from 471 speakers</b>. The sample is not drawn
|
| 319 |
+
at random: it is built by taking turns across speakers, each contributing a
|
| 320 |
+
little before any one of them dominates. For generalisation, the diversity of
|
| 321 |
+
voices matters more than raw volume.</p>
|
| 322 |
+
<p>The 471 training speakers and the 45 test speakers are <b>strictly
|
| 323 |
+
disjoint</b> — verified, zero in common. A temporal cutoff separates the
|
| 324 |
+
transcripts; 20 sentences out of 2,555 (0.8%) do appear in both, too few to move
|
| 325 |
+
a score but worth stating rather than claiming a perfect separation.</p>
|
| 326 |
+
<p><b>The protocol.</b> Identical across all four: 3 epochs, i.e. 2,574 steps
|
| 327 |
+
with 257 of warmup. CTC decoding, character vocabulary built from the training
|
| 328 |
+
corpus, feature extractor taken from the base model. A single 24 GB L4,
|
| 329 |
+
<code>fp16</code>, gradient checkpointing.</p>
|
| 330 |
+
<table>
|
| 331 |
+
<tr><th>Resulting model</th><th>Base</th><th>LR</th><th>Batch</th>
|
| 332 |
+
<th>Wall time</th><th>Dev loss</th><th>WER_seg</th></tr>
|
| 333 |
+
<tr><td><code>wav2vec2-large-xlsr-53-fon-30h</code></td><td>wav2vec2-large-xlsr-53</td>
|
| 334 |
+
<td>3·10⁻⁴</td><td>8×2</td><td>54 min</td><td><b>0.468</b></td><td><b>38.6%</b></td></tr>
|
| 335 |
+
<tr><td><code>mms-300m-fon-30h</code></td><td>mms-300m</td><td>3·10⁻⁴</td>
|
| 336 |
+
<td>8×2</td><td>not retained</td><td>not retained</td><td><b>42.8%</b></td></tr>
|
| 337 |
+
<tr><td><code>w2v-bert-2.0-fon-30h</code></td><td>w2v-bert-2.0</td><td>3·10⁻⁵</td>
|
| 338 |
+
<td>4×4</td><td>116 min</td><td>3.102</td><td>96.9%</td></tr>
|
| 339 |
+
<tr><td><code>AfriHuBERT-fon-30h</code></td><td>ajesujoba/AfriHuBERT</td>
|
| 340 |
+
<td>3·10⁻⁵</td><td>8×2</td><td>24 min</td><td>3.078</td><td>100.0%</td></tr>
|
| 341 |
+
</table>
|
| 342 |
+
<p><b>Fine-tuning makes the difference, not the base.</b>
|
| 343 |
+
<code>chrisjay/fonxlsr</code> starts from exactly the same model as our best run
|
| 344 |
+
— <code>facebook/wav2vec2-large-xlsr-53</code>, confirmed in its configuration —
|
| 345 |
+
and reaches 69.3% where we reach 38.6%. The error is cut by a factor of 1.8 with
|
| 346 |
+
30 hours of well-chosen data.</p>
|
| 347 |
+
<p><b>Two runs out of four failed</b>, and that deserves saying plainly.
|
| 348 |
+
<code>w2v-bert-2.0</code> and <code>AfriHuBERT</code> do learn: their loss drops
|
| 349 |
+
clearly during training. But it plateaus around 3.1 against 0.47 for the run that
|
| 350 |
+
succeeds — the signature of settling into the trivial CTC solution, emitting the
|
| 351 |
+
blank symbol everywhere. This is not a learning-rate problem: both were first run
|
| 352 |
+
at 3·10⁻⁴, then rerun at 3·10⁻⁵ — the figures in the table — for exactly the same
|
| 353 |
+
outcome. The same collapse was later reproduced independently by three
|
| 354 |
+
<code>afrihubert-fon-asr-vanilla</code> runs from another team, all between 97.8%
|
| 355 |
+
and 99.7%; the same encoder preceded by continued pretraining on 960 h of Fon
|
| 356 |
+
reaches 18.3%. Four failures and one success point at the same cause.</p>
|
| 357 |
+
<p>Both failures stay in the leaderboard. Removing them would paint a flattering
|
| 358 |
+
and false picture of what fine-tuning guarantees.</p>
|
| 359 |
+
<p><b>Reproducing.</b> The training and evaluation code is published at
|
| 360 |
+
<a href="https://github.com/Izzoudine/EvalScripts" target="_blank" rel="noopener">
|
| 361 |
+
github.com/Izzoudine/EvalScripts</a> — one script per evaluated model.</p>`;
|
| 362 |
+
|
| 363 |
+
const A_PROPOS = `
|
| 364 |
+
<h3>FonBench</h3>
|
| 365 |
+
<p>The first public speech-recognition leaderboard for <b>Fon</b>, a tonal
|
| 366 |
+
language spoken by over two million people in Benin. The project answers a
|
| 367 |
+
concrete difficulty: until now, published Fon results were not comparable with
|
| 368 |
+
one another, for lack of a shared protocol and a shared test set.</p>
|
| 369 |
+
<h3>Why the test set is not published</h3>
|
| 370 |
+
<p>Public Fon corpora have been circulating since 2016, and several released
|
| 371 |
+
models were trained on them. Their WER on that data then looks remarkable —
|
| 372 |
+
while saying nothing about their real ability to transcribe an unseen voice. We
|
| 373 |
+
measured it: one of the test sets we were using shared <b>83% of its
|
| 374 |
+
utterances</b> with the training split of a public dataset.</p>
|
| 375 |
+
<p>The main test set (<b>2,555 utterances, 4.98 hours, 45 speakers</b>) is
|
| 376 |
+
therefore not distributed. Only aggregate scores are made public. It is not
|
| 377 |
+
secret, though: request access to <code>JMLdata/fon-test-v1</code> and you can
|
| 378 |
+
recompute any row yourself.</p>
|
| 379 |
+
<h3>The protocol</h3>
|
| 380 |
+
<ul>
|
| 381 |
+
<li><b>Pinned revision.</b> Every score is tied to the exact commit hash of the
|
| 382 |
+
repository evaluated.</li>
|
| 383 |
+
<li><b>Shared normalisation.</b> The same scoring code for every model, tones
|
| 384 |
+
included. It is open: <code>fonbench_eval.py</code>.</li>
|
| 385 |
+
<li><b>No arbitrary code.</b> Models are loaded with
|
| 386 |
+
<code>trust_remote_code=False</code>.</li>
|
| 387 |
+
<li><b>No duplicate work.</b> A (model, revision, corpus) triple is never
|
| 388 |
+
re-evaluated.</li>
|
| 389 |
+
</ul>
|
| 390 |
+
<h3>Where things run</h3>
|
| 391 |
+
<p>This page is a <i>static</i> Space: it renders the leaderboard and accepts
|
| 392 |
+
submissions, but cannot execute Python. Evaluation runs on
|
| 393 |
+
<a href="${EVALUATEUR}" target="_blank" rel="noopener">Kimyayd/FonBench</a>,
|
| 394 |
+
which holds the GPU and the read access to the private test set. Both read and
|
| 395 |
+
write the same database, so what you see here is live, not a copy.</p>
|
| 396 |
+
<h3>Verify any number</h3>
|
| 397 |
+
<p>Scoring code, a standalone evaluator and one script per evaluated model:
|
| 398 |
+
<a href="https://github.com/Izzoudine/EvalScripts" target="_blank" rel="noopener">
|
| 399 |
+
github.com/Izzoudine/EvalScripts</a>. Expect agreement within ±0.0002 — CTC
|
| 400 |
+
padding depends on batch composition, and we would rather document that than
|
| 401 |
+
round the published figures to three decimals.</p>`;
|
| 402 |
+
|
| 403 |
+
/* --- démarrage --------------------------------------------------------- */
|
| 404 |
+
|
| 405 |
+
function onglets() {
|
| 406 |
+
const nav = document.getElementById("tabs");
|
| 407 |
+
nav.innerHTML = ONGLETS.map(([id, titre], i) =>
|
| 408 |
+
`<button data-tab="${id}" aria-selected="${i === 0}">${titre}</button>`).join("");
|
| 409 |
+
nav.querySelectorAll("button").forEach((b) => {
|
| 410 |
+
b.onclick = () => {
|
| 411 |
+
nav.querySelectorAll("button").forEach((x) =>
|
| 412 |
+
x.setAttribute("aria-selected", x === b));
|
| 413 |
+
ONGLETS.forEach(([id]) =>
|
| 414 |
+
document.getElementById(`tab-${id}`).classList.toggle("hidden", id !== b.dataset.tab));
|
| 415 |
+
if (b.dataset.tab === "queue") chargerFile();
|
| 416 |
+
};
|
| 417 |
+
});
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
async function demarrer() {
|
| 421 |
+
onglets();
|
| 422 |
+
document.getElementById("metrics-note").innerHTML = NOTE_METRIQUES;
|
| 423 |
+
document.getElementById("ft").innerHTML = FINE_TUNING;
|
| 424 |
+
document.getElementById("about").innerHTML = A_PROPOS;
|
| 425 |
+
|
| 426 |
+
document.getElementById("views").innerHTML = VUES.map(([t], i) =>
|
| 427 |
+
`<button data-vue="${i}" aria-pressed="${i === 0}">${t}</button>`).join("");
|
| 428 |
+
document.querySelectorAll("#views button").forEach((b) => {
|
| 429 |
+
b.onclick = () => {
|
| 430 |
+
etat.vue = +b.dataset.vue;
|
| 431 |
+
document.querySelectorAll("#views button").forEach((x) =>
|
| 432 |
+
x.setAttribute("aria-pressed", x === b));
|
| 433 |
+
dessinerClassement();
|
| 434 |
+
};
|
| 435 |
+
});
|
| 436 |
+
|
| 437 |
+
try {
|
| 438 |
+
etat.benchmarks = await lire("benchmarks?is_active=eq.true&select=*&order=id");
|
| 439 |
+
} catch (e) {
|
| 440 |
+
document.getElementById("meta").textContent = `Database unreachable: ${e.message}`;
|
| 441 |
+
return;
|
| 442 |
+
}
|
| 443 |
+
const options = etat.benchmarks.map((b) =>
|
| 444 |
+
`<option value="${esc(b.id)}"${b.id === "jml-test-v1" ? " selected" : ""}>
|
| 445 |
+
${esc(b.name)} (${b.num_utterances ?? "?"} utterances)</option>`).join("");
|
| 446 |
+
document.getElementById("bench").innerHTML = options;
|
| 447 |
+
document.getElementById("f-bench").innerHTML = options;
|
| 448 |
+
|
| 449 |
+
["bench", "sort", "arch", "dec"].forEach((id) => {
|
| 450 |
+
document.getElementById(id).onchange = () => {
|
| 451 |
+
etat.tri = null;
|
| 452 |
+
if (id === "bench") chargerClassement(); else dessinerClassement();
|
| 453 |
+
};
|
| 454 |
+
});
|
| 455 |
+
document.getElementById("contam").onchange = dessinerClassement;
|
| 456 |
+
document.getElementById("refresh").onclick = chargerClassement;
|
| 457 |
+
document.getElementById("refresh-queue").onclick = chargerFile;
|
| 458 |
+
document.getElementById("submit-form").onsubmit = soumettre;
|
| 459 |
+
|
| 460 |
+
chargerClassement();
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
demarrer();
|
index.html
CHANGED
|
@@ -1,19 +1,176 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>FonBench — Fon ASR leaderboard</title>
|
| 7 |
+
<style>
|
| 8 |
+
/* Indigo profond et ocre — les couleurs des tentures appliquées d'Abomey. */
|
| 9 |
+
:root{
|
| 10 |
+
--indigo:#3b3b7a; --ocre:#c8873b; --ink:#1b1b2e; --muted:#6b6b7b;
|
| 11 |
+
--line:rgba(120,120,160,.22); --fond:#fff; --carte:#fafaff;
|
| 12 |
+
}
|
| 13 |
+
@media (prefers-color-scheme:dark){
|
| 14 |
+
:root{--indigo:#a9a9f0;--ink:#ececf6;--muted:#9a9ab0;--fond:#0e0e18;--carte:#16161f;}
|
| 15 |
+
}
|
| 16 |
+
*{box-sizing:border-box}
|
| 17 |
+
body{margin:0;background:var(--fond);color:var(--ink);
|
| 18 |
+
font:15px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
|
| 19 |
+
.wrap{max-width:1500px;margin:0 auto;padding:0 1.1rem 3rem}
|
| 20 |
+
|
| 21 |
+
header{padding:1.6rem 0 .6rem;border-bottom:1px solid var(--line)}
|
| 22 |
+
h1{display:flex;align-items:center;gap:.55rem;margin:0;font-size:2.4rem;
|
| 23 |
+
font-weight:800;letter-spacing:-.03em;line-height:1.05;color:var(--indigo)}
|
| 24 |
+
h1 span{color:var(--ocre)}
|
| 25 |
+
h1 svg{flex:0 0 auto;border-radius:2px;box-shadow:0 0 0 1px rgba(0,0,0,.12)}
|
| 26 |
+
.sub{color:var(--muted);margin:.35rem 0 0;font-size:1.02rem}
|
| 27 |
+
|
| 28 |
+
nav{display:flex;gap:.2rem;flex-wrap:wrap;margin:1rem 0 .2rem;
|
| 29 |
+
border-bottom:1px solid var(--line)}
|
| 30 |
+
nav button{background:none;border:none;border-bottom:2px solid transparent;
|
| 31 |
+
padding:.6rem .9rem;font:inherit;font-weight:600;color:var(--muted);cursor:pointer}
|
| 32 |
+
nav button[aria-selected=true]{color:var(--indigo);border-bottom-color:var(--indigo)}
|
| 33 |
+
nav button:hover{color:var(--ink)}
|
| 34 |
+
|
| 35 |
+
.filters{display:flex;flex-wrap:wrap;gap:.9rem;margin:1rem 0 .4rem}
|
| 36 |
+
.field{display:flex;flex-direction:column;gap:.28rem;min-width:170px;flex:1}
|
| 37 |
+
.field>label{font-size:.7rem;font-weight:700;letter-spacing:.07em;
|
| 38 |
+
text-transform:uppercase;color:var(--muted)}
|
| 39 |
+
select,input,textarea{font:inherit;padding:.45rem .6rem;border-radius:9px;
|
| 40 |
+
border:1px solid var(--line);background:var(--carte);color:var(--ink);width:100%}
|
| 41 |
+
.views{display:flex;flex-wrap:wrap;gap:.35rem;margin:.2rem 0 .5rem}
|
| 42 |
+
.views button{border:1px solid var(--line);background:var(--carte);color:var(--ink);
|
| 43 |
+
border-radius:999px;padding:.32rem .9rem;font:inherit;font-weight:600;
|
| 44 |
+
font-size:.86rem;cursor:pointer;transition:background .12s}
|
| 45 |
+
.views button:hover{background:rgba(120,120,180,.12)}
|
| 46 |
+
.views button[aria-pressed=true]{background:var(--indigo);color:#fff;
|
| 47 |
+
border-color:var(--indigo)}
|
| 48 |
+
.row2{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap;margin-bottom:.5rem}
|
| 49 |
+
.row2 label{font-size:.88rem;color:var(--muted);display:flex;align-items:center;gap:.4rem}
|
| 50 |
+
.row2 input[type=checkbox]{width:auto}
|
| 51 |
+
button.act{border:1px solid var(--line);background:var(--carte);color:var(--ink);
|
| 52 |
+
border-radius:9px;padding:.4rem .9rem;font:inherit;font-weight:600;cursor:pointer}
|
| 53 |
+
|
| 54 |
+
.meta{display:flex;flex-wrap:wrap;gap:.45rem 1.4rem;align-items:center;
|
| 55 |
+
padding:.7rem .9rem;margin:.2rem 0 .7rem;border:1px solid var(--line);
|
| 56 |
+
border-radius:12px;font-size:.88rem;color:var(--muted)}
|
| 57 |
+
.meta>span{display:inline-flex;align-items:center;gap:.35rem}
|
| 58 |
+
.meta b{color:var(--ink);font-weight:650}
|
| 59 |
+
.badge{display:inline-block;padding:.12rem .55rem;border-radius:999px;
|
| 60 |
+
font-size:.72rem;font-weight:700;background:var(--ocre);color:#fff}
|
| 61 |
+
.legend{font-size:.82rem;color:var(--muted);margin:.15rem 0 .9rem}
|
| 62 |
+
|
| 63 |
+
.tablebox{overflow-x:auto;border:1px solid var(--line);border-radius:12px}
|
| 64 |
+
table{border-collapse:collapse;width:100%;font-size:.86rem}
|
| 65 |
+
th,td{padding:.45rem .6rem;text-align:left;white-space:nowrap;
|
| 66 |
+
border-bottom:1px solid var(--line)}
|
| 67 |
+
th{position:sticky;top:0;background:var(--carte);font-weight:700;
|
| 68 |
+
font-size:.75rem;letter-spacing:.04em;text-transform:uppercase;
|
| 69 |
+
color:var(--muted);cursor:pointer;user-select:none}
|
| 70 |
+
th:hover{color:var(--ink)}
|
| 71 |
+
tbody tr:hover{background:rgba(120,120,180,.07)}
|
| 72 |
+
td.num{text-align:right;font-variant-numeric:tabular-nums}
|
| 73 |
+
a{color:var(--indigo)}
|
| 74 |
+
.note{font-size:.9rem;line-height:1.6;max-width:62rem}
|
| 75 |
+
.note h3{margin-top:1.5rem;font-size:1.05rem}
|
| 76 |
+
.note table{font-size:.86rem;margin:.6rem 0;width:auto}
|
| 77 |
+
.note th{position:static;cursor:default;text-transform:none;font-size:.8rem}
|
| 78 |
+
.hidden{display:none}
|
| 79 |
+
.msg{padding:.7rem .9rem;border-radius:10px;border:1px solid var(--line);
|
| 80 |
+
background:var(--carte);margin:.7rem 0}
|
| 81 |
+
form{max-width:44rem}
|
| 82 |
+
form .field{margin-bottom:.8rem}
|
| 83 |
+
.hint{font-size:.8rem;color:var(--muted);margin-top:.2rem}
|
| 84 |
+
</style>
|
| 85 |
+
</head>
|
| 86 |
+
<body>
|
| 87 |
+
<div class="wrap">
|
| 88 |
+
<header>
|
| 89 |
+
<h1>Fon<span>Bench</span>
|
| 90 |
+
<!-- Drapeau béninois en SVG : l'emoji est une paire d'indicateurs
|
| 91 |
+
régionaux que Windows ne compose pas, il s'affiche « BJ ». -->
|
| 92 |
+
<svg viewBox="0 0 30 20" width="39" height="26" role="img" aria-label="Benin">
|
| 93 |
+
<rect width="30" height="20" fill="#FCD116"/>
|
| 94 |
+
<rect y="10" width="30" height="10" fill="#E8112D"/>
|
| 95 |
+
<rect width="12" height="20" fill="#008751"/>
|
| 96 |
+
</svg>
|
| 97 |
+
</h1>
|
| 98 |
+
<p class="sub">The public speech-recognition leaderboard for Fon, a tonal
|
| 99 |
+
language of Benin</p>
|
| 100 |
+
</header>
|
| 101 |
+
|
| 102 |
+
<nav id="tabs"></nav>
|
| 103 |
+
|
| 104 |
+
<section id="tab-leaderboard">
|
| 105 |
+
<div class="filters">
|
| 106 |
+
<div class="field"><label for="bench">Test set</label>
|
| 107 |
+
<select id="bench"></select></div>
|
| 108 |
+
<div class="field"><label for="sort">Sort by</label>
|
| 109 |
+
<select id="sort">
|
| 110 |
+
<option value="quality">Quality (ranking metric)</option>
|
| 111 |
+
<option value="speed">Speed (RTFx)</option>
|
| 112 |
+
<option value="size">Model size</option>
|
| 113 |
+
</select></div>
|
| 114 |
+
<div class="field"><label for="arch">Architecture</label>
|
| 115 |
+
<select id="arch"><option value="">all</option></select></div>
|
| 116 |
+
<div class="field"><label for="dec">Decoder</label>
|
| 117 |
+
<select id="dec"><option value="">all</option></select></div>
|
| 118 |
+
</div>
|
| 119 |
+
<div class="views" id="views"></div>
|
| 120 |
+
<div class="row2">
|
| 121 |
+
<label><input type="checkbox" id="contam"> Hide contaminated models</label>
|
| 122 |
+
<button class="act" id="refresh">Refresh</button>
|
| 123 |
+
</div>
|
| 124 |
+
<div class="meta" id="meta"></div>
|
| 125 |
+
<p class="legend" id="legend"></p>
|
| 126 |
+
<div class="tablebox"><table id="board"></table></div>
|
| 127 |
+
<div class="note" id="metrics-note"></div>
|
| 128 |
+
</section>
|
| 129 |
+
|
| 130 |
+
<section id="tab-queue" class="hidden">
|
| 131 |
+
<p class="note">Evaluation runs on the GPU of
|
| 132 |
+
<a href="https://huggingface.co/spaces/Kimyayd/FonBench" target="_blank"
|
| 133 |
+
rel="noopener">Kimyayd/FonBench</a>, in slices. The compute itself is
|
| 134 |
+
fast — a few minutes for a CTC model — but the daily GPU quota is limited,
|
| 135 |
+
so a run may spread over several hours, pausing between slices. An
|
| 136 |
+
interrupted run resumes exactly where it stopped.</p>
|
| 137 |
+
<button class="act" id="refresh-queue">Refresh</button>
|
| 138 |
+
<div class="tablebox" style="margin-top:.7rem"><table id="queue"></table></div>
|
| 139 |
+
</section>
|
| 140 |
+
|
| 141 |
+
<section id="tab-submit" class="hidden">
|
| 142 |
+
<p class="note">The repository must be <b>public</b> and must not rely on
|
| 143 |
+
custom code: <code>trust_remote_code</code> is disabled. Both
|
| 144 |
+
<code>safetensors</code> and <code>.bin</code> weights are accepted. No
|
| 145 |
+
account required.</p>
|
| 146 |
+
<form id="submit-form">
|
| 147 |
+
<div class="field"><label for="f-model">Hugging Face model ID</label>
|
| 148 |
+
<input id="f-model" placeholder="organisation/model-name" required>
|
| 149 |
+
<span class="hint">Exactly as it appears in the model URL.</span></div>
|
| 150 |
+
<div class="field"><label for="f-base">Base model (optional)</label>
|
| 151 |
+
<input id="f-base" placeholder="facebook/wav2vec2-large-xlsr-53"></div>
|
| 152 |
+
<div class="field"><label for="f-data">Training data (optional)</label>
|
| 153 |
+
<input id="f-data" placeholder="ALFFA, Zenodo, private corpus…">
|
| 154 |
+
<span class="hint">Leave empty for a model never fine-tuned on Fon.</span></div>
|
| 155 |
+
<div class="field"><label for="f-hours">Hours of Fon audio (optional)</label>
|
| 156 |
+
<input id="f-hours" type="number" min="0" step="0.1"></div>
|
| 157 |
+
<div class="field"><label for="f-user">Your HF username (optional)</label>
|
| 158 |
+
<input id="f-user"></div>
|
| 159 |
+
<div class="field"><label for="f-contact">Contact (optional)</label>
|
| 160 |
+
<input id="f-contact"></div>
|
| 161 |
+
<div class="field"><label for="f-note">Note (optional)</label>
|
| 162 |
+
<textarea id="f-note" rows="2"></textarea></div>
|
| 163 |
+
<div class="field"><label for="f-bench">Test set</label>
|
| 164 |
+
<select id="f-bench"></select></div>
|
| 165 |
+
<button class="act" type="submit" style="background:var(--indigo);color:#fff;
|
| 166 |
+
border-color:var(--indigo)">Submit</button>
|
| 167 |
+
</form>
|
| 168 |
+
<div id="submit-out"></div>
|
| 169 |
+
</section>
|
| 170 |
+
|
| 171 |
+
<section id="tab-finetuning" class="hidden"><div class="note" id="ft"></div></section>
|
| 172 |
+
<section id="tab-about" class="hidden"><div class="note" id="about"></div></section>
|
| 173 |
+
</div>
|
| 174 |
+
<script src="app.js"></script>
|
| 175 |
+
</body>
|
| 176 |
</html>
|