品質検問 — 三つの検問所
生成AIを使った制作でいちばん時間を取られるのは、エラーを出さない不具合です。 無音、誤読、空のフォールバック、未デプロイ。どれも赤くなりません。 ここには、まだ型が存在していません。
プログラムの不具合は、たいてい大声で失敗します。例外が飛び、テストが赤くなり、ビルドが止まる。 私たちはその大声に慣れていて、静かなら正常だと思う癖がついています。
ところが制作物が音声やレイアウトや自分に読めない言語に広がった瞬間、 その前提が崩れます。次の4つは、どれもエラーを出しません。
| 種類 | 起きること | 画面上の見え方 |
|---|---|---|
| 無音 | 音声ファイルが404 | 普通に進む。声だけ無い |
| 誤読 | 漢字を別の読みで発音 | 字幕は正しい。音だけ違う |
| 空のフォールバック | 翻訳が無く原文へ退避 | 完璧に表示される。ただし全部原語 |
| 未デプロイ | 手元は正しく、公開物が古い | ローカルのテストは全部通る |
これらに共通するのは、「動いている」と「正しい」がズレていることです。
そしてズレていることを、システムは教えてくれません。
音声ファイル名を「話者+台詞」のハッシュにすると、管理表が要らなくなります。台詞を足せば、その音声が自動的に決まる。
function keyOf(t){ let h=0; for (let i=0;i<t.length;i++) h=(h*31+t.charCodeAt(i))|0; return 'k'+h; }
美しい仕組みですが、鋭い刃が1つあります。本文を1文字直すと、その台詞は永久に無音になる。 参照先が変わり、404になり、再生は静かに失敗し、ゲームは平然と進みます。
前の作品で、161本が無音のまま数週間動いていました。改稿で足した心内描写が、まるごと沈黙していた。 誰も気づかなかったのは、エラーが出なかったからです。
改稿のたびに、台本から期待されるファイル名を再計算して、実ファイルと両方向で突き合わせます。
参照=237 実ファイル=237 欠落=0 孤児=0 破損疑い(<800B)=0
合成音声のQAとして自然に思いつくのは、書き起こして原文と比較することです。 切れや幻覚は捕まります。しかし誤読に対しては、原理的に無力です。
「病」は やまい とも びょう とも読めます。書き起こしは音を漢字に戻すので、 どちらで発音しても同じ「病」が返る。差分はゼロ。テストは緑。音は間違っている。
最初の1件は、読者からの報告で知りました。
「病だった、が、やまいだった、です」——機械は何も言いませんでした。
解決策は、書き起こし側から漢字を奪うことでした。語彙から漢字を含むトークンを全部禁止し、仮名で出させます。
kanji = re.compile(r'[一-鿿]')
suppress = [-1] + [i for i in range(tok.get_vocab_size()) if kanji.search(tok.decode([i]))]
segs, _ = model.transcribe(path, language="ja", beam_size=8,
initial_prompt="ひらがなだけでかきます。",
suppress_tokens=suppress)
プロンプトだけで仮名になる確率は約29%ですが、トークン抑制を入れるとほぼ確実になります。 これで比較が音の比較になり、誤読に居場所がなくなります。
| 表記 | 誤った読み | 正しい読み |
|---|---|---|
| 病 | びょう | やまい |
| 音を上げる | おと | ね |
| 正史 | しょうし | せいし |
| 罹って | りって | かかって |
修正は合成に送る文字列だけを仮名に置き換えます。ファイル名は原文のハッシュのままなので、参照は壊れません。
READINGS = [("罹って","かかって"), ("音を上げ","ねをあげ"), ("正史","せいし"), ("病","やまい"), ...]
順序が重要です。「病」を先に置換すると「病死」が「やまいし」になります。長い語から当てます。
作中の造語「灰霞」をカタカナで「ハイガスミ」と指定したところ、読みは正しいのに音が明らかに変でした。 カタカナは日本語で外来語を示すので、合成器が外来語の抑揚で読んだのです。 ひらがなに変えただけで直りました。文字はレジスターまで一緒に渡しています。
いちばん恥ずかしく、いちばんありふれた失敗です。
多言語化のとき、「翻訳が無ければ原文を出す」フォールバックを書きました。正しい実装です。 ところが台本ファイルのデプロイを忘れたとき、参照先が全部undefinedになり、全言語が 完璧な日本語で表示されました。エラーはゼロ。ローカルのテストは全部緑(手元には正しいファイルがあるので)。
安全なフォールバックは、大きな失敗を静かな失敗に変換する決定でもある。
利用者にとって正しいことは多いが、無料ではありません。
以来、検証は公開URLを叩いて行います。ハッシュを突き合わせれば、配信されている実物と手元の差が出ます。
# ローカルと公開物が一致しているかを直接見る
LOCAL=$(md5sum assets/voice/k2102619210.mp3 | cut -d' ' -f1)
curl -s https://example.com/assets/voice/k2102619210.mp3 | md5sum
レイアウトも同じ考え方で検証できます。二言語同時字幕では、 2つ目のテキストの上端が1つ目の下端を越えないことを、 ヘッドレスブラウザで座標として確認しています。見た目の不具合も、静かな不具合です。
計測スクリプトを書くときは、取得に失敗した項目をゼロで埋めないこと。
null にして「取得できなかった」と明記します。0で埋めると、
数字は揃って見えるのに判断材料が静かに壊れます。同じ病気です。
『百の悪行』は、日本語237本・英語237本の音声すべてを上の3つの検問所に通しています (欠落0・孤児0、読み検証済み、公開物のハッシュ照合済み)。7言語・約30分・無料。
遊んでみるもっと安上がりな方法も1つあります。実際に読んでくれる人に渡して、 「その言葉、音が変」と言われたら聞くこと。 いちばん効いた誤読の指摘は、2件ともそこから来ました。
他の型は 新しい形の研究 に置いています。