DEV Community

orca_forge
orca_forge

Posted on Originally published at forge.workstyle.tech

Why AI Models Sometimes Elongate Their Greetings Like 'Kon'nichiwa~'

πŸ“ Originally published (in Japanese) at forge.workstyle.tech.

When I had the trained voice model read "こんにけは" (Hello), it stretched the phrase to "こんにけわぁ." There was no instruction to stretch it in the script.

The feedback was as follows:

For "こんにけは," it's pronounced as "こんにけわぁ" with an accent on the last syllable. It feels like something is mixed in.

"Something mixed in" was accurate, and indeed, something was mixed in. The training corpus contained clips with stretched endings.

The problem was that the mechanism to detect it was fundamentally non-functional by design.

Script Matching Was Done

In corpus generation, the audio read by TTS is transcribed using Whisper and then compared with the script.

def _kana(s: str) -> str:
    # Katakana to Hiragana
    return "".join(chr(ord(c) - 0x60) if "γ‚‘" <= c <= "γƒΆ" else c for c in s)

_PUNCT_RE  = re.compile(r"[γ€γ€‚οΌοΌŸ!?…・\sγ€Œγ€γƒΌγ€œ,\.]")
_REPEAT_RE = re.compile(r"(.)\1+")

def _collapse(s):
    return _REPEAT_RE.sub(r"\1", _PUNCT_RE.sub("", s or ""))

def judge_transcript(script_text, transcript, ...):
    a = _kana(_collapse(script_text))
    b = _kana(_collapse(transcript))
    sm = difflib.SequenceMatcher(None, a, b)
    ...
Enter fullscreen mode Exit fullscreen mode

Normalization is performed before comparison. It’s a straightforward implementation.

Here, look at _PUNCT_RE. Among the characters to be removed is γƒΌ (prolongation mark). And _REPEAT_RE compresses consecutive identical characters into one.

Script: こんにけは
Transcription: こんにけわー

After normalization:
  Script β†’ こんにけは
  Transcription β†’ こんにけわ      ← The `γƒΌ` is removed
Enter fullscreen mode Exit fullscreen mode

The match rate is high. It becomes a difference of just one character between "は" and "わ." The information that the ending was stretched is discarded during normalization.

The same happens with consecutive vowels.

Transcription: こんにけわあ  β†’  _REPEAT_RE compresses consecutive "あ" β†’  こんにけわ
Enter fullscreen mode Exit fullscreen mode

This means this verification cannot detect stretched endings no matter what. The normalization written to ignore prolongation marks works the same way even when we want to detect them.

The normalization itself is correct. If the goal is to absorb variations in notation and check for content consistency, prolongation marks should be removed. The problem was that there was only one normalization for two purposes.

Judging with Raw Transcription

I separated the judgment for content consistency from the judgment for stretched endings. The latter uses the raw string before normalization.

_TAIL_LONG_RE = re.compile(r"[ーぁ-γ‚“]$")

def trailing_elongation_mismatch(script_text: str, raw_transcript: str) -> bool:
    """Detects stretched endings not present in the script.

    ⚠️ Pass the raw transcription from Whisper to `raw_transcript`.
    Kana normalization discards prolongation marks, so detection is impossible with normalized strings.
    """
    script = (script_text or "").rstrip("γ€‚γ€οΌοΌŸ!? ")
    trans  = (raw_transcript or "").rstrip("γ€‚γ€οΌοΌŸ!? ")
    if not script or not trans:
        return False

    # Check if the last character of the script is "stretched" in the transcription
    tail_script = script[-1]
    # Prolongation mark exists in transcription but not in script
    if "γƒΌ" not in script and trans.endswith("γƒΌ"):
        return True
    # Consecutive identical vowels exist only in transcription (e.g., "です" β†’ "ですぅ," "ですう")
    if len(trans) > len(script) and trans[len(script)-1:].startswith(tail_script):
        extra = trans[len(script):]
        if extra and all(c in "γγƒγ…γ‡γ‰γ‚γ„γ†γˆγŠγƒΌ" for c in extra):
            return True
    return False
Enter fullscreen mode Exit fullscreen mode

With the judgments separated, the caller checks them independently.

res  = judge_transcript(text, tr["text"])                  # Content consistency (with normalization)
tail = trailing_elongation_mismatch(text, tr["text"])      # Stretched endings (raw string)

if tail:
    continue          # If the ending is stretched, immediately redraw (even if the content matches)
if res.ok:
    save(wav)
Enter fullscreen mode Exit fullscreen mode

Stretched endings are disqualified even if the content matches. No matter how high the content consistency, if the ending is stretched, it’s not included in the training material. Relaxing this would ingrain the habit.

Designing Tolerance

However, setting it to zero completely reduces yield. In emotionally charged speech, some stretching naturally occurs.

Ultimately, I used this two-condition OR logic:

def accept(text, transcript):
    v    = judge_transcript(text, transcript)
    tail = trailing_elongation_mismatch(text, transcript)
    return (v.ratio >= 0.82 and tail <= 2) or (v.ratio >= 0.70 and tail == 0)
Enter fullscreen mode Exit fullscreen mode
  • If content consistency is high (β‰₯ 0.82), allow up to 2 stretched endings.
  • If content consistency is somewhat low (β‰₯ 0.70), stretched endings must be 0.

