當 Azure 音素標籤與課程 IPA 不一致時
When the engine's phoneme and the course IPA disagree
一款英語口説練習 app 調整了 Azure Pronunciation Assessment 的音素顯示方式:抽屜只以詞庫中的 iscommon American IPA 作為目標音,不再把 Azure 的 SAPI 標籤當成課程要求。兩者音素一致時顯示 Azure 評分;不一致時保留課程 IPA、隱藏評分,並提示錄音與目標音不符。
A user said our IPA for "cause" was wrong. We almost changed the lexicon.
We run an English speaking-practice app. After someone records a word, we show a score from Azure Pronunciation Assessment and a drawer of phonemes: the sound the course expects, a short hint in Chinese, and how close that attempt was.
They hit cause. Our course IPA is /kɑːz/: an open, unrounded "ah." The drawer marked /ɔ/ and explained it as a rounded "oh." Both symbols sat on the same screen. If you are learning, that looks like the app cannot agree with itself.
First wrong fix: change the lexicon
Did we store the wrong American IPA for cause? Plenty of dictionaries still print /kɔːz/. Our teaching accent is General American with the cot–caught merger, so law, thought, caught, and cause all go to ɑː. More and horse keep a rounded ɔː because of the following r. /kɑːz/ is the form we mean to teach. Changing the lexicon would have cleaned up one drawer by breaking the course.
Second wrong fix: blame Azure's ear
Maybe Azure misheard the recording. That was wrong for this bug. Azure scores against its own phoneme set, SAPI. In SAPI, ao is a rounded ɔ and aa is ɑ. For cause Azure often expects k ao z. If the learner produced something in that neighborhood, Azure can give the vowel a decent score and still label it ao. We mapped ao → ɔ for display and attached our hint for ɔ. The score was about Azure's phone. The symbol and the hint were about our course.
Both systems are consistent on their own terms. The bug was putting Azure's label in the slot learners read as "the sound we asked you to make."
Third wrong fix: remap every ao to ɑː
We looked at a global remap next: treat every ao as ɑː, since that is how we teach the thought vowel without r. That fails as soon as you leave cause.
More is m ao r on Azure's side and /mɔːr/ on ours. The ao has to stay round because of the r. School is /skuːl/ in the lexicon; some recordings come back as s k ao l. Those ao tokens are what Azure heard, not the target. Your, one, and the each mismatch in a different way. A 1:1 table from SAPI onto teaching IPA cannot carry merger, rhotic vowels, and "what the user actually said" at the same time. Changing the shared SAPI→IPA map would also have recolored letters, rewritten daily-report snippets, and reinterpreted old stored SAPI strings. We left that table alone.
The rule we kept
The lexicon's iscommon American IPA is the only target the drawer is allowed to show. Azure still computes the word score, the letter colors, and a guess at what the attempt sounded like.
- If Azure's expected phone is the same sound as the lexicon token under our encoding (
ɑːacceptsaaandao;ɔːonly acceptsao; final laxiacceptsiy), we keep the lexicon symbol and show Azure's accuracy. - If it is a different sound, we keep the lexicon symbol, hide the score, and write "this doesn't match," plus "what you sounded like" in teaching IPA.
- If the recording is too far gone (no IPA, or at least three accuracy-0 phones), we do not invent a phone-by-phone story. The copy tells the user we cannot point at a single sound, and to play the recording back against the model audio.
We did not change the Azure request, the score we store, or the SAPI error phones on the record. Letter spans on the word still follow Azure. The coach character uses the same aligned phones, so it talks about /ɑː/ on cause, not /ɔ/. On a mismatch it says the recording does not line up with that sound, instead of calling a foreign label "inaccurate."
Real samples beat inventing copy
The design only held up because we stopped inventing examples. We pulled real Azure JSON from past practice attempts, attached the iscommon US IPA for that word, and generated a side-by-side HTML sheet: old drawer versus proposed drawer. Fake copy would have looked fine. The samples did not.
A few of the failures:
Wrong POS dump. Exporting every POS IPA and then picking the longest string is not what practice does. Practice uses one iscommon American form. Our first dump mixed readings, so the preview "fixed" the wrong target.
Alignment greed. Same-length strings should stay one-to-one. If you insert a gap whenever a later lexicon phone could claim the current Azure phone, believe steals iy for a later /iː/ and marks the earlier /ɪ/ as a mismatch it did not earn. We only insert a gap when there are more lexicon phones left than Azure phones left, and a later lexicon token actually matches the current Azure phone. That is how /ˈriː.əl/ can still show the schwa when Azure returns r iy l.
Happy vowel. Word-final /i/ has to match Azure iy. Mapping only iː → iy left sorry looking like a miss.
Schwa + r. ax plus r is one teaching phone, /ɚ/ or /ɝː/, not two rows.
All-zero traces. Forcing a pairing on water or person produces confident nonsense. Those go to the unmatched fallback.
The preview also forced us to rewrite the Chinese. "More like /ɔ/" sounds like we are naming the target. "Your recording sounds like /ɔ/" does not, once the big symbol is already /ɑː/. We dropped "the standard pronunciation is" because the large /ɑː/ already is that. We kept the two playback controls: model audio and the user's take (button elements next to each other in the drawer).
Index remapping was the production risk
On the way into production the risky part was not the encoding table. It was index remapping. Azure still colors bet and ter from its own phones. After we group ax r into one /ɚ/, the drawer rows are shorter than Azure's list. The syllable indexes have to move with the rows or a tap on ter opens the wrong phones. Custom practice had been dumping every phone into the drawer regardless of which letters you tapped; it now uses the same component as daily practice.
A handful of high-frequency words still have no iscommon POS in our data: are, been, got, was, were, won. Those trips currently hit the unmatched message. That is a data stamp, not another mapping trick. Daily-report text still talks in Azure phones; we left that for a second pass so the live drawer and the stored score could not drift together.
Takeaway
If you show engine phones to learners, split the jobs: the engine may score; the course owns the symbols. A translation table between those two alphabets is a display convenience, not a teaching standard. You can break the drawer. You usually should not break scoring or the rows you already saved. And a folder of real misrecognitions will find the cases a mapping table will never list: school heard as ao, one heard as aa against /ʌ/, the heard as iy against /ə/.
Those are the cases the drawer has to survive, not the one word a user happened to screenshot.
來源:DEV Community · dev.to