Skip to content

Commit 05e53d3

Browse files
Shinji Watanabeclaude
authored andcommitted
A demo for POWSM's four tasks
Phone recognition has a command, an MCP tool and, with espnet#6792, a Space; this is its notebook, and it is the only one of the four places where all four of POWSM's tasks can be read side by side. <pr> hears the phones, <asr> transcribes and is visibly worse at it - which is what a phonetic model is - and then the two that take something written with the audio: <g2p> is given the words and answers with the phones, <p2g> is given the phones and answers with the words. Same six seconds of speech throughout, so the four answers can be compared. Named s2t_pr_demo.ipynb for the rule in Demos/README.md: <task>_<variant>, and phone recognition is a variant of s2t rather than a task of its own. Pins espnet==202610.post2, which carries the page and the decode_window that passes a prompt on; until that release is on PyPI the job says so and skips, which is what allow_unreleased_pin is for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 671ef21 commit 05e53d3

4 files changed

Lines changed: 254 additions & 0 deletions

File tree

‎.github/workflows/s2t_pr_demo.yml‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Demos/s2t_pr_demo.ipynb, every Sunday. The badge on this workflow is what
2+
# the README shows beside that notebook, which is why it has a file of its own:
3+
# GitHub's badge is per workflow, and cannot show one job of a matrix.
4+
5+
name: s2t_pr_demo
6+
7+
on:
8+
schedule:
9+
# Sundays, 05:00 UTC
10+
- cron: "0 5 * * 0"
11+
workflow_dispatch:
12+
pull_request:
13+
paths:
14+
- "Demos/s2t_pr_demo.ipynb"
15+
- "tools/**"
16+
- ".github/workflows/s2t_pr_demo.yml"
17+
- ".github/workflows/_run_notebook.yml"
18+
19+
permissions:
20+
contents: read
21+
22+
jobs:
23+
run:
24+
uses: ./.github/workflows/_run_notebook.yml
25+
with:
26+
notebook: Demos/s2t_pr_demo.ipynb
27+
# POWSM's <g2p> and <p2g> reach the page through espnet2.bin.demo, and
28+
# this notebook pins the release that carries them (espnet#6791 cuts
29+
# it). Until it is on PyPI the job says so and skips. Delete this line
30+
# when 202610.post2 is published.
31+
allow_unreleased_pin: true

