2026年8月14日 金曜日
AI時短ラボ
活用· 約28

TTSの読み間違いはASRで書き起こしても検出できない──「一行」を「いっこう」と読ませた音声が、元テキストと差分ゼロで戻ってきた実測

合成音声の読み間違いをASRで自動検出しようとすると、誤読された音声ほど検出できない。VOICEVOX ENGINE 0.25.1に「一行」を「いっこう」と誤読させた音声をwhisper.cpp 1.8.4で書き起こしたところ、出力は「この一行が原因なのだ」で元テキストと差分ゼロだった。誤読させた6件中4件が差分ゼロになった一方、正しく読ませた音声はsmallで2件・mediumで3件が「差分あり」と判定され、差分の有無は誤読の有無と対応しなかった(2026年8月14日実測)。

執筆・編集:
目次

日本語TTSの読み間違いを「ASRで書き起こして元テキストと突合すれば自動検出できる」と考えるのは、方向としては自然だが機能しない。誤読された音声を書き起こすと、出力は元の漢字表記に戻るので差分がゼロになるからである。当サイトは2026年8月14日に、VOICEVOX ENGINE 0.25.1に「一行」を「いっこう」と誤読させた音声を作り、whisper.cpp 1.8.4(ggml-small.binggml-medium.bin)で書き起こして元テキストと突合した。誤読させた6件のうち4件で差分はゼロだった。しかも同じ文を正しく読ませた音声のほうが差分が多く出た(smallで2件、mediumで3件)。対処は自動検出をあきらめることではなく、検出しようとするのをやめて「一度直した語が元に戻っていないこと」の回帰チェックに用途を絞り、新しい誤読は耳で見つけて辞書に積む運用に切り替えることである。

  1. 誤読音声6件中4件で、ASR出力が台本の元テキストと1文字も違わなかった。 「席が開いているのだ」をヒラ'イテと誤読させた音声も、書き起こすと「席が開いているのだ」に戻る(2026年8月14日・whisper.cpp 1.8.4 small/medium両方で同じ4件)
  2. 差分の有無は誤読の有無と逆に出ることがある。 mediumでは、誤読(ひらいて)の音声が差分ゼロ、正しく読ませた(あいて)音声が「席がいているのだ」となって差分ありになった。差分を誤読のシグナルに使うと、正しい音声のほうが弾かれる
  3. ASRを強くしても解決しない。 smallからmediumに上げても差分ゼロの4件は同じままで、増えたのは正読音声の誤検出(2件→3件)だった。モデルが賢いほど漢字表記を正しく復元するので、読みの情報はより確実に消える

症状:差分がゼロなのに、音は間違っている

当サイトの動画パイプライン(~/projects/motion_pipeline/)にある回帰チェックスクリプトcheck_readings.pyのdocstringには、この結論がそのまま書いてある。

できること : 一度登録した語が、次のビルドで元に戻っていないことの保証
できないこと: 辞書に無い新しい読み間違いの検出
             (ASRで書き起こしても漢字に戻るため、テキスト突合では原理的に不可能)

この「原理的に不可能」を実際に測ったのが本記事である。まず、VOICEVOXが何と読んでいるかは/audio_querykanaフィールドで機械的に取れる。合成せずに読みだけ確認できる。

curl -s -X POST "http://127.0.0.1:50021/audio_query?speaker=3" \
  --get --data-urlencode "text=この一行が原因なのだ" \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['kana'])"

返ってきた文字列がこれである。

コノ'/イッコオガ'/ゲンインナ'/ノダ'

イッコオ=「いっこう」である。「一行」は本来ここでは「いちぎょう」で、VOICEVOX 0.25.1は旅の一行(いっこう)のほうに倒している。当サイトが試した7つの文脈すべてで同じイッコオになった(下表はそのうち4つ)。一方で「二行目」ニギョオ'メ、「3行」サン'ギョオ、「四行」ヨン'ギョオ、「五行」ゴ'ギョオ、「六行」ロク'ギョオ、「十行」ジュウ'ギョオはすべて正しく読めた。試した7つの数詞のうち、崩れたのは「一」だけである。

入力テキスト kanaの出力
この一行が原因なのだ コノ'/イッコオガ'/ゲンインナ'/ノダ'
辞書に一行だけ足したのだ ジ'ショニ/イッコオダケ'/タ_シタ'/ノダ'
一行ずつ確認するのだ イッコオズ'_ツ/カクニン'/スル'/ノダ'
最後の一行なのだ サ'イゴノ/イッコオナ'/ノダ'

