メインコンテンツへスキップ

RNNoiseかSileroか:音声入力のための音声区間検出器の選び方

RNNoiseはノイズを抑制し、副産物として発話確率を出力します。Sileroは音声を検出するだけで、音声そのものには手を加えません。Voice TypeがSileroでゲートをかける理由と、実際に出荷している閾値を紹介します。

要点

結論から
  • セグメンテーションが、認識器に何を渡すかを決めます。無音を与えられたモデルでも、テキストを返します。テキストを返すのがモデルの仕事だからです。
  • ゲートは、16 kHzの512サンプルのフレームで動作し、発話の閾値は0.30です。
  • 発話区間には、最初のフレーズでは1,000 msのプリロール、その後は前後それぞれ150 msが付き、境界で単語が途切れないようにしています。
  • 5秒未満のバッチは、ポーズがあっても送信されません。次のフレーズを待ちます。

認識器は、キーボードの音も、エアコンの音も、考えている間の3秒間の無音も聞く必要はありません。

Voice Typeは、ストリーミング対応の Silero 音声区間検出器(VAD)を使い、マイクのストリームのどの部分が音声かを判定します。各バッチの長さやモデルを実行するタイミングなど、下流のすべては、このゲートが何を音声としてマークするかによって決まります。

訂正です。 このページの以前のバージョンでは、Voice TypeはRNNoiseを使っていると書かれていました。実際には使っておらず、過去にも使っていません。アプリがRNNoiseを出荷したことは一度もなく、公開時点でその記述は誤りでした。ゲートはSileroで、以下の設定が実際に出荷されているものです。RNNoiseを検索してここにたどり着いた方は、次のセクションをご覧ください。

RNNoiseと、それがゲートではない理由

RNNoiseは、Xiph.Orgが公開しBSDライセンスで提供されているノイズ抑制用のリカレントニューラルネットワークです。小さくて高速で、本来の役割においては非常に優れています。ノイズの多い音声を受け取り、よりクリーンな音声を返します。多くの音声ソフトウェアに組み込まれており、最もよく知られているのはOBSに同梱されているバージョンです。

Voice Typeに入っていない理由は、ノイズ抑制と音声区間検出が別の仕事だからです。

  • ノイズ抑制は音声を変更します。ノイズと判断したものを減衰させ、それ以外を通過させるため、下流に渡される信号は、キャプチャされた信号とは異なります。
  • 音声区間検出は何も変更しません。音声を読み取り、どの区間に発話が含まれているかをマークするだけです。認識器には元のサンプルがそのまま渡されます。

RNNoiseは、ノイズ抑制を行う際の副産物として発話確率(voice activity probability)を出力します。そのため、VADを探す検索に引っかかります。しかしその出力をゲートに使うということは、主目的でない信号を得るために、必要のない抑制処理を走らせることを意味します。Sileroは検出器なので、検出という仕事を直接行い、音声には手を加えません。

この違いは、通話よりも音声入力のほうで大きな意味を持ちます。通話では、聞こえ方がクリーンになることが目的であり、ファンの音と一緒に本物の音声の一部が除去されても、聞き手が補ってくれるので許容できるトレードオフです。認識器は補ってくれません。与えられたものをそのまま文字にするため、認識の前に音声が加工されていると、抑制処理がノイズと区別しにくい子音の部分で、精度を損なうおそれがあります。

RNNoiseのVAD閾値を探している場合、その値はどのラッパーを使うかによります。RNNoise自体は0から1の確率を返し、各実装が独自のカットオフを選ぶためです。参考までに、このアプリのSileroゲートで対応する数値は、発話を継続するのが0.30、発話に入るのが0.22です。この2つの値の理由は、下のセクションで説明しています。これらはSileroの値であり、RNNoiseにはそのまま当てはまりません。

RNNoiseは優れたソフトウェアです。ただ、私たちにはない問題を解くためのものです。

要点

  • セグメンテーションが、認識器に何を渡すかを決めます。無音を与えられたモデルでも、テキストを返します。テキストを返すのがモデルの仕事だからです。
  • ゲートは、16 kHzの512サンプルのフレームで動作し、発話の閾値は0.30です。
  • 発話区間には、最初のフレーズでは1,000 msのプリロール、その後は前後それぞれ150 msが付き、境界で単語が途切れないようにしています。
  • 5秒未満のバッチは、ポーズがあっても送信されません。次のフレーズを待ちます。

精度の上限を決めるのはモデルではなくセグメンテーション

