LabHub
はじめる
学ぶ 学習パス コース

音声 AI エージェント — 聞いて、調べて、話すパイプライン

部分結果から確定まで — ストリーミング ASR と Whisper を WER で測る

LabHubで続きを見る

目標

WER計算機を自分で作り、ストリーミングASRとオフラインASRを同じ発話で比較して、精度と「話が終わったあと確定まで」の遅延を測ります。電話帯域・サンプルレートの誤記・ノイズがWERをどう変えるかを確認します。

なぜ重要なのか

音声アシスタントが応答を始める時刻は、「確定結果がいつ出るか」に結び付いています。ストリーミングは話している間に書き起こすので確定がすぐに出て、オフラインは話が終わらないと始まりません。精度の数字(WER)は、正規化と集計方式、評価データによって大きく変わるため、計算機を自分で作ってみてはじめて、数字を信じられます。採点ツールは、wer.pyを隠しケースで試し、同じモデルをもう一度動かして、結果と照合します。

ステップ

  1. wer(ref, hyp)が{S, D, I, N, wer}を返す/root/voice/asr/wer.pyを作ってください(小文字化・句読点除去・アポストロフィ保持の正規化)。
  2. /opt/lab/fixtures/voice/librispeech/ls01~ls08.wavをストリーミングASRに100 msずつ入れて、部分結果・確定結果・確定遅延を/root/voice/asr/stream.jsonlに書いてください。
  3. 確定結果のコーパスWERを/root/voice/asr/wer_stream.jsonに書いてください。
  4. 同じ8つをWhisper tiny.enで書き起こして、/root/voice/asr/whisper.jsonlと/root/voice/asr/wer_whisper.jsonを作ってください。
  5. 2つの方式の「話が終わったあと確定まで」の中央値を、/root/voice/asr/latency.jsonに書いてください。
  6. 8 kHzの電話録音(phone_8k.wav)のWERを16 kHzの原本(ls04)と比べ、8 kHzを16000だと偽った場合も測って、/root/voice/asr/phone.jsonに書いてください。
  7. SNR 20・10・0 dBのホワイトノイズを混ぜたときのコーパスWERを、/root/voice/asr/noise.jsonに書いてください。
  8. 測定結果をまとめて、どの構成を選ぶかを/root/voice/asr/report.jsonに書いてください。

参考

WER計算機を作る

/root/voice/asr/wer.pyにwer(ref, hyp)を作ってください。2つの文を小文字に変換し、アポストロフィを除く句読点を消したあと、単語単位の最小編集距離を求めて、{"S": 치환, "D": 삭제, "I": 삽입, "N": 정답 단어 수, "wer": (S+D+I)/N}を返します(プレースホルダーは置換数と削除数と挿入数と正解の単語数です。正解が空ならwerは0です)。採点ツールが、隠しケース8件で試します。

レーベンシュタイン距離を、文字ではなく単語で行います。表d[i][j]を埋めたあと、右下から逆にたどって、どの編集だったかを数えます。"don't"のアポストロフィは単語の一部なので、残す必要があります。

100 msずつ入れて部分結果を記録する

/opt/lab/fixtures/voice/librispeech/refs.tsvのls01–ls08をストリーミングASRに1600サンプル(100 ms)ずつ入れながら、結果が変わるたびに{"audio_s": 지금까지 넣은 소리 길이, "text": 부분 결과}を集めます(プレースホルダーはここまでに入力した音の長さと部分結果です)。最後の音を入れた瞬間から確定結果が出るまでのmsをfinal_msとして測り、/root/voice/asr/stream.jsonlに1行ずつ(id・partials・final・final_ms)書いてください。

最後の断片のあとに0.66秒の無音を入れてinput_finished()を呼ばないと、最後の単語まで出ません。final_msはその仕上げにかかった時間で、話が終わったあとにユーザーが待つ分です。

コーパスWER

自分のwer.pyで8つの発話のエラー数と単語数をすべて足して、コーパスWERを求め、/root/voice/asr/wer_stream.jsonにS・D・I・N・werとして書いてください。

ファイルごとのWERの平均ではありません。長い発話がより重く反映されるように、エラーの合計 ÷ 単語数の合計で出します。

Whisper tiny.enと比較する

同じ8つの発話をvoicekit.models.whisper()で丸ごと一度に書き起こして、/root/voice/asr/whisper.jsonl(id・text・compute_ms・audio_s)を作り、コーパスWERを/root/voice/asr/wer_whisper.jsonに書いてください。

オフライン認識器は、s = rec.create_stream(); s.accept_waveform(sr, x); rec.decode_stream(s); s.result.textです。Whisperは句読点と大文字小文字を付けるので、自分の正規化がここで働きます。

話が終わってから確定まで

stream.jsonlのfinal_msとwhisper.jsonlのcompute_msそれぞれの中央値(最近傍順位: 並べ替えたあと⌈0.5·n⌉番目の値)を、/root/voice/asr/latency.jsonにstream_final_ms_p50・whisper_ms_p50として書いてください。

Whisperは話が終わらないと始まらないので、compute_ms全体がユーザーの待ち時間です。ストリーミングは話している間にほとんどを終えているので、final_msだけを待ちます。

8 kHzの電話帯域、そしてサンプルレートを偽ると

ls04(16 kHz)と同じ文の/opt/lab/fixtures/voice/librispeech/phone_8k.wav(8 kHz)をストリーミングASRで書き起こして、ls04の正解に対するWERをwer_16k・wer_8kとして、8 kHzのサンプルをaccept_waveform(16000, …)で入れた結果のWERをwer_8k_as_16kとして、/root/voice/asr/phone.jsonに書いてください。

accept_waveformの最初の引数が、音のサンプルレートです。8000と伝えると、sherpa-onnxが内部で16 kHzに変換します。16000と偽ると、8,000個が0.5秒として読まれて、音が2倍速くなります。

ノイズが強いほど

ls01–ls08(並べ替え順でk番目のファイル)に、np.random.default_rng(100 + k).standard_normal(n)のノイズをSNR 20・10・0 dB(電力比)で混ぜて、ストリーミングASR(100 msの断片)で書き起こし、コーパスWERを/root/voice/asr/noise.jsonに{"20": …, "10": …, "0": …}の形で書いてください。

倍率はモジュール1と同じです: sqrt(P信号 / 10^(SNR/10) / Pノイズ)。シードをファイルごとに固定すれば、採点ツールが同じノイズを作り直して照合できます。

何を選ぶか

/root/voice/asr/report.jsonに、wer_stream・wer_whisper・stream_final_ms_p50・whisper_ms_p50・wer_8k・wer_0dbを前のステップのファイルから書き写し、choiceにstream・whisper・two-passのうち1つを書いてください。

遅延・精度・データの類似性を合わせて見ます。このデータ(LibriSpeech)が、ストリーミングモデルの学習データと同じだという点も忘れないでください。