同じ日に見つかった他の誤読も挙げる。「開いて」は文脈で読みが変わる語で、開いてと書くと「席」「穴」「店」のどれでもヒラ'イテになり、同じ3つの主語を空いてに書き換えると3つともアイテ'と正しく読んだ。読みを決めているのは文脈ではなく表記のほうである。

入力テキスト kanaの出力 本来の読み
席が開いているのだ セ'キガ/ヒラ'イテ/イル'/ノダ' あいて
穴が開いているのだ アナ'ガ/ヒラ'イテ/イル'/ノダ' あいて
店が開いているのだ ミセ'ガ/ヒラ'イテ/イル'/ノダ' あいて
席がいているのだ セ'キガ/アイテ'/イル'/ノダ' あいて(正しい)
穴がいているのだ アナ'ガ/アイテ'/イル'/ノダ' あいて(正しい)
店がいているのだ ミセ'ガ/アイテ'/イル'/ノダ' あいて(正しい)
対日投資が増えたのだ タイ_ヒト'オシガ/フエ'タ/ノダ' たいにちとうし

問題は、ここまでは機械で分かるのに、音声を書き起こした瞬間にこの情報が消えることである。

原因:漢字表記は読みを捨てる、ASRは表記を復元する

kanaフィールドの正体は、VOICEVOX公式のAPIドキュメントにこう書かれている。

[読み取り専用]AquesTalk 風記法によるテキスト。音声合成用のクエリとしては無視される

記法そのものの定義は/accent_phrasesの説明にある。

is_kanaがtrueのとき、テキストは次のAquesTalk 風記法で解釈されます。デフォルトはfalseです。

  • 全てのカナはカタカナで記述される
  • アクセント句は/またはで区切る。で区切った場合に限り無音区間が挿入される。
  • カナの手前に_を入れるとそのカナは無声化される
  • アクセント位置を'で指定する。全てのアクセント句にはアクセント位置を1つ指定する必要がある。

引用したのは記法の4項目までで、原文にはこのあと疑問文のに関する項目が続く。本記事のkana出力に現れる記号はこの4つである。

つまりkana読みだけを表す表現で、漢字の情報を持たない。逆に台本に書いてある「一行」という漢字表記は読みの情報を持たない。「いちぎょう」と読んでも「いっこう」と読んでも、書けば同じ「一行」である。

そこにASRが入るとどうなるか。Whisperの公式READMEは、モデルの性質をこう説明している。

Whisper is a general-purpose speech recognition model. It is trained on a large dataset of diverse audio and is also a multitasking model that can perform multilingual speech recognition, speech translation, and language identification.

(Whisperは汎用の音声認識モデルである。多様な音声の大規模データセットで学習されており、多言語の音声認識・音声翻訳・言語識別を行えるマルチタスクモデルでもある)

A Transformer sequence-to-sequence model is trained on various speech processing tasks, including multilingual speech recognition, speech translation, spoken language identification, and voice activity detection. These tasks are jointly represented as a sequence of tokens to be predicted by the decoder, allowing a single model to replace many stages of a traditional speech-processing pipeline.

(Transformerのsequence-to-sequenceモデルが、多言語音声認識・音声翻訳・話者言語識別・音声区間検出を含むさまざまな音声処理タスクで学習される。これらのタスクはデコーダが予測するトークン列として統合的に表現され、単一のモデルが従来の音声処理パイプラインの多くの段を置き換えられるようになっている)

デコーダが出力するのは日本語として自然なトークン列であって、音の忠実な写しではない。日本語の書き言葉として自然なのは「一行」という漢字表記なので、イッコオと発音された音声からでもデコーダは「一行」を復元する。**TTSが読みを捨てて音にし、ASRが音を捨てて表記に戻す。**両端で情報が落ちるので、往復すると元に戻ってしまう。

実測:6文×2モデルを往復させた結果

各文を2通り合成した。raw=読み辞書を通していない台本そのままのテキスト(誤読する)、fixed=辞書で読みを与えたテキスト(正しく読む)である。**どちらの音声も、突合相手は台本に書いてある元テキスト(=rawの文字列)**である。運用上、突合できる正解はそれしかない。

合成条件は当サイトの本番パイプライン(prep.py)と同じspeaker=3(ずんだもん)・speedScale=1.15intonationScale=1.25に揃えた。書き起こしは以下のコマンドである。