認識器にクリーンな発話セグメントを与えれば、発話を文字にします。単語が1つだけ埋もれた3秒間の空調のうなりを与えても、デコーダはテキストを生成します。その失敗はモデルの誤りに見えますが、実際には入力の誤りです。

だからこそ、ゲートは興味深い構成要素なのです。人間に音を聞きやすくするためのものではありません。モデルに問われるのは、問いを含んだ音声だけにするためのものです。

出荷している設定

以下は、バランス型の音声入力モードの現在のデフォルト値で、生のキャプチャに切り替えない限りこれが動作します。

| 設定 | 値 | | --- | --- | | フレームサイズ | 16 kHzで512サンプル | | 発話の閾値 | 0.30 | | 小さな発声の開始閾値 | 0.22 | | 最初のフレーズ前のプリロール | 1,000 ms | | 以降のフレーズのプリロール | 150 ms | | ポストロール | 150 ms | | フレーズを閉じる無音(ライブ時) | 400 ms | | フレーズを閉じる無音(停止時) | 350 ms |

2つの閾値が、理解しておく価値のある部分です。単一の閾値は、小さな最初の音節を拾えるほど敏感で、かつファンの音を無視できるほど厳格でなければなりません。この2つは別の仕事なので、ゲートは発話の開始を待っているときは低い基準(0.22)を使い、発話が続いているときは通常の基準(0.30)を使います。

非対称なプリロールは、時間軸で同じ工夫をしています。発話の最初の単語は最も途切れやすい部分です。その前にゲートが参照できるものがないためで、検出ポイントの前に1秒分の音声が付きます。後続のフレーズは、ゲートがすでにあなたを追跡しているので、150 msで十分です。

短いバッチが待つ理由

バッチャーは発話区間を丸ごと追加し、途中で切ることはしません。また、デコードする価値がないほど短いバッチを送信することもしません。

現在のバッチに選択された音声が5秒未満しかなく、あなたが間を置いても、何も送信されません。バッチはポーズをまたいで保持され、次のフレーズがそこに加わります。これは意図的な動作です。1.5秒の断片を単独でデコードしても周囲の文脈がなく、Whisper系のモデルは文脈を利用します。2つの断片を一緒にデコードするほうが、別々にデコードするより優れています。

5秒に達すると通常のルールに戻ります。閉じたフレーズの後に400 msのポーズがあるとバッチがフラッシュされ、バッチが29秒の目標を超えてしまうフレーズが来た場合も同様です。ホットキーを離すと、長さに関係なく残りがすべてフラッシュされます。

Voice Typeの音声ゲートのフロー図。16 kHzのマイクストリームが、GPUを無効にして単一のCPUスレッドでggml上で動作するSilero音声区間検出器に入力される。検出器が発話区間をマークし、バッチャーは区間が目標の長さ(15〜29秒の範囲に制限され、フレーズの途中では分割されない)に達するまで保持し、その後で初めて、Metal GPU上で動作するwhisper.cpp認識器にバッチを渡す。

ゲートは1つのCPUスレッドで動作するため、認識器とGPUを奪い合うことはありません。出典: StreamingSileroSpeechGate.swift。

バッチがたどる経路

バッチがフラッシュされると、コンディショニングされてからデコードされます。

  1. DCオフセットを除去します。
  2. 有効なフィルタとラウドネス制御を適用します。デフォルトでは50 Hzのハイパスが含まれます。
  3. キャプチャデバイスがまだ16 kHzでない場合は、Whisperが期待する16 kHzにリサンプリングします。
  4. メインアクターの外でモデルを実行します。
  5. 得られたテキストをトランスクリプトに連結し、バッチ境界で重複した単語を取り除きます。

手順5は見た目以上に重要です。バッチは独立してデコードされるため、同じ単語が1つのバッチの末尾と次のバッチの先頭に現れることがあります。また、最後に確定した単語はプロンプトのコンテキストとして次に渡されるので、モデルは古い音声を再デコードしなくても、直前に何を言ったかを把握できます。

ゲートが失敗するとき

音声区間検出もモデルであり、誤ることがあります。Sileroが完全に失敗した場合、パイプラインはキャプチャした音声をフォールバック用のセグメントとしてマークして使うことがあります。

意図的な例外が1つあります。ゲートが正常に動作して「発話なし」と報告した場合、アプリはエネルギー閾値にフォールバックしません。それは、ファンの音、タイピング、停止キーのクリック音がトランスクリプトになってしまう失敗モードであり、誤ったトランスクリプトは、トランスクリプトがないことよりも悪いからです。

関連記事: Core MLエンコーダをオフにした理由 · 16 kHzへのリサンプリング