Spaces:
Paused
Paused
KEEP Listen: English UI, modern design, SVG icons, 10 words only
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import os, json, sys, numpy as np, gradio as gr
|
| 2 |
import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt
|
| 3 |
from huggingface_hub import snapshot_download
|
| 4 |
|
|
@@ -10,12 +10,37 @@ cfg = json.load(open(os.path.join(d, "config.json"))) if os.path.exists(os.path.
|
|
| 10 |
R = K.Recognizer(os.path.join(d, "audio2b_KEEP_graine0.pt"), os.path.join(d, "audio2b_normalisation.pt"), gain=cfg.get("gain", "peak"))
|
| 11 |
TAU, MIN_FRAME = 0.95, 10
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
sr, wav = audio
|
| 18 |
-
_, P = R(wav, sr) # P : [101
|
| 19 |
final = P[-1]; top = final.argsort()[::-1][:3]
|
| 20 |
lab = {K.WORDS[i]: float(final[i]) for i in range(len(K.WORDS))}
|
| 21 |
sure = np.where(P[MIN_FRAME:].max(1) >= TAU)[0]
|
|
@@ -25,78 +50,31 @@ def ecoute(audio, gain_choisi):
|
|
| 25 |
ax.axhline(TAU, color="gray", ls=":", lw=1)
|
| 26 |
if len(sure):
|
| 27 |
td = (sure[0] + MIN_FRAME) * 10; ax.axvline(td, color="black", ls="--", lw=1)
|
| 28 |
-
ax.set_xlabel("
|
| 29 |
fig.tight_layout()
|
| 30 |
w = K.WORDS[int(final.argmax())]
|
| 31 |
if final.max() < 0.6:
|
| 32 |
-
msg = f"**
|
| 33 |
elif len(sure):
|
| 34 |
w0 = K.WORDS[int(P[sure[0] + MIN_FRAME].argmax())]
|
| 35 |
-
msg = f"
|
| 36 |
else:
|
| 37 |
-
msg = f"
|
| 38 |
return lab, fig, msg
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
def reconnait(audio, gain_choisi, mem):
|
| 57 |
-
if not mem or len(mem) < 2: return None, "Apprenez-lui au moins **2 mots** d'abord (champ du haut)."
|
| 58 |
-
if audio is None: return None, "Dites un des mots appris."
|
| 59 |
-
R.gain = gain_choisi; sr, wav = audio; q = R.embedding(wav, sr)
|
| 60 |
-
noms = list(mem); P = torch.nn.functional.normalize(torch.stack([torch.tensor(mem[w]).mean(0) for w in noms]), dim=-1)
|
| 61 |
-
sim = (P @ q).numpy(); p = np.exp(20 * (sim - sim.max())); p = p / p.sum()
|
| 62 |
-
i = int(sim.argmax()); j = np.argsort(-sim)[1] if len(sim) > 1 else i
|
| 63 |
-
ecart = sim[i] - sim[j]
|
| 64 |
-
msg = (f"J'ai entendu : **{noms[i]}**." if ecart > 0.05 else f"J'hésite… plutôt **{noms[i]}**, mais ça ressemble aussi à « {noms[j]} ». Ajoutez des exemples.")
|
| 65 |
-
return {w: float(x) for w, x in zip(noms, p)}, msg
|
| 66 |
-
|
| 67 |
-
with gr.Blocks(title="KEEP écoute") as demo:
|
| 68 |
-
gr.Markdown("# KEEP écoute\nUn petit modèle AMEFORGE (environ 265 000 paramètres) qui écoute le son au fil du temps.")
|
| 69 |
-
gain_radio = gr.Radio(["peak", "none"], value=cfg.get("gain", "peak"), label="Réglage du volume (peak = automatique, none = brut)")
|
| 70 |
-
mem = gr.State({})
|
| 71 |
-
with gr.Tab("Les 10 mots qu'il connaît"):
|
| 72 |
-
gr.Markdown("**Mots connus :** yes · no · up · down · left · right · on · off · stop · go — dites un seul mot, près du micro. "
|
| 73 |
-
"La courbe montre comment sa confiance monte pendant l'écoute.")
|
| 74 |
-
with gr.Row():
|
| 75 |
-
a = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Votre mot (environ 1 seconde)")
|
| 76 |
-
lab = gr.Label(num_top_classes=3, label="Ce que KEEP a entendu")
|
| 77 |
-
msg = gr.Markdown(); plot = gr.Plot(label="Confiance au fil de l'écoute")
|
| 78 |
-
gr.Button("Écouter", variant="primary").click(ecoute, [a, gain_radio], [lab, plot, msg])
|
| 79 |
-
with gr.Tab("Apprends-lui un mot nouveau"):
|
| 80 |
-
gr.Markdown("Apprenez à KEEP des mots **qu'il n'a jamais entendus** (n'importe quelle langue), **sans le ré-entraîner** : "
|
| 81 |
-
"il garde l'empreinte de vos exemples, puis reconnaît par ressemblance.\n\n"
|
| 82 |
-
"**1.** Écrivez un nom, enregistrez le mot (1 à 3 fois) et cliquez « Mémoriser ». Faites-le pour 2 à 5 mots. \n"
|
| 83 |
-
"**2.** Plus bas, redites un des mots : KEEP dit lequel il reconnaît.")
|
| 84 |
-
with gr.Row():
|
| 85 |
-
nom = gr.Textbox(label="Nom du mot", placeholder="ex. : bonjour")
|
| 86 |
-
ex_audio = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Dites ce mot")
|
| 87 |
-
with gr.Row():
|
| 88 |
-
b_add = gr.Button("Mémoriser cet exemple", variant="primary"); b_forget = gr.Button("Tout oublier")
|
| 89 |
-
info = gr.Markdown(); appris = gr.Markdown(resume({}))
|
| 90 |
-
b_add.click(ajoute, [nom, ex_audio, gain_radio, mem], [mem, appris, info, ex_audio])
|
| 91 |
-
b_forget.click(oublie, None, [mem, appris, info, ex_audio])
|
| 92 |
-
gr.Markdown("---")
|
| 93 |
-
with gr.Row():
|
| 94 |
-
q_audio = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Redites un des mots appris")
|
| 95 |
-
q_lab = gr.Label(label="Ressemblance avec chaque mot appris")
|
| 96 |
-
q_msg = gr.Markdown()
|
| 97 |
-
gr.Button("Qu'ai-je dit ?", variant="primary").click(reconnait, [q_audio, gain_radio, mem], [q_lab, q_msg])
|
| 98 |
-
gr.Markdown("_Expérimental : ce qu'il apprend ici est oublié quand vous fermez la page. "
|
| 99 |
-
"Ça marche le mieux avec la même voix que les exemples, et 2 à 3 exemples par mot._")
|
| 100 |
-
gr.Markdown("**Limites.** Onglet 1 : il ne connaît que ces 10 mots (environ 97 % sur des voix jamais entendues). "
|
| 101 |
-
"Un micro bruyant ou un accent très différent peut faire baisser ces chiffres. Il reconnaît des sons de mots, il ne comprend pas la parole.")
|
| 102 |
demo.launch()
|
|
|
|
| 1 |
+
import os, json, sys, numpy as np, gradio as gr
|
| 2 |
import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt
|
| 3 |
from huggingface_hub import snapshot_download
|
| 4 |
|
|
|
|
| 10 |
R = K.Recognizer(os.path.join(d, "audio2b_KEEP_graine0.pt"), os.path.join(d, "audio2b_normalisation.pt"), gain=cfg.get("gain", "peak"))
|
| 11 |
TAU, MIN_FRAME = 0.95, 10
|
| 12 |
|
| 13 |
+
CSS = """
|
| 14 |
+
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@600;700&family=Space+Grotesk:wght@400;500;600&display=swap');
|
| 15 |
+
.gradio-container { max-width: 920px !important; margin: auto !important; background: #F5F4F0 !important; font-family: 'Space Grotesk', system-ui, sans-serif !important; }
|
| 16 |
+
.hero { background: linear-gradient(135deg, #0D1B2A 0%, #1B3A5C 100%); border-radius: 22px; padding: 34px 32px 26px; color: #fff; margin-bottom: 22px; box-shadow: 0 12px 40px rgba(13,27,42,.25); }
|
| 17 |
+
.hero .hero-top { display: flex; gap: 18px; align-items: flex-start; }
|
| 18 |
+
.hero .hero-icon { flex: 0 0 auto; width: 56px; height: 56px; border-radius: 16px; background: rgba(201,162,39,.18); border: 1px solid rgba(201,162,39,.5); display: flex; align-items: center; justify-content: center; color: #C9A227; }
|
| 19 |
+
.hero h1 { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 46px; margin: 0 0 6px; font-weight: 700; letter-spacing: -.5px; line-height: 1.05; }
|
| 20 |
+
.hero .tagline { font-size: 16.5px; opacity: .92; margin: 0 0 16px; line-height: 1.5; }
|
| 21 |
+
.hero .pills { display: flex; flex-wrap: wrap; gap: 8px; }
|
| 22 |
+
.hero .pill { background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.22); border-radius: 999px; padding: 6px 14px; font-size: 13.5px; }
|
| 23 |
+
.hero .pill b { color: #C9A227; }
|
| 24 |
+
button.primary { background: #0D1B2A !important; border-radius: 12px !important; }
|
| 25 |
+
.footnote { color: #64748b; font-size: 13.5px; line-height: 1.55; margin-top: 18px; }
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
MIC_SVG = """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10a7 7 0 0 0 14 0"/><path d="M12 19v3"/></svg>"""
|
| 29 |
+
|
| 30 |
+
HERO = f"""<div class="hero"><div class="hero-top"><div class="hero-icon">{MIC_SVG}</div><div>
|
| 31 |
+
<h1>KEEP Listen</h1>
|
| 32 |
+
<p class="tagline">A small AMEFORGE model (about 265,000 parameters) that recognizes <b>10 spoken English words</b> as it listens, over time.</p>
|
| 33 |
+
</div></div><div class="pills">
|
| 34 |
+
<span class="pill"><b>10</b> known words</span>
|
| 35 |
+
<span class="pill"><b>~97%</b> on never-heard voices</span>
|
| 36 |
+
<span class="pill">Runs on CPU</span>
|
| 37 |
+
</div></div>"""
|
| 38 |
+
|
| 39 |
+
def listen(audio, gain_choice):
|
| 40 |
+
R.gain = gain_choice
|
| 41 |
+
if audio is None: return None, None, "Record or upload a word first."
|
| 42 |
sr, wav = audio
|
| 43 |
+
_, P = R(wav, sr) # P : [101 steps (10 ms), 10 words]
|
| 44 |
final = P[-1]; top = final.argsort()[::-1][:3]
|
| 45 |
lab = {K.WORDS[i]: float(final[i]) for i in range(len(K.WORDS))}
|
| 46 |
sure = np.where(P[MIN_FRAME:].max(1) >= TAU)[0]
|
|
|
|
| 50 |
ax.axhline(TAU, color="gray", ls=":", lw=1)
|
| 51 |
if len(sure):
|
| 52 |
td = (sure[0] + MIN_FRAME) * 10; ax.axvline(td, color="black", ls="--", lw=1)
|
| 53 |
+
ax.set_xlabel("listened time (ms)"); ax.set_ylabel("confidence"); ax.set_ylim(0, 1.02); ax.legend(loc="upper left"); ax.grid(alpha=.3)
|
| 54 |
fig.tight_layout()
|
| 55 |
w = K.WORDS[int(final.argmax())]
|
| 56 |
if final.max() < 0.6:
|
| 57 |
+
msg = f"**Not sure** (best guess: “{w}”, {final.max():.0%}). Say one of the 10 words clearly."
|
| 58 |
elif len(sure):
|
| 59 |
w0 = K.WORDS[int(P[sure[0] + MIN_FRAME].argmax())]
|
| 60 |
+
msg = f"Word recognized: **{w}** ({final.max():.0%}). It was already sure of “{w0}” after **{(sure[0] + MIN_FRAME) * 10} ms** of listening."
|
| 61 |
else:
|
| 62 |
+
msg = f"Word recognized: **{w}** ({final.max():.0%}), at the end of listening."
|
| 63 |
return lab, fig, msg
|
| 64 |
|
| 65 |
+
with gr.Blocks(title="KEEP Listen", css=CSS) as demo:
|
| 66 |
+
gr.HTML(HERO)
|
| 67 |
+
gr.Markdown("**Known words:** yes · no · up · down · left · right · on · off · stop · go — say a single word, close to the microphone. "
|
| 68 |
+
"The curve shows how its confidence rises while listening.")
|
| 69 |
+
with gr.Row():
|
| 70 |
+
a = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Your word (about 1 second)")
|
| 71 |
+
lab = gr.Label(num_top_classes=3, label="What KEEP heard")
|
| 72 |
+
msg = gr.Markdown(); plot = gr.Plot(label="Confidence over listening time")
|
| 73 |
+
gain_radio = gr.Radio(["peak", "none"], value=cfg.get("gain", "peak"),
|
| 74 |
+
label="Volume setting (peak = automatic, none = raw)")
|
| 75 |
+
gr.Button("Listen", variant="primary").click(listen, [a, gain_radio], [lab, plot, msg])
|
| 76 |
+
gr.Markdown("**Limits.** It only knows these 10 words: any other sound will be classified as one of them. "
|
| 77 |
+
"Tested at about 97% on never-heard voices (Speech Commands test set); a noisy microphone or a very "
|
| 78 |
+
"different accent can lower that number. It recognizes words, it does not understand speech.",
|
| 79 |
+
elem_classes=["footnote"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
demo.launch()
|