ffmpeg -loglevel error -y -i c1_raw.wav -ar 16000 -ac 1 -c:a pcm_s16le c1_raw_16k.wav
whisper-cli -m ggml-small.bin -l ja -nt -np -otxt -of c1_raw_out c1_raw_16k.wav
# 台本テキスト 合成 実際の読み(kana ASR出力(small) ASR出力(medium)
c1 この一行が原因なのだ raw(誤読) イッコオガ' この一行が原因なのだ 一致 この一行が原因なのだ 一致
c1 fixed(正読) イチ'ギョウガ この一行が原因なのだ 一致 この一行が原因なのだ 一致
c2 辞書に一行だけ足したのだ raw(誤読) イッコオダケ' 辞書に一口だけ出したのだ 辞書に一項だけ足したのだ
c2 fixed(正読) イ'/チギョオダケ' 師匠に一両だけ足したのだ 師匠に一用だけ足したのだ
c3 最後の一行なのだ raw(誤読) イッコオナ' 最後の一行なのだ 一致 最後の一行なのだ 一致
c3 fixed(正読) イチ'ギョウナ 最後の一行なのだ 一致 最後の一行なのだ 一致
c4 席が開いているのだ raw(誤読) ヒラ'イテ 席が開いているのだ 一致 席が開いているのだ 一致
c4 fixed(正読) ア'イテ 席が開いているのだ 一致 席がいているのだ
c5 穴が開いているのだ raw(誤読) ヒラ'イテ 穴が開いているのだ 一致 穴が開いているのだ 一致
c5 fixed(正読) ア'イテ 穴が開いているのだ 一致 穴が開いているのだ 一致
c6 対日投資が増えたのだ raw(誤読) タイ_ヒト'オシガ 大被闘士が増えたのだ 対比投資が増えたのだ
c6 fixed(正読) タ'イニ/チ'/トオシガ' 毎日投資が増えたのだ たい日投資が増えたのだ

集計するとこうなる。

small medium
誤読音声6件のうち、差分ゼロ(=検出できなかった) 4件 4件
正読音声6件のうち、差分あり(=誤検出) 2件 3件

差分の有無は、誤読の有無とまったく対応しなかった。 6文のうち、差分が誤読を正しく言い当てた文は1つも無い。内訳は次の2種類である。

偽陰性(c1・c3・c4・c5)。 誤読しているのに差分がゼロになった。しかもc1・c3・c5では、rawfixedのASR出力が文字列として完全に同一だった(smallではc4も同一で、両モデルに共通して同一だったのがこの3件である)。音は違うのに書き起こしは1文字も違わない。突合という手段では、この2つを区別する情報がそもそも残っていない。

偽陽性(c2・c6)。 差分は出たが、その差分は誤読の有無を分けていない。c2・c6はraw(誤読)とfixed(正読)の両方が差分ありになっており、差分が出たかどうかで両者を区別できない。しかも正しく読ませたfixedのほうが壊れることがある。c2のsmallは「辞書」を「師匠」、「いちぎょう」を「一両」と取り違えて元テキストと3文字違い、誤読させたrawの2文字より差分が大きかった。ただし壊れる向きは一定しない(c6のsmallは逆にrawが4文字・fixedが1文字だった)。いずれにしても差分を誤読のシグナルとして扱えば、正しく読めている音声を「誤読あり」と報告することになる。

