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

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

いつ答えるか — VAD・ターン終了判定・割り込み

LabHubで続きを見る

一言でいうと

音声アシスタントがいつ口を開くかは、話が終わったという判定(エンドポイント検出、endpointing)が決めます。VAD(音声区間検出)は、音が話し声かどうかをフレームごとに分け、ターン判定は「話が止まってからどれくらい経ったか」でVAD区間をターンにまとめます。停止のしきい値(gap)を短くすると話の途中で切れ、長くすると応答が遅れます。アシスタントが話している間に人が割り込んだら(barge-in)、スピーカーの音がマイクに戻ってきたエコーを先に引かなければ、人の声が見えません。

なぜ必要なのか

人同士の会話では、順番が移る間隔は短いです。10言語の会話を測った研究(Stivers et al., 2009, PNAS)は、応答が始まるまでの間隔がおおむね0–200 ms付近に集中すると報告しました。人は相手が話し終える前に、すでに終わりを予測しています。機械はそうできず、静かになったことを確認してはじめて終わったとわかります。その確認時間がそのまま応答の最小遅延です。モデルをどれだけ速くしても減りません(モジュール8で、この分を別に測ります)。

しきい値1つで解決するものでもありません。「火曜日に予約したのですが…(考える間)…木曜日に変更したいです」では、間が0.5秒を超えることがあります。そこで切ると、アシスタントは「火曜日に予約したのですが」に応答してしまいます。

どう動くのか

エネルギーVAD。フレームのdBFSがしきい値(例: -35)より大きければ話し声とみなします。速くて説明しやすいのですが、大きな音はすべて話し声になります。ラボの通話には、6秒の地点にドアをノックする短いノイズを入れてあり、エネルギーVADはこれを1つの発話区間として検出します(誤検出)。逆に小さく話すと見逃します。

ニューラルネットワークVAD(silero)。小さなニューラルネットワークが、フレーム(16 kHzで512サンプル、32 ms)ごとに「話し声である確率」を出し、確率がthresholdを超えれば話し声、min_silence_duration以上静かなら区間を閉じます。このイメージのsilero VAD(onnx、MIT)は、16.7秒の音をCPU 2コアのPodで56 msで処理しました(nuc1で実測)。ラボの素材では、ドアのノックを話し声として検出しませんでした。ただし区間の終わりを、実際の話の終わりより0.5秒前後遅れて閉じます(この素材で実測0.5–0.7秒)。語尾の余韻や息づかいを話し声として扱うためです。この遅れも、応答遅延にそのまま加わります。

ターンにまとめる。VADは、間があるたびに区間を切ります。ターンは、その区間のうち間隔がgapより短いものをつなげたものです。gapを0.2秒にすると、考える間でもターンを切り(話の途中での切断)、1.2秒にすると、次の人の番まで1つのターンとしてつながるか、毎回1秒以上待つことになります。ラボでgap 0.2–1.2秒を順に試して、ターン数が合う範囲を見つけ、そのうち最も短い値の応答遅延、つまり(VADが見た話の終わり + gap) − 実際の話の終わりを測ります。

実務では、ここに意味の信号を加えます。ASRの部分結果が文として終わっているか(「…変更したいです」)、イントネーションが下がったかを見て、gapを短くしたり長くしたりします。ストリーミングASRモデルにもエンドポイントのルールがあります。sherpa-onnxのrule2は、「何かが認識されたあと、N秒静かなら終了」です。

割り込みとエコー。アシスタントが話している間、マイクには人の声だけでなく、スピーカーから漏れ込んだアシスタント自身の声も入ってきます。この状態でVADを動かすと、再生を始めた途端に「人が割り込んだ」と判定して、自分で話を止めてしまいます。解決策は、こちらが出した音(参照信号)を知っているという点を使うことです。マイク = 人 + g·(遅延dだけ遅れた参照)とみなし、人が話す前の区間で相互相関が最も大きいdと最小二乗の倍率gを求めて引くと、人の声が残ります。実際のエコーキャンセラー(AEC)は、部屋の反射まで追従する適応フィルターで、ブラウザーではgetUserMediaのechoCancellation制約で有効にします。ラボのエコーは、遅延1つ・倍率1つで作った単純なモデルなので、原理だけを見ます。

現場での姿

LabHubの会話練習は、エンドポイント判定を人に任せています。録音ボタンを押して離す方式です(static/lang/talk.jsのMediaRecorder)。間違える余地がなく実装も単純ですが、人が話し終えたあとにボタンを押し、そのあとではじめて録音全体が送信されます。電話のように手を使わない音声アシスタントは、この判定を機械が行う必要があり、その瞬間にこのモジュールのトレードオフが生じます。

しきい値を決めるときに最もよくある間違いは、1つの録音に合わせて覚えてしまうことです。このコースの2つの通話だけを見ても、VAD区間の間隔は、一方の通話では0.29秒(話の途中)と1.06秒(ターンの交代)にゆったり分かれますが、別の通話では、ターンが交代した箇所の間隔が0.73秒まで狭まります。1つのファイルで0.74秒を選ぶと、別のファイルで2人のターンが1つにつながってしまいます。そのため、ルールは見たことのない素材で試し、運用では、渡されたターンと切られたターンをサンプルとして集めて、しきい値を見直します。

次のラボですること

通話call_aで、エネルギーVADとsilero VADの区間を正解と重ねて、誤検出と見逃しを数えます。VAD区間をターンにまとめるturns.pyを作り、初めて見る通話でも合っているかを採点ツールが試し、gapを順に試して、ターンが合う範囲と応答遅延を測ります。最後に、エコーが混ざったマイクから遅延と倍率を推定して引き、人が割り込んだ時刻を見つけます。