This ensures that clips with "questionable content and stretched endings" are reliably discarded. It tolerates recognition degradation due to emotional expression while not allowing the stretching habit to pass.

Hitting from the Caption Side

Another effective measure was writing it in the caption during generation.

When designing role-specific voices, I included this in the caption:

A female announcer's voice accurately reading a news script. Clear and easy to understand,
with a calm, intellectual tone, pronouncing each word distinctly, including the endings.
Enter fullscreen mode Exit fullscreen mode

The last part, "pronouncing each word distinctly, including the endings," was key.

The effect was clear. Measuring 24 candidates Γ— 5 probe sentences = 120 clips showed zero stretched endings. Before the quality gate could reject them, stretched audio simply wasn’t generated.

Role-included caption (distinct endings) β†’ Stretched endings 0/120
Enter fullscreen mode Exit fullscreen mode

The gate is a mechanism to "discard bad ones," but discarding reduces yield. If it can be prevented upstream, that’s cheaper. For TTS that can be instructed via captions, it’s worth writing items the quality gate checks into the caption as well.

Relationship with Speech Style

As I pursued this phenomenon, a deeper structure emerged. The desirability of stretched endings varies by use case.

  • Narrator, Announcer, Call Center β†’ Prefer tight endings.
  • VTuber, Streaming β†’ Stretching is more natural.

So, I defined "speech styles" for each use case and varied the corpus script and quality gate strictness by speech style. For business-style speech, the ending gate is strictly applied; for casual styles, it’s relaxed.

What’s important is that speech style is baked into the corpus and cannot be changed during synthesis (as discussed in [[speaking-style-is-baked-into-the-corpus|speaking speed cannot be changed after training]]). If you want both "tight endings" and "stretched endings" for the same voice, bake two versions with the same design values (caption + seed) but different speech styles. That’s what I’m doing.

Summary

Separate normalization by purpose. Normalization for content consistency and normalization for detecting specific anomalies are different. Trying to do both with one normalization causes one to fail.

Be aware of discarded information. Including γƒΌ in _PUNCT_RE was the right decision, but a judgment needing that information arose later. Adding comments to normalization about "what is being discarded" helps the next person notice.

It’s cheaper to prevent it upstream. Discarding at the gate reduces yield. If it can be instructed during generation, hit it there.

Symptoms appear in model behavior. Even if the corpus data shows "a few clips with stretched endings," it doesn’t look abnormal. The habit only appears after training and speaking. Dataset inspection alone is insufficient; a process to confirm post-training output is necessary.


Series: Mass-Producing Practical Voices from Diffusion TTS

This is a record of designing voices from a single caption, manufacturing training corpora, and mass-producing role-specific practical voices. This article is Part 3: Quality Gate.

← Previous: [[one-rough-clip-ruins-the-whole-style|One Rough Clip Ruins the Whole Style]]

β†’ Next: [[the-character-that-broke-the-tts-input|"ε°‘γ€…" Becomes "しょも" β€” Permitted Character List Was Trimming Japanese]]

All 18 Articles in the Series

  1. [[diffusion-tts-too-slow-for-conversation|The TTS Chosen for Quality Was Too Slow for Conversation]]
  2. [[deterministic-voice-gacha-and-design-ledger|Drawing Voices Like Gacha]]
  3. [[screening-voices-by-metrics-not-ears|Having a Machine Select "Narrator-Like Voices" from 24 Candidates]]
  4. [[quality-gate-selection-bias-flat-takes|The Stricter the Quality Gate, the More Monotone Voices Survive]]
  5. [[speaking-style-is-baked-into-the-corpus|Speaking Speed Cannot Be Changed After Training]]
  6. [[tts-changes-recording-room-every-time|TTS That Changes "Recording Room" Every Generation]]
  7. [[one-rough-clip-ruins-the-whole-style|One Rough Clip Ruins the Whole Style]] 8. [[where-did-the-elongated-ending-come-from|Where Did the AI's Habit of Stretching "こんにけわー" Come From? ← You are here
  8. [[the-character-that-broke-the-tts-input|"ε°‘γ€…" Becomes "しょも" β€” Permitted Character List Was Trimming Japanese]]
  9. [[hallucination-guard-that-never-fired|Hallucination Countermeasure Code Only Ran When There Was No Hallucination]]
  10. [[three-chars-became-a-verbal-tic|The "3 Characters" Allowed by the Quality Gate Became the Model's Verbal Tic]]
  11. [[measuring-factory-defects-as-product-traits|Discarding Candidates Over Fixable Defects]]
  12. [[defects-invisible-to-transcription|Defects Invisible to Transcription]]
  13. [[70-minutes-lost-to-a-network-blink|70 Minutes of Training Material Lost to a Network Blink]]
  14. [[ja-vs-JP-babbling-model|From "ja" to "JP" β€” Creating a Babbling Model]]
  15. [[four-registration-paths-one-exit|Four Registration Paths, Zero Management Screens]]
  16. [[who-is-rolling-back-whom|Deploying and Overwriting Each Other's Work]]
  17. [[chasing-unmeasured-targets-with-thresholds|Chasing Unmeasured Targets with Thresholds Always Fails]]

The insights are summarized in [[Manufacturing Pipeline for Mass-Producing Practical Voices from Diffusion TTS]].

Top comments (0)