そしてc4のmediumが、この手法の性質を一番はっきり示している。

  • 誤読(ヒラ'イテ)の音声 → 「席がいているのだ」→ 台本と一致 → 合格と判定される
  • 正読(ア'イテ)の音声 → 「席がいているのだ」→ 台本(開いて)と差分 → 不合格と判定される

mediumはア'イテを正しく聞き取れるだけの精度があるからこそ、日本語として自然な「空いて」という別の漢字を当てて差分を作った。ASRが賢くなるほど、正しい音声が弾かれ、間違った音声が通る。 シグナルの符号が逆転している。

かな比較に変えても直らない

「漢字に戻るのが問題なら、両方をかなにして比べればいい」という発想が次に来る。当サイトはこれを実装していないので実測していないが、採用しなかった理由は書いておく。突合するには「正解のかな」を作る側が必要で、その役をやれるのは同じ形態素解析器(VOICEVOXが内部で使っているもの)しかない。同じ解析器に正解を作らせれば、同じ「いっこう」を正解として出す。 差分はやはりゼロになる。別の解析器を正解役にすれば差分は出るが、それは2つの解析器の不一致であって誤読の証拠ではない。判定するには結局、人が耳で聞いて「どちらが正しいか」を決める工程が要る。

対処:検出をあきらめ、回帰チェックに用途を絞る

当サイトが2026年7月29日に恒久化した運用は次のとおりである。

  1. 共通辞書と台本辞書を併用する。 readings_common.jsonが全動画共通、台本JSONのreadingsが個別。prep.pyが両方を読んでマージし、同じキーは台本側を優先する
  2. 文脈で読みが変わる語を共通辞書に入れない。 「開いて」がまさにこれで、ひらいてあいてのどちらで一律置換しても別の場所を壊す。こういう語は台本側のreadingsで個別に指定する
  3. 新しい誤読は耳で見つける。 視聴者から指摘をもらったらreadings_common.jsonに追記する(次の動画から自動で効く)。事前に総当たりで探す運用は取っていない
  4. 回帰チェックは「戻っていないこと」だけを見る。 check_readings.pyは①登録語が確実に置換されるか②共通辞書と台本の値の衝突③辞書に無い同形異音の候補、の3つを出す。新しい誤読の検出は目的に入れない

辞書の実装で効いているのは、地味だが次の2点である。

common = {k: v for k, v in json.load(f).items() if not k.startswith("_")}
...
return common, local, {**common, **local}, sc

_で始まるキーをコメント扱いで捨てるので、辞書ファイルの中に見出しや運用メモをそのまま書ける。実際readings_common.jsonは総キー153のうち9個が_始まりで、実語数は144語である。そして{**common, **local}の順で、台本側が共通側を上書きする。

2026年8月14日に既存の台本anthropic_openweights.jsonで実行した結果がこれである。

=== check_readings: anthropic_openweights.json ===
共通144語 + 台本16語 → 計145語

[1] 登録語の使用: 45語が台本に出現
    AI×29 / Anthropic×12 / 1つ×10 / 米国×8 / 生物×7 / 3つ×6 / Amodei×5 / 2つ×5

✅ 登録語はすべて置換される

144+16なのに合計が145なのは、台本側16語のうち15語が共通辞書と重複しているからである(重複15語の値はすべて共通側と同一で、実際に上書きが起きた語は0だった。台本にしか無い語はKimi K3の1語)。この足し算が合わないこと自体が、マージが効いている証拠になっている。

観測条件

すべて2026年8月14日、macOS 15.1.1(arm64・Apple M2)の同一マシンで実施した。

項目
TTS VOICEVOX ENGINE 0.25.1(http://127.0.0.1:50021speaker=3
合成パラメータ speedScale=1.15 / intonationScale=1.25(本番prep.pyと同一)
ASR whisper.cpp 1.8.4(Homebrew whisper-cpp)・-l ja -nt -np
モデル ggml-small.bin(487,601,967バイト)/ggml-medium.bin(1,533,763,059バイト)
リサンプル ffmpeg 8.0.1で16kHz・モノラル・pcm_s16leに変換
試行数 6文×2通り(raw/fixed)=12音声、×2モデル=24回の書き起こし
読み辞書 readings_common.json 実語数144語(総キー153・_始まり9)

読みの調査は、台本に書く形の日本語文に/audio_queryを投げてkanaを読む形で行い、そのうち6文を実際に合成して往復させた。調査スクリプトに残っている投入テキストは58文(自然文52文+辞書で読みを与えた版6文。いずれも重複を除く)である。本文の表にある「席/穴/店が空いているのだ」と「五行/六行/十行」の6文はスクリプトに残っていないため、検品時に同一マシン・同一バージョンで取り直し、表の値と一致することを確認した。この6文を含めると自然文は58文、投入テキストの総数は64文になる。

58文のうち、当サイトが明確な誤読と判断したのは「一行」「開いて」「対日投資」の3語にとどまった。判断を保留したものもある。「重複」はジュウ_フクオ'、「早急」はソオキュウニ'、「他人事」はタニンゴトデワ'と読まれたが、これらは規範的な読み(ちょうふく・さっきゅう・ひとごと)と慣用読みが並立する語で、誤りと断定していない。「人気がないのだ」のニンキガ'も、文脈次第で「ひとけ」「にんき」のどちらもありうるため保留した。

また「対日」は一律に外れるわけではない。「対日投資」はタイ_ヒト'オシと崩れたが、「対日政策」はタイニ_チセ'エサク=たいにちせいさく と正しく読めた。誤読は語ではなく語の並びで起きている。

辞書に載っている144語のうち、当サイトが個別に確認した範囲では0.25.1は指定なしでも正しく読めた(今年→コトシ、市場→シ'ジョオ、大手→オ'オテ、生物→セ'エブツ、米国→ベエコク、1つ目→_ヒトツ'メ、3行→サン'ギョオの7語を確認)。144語すべてを確認したわけではないので、「大半が不要になった」とは言えない。辞書の語数は「今も間違える語の数」ではなく、過去に一度でも指摘を受けた語の累積である。

正直な但し書き

実測は6文・2モデルであって、「あらゆる誤読で差分がゼロになる」を示したものではない。 示したのは「6文中4文で差分がゼロだった」「差分の有無が誤読の有無と対応しなかった」という観測である。check_readings.pyのdocstringにある「原理的に不可能」は当サイトの運用上の結論であり、本記事の測定がそれを証明したとまでは言えない。反証があるとすれば、読みの違いが表記の違いに必ず現れる語彙を選んだ場合で、その条件は本記事では特定していない。

音声は聴いていない。 当サイトのClaudeは音を聴けないため、判定はすべてkanaフィールドとASRの出力テキストという機械可読な文字列だけで行っている。イッコオという記法が実際にどう聞こえるかは、人の耳で確認していない。この記事の測定そのものが、記事が主張している限界の内側にある。

fixed側でアクセント句の切れ目が変わった件は未検証である。 辞書で読みを与えた6件のうち2件で、kanaのアクセント句の区切りが変化した。c2は「いちぎょう」がイ'チギョオダケ'に、c6は「たいにち」がタ'イニチ'に割れており、本来1語のはずの箇所がアクセント句をまたいでいる。同じ「いちぎょう」でもc1はイチ'ギョウガ、c3はイチ'ギョウナと割れていないので、同じ置換でも前後の語によって結果が変わる。読みを直すと別の何かが崩れうることを示す出力だが、これが耳で聞いて不自然かどうかは確認していない。割れる条件も特定していない。

whisper.cpp の small と medium しか試していない。 large-v3large-v3-turbo、クラウドのASR APIでは結果が変わる可能性がある。ただし本記事の観測が正しければ、モデルを強くするほど漢字表記の復元は正確になるので、偽陰性(誤読なのに差分ゼロ)は減らずに増える方向に働くと考えている。これは推測であって、測っていない。

VOICEVOX 0.25.1の1バージョンだけの結果である。 読み間違いは辞書と解析器の更新で変わる。本記事のイッコオヒラ'イテが将来のバージョンで再現する保証はない。他のTTS(ElevenLabs等)については何も検証していない。

check_readings.pyはビルドに組み込まれていない。 build.shが台本に対して自動で走らせるのはscript_lint.pyduration_check.py(尺ゲート。ショートビルドとDURATION_GATE_SKIP=1のときは飛ばす)で、check_readings.pyはどちらの経路でも呼ばれず、prep前に手で実行する運用である。回帰チェックを名乗る以上は自動化すべき箇所だが、2026年8月14日時点では手動のままである。

出典

  • voicevox_engine API Document(2026年8月14日確認): https://voicevox.github.io/voicevox_engine/api/kanaフィールドと/accent_phrasesのAquesTalk風記法の説明。同内容をローカル実行中のVOICEVOX ENGINE 0.25.1の/openapi.jsonでも突合した
  • openai/whisper - README(2026年8月14日確認): https://github.com/openai/whisper /引用2件はraw.githubusercontent.comのREADME原文(8,246バイト)と一致を確認
  • ggml-org/whisper.cpp(2026年8月14日確認): https://github.com/ggml-org/whisper.cpp /About欄は「Port of OpenAI's Whisper model in C/C++」、READMEは「High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model」「Plain C/C++ implementation without dependencies」(raw.githubusercontent.comのREADME原文36,779バイトと突合)
    • ⚠️GitHubのggerganov/whisper.cppggml-org/whisper.cppへ転送される(2026年8月14日、curl -Lで最終URLがggml-org側になることを確認)。一方Hugging Faceのモデル置き場はggerganovのままで、https://huggingface.co/ggerganov/whisper.cppは転送されず200を返す(ggml-org側は401)。本記事のggml-small.binggml-medium.binhuggingface.co/ggerganov/whisper.cpp/resolve/main/から取得した
  • AI時短ラボ 実測ログ: check_readings.pyreadings_common.jsonprep.py~/projects/motion_pipeline/)、および2026年8月14日に取得した書き起こし結果24件(result_small.jsonresult_medium.json
シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事