ameforge commited on
Commit
9bb4f7a
·
verified ·
1 Parent(s): af532d3

KEEP Listen: English UI, modern design, SVG icons, 10 words only

Browse files
Files changed (1) hide show
  1. app.py +50 -72
app.py CHANGED
@@ -1,4 +1,4 @@
1
- import os, json, sys, numpy as np, gradio as gr, torch
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
- # ─── onglet 1 : les 10 mots appris à l'entraînement ───
14
- def ecoute(audio, gain_choisi):
15
- R.gain = gain_choisi
16
- if audio is None: return None, None, "Enregistrez ou chargez un mot d'abord."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  sr, wav = audio
18
- _, P = R(wav, sr) # P : [101 instants (10 ms), 10 mots]
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("temps écouté (ms)"); ax.set_ylabel("confiance"); ax.set_ylim(0, 1.02); ax.legend(loc="upper left"); ax.grid(alpha=.3)
29
  fig.tight_layout()
30
  w = K.WORDS[int(final.argmax())]
31
  if final.max() < 0.6:
32
- msg = f"**Pas sûr** (meilleure hypothèse : « {w} », {final.max():.0%}). Dites clairement un des 10 mots."
33
  elif len(sure):
34
  w0 = K.WORDS[int(P[sure[0] + MIN_FRAME].argmax())]
35
- msg = f"Mot reconnu : **{w}** ({final.max():.0%}). Il était déjà sûr de « {w0} » après **{(sure[0] + MIN_FRAME) * 10} ms** d'écoute."
36
  else:
37
- msg = f"Mot reconnu : **{w}** ({final.max():.0%}), à la fin de l'écoute."
38
  return lab, fig, msg
39
 
40
- # ─── onglet 2 : apprendre un mot NOUVEAU en direct (aucun ré-entraînement : on garde l'empreinte des exemples) ───
41
- def resume(mem):
42
- if not mem: return "_Aucun mot appris pour l'instant._"
43
- return "**Mots appris :** " + " · ".join(f"{w} ({len(v)} exemple{'s' if len(v) > 1 else ''})" for w, v in mem.items())
44
-
45
- def ajoute(nom, audio, gain_choisi, mem):
46
- mem = dict(mem or {}); nom = (nom or "").strip().lower()
47
- if not nom: return mem, resume(mem), "Écrivez d'abord le nom du mot.", gr.update()
48
- if audio is None: return mem, resume(mem), "Enregistrez le mot d'abord.", gr.update()
49
- R.gain = gain_choisi; sr, wav = audio
50
- mem[nom] = list(mem.get(nom, [])) + [R.embedding(wav, sr).tolist()]
51
- n = len(mem[nom]); conseil = " Ajoutez encore 1 ou 2 exemples pour qu'il soit plus sûr." if n < 3 else ""
52
- return mem, resume(mem), f"Exemple n° {n} de « {nom} » mémorisé.{conseil}", None
53
-
54
- def oublie(): return {}, resume({}), "Tout est oublié.", None
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()