
2026/01/31 9:51
**Show HN:** 私は自分の中国語(普通話)の声調を直すため、9Mパラメータの音声モデルを訓練しました。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
概要:
AISHELL‑1 と Primewords から約300時間分の文字起こし済み中国語音声を用い、SpecAugment と4台の RTX 4090 GPU を使用して、約9 Mパラメータのコンフォーマーモデル(Conformer)を訓練しました。ネットワークは40 msごとにトークン確率を出力し、Viterbi アルゴリズムでピンイン音調トークン列(各音節+音調がユニークなトークン;中性音調はトーン5へマッピング)に沿ってアラインメントを強制します。語彙には1,254個のトークンと、<unk>が含まれます。<blank>
このモデルでは Token Error Rate(TER)が約 5.3 %、音調精度が約 98.3 %です。INT8 量子化によりサイズを約37 MB から約11 MB に縮小し、TER は +0.0003 の増加のみで済みました。先頭の無音によるアラインメントミスを修正するため、確率が0.7 を超えるフレームは採点前に除外しました。<blank>
最終的な量子化モデル(約13 MB のダウンロード)は ONNX Runtime Web 経由でブラウザ上で完全に実行され、リアルタイムの発音フィードバックを可能にします。今後は Common Voice などの会話データセットを取り入れ、カジュアルまたは子供の話し言葉(通常より速く変動が大きい)に対する頑健性を向上させる予定です。
短縮版(すべてのポイントを網羅):
概要:
AISHELL‑1/Primewords から約300時間分の中国語音声で、SpecAugment と4× RTX 4090 を用いて9 Mパラメータのコンフォーマーを訓練しました。40 ms ごとにトークン確率を出力し、ピンイン‑音調語彙(1,254トークン+、<unk>)で Viterbi アラインメントを行います。TER は約 5.3 %、音調精度は約 98.3 %です。INT8 量子化によりモデルサイズが約11 MB に縮小され、TER は +0.0003 の増加のみでした。先頭無音のバグは、高い<blank>確率(閾値0.7)を持つフレームを除外することで修正しました。13 MB の ONNX Runtime Web バージョンはブラウザ内で完全に動作し、即時発音フィードバックを提供します。将来的には会話データを追加してドメインシフトへの対処を図ります。<blank>
本文
TL;DR:
中国語の発音は私にとって難しく、約300時間分の文字起こし済みスピーチを使い、小規模なCTCモデルで自分の発音を評価しました。こちらから試していただけます。
前回「Langseed」について書いた投稿では、すでに習得した語彙だけで単語を定義するプラットフォームを紹介しました。その後語彙は増えましたが、人々はまだ私の話す内容を理解できないようです。
問題の一因は声調です。私は声調に慣れておらず、自分のミスを聞き取ることも苦手で、教師がいないと非常に苛立ちます。
最初の試み:ピッチ可視化
最初のアイデアはピッチビジュアライザーを作ることでした。音声を小さなチャンクに分割し FFT を実行して時間経過に沿った支配的なピッチを抽出、Praat にヒントを得たエネルギーベースのヒューリスティックでマッピングするというものでした。しかしこの手法はすぐに脆弱になり、背景雑音・コアートキュレーション・話者差異・声門移行など無数の特殊ケースが発生しました。
過去10年で学んだ最も辛い教訓は「十分なデータと計算資源があれば、学習済み表現が手作業で調整したシステムを上回る」ということです。そこで私は完全にオンデバイスで動くディープラーニングベースのComputer‑Assisted Pronunciation Training(CAPT)システムを構築することにしました。既に商用APIは存在しますが、そこに面白さはありません。
あなたのブラウザはvideoタグをサポートしていません。
アーキテクチャ
私はこれを特殊化された自動音声認識(ASR)タスクとして扱いました。単なる文字起こしではなく、発話方法に対してペダンティックである必要があります。
ConformerエンコーダをCTC(Connectionist Temporal Classification)損失でトレーニングすることにしました。
Conformer を選んだ理由
音声は奇妙です:ローカルとグローバルの両方のパターンを捉える必要があります。
- 局所相互作用 – 逆舌 zh と歯茎 z の違いは数十ミリ秒で現れます。CNN は短距離スペクトル特徴を捉えるのに優れています。
- グローバル相互作用 – 中国語の声調は相対的(私にとって「高」なピッチが子供には低)かつ文脈依存(声調変換)。Transformer はこの長距離コンテキストをモデリングするのに秀でています。
Conformer は両方を組み合わせます:局所詳細は畳み込み、全体構造は注意機構です。
CTC を選んだ理由
ほとんどの現代ASRモデル(例:Whisper)はシーケンス・トゥ・シーケンスで、音声を最も確率が高いテキストに変換します。欠点は「自動訂正」してしまうことです。文字起こしには便利ですが、語学学習ではバグです。もし私の声調が間違っていても、モデルは意味を推測したくありません ― 実際に発音した内容を伝えてほしいのです。
CTC は異なる働きをします。各フレーム(約40 ms)ごとに確率分布を出力し、対齐のため
<blank> トークンを導入します。例えば「hello」を音声化すると、原始的な出力は次のようになります:
h h h <blank> e e <blank> l l l l <blank> l l o o o
繰り返しを折りたたみ
<blank> を除去すると「hello」が得られます。これによりモデルは実際に発音した内容をフレーム単位で処理するよう強制されます。
強制対齐:いつ言ったか知る
CTC は何が言われたかを教えてくれるだけで、正確なタイミングまでは示しません。3秒のクリップの場合、モデルは約150個の時間ステップ(列)からなる行列を出力します。この中でほとんどは
<blank> です。
ユーザーが「Nǐ hǎo」(ni3, hao3)と言うと、ni3 と hao3 の2つの高確率領域が期待されます。
行列を通過する単一かつ最適なパスを見つける必要があります:
- 開始点からスタート
- 終了点で終わる
- 順序で ni3 → hao3 を通過
- 総確率を最大化
これがまさに Viterbi アルゴリズムの動作です。動的計画法で解決します。
トークナイズ:ピンイン+声調を一等級トークンとして扱う
ほとんどの中国語ASRは漢字(Hanzi)を出力し、発音エラーを隠してしまいます。代わりに私は「ピンイン + 声調」を1つのトークンとしました:
- zhong1 は一つのトークン
- zhong4 は全く別のトークン
誤った声調を言うと、モデルは明示的に間違いのトークンIDを予測します。中性声調は5番目(ma5)として正規化しました。この結果、1,254個のトークン+
<unk> と <blank> の語彙セットが完成しました。
トレーニング
AISHELL‑1 と Primewords データセット(合計約300時間)を組み合わせ、SpecAugment(時間/周波数マスキング)で拡張。4台の NVIDIA GeForce RTX 4090 で約8時間のトレーニングに要しました。
損失関数よりも次の指標に注目しました:
- TER(Token Error Rate):全体精度
- Tone Accuracy:声調1〜5 の正確さ
- Confusion Groups:zh/ch/sh と z/c/s など、初音が難しいペア間での混同
モデルサイズを縮小した話
最初は「ミディアム」モデル(約75 Mパラメータ)から始めました。ブラウザや携帯電話で動かせるようにさらに縮小し、精度がほとんど落ちないことに驚きました:
| パラメータ数 | TER | Tone Accuracy |
|---|---|---|
| 75 M | 4.83% | 98.47% |
| 35 M | 5.16% | 98.36% |
| 9 M | 5.27% | 98.29% |
9 Mパラメータモデルはほぼ同等でした。これはタスクが計算リソースではなくデータに制約されていることを示唆しています。
FP32 モデルは約37 MB、INT8量子化後はわずか11 MBで精度の低下は無視できる(+0.0003 TER)です。onnxruntime‑web を介して即座にロード可能なサイズです。
対齐バグ:沈黙が全てを台無しにする
ミスを強調表示するには強制対齐が必要です。リードインの沈黙でひどいバグに遭遇しました。「我喜欢…」と言う前に1秒間止まった録音では、モデルは最初の音節が間違っていると自信満々に示し、確信度 0.0 を返していました。原因は沈黙フレームが wo3 に割り当てられ、その期間で
<blank> の確率が圧倒的に高くなったためです。
修正策
UI のハイライト(表示される領域)とスコアリングフレーム(確信度計算に使われるフレーム)を分離しました。モデルが沈黙であると確信しているフレームは無視します:
def _filter_nonblank_frames(span_logp: torch.Tensor, blank_id: int = 0, thr: float = 0.7): """ <blank> の確率が閾値より低いフレームだけを残す。 全てのフレームが沈黙の場合はスパン全体で評価するフォールバック。 """ p_blank = span_logp[:, blank_id].exp() keep = p_blank < thr if keep.any(): return span_logp[keep] return span_logp # フォールバック
この一行の変更で、最初の音節の確信度が 0.0 → 0.99 に改善しました。
結論
ベータテストを通じて自分の発音は向上していると実感しています。厳格かつ容赦なく、まさに必要だったものです。ネイティブスピーカーからは「正しく評価されるには過剰に明瞭に話す必要がある」との指摘がありました――これはドメインシフト問題で、AISHELL は読み上げ音声主体で対話的な音声は速く滑らかです。子どもたちも低い精度で、ピッチが高く訓練データにほぼ含まれていないため改善の余地があります。Common Voice のような会話データセットを追加することが次なるステップと考えています。
ライブデモはこちらからお試しください。完全にブラウザ上で動作します。ダウンロードサイズは約13 MB、現代ウェブサイトの多くよりも小さく済みます。