‎Demos/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ the answer now.
3939
| [`asr_demo.ipynb`](asr_demo.ipynb) | [![asr_demo](https://github.com/espnet/notebook/actions/workflows/asr_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/asr_demo.yml) | Transcribe speech with OWSM-CTC, and let it work out the language |
4040
| [`asr_streaming_demo.ipynb`](asr_streaming_demo.ipynb) | [![asr_streaming_demo](https://github.com/espnet/notebook/actions/workflows/asr_streaming_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/asr_streaming_demo.yml) | Watch the words appear while the audio is still arriving |
4141
| [`st_demo.ipynb`](st_demo.ipynb) | [![st_demo](https://github.com/espnet/notebook/actions/workflows/st_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/st_demo.yml) | Translate English speech into German, French and Chinese — the same model |
42+
| [`s2t_pr_demo.ipynb`](s2t_pr_demo.ipynb) | [![s2t_pr_demo](https://github.com/espnet/notebook/actions/workflows/s2t_pr_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/s2t_pr_demo.yml) | Hear the phones, and cross between phones and words with POWSM |
4243
| [`tts_demo.ipynb`](tts_demo.ipynb) | [![tts_demo](https://github.com/espnet/notebook/actions/workflows/tts_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/tts_demo.yml) | Type a sentence, hear it spoken — one English voice, then 128 of them |
4344
| [`enh_demo.ipynb`](enh_demo.ipynb) | [![enh_demo](https://github.com/espnet/notebook/actions/workflows/enh_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/enh_demo.yml) | Pull speech out of noise, and measure how much it helped |
4445
| [`spk_demo.ipynb`](spk_demo.ipynb) | [![spk_demo](https://github.com/espnet/notebook/actions/workflows/spk_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/spk_demo.yml) | Turn a voice into a vector, and score two recordings against each other |

‎Demos/s2t_pr_demo.ipynb‎

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Phones, and the two ways across to words\n",
8+
"\n",
9+
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/espnet/notebook/blob/master/Demos/s2t_pr_demo.ipynb) [![s2t_pr_demo](https://github.com/espnet/notebook/actions/workflows/s2t_pr_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/s2t_pr_demo.yml)\n",
10+
"\n",
11+
"[POWSM](https://arxiv.org/abs/2510.24992) is a phonetic foundation model:\n",
12+
"it hears speech and answers in phones. POWSM-CTC is the encoder-only\n",
13+
"variant, and it does four things with one recording —\n",
14+
"\n",
15+
"| task | you give | it answers |\n",
16+
"| :-- | :-- | :-- |\n",
17+
"| `<asr>` | the audio | the words |\n",
18+
"| `<pr>` | the audio | the phones, in IPA |\n",
19+
"| `<g2p>` | the audio **and the words** | the phones |\n",
20+
"| `<p2g>` | the audio **and the phones** | the words |\n",
21+
"\n",
22+
"— which is what this notebook runs, one after the other, on the same six\n",
23+
"seconds of speech. CPU is enough."
24+
]
25+
},
26+
{
27+
"cell_type": "markdown",
28+
"metadata": {},
29+
"source": [
30+
"## Install"
31+
]
32+
},
33+
{
34+
"cell_type": "code",
35+
"execution_count": null,
36+
"metadata": {},
37+
"outputs": [],
38+
"source": [
39+
"%pip install -q \"espnet==202610.post2\" espnet_model_zoo librosa"
40+
]
41+
},
42+
{
43+
"cell_type": "markdown",
44+
"metadata": {},
45+
"source": [
46+
"## A recording"
47+
]
48+
},
49+
{
50+
"cell_type": "code",
51+
"execution_count": null,
52+
"metadata": {},
53+
"outputs": [],
54+
"source": [
55+
"import librosa\n",
56+
"from IPython.display import Audio, display\n",
57+
"\n",
58+
"!wget -q -O sample.wav https://github.com/espnet/espnet/raw/master/test_utils/ctc_align_test.wav\n",
59+
"speech, rate = librosa.load(\"sample.wav\", sr=16000)\n",
60+
"display(Audio(speech, rate=rate))"
61+
]
62+
},
63+
{
64+
"cell_type": "markdown",
65+
"metadata": {},
66+
"source": [
67+
"## The model\n",
68+
"\n",
69+
"POWSM reads a 20-second window, so a shorter clip is padded to it. The\n",
70+
"checkpoint also carries its own spelling of \"work the language out\n",
71+
"yourself\" — `<unk>` here, where OWSM writes `<nolang>` — so ask the model\n",
72+
"rather than typing a symbol."
73+
]
74+
},
75+
{
76+
"cell_type": "code",
77+
"execution_count": null,
78+
"metadata": {},
79+
"outputs": [],
80+
"source": [
81+
"import librosa\n",
82+
"from espnet2.bin.s2t_inference import Speech2Text\n",
83+
"\n",
84+
"s2t = Speech2Text.from_pretrained(\"espnet/powsm_ctc\", device=\"cpu\")\n",
85+
"\n",
86+
"window = s2t.preprocessor_conf[\"speech_length\"]\n",
87+
"nolang = s2t.no_language()\n",
88+
"padded = librosa.util.fix_length(speech, size=rate * window)\n",
89+
"print(f\"{window} s window, language symbol {nolang}, CTC-only: {s2t.ctc_only}\")"
90+
]
91+
},
92+
{
93+
"cell_type": "markdown",
94+
"metadata": {},
95+
"source": [
96+
"## `<pr>`: the phones\n",
97+
"\n",
98+
"The task it is built for. POWSM writes each phone between slashes, so that\n",
99+
"a phone spelled like a BPE token is still one token; spaced out is easier\n",
100+
"to read and is what anything counting or aligning them wants."
101+
]
102+
},
103+
{
104+
"cell_type": "code",
105+
"execution_count": null,
106+
"metadata": {},
107+
"outputs": [],
108+
"source": [
109+
"import re\n",
110+
"\n",
111+
"\n",
112+
"def phones(decoded):\n",
113+
" \"\"\"The phones out of POWSM's /p//h//o/ spelling.\"\"\"\n",
114+
" return \" \".join(re.findall(r\"/([^/]+)/\", decoded))\n",
115+
"\n",
116+
"\n",
117+
"def run(task, text_prev=\"<na>\"):\n",
118+
" decoded = s2t.best_path(\n",
119+
" padded, text_prev=text_prev, lang_sym=nolang, task_sym=task\n",
120+
" )[0][0]\n",
121+
" return decoded.split(\">\")[-1].strip() if \"<\" in decoded else decoded\n",
122+
"\n",
123+
"\n",
124+
"said_in_phones = phones(run(\"<pr>\"))\n",
125+
"print(said_in_phones)"
126+
]
127+
},
128+
{
129+
"cell_type": "markdown",
130+
"metadata": {},
131+
"source": [
132+
"## `<asr>`: the words, for contrast\n",
133+
"\n",
134+
"A phonetic model will transcribe, and it is worse at it than a model built\n",
135+
"for text. That is not a bug to report; it is what the model is for."
136+
]
137+
},
138+
{
139+
"cell_type": "code",
140+
"execution_count": null,
141+
"metadata": {},
142+
"outputs": [],
143+
"source": [
144+
"print(run(\"<asr>\"))"
145+
]
146+
},
147+
{
148+
"cell_type": "markdown",
149+
"metadata": {},
150+
"source": [
151+
"## `<g2p>`: phones for words you give\n",
152+
"\n",
153+
"Now the audio is not the only input. Hand it the words that were said, and\n",
154+
"it answers with the phones for *those* words rather than for whatever it\n",
155+
"thought it heard."
156+
]
157+
},
158+
{
159+
"cell_type": "code",
160+
"execution_count": null,
161+
"metadata": {},
162+
"outputs": [],
163+
"source": [
164+
"said = \"the sale of the hotels is part of holiday's strategy\"\n",
165+
"\n",
166+
"print(phones(run(\"<g2p>\", text_prev=said)))"
167+
]
168+
},
169+
{
170+
"cell_type": "markdown",
171+
"metadata": {},
172+
"source": [
173+
"## `<p2g>`: words for phones you give\n",
174+
"\n",
175+
"The other direction. The prompt is phones, in the spelling the model was\n",
176+
"trained on — each between slashes."
177+
]
178+
},
179+
{
180+
"cell_type": "code",
181+
"execution_count": null,
182+
"metadata": {},
183+
"outputs": [],
184+
"source": [
185+
"as_prompt = \"/\" + \"//\".join(said_in_phones.split()) + \"/\"\n",
186+
"print(as_prompt[:70], \"...\")\n",
187+
"\n",
188+
"print(run(\"<p2g>\", text_prev=as_prompt))"
189+
]
190+
},
191+
{
192+
"cell_type": "markdown",
193+
"metadata": {},
194+
"source": [
195+
"## Where next\n",
196+
"\n",
197+
"- **From the terminal**: `espnet phonemize sample.wav` is `<pr>`, in one line\n",
198+
"- **In the browser**: the [powsm-ctc Space](https://huggingface.co/spaces/espnet/powsm-ctc)\n",
199+
" offers all four tasks, and `espnet demo --model espnet/powsm_ctc` is the same page\n",
200+
"- **From an assistant**: the MCP server's `phonemize` tool\n",
201+
"- **Alignment**, with the same kind of model: [`s2t_align_demo.ipynb`](s2t_align_demo.ipynb)\n",
202+
"- **The encoder-decoder POWSM**, [`espnet/powsm`](https://huggingface.co/espnet/powsm),\n",
203+
" answers the same four tasks with a search rather than a CTC pass"
204+
]
205+
}
206+
],
207+
"metadata": {
208+
"colab": {
209+
"provenance": []
210+
},
211+
"kernelspec": {
212+
"display_name": "Python 3",
213+
"name": "python3"
214+
},
215+
"language_info": {
216+
"name": "python"
217+
}
218+
},
219+
"nbformat": 4,
220+
"nbformat_minor": 0
221+
}

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ One per task, flat in [`Demos/`](Demos), each short enough to read in a sitting.
1717
| [`asr_demo.ipynb`](Demos/asr_demo.ipynb) | [![asr_demo](https://github.com/espnet/notebook/actions/workflows/asr_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/asr_demo.yml) | Transcribe speech with OWSM-CTC, and let it work out the language |
1818
| [`asr_streaming_demo.ipynb`](Demos/asr_streaming_demo.ipynb) | [![asr_streaming_demo](https://github.com/espnet/notebook/actions/workflows/asr_streaming_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/asr_streaming_demo.yml) | Watch the words appear while the audio is still arriving |
1919
| [`st_demo.ipynb`](Demos/st_demo.ipynb) | [![st_demo](https://github.com/espnet/notebook/actions/workflows/st_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/st_demo.yml) | Translate English speech into German, French and Chinese — the same model |
20+
| [`s2t_pr_demo.ipynb`](Demos/s2t_pr_demo.ipynb) | [![s2t_pr_demo](https://github.com/espnet/notebook/actions/workflows/s2t_pr_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/s2t_pr_demo.yml) | Hear the phones, and cross between phones and words with POWSM |
2021
| [`tts_demo.ipynb`](Demos/tts_demo.ipynb) | [![tts_demo](https://github.com/espnet/notebook/actions/workflows/tts_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/tts_demo.yml) | Type a sentence, hear it spoken — one English voice, then 128 of them |
2122
| [`enh_demo.ipynb`](Demos/enh_demo.ipynb) | [![enh_demo](https://github.com/espnet/notebook/actions/workflows/enh_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/enh_demo.yml) | Pull speech out of noise, and measure how much it helped |
2223
| [`spk_demo.ipynb`](Demos/spk_demo.ipynb) | [![spk_demo](https://github.com/espnet/notebook/actions/workflows/spk_demo.yml/badge.svg)](https://github.com/espnet/notebook/actions/workflows/spk_demo.yml) | Turn a voice into a vector, and score two recordings against each other |

0 commit comments

Comments
 (0)