<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GMM on M1KE BL0G</title><link>https://www.m1ke.org/tags/gmm/</link><description>Recent content in GMM on M1KE BL0G</description><generator>Hugo -- gohugo.io</generator><language>ja-jp</language><copyright>mike</copyright><lastBuildDate>Sun, 23 Aug 2026 12:00:00 +0900</lastBuildDate><atom:link href="https://www.m1ke.org/tags/gmm/index.xml" rel="self" type="application/rss+xml"/><item><title>WebRTC VADの仕組み</title><link>https://www.m1ke.org/p/webrtc-vad%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF/</link><pubDate>Sun, 23 Aug 2026 12:00:00 +0900</pubDate><guid>https://www.m1ke.org/p/webrtc-vad%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF/</guid><description>&lt;img src="https://www.m1ke.org/p/webrtc-vad%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF/webrtcvad.jpg" alt="Featured image of post WebRTC VADの仕組み" /&gt;&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;音声区間検出（VAD）でよく使われる&lt;code&gt;webrtcvad&lt;/code&gt;について調べていた&lt;/li&gt;
&lt;li&gt;中身がGMM（混合ガウス分布）と尤度比でできていることが分かった&lt;/li&gt;
&lt;li&gt;そこで、その中身をAIで詳解した&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="vadvoice-activity-detectionとは"&gt;VAD（Voice Activity Detection）とは&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;音声データの中から「人が喋っている区間」と「喋っていない区間」を判定する技術&lt;/li&gt;
&lt;li&gt;無音部分のトリミング、音声認識（ASR）の前処理、通話の帯域節約など、幅広く使われる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webrtcvad&lt;/code&gt;は、Google WebRTCプロジェクトのVADエンジンをPythonから使えるようにしたライブラリ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="webrtc-vadの全体像"&gt;WebRTC VADの全体像&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;音声を短いフレーム（10ms・20ms・30msのいずれか）に分割し、フレームごとにspeech/non-speechを判定する&lt;/li&gt;
&lt;li&gt;対応サンプリングレートは8k/16k/32k/48kHzのモノラルPCM&lt;/li&gt;
&lt;li&gt;内部処理は8kHzで行われており、16k/32k/48kHzの入力は、まず8kHzにダウンサンプルしてから同じ判定ロジックにかけている&lt;/li&gt;
&lt;li&gt;判定の厳しさを&lt;code&gt;aggressiveness&lt;/code&gt;（0〜3）で指定でき、数字が大きいほどnon-speech寄りに判定する&lt;/li&gt;
&lt;li&gt;ディープラーニングは使っておらず、GMMという古典的な統計モデルで判定している&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="処理の流れ"&gt;処理の流れ&lt;/h3&gt;
&lt;p&gt;WebRTC VADの判定は、大まかに以下の流れで行われる。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;音声をフレーム単位に分割する&lt;/li&gt;
&lt;li&gt;各フレームを6つの周波数帯域（チャンネル）に分け、帯域ごとのエネルギーを計算する&lt;/li&gt;
&lt;li&gt;帯域ごとに、そのエネルギーをspeech用・noise用のGMMに入力して尤度比を計算する&lt;/li&gt;
&lt;li&gt;全帯域の尤度比を合算し、speechらしい方を採用する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この流れを理解するには、GMMと尤度という2つの概念を知っておく必要がある。（この後、実際のソースコードで確認したより正確な仕組みも解説する）&lt;/p&gt;
&lt;h2 id="gmm混合ガウス分布とは"&gt;GMM（混合ガウス分布）とは&lt;/h2&gt;
&lt;h3 id="正規分布のおさらい"&gt;正規分布のおさらい&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;正規分布（ガウス分布）は、平均$\mu$を中心とした釣鐘型の分布&lt;/li&gt;
&lt;li&gt;以下の式で表される&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\mathcal{N}(x \mid \mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mu$は分布の中心、$\sigma^2$は分布の広がり（分散）を表す&lt;/li&gt;
&lt;li&gt;山が1つしかない、単純な形の分布&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;実際、WebRTC VADのソース（&lt;code&gt;vad_gmm.c&lt;/code&gt;の&lt;code&gt;WebRtcVad_GaussianProbability&lt;/code&gt;）のコメントにも、この式がそのまま書かれている。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// For a normal distribution, the probability of |input| is calculated and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// returned (in Q20). The formula for normal distributed probability is
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 1 / s * exp(-(x - m)^2 / (2 * s^2))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// where the parameters are given in the following Q domains:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// m = |mean| (Q7)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// s = |std| (Q7)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// x = |input| (Q4)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Q7&lt;/code&gt;・&lt;code&gt;Q4&lt;/code&gt;・&lt;code&gt;Q20&lt;/code&gt;は固定小数点の表現方法（浮動小数点を使わない代わりに、整数のどこまでを小数部として扱うかを表す）&lt;/li&gt;
&lt;li&gt;数式としては$\frac{1}{s}\exp\left(-\frac{(x-m)^2}{2s^2}\right)$そのもので、前述の$\mathcal{N}(x \mid \mu, \sigma^2)$と完全に一致する&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="正規分布1つでは表現できない分布"&gt;正規分布1つでは表現できない分布&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;実際のデータは、山が複数ある分布になることが多い&lt;/li&gt;
&lt;li&gt;例えば「静かな部屋のノイズのエネルギー」と「人が喋っているときのエネルギー」を混ぜて集計すると、低い値のあたりと高い値のあたりに、それぞれ山ができる&lt;/li&gt;
&lt;li&gt;こうした分布は、正規分布1つでは無理に1つの山として近似することになり、うまく表現できない&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="複数の正規分布を混ぜる"&gt;複数の正規分布を混ぜる&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;そこで、複数の正規分布を重みつきで足し合わせたものがGMM&lt;/li&gt;
&lt;li&gt;以下の式で表される&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
p(x) = \sum_{k=1}^{K} \pi_k , \mathcal{N}(x \mid \mu_k, \sigma_k^2)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$K$個の正規分布を混ぜ合わせる&lt;/li&gt;
&lt;li&gt;各正規分布は、自分専用の平均$\mu_k$・分散$\sigma_k^2$を持つ&lt;/li&gt;
&lt;li&gt;$\pi_k$は「その正規分布がどれくらいの割合で混ざっているか」を表す重みで、全て足すと1になる&lt;/li&gt;
&lt;li&gt;イメージとしては、複数の釣鐘型の山を、それぞれ違う高さ・違う場所に立てて、足し合わせたもの&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gmmの学習"&gt;GMMの学習&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$\mu_k$・$\sigma_k^2$・$\pi_k$は、手元のデータから推定する必要がある&lt;/li&gt;
&lt;li&gt;一般にはEM（Expectation-Maximization）アルゴリズムという手法で推定する&lt;/li&gt;
&lt;li&gt;大まかには以下の2ステップを繰り返す
&lt;ul&gt;
&lt;li&gt;Eステップ: 今のパラメータのもとで、各データ点がどの正規分布に属していそうかを確率的に割り当てる&lt;/li&gt;
&lt;li&gt;Mステップ: その割り当てをもとに、各正規分布の$\mu_k$・$\sigma_k^2$・$\pi_k$を更新する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;これを収束するまで繰り返すことで、データの分布にフィットするGMMが得られる&lt;/li&gt;
&lt;li&gt;一般的には、こうして学習したパラメータは固定して使うことが多いが、WebRTC VADは少し違う（後述の「モデルは学習後も動き続ける」を参照）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="尤度で分析するとは"&gt;尤度で分析するとは&lt;/h2&gt;
&lt;h3 id="尤度likelihoodの意味"&gt;尤度（Likelihood）の意味&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;尤度とは、「あるモデルのもとで、このデータが観測される確からしさ」を表す値&lt;/li&gt;
&lt;li&gt;GMMの場合、あるデータ点$x$に対する尤度は、前述の$p(x)$そのもの&lt;/li&gt;
&lt;li&gt;$p(x)$が大きいほど、そのモデルにとって「よくあるデータ」、小さいほど「珍しいデータ」ということになる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="対数尤度とはなぜ対数を取るのか"&gt;対数尤度とは（なぜ対数を取るのか）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;対数尤度（log-likelihood）とは、尤度$p(x)$そのものではなく、その対数$\log p(x)$を指す&lt;/li&gt;
&lt;li&gt;わざわざ対数を取る理由は、主に以下の2つ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理由1は、掛け算が足し算になること。&lt;/p&gt;
&lt;p&gt;以下は、独立した2つのデータ点$x_1$・$x_2$を同時に観測する尤度（同時尤度）の式である。&lt;/p&gt;
&lt;p&gt;$$
p(x_1, x_2) = p(x_1) \cdot p(x_2)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;複数のデータを同時に扱うとき、各データが互いに独立なら、全体の尤度はそれぞれの尤度の掛け算になる&lt;/li&gt;
&lt;li&gt;しかし対数を取ると、この掛け算が足し算に変わる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\log p(x_1, x_2) = \log p(x_1) + \log p(x_2)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;足し算の方が、掛け算より計算コストが低く、桁あふれ（多くの小さい確率を掛け続けると、値がどんどん0に近づいてアンダーフローする問題）にも強い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理由2は、大小関係が変わらないこと。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\log$は単調増加関数なので、$p_A(x)$と$p_B(x)$の大小関係は、$\log p_A(x)$と$\log p_B(x)$の大小関係とそのまま一致する&lt;/li&gt;
&lt;li&gt;つまり、分類のために大小を比較する用途では、対数を取っても比較結果は変わらない&lt;/li&gt;
&lt;li&gt;こうした理由から、尤度は生の値ではなく対数を取った状態（対数尤度）で扱われることが多い&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="尤度比によるクラス判定"&gt;尤度比によるクラス判定&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;クラスごとに別々のGMMを学習しておけば、そのGMM同士の尤度を比較することで分類ができる&lt;/li&gt;
&lt;li&gt;具体的には、以下のような手順になる
&lt;ul&gt;
&lt;li&gt;クラスAのデータだけを使ってGMM_Aを学習する&lt;/li&gt;
&lt;li&gt;クラスBのデータだけを使ってGMM_Bを学習する&lt;/li&gt;
&lt;li&gt;新しいデータ$x$が来たら、$p_A(x)$と$p_B(x)$を両方計算する&lt;/li&gt;
&lt;li&gt;$p_A(x)$の方が大きければクラスA、$p_B(x)$の方が大きければクラスBと判定する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;以下のような尤度比（対数を取ったもの）を閾値と比較する形で実装されることが多い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\log p_A(x) - \log p_B(x) &amp;gt; \text{threshold}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;これは「そのデータが、クラスAのモデルとクラスBのモデル、どちらにとってより自然か」を比較しているだけで、仕組みとしてはシンプル&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="入力となる音の特徴量に関する基礎知識"&gt;入力となる音の特徴量に関する基礎知識&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;WebRTC VADの中身に入る前に、音声を扱う上でよく出てくる特徴量や単位を整理しておく&lt;/li&gt;
&lt;li&gt;これらはWebRTC VADが直接使っているものと使っていないものが混ざっているが、音声処理全般でよく出てくる基礎知識として押さえておく&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="pcmpulse-code-modulationとは"&gt;PCM（Pulse Code Modulation）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;アナログの音声波形を、一定間隔でサンプリングし、各時点の振幅を一定のビット数で量子化してデジタル化する、最も基本的な音声のデジタル表現方式&lt;/li&gt;
&lt;li&gt;圧縮を一切行わない、生の波形データそのもの&lt;/li&gt;
&lt;li&gt;主なパラメータ
&lt;ul&gt;
&lt;li&gt;サンプリング周波数: 1秒間に何回サンプリングするか（例: 8kHzなら1秒間に8000回）&lt;/li&gt;
&lt;li&gt;ビット深度: 各サンプルを何ビットの整数で表現するか（例: 16bitなら$-32768$〜$32767$の範囲）&lt;/li&gt;
&lt;li&gt;チャンネル数: モノラル（1）かステレオ（2）か&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WebRTC VADへの入力も、16bitモノラルPCMのサンプル列（&lt;code&gt;int16_t&lt;/code&gt;の配列）そのもの&lt;/li&gt;
&lt;li&gt;後述のRMSやdBは、このPCMのサンプル値$x_i$から計算される。つまりPCMは、これから説明する特徴量すべての出発点になる生データ&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="rmsエネルギーとは"&gt;RMSエネルギーとは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;波形のエネルギー（音量）を表す、最も基本的な指標の1つ&lt;/li&gt;
&lt;li&gt;一定区間の波形のサンプル値を2乗して平均し、平方根を取ったもの&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\text{RMS} = \sqrt{\frac{1}{N}\sum_{i=1}^{N} x_i^2}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x_i$がその区間の$i$番目のサンプル値、$N$がサンプル数&lt;/li&gt;
&lt;li&gt;音量が大きいほど値も大きくなる、直感的にわかりやすい指標&lt;/li&gt;
&lt;li&gt;後述のdBは、このRMS（やパワー、振幅）を対数スケールに変換したもの。「RMSを計算してから、dBに変換する」という流れになることが多い&lt;/li&gt;
&lt;li&gt;WebRTC VADの&lt;code&gt;LogOfEnergy&lt;/code&gt;が扱う「エネルギー」も、この系統の指標（信号のサンプル値の2乗和）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="デシベルdbとは"&gt;デシベル（dB）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;音の大きさ（パワーや振幅の比）を対数スケールで表す単位&lt;/li&gt;
&lt;li&gt;パワー比の場合、以下の式で表される&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\text{dB} = 10 \log_{10}\left(\frac{P}{P_{\text{ref}}}\right)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$P$が測定対象のパワー、$P_{\text{ref}}$が基準となるパワー&lt;/li&gt;
&lt;li&gt;人間の聴覚は、音の大きさを線形ではなく対数的に感じる（音のパワーが10倍になっても、感覚的には「少し大きくなった」程度にしか感じない）ため、dBのような対数スケールの方が感覚に合いやすい&lt;/li&gt;
&lt;li&gt;WebRTC VADの&lt;code&gt;LogOfEnergy&lt;/code&gt;関数が各帯域のエネルギーをdBスケールに変換していたのも、この考え方に基づく&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dbfsdb-full-scaleとは"&gt;dBFS（dB Full Scale）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;デジタル音声で使われる、基準の取り方が特殊なdB&lt;/li&gt;
&lt;li&gt;基準$P_{\text{ref}}$を「そのビット深度で表現できる最大振幅（クリッピング直前の値）」に取る&lt;/li&gt;
&lt;li&gt;そのため、0dBFSが表現できる最大値で、実際の音声データはほぼ常にそれより小さい負の値（例: -20dBFS、-40dBFS）になる&lt;/li&gt;
&lt;li&gt;アナログの音圧レベル（dB SPL）のような絶対的な物理量ではなく、「そのデジタルシステムの上限からどれだけ離れているか」という相対値である点に注意&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="メル尺度mel-scaleとは"&gt;メル尺度（Mel scale）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;人間の聴覚は、低い周波数の変化には敏感だが、高い周波数になるほど変化を感じにくくなる&lt;/li&gt;
&lt;li&gt;この人間の知覚特性に合わせて周波数を変換した尺度がメル尺度で、以下のような式で変換する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
m = 2595 \log_{10}\left(1 + \frac{f}{700}\right)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$f$が実際の周波数（Hz）、$m$がメル尺度上の値&lt;/li&gt;
&lt;li&gt;低周波数では実周波数とほぼ線形の関係になり、高周波数になるほど圧縮されていく&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="log-melスペクトログラムとは"&gt;log-melスペクトログラムとは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;音声をSTFT（短時間フーリエ変換）でスペクトログラム（時間×周波数のエネルギー分布）にした後、周波数軸をメル尺度に沿ったフィルタバンク（メルフィルタバンク、通常20〜80程度）でまとめ、さらにdB（対数）に変換したもの&lt;/li&gt;
&lt;li&gt;音声認識・話者認識・音声合成など、ディープラーニングを使う音声モデルで、生波形の次によく使われる入力表現&lt;/li&gt;
&lt;li&gt;WebRTC VADの6帯域log-energyと似ているようで違う点がある
&lt;ul&gt;
&lt;li&gt;WebRTC VADの帯域は、オールパスフィルタによる単純な2分割を繰り返して作った6つの帯域で、メル尺度を意識した設計ではない&lt;/li&gt;
&lt;li&gt;log-melは、メル尺度に沿ってもっと多くのフィルタバンク（20〜80程度）を使い、人間の聴覚特性によりよく合わせて周波数軸を圧縮している&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ゼロ交差率zero-crossing-rate-zcrとは"&gt;ゼロ交差率（Zero-Crossing Rate, ZCR）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;波形が正から負、または負から正に切り替わる回数を、一定区間内で数えた指標&lt;/li&gt;
&lt;li&gt;以下のような式で表される&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\text{ZCR} = \frac{1}{2(N-1)} \sum_{i=1}^{N-1} \left| \text{sign}(x_i) - \text{sign}(x_{i+1}) \right|
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\text{sign}(x)$は、$x$の符号（正なら1、負なら-1）を返す関数&lt;/li&gt;
&lt;li&gt;隣り合うサンプルの符号が変わるたびに差の絶対値が2になるので、それを$2(N-1)$で割ることで「切り替わった割合」になる&lt;/li&gt;
&lt;li&gt;高い周波数成分が多いほどZCRは大きくなる傾向がある&lt;/li&gt;
&lt;li&gt;無声子音（s、shなど、声帯を震わせずに出す音）はノイズに近い波形になりやすく、ZCRが高くなる&lt;/li&gt;
&lt;li&gt;逆に母音のような有声音は、周期的な波形になりやすく、ZCRは低くなる傾向がある&lt;/li&gt;
&lt;li&gt;古典的なVADでは、エネルギー（音量が小さい＝無音）だけでなく、ZCR（ノイズっぽい波形かどうか）も組み合わせて判定に使うことがある
&lt;ul&gt;
&lt;li&gt;WebRTC VADはZCRを使っておらず、6帯域のlog-energyのみで判定している&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="基本周波数f0ピッチとは"&gt;基本周波数（F0・ピッチ）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;声帯の振動によって生まれる、音の高さを決める周波数&lt;/li&gt;
&lt;li&gt;有声音（母音など、声帯が振動する音）にはF0が存在し、無声音（子音の一部など、声帯が振動しない音）にはF0が存在しない&lt;/li&gt;
&lt;li&gt;F0が検出できるかどうかは、有声音/無声音の判定や、話者の特定（声の高さの特徴）に使われる&lt;/li&gt;
&lt;li&gt;抽出方法としては、自己相関関数を使う方法、YINアルゴリズム、CREPEのようなディープラーニングベースの手法などがある&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="スペクトル重心スペクトル平坦度とは"&gt;スペクトル重心・スペクトル平坦度とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;どちらも、周波数スペクトル（音をどの周波数がどれだけ含むかに分解したもの）の形状を要約する指標&lt;/li&gt;
&lt;li&gt;スペクトル重心（Spectral Centroid）は、スペクトルの「重心」にあたる周波数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\text{Centroid} = \frac{\sum_{k} f_k , S(f_k)}{\sum_{k} S(f_k)}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$f_k$が周波数、$S(f_k)$がその周波数のパワー&lt;/li&gt;
&lt;li&gt;値が高いほど高周波成分が多く「明るい」音、低いほど「こもった」音という印象になる&lt;/li&gt;
&lt;li&gt;スペクトル平坦度（Spectral Flatness）は、スペクトルがノイズのように平坦か、特定の周波数に偏っているかを表す指標&lt;/li&gt;
&lt;li&gt;幾何平均と算術平均の比で計算され、1に近いほどホワイトノイズのような平坦なスペクトル、0に近いほど特定の周波数（ピッチなど）に偏ったスペクトルになる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mfccmel-frequency-cepstral-coefficientsとは"&gt;MFCC（Mel-Frequency Cepstral Coefficients）とは&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;log-melスペクトログラムに、さらにDCT（離散コサイン変換）をかけて得られる係数&lt;/li&gt;
&lt;li&gt;DCTによって、隣り合うメルバンド同士の相関（情報の重複）を取り除き、少ない次元（通常12〜13次元程度）に情報を圧縮できる&lt;/li&gt;
&lt;li&gt;音声認識・話者認識で長年使われてきた定番の特徴量&lt;/li&gt;
&lt;li&gt;近年はディープラーニングの発展により、DCTで圧縮する前のlog-melスペクトログラムをそのまま入力に使うケースも増えている
&lt;ul&gt;
&lt;li&gt;次元圧縮の仕方を人間が設計するより、ニューラルネットワーク自身に学習させた方が精度が良いことが多いため&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="整理"&gt;整理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PCM: アナログ波形をサンプリング・量子化しただけの、圧縮なしの生のデジタル音声データ&lt;/li&gt;
&lt;li&gt;RMSエネルギー: 波形のサンプル値から求める、最も基本的な音量の指標&lt;/li&gt;
&lt;li&gt;dB: パワーや振幅の比を対数スケールで表す一般的な単位&lt;/li&gt;
&lt;li&gt;dBFS: デジタル音声で、表現できる最大振幅を基準に取ったdB&lt;/li&gt;
&lt;li&gt;ZCR: 波形が正負を切り替える頻度。ノイズっぽさや無声子音の判定に使われる&lt;/li&gt;
&lt;li&gt;F0（ピッチ）: 声帯の振動に対応する周波数。有声音/無声音の判定や話者性の分析に使われる&lt;/li&gt;
&lt;li&gt;スペクトル重心・スペクトル平坦度: スペクトルの形状（明るさ・ノイズっぽさ）を要約する指標&lt;/li&gt;
&lt;li&gt;log-mel: 人間の聴覚特性（メル尺度）で周波数を圧縮し、さらにdBに変換したスペクトログラム&lt;/li&gt;
&lt;li&gt;MFCC: log-melスペクトログラムを、さらにDCTで少ない次元に圧縮した特徴量&lt;/li&gt;
&lt;li&gt;WebRTC VADは、この中のどれでもなく、オールパスフィルタで再帰的に分割した6つの帯域のlog-energyという、もっとシンプルな特徴量を使っている&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="webrtc-vadの判定の仕組み"&gt;WebRTC VADの判定の仕組み&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;前述のGMM・尤度比の考え方を、そのままspeech/non-speechの2クラス分類に当てはめたものがWebRTC VAD&lt;/li&gt;
&lt;li&gt;ここから先は、実際に&lt;code&gt;libwebrtc&lt;/code&gt;のソース（&lt;code&gt;vad_core.c&lt;/code&gt;・&lt;code&gt;vad_gmm.c&lt;/code&gt;）をcloneして確認した内容を元に、より正確な仕組みを解説する&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="特徴量6つの周波数帯域のlog-energy"&gt;特徴量：6つの周波数帯域のlog-energy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;前半で「帯域ごとのエネルギー」と書いたが、正確にはエネルギーをdBスケールに変換したもの（log-energy）を特徴量として使っている&lt;/li&gt;
&lt;li&gt;内部は8kHzで動くので、ナイキスト周波数は4kHz。この0〜4kHzを、以下のように繰り返し半分に分割して6つの帯域を作る（&lt;code&gt;vad_filterbank.c&lt;/code&gt;の&lt;code&gt;WebRtcVad_CalculateFeatures&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;チャンネル&lt;/th&gt;
&lt;th&gt;周波数帯域&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;80 - 250 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;250 - 500 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;500 - 1000 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1000 - 2000 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2000 - 3000 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3000 - 4000 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;分割には、ローパス/ハイパスに分ける&lt;code&gt;SplitFilter&lt;/code&gt;（オールパスフィルタ2つの組み合わせ）を使い、それを繰り返し適用して帯域を半分ずつに絞り込んでいく&lt;/li&gt;
&lt;li&gt;80Hz未満は、別途ハイパスフィルタで除去している（無音時の低周波ノイズ・DCオフセットの影響を避けるため）&lt;/li&gt;
&lt;li&gt;各帯域のエネルギーは、&lt;code&gt;LogOfEnergy&lt;/code&gt;という関数で対数（dB相当、Q4固定小数点）に変換されてから特徴量になる。以下がそのコメント部分（&lt;code&gt;vad_filterbank.c&lt;/code&gt;より抜粋）&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Calculates the energy of |data_in| in dB, and also updates an overall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// |total_energy| if necessary.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// - log_energy [o] : 10 * log10(&amp;#34;energy of |data_in|&amp;#34;) given in Q4.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LogOfEnergy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int16_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;data_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;data_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int16_t&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int16_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;total_energy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int16_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log_energy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;つまりGMMに入力される$x$は「その帯域の生のエネルギー」ではなく「その帯域のエネルギーをdB変換した値」であり、これによって音量の桁の違い（静かな音と大きい音の差）を扱いやすくしている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="チャンネルごとに独立したgmm"&gt;チャンネルごとに独立したGMM&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;WebRTC VADは、音声を6つの周波数帯域（チャンネル）に分けて処理する&lt;/li&gt;
&lt;li&gt;各チャンネルに、noise用・speech用それぞれ2つの正規分布を混ぜたGMM（$K=2$）を持っている&lt;/li&gt;
&lt;li&gt;つまり全体では、6チャンネル×2クラス×2つの正規分布で、合計24個の正規分布が使われている&lt;/li&gt;
&lt;li&gt;前半で説明した「1つのGMMで全体を判定する」という書き方は簡略化しすぎで、正確には「チャンネルごとに独立したGMMを持ち、後で結果を合算する」という構造になっている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ローカル判定とグローバル判定"&gt;ローカル判定とグローバル判定&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;各チャンネルについて、noise側の尤度とspeech側の尤度から、対数尤度比（log-likelihood ratio）を計算する&lt;/li&gt;
&lt;li&gt;各チャンネルの尤度比に、周波数帯域ごとの重み（高い帯域ほど大きい重み）をかけて合算し、全チャンネルまとめたグローバルな尤度比を作る&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下は、$C=6$個のチャンネルの尤度比$\text{LLR}_c$を、重み$w_c$で合算する式である。&lt;/p&gt;
&lt;p&gt;$$
\text{LLR}_{\text{global}} = \sum_{c=1}^{C} w_c \cdot \text{LLR}_c
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「掛け算」ではなく「足し算」で合算しているのは、前述の対数の性質（各チャンネルが独立なら、全体の尤度は各チャンネルの尤度の掛け算になり、対数を取ると足し算になる）を使っているため&lt;/li&gt;
&lt;li&gt;6つのチャンネルをそれぞれ独立な情報源とみなし、各チャンネルの対数尤度比を（重みつきで）足し合わせることで、全帯域を総合した1つの証拠にまとめている、という考え方&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;判定は、以下の2つのテストの組み合わせ（どちらか一方でも満たせばspeech）で行われる。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ローカル判定: いずれか1つのチャンネルだけでも、尤度比が強くspeech寄りであればspeechとする&lt;/li&gt;
&lt;li&gt;グローバル判定: 全チャンネル合算の$\text{LLR}_{\text{global}}$が閾値を超えていればspeechとする&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;実際の該当コードは以下（&lt;code&gt;vad_core.c&lt;/code&gt;の&lt;code&gt;GmmProbability&lt;/code&gt;より抜粋、コメントも含む）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// The detection scheme is an LRT with hypothesis
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// H0: Noise
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// H1: Speech
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// We combine a global LRT with local tests, for each frequency sub-band,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// here defined as |channel|.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;kNumChannels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Calculate the log likelihood ratio: log2(Pr{X|H1} / Pr{X|H0}).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sum_log_likelihood_ratios&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_likelihood_ratio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;kSpectrumWeight&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Local VAD decision.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;log_likelihood_ratio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;individualTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;vadflag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Make a global VAD decision.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;vadflag&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum_log_likelihood_ratios&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;totalTest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;H0（帰無仮説）を「ノイズ」、H1（対立仮説）を「音声」とする、統計学の仮説検定（LRT: Likelihood Ratio Test）そのものの形になっている&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kSpectrumWeight[channel]&lt;/code&gt;は、チャンネル（帯域）ごとの重み。値は&lt;code&gt;{ 6, 8, 10, 12, 14, 16 }&lt;/code&gt;で、高い周波数帯域ほど大きい重みがついている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="対数尤度比をビットシフトだけで近似計算する仕組み"&gt;対数尤度比をビットシフトだけで近似計算する仕組み&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;組み込み機器向けの実装なので、浮動小数点の&lt;code&gt;log&lt;/code&gt;関数は一切使わず、整数のビットシフトだけで対数尤度比を近似している&lt;/li&gt;
&lt;li&gt;該当コードとコメントは以下（&lt;code&gt;vad_core.c&lt;/code&gt;より抜粋）&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Calculate the log likelihood ratio: log2(Pr{X|H1} / Pr{X|H1}).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Approximation:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// log2(Pr{X|H1} / Pr{X|H1}) = log2(Pr{X|H1}*2^Q) - log2(Pr{X|H1}*2^Q)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// = log2(h1_test) - log2(h0_test)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// = log2(2^(31-shifts_h1)*(1+b1))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// - log2(2^(31-shifts_h0)*(1+b0))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// = shifts_h0 - shifts_h1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// + log2(1+b1) - log2(1+b0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ~= shifts_h0 - shifts_h1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shifts_h0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;WebRtcSpl_NormW32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h0_test&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shifts_h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;WebRtcSpl_NormW32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1_test&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h0_test&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;shifts_h0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1_test&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;shifts_h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;log_likelihood_ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shifts_h0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shifts_h1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;h0_test&lt;/code&gt;・&lt;code&gt;h1_test&lt;/code&gt;は、それぞれH0（ノイズ）・H1（音声）の尤度に相当する32bit整数値&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebRtcSpl_NormW32(v)&lt;/code&gt;は、「符号ビットを除いた最上位ビットが1になるまで、&lt;code&gt;v&lt;/code&gt;を何ビット左シフトできるか」を返す関数
&lt;ul&gt;
&lt;li&gt;言い換えると、&lt;code&gt;v&lt;/code&gt;の最上位の1が立っているビット位置を求めているのと同じ&lt;/li&gt;
&lt;li&gt;ある正の整数の最上位ビットの位置は、おおよそ$\lfloor \log_2 v \rfloor$に一致する（例えば&lt;code&gt;v&lt;/code&gt;が$2^{20}$〜$2^{21}-1$の範囲なら、最上位ビット位置は20で、シフト数は「31引く21」あたりの値になる）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;つまり、必要なシフト数は「$v$の桁の大きさ」を表す指標であり、$v$が大きいほどシフト数は小さく、$v$が小さいほどシフト数は大きくなる、という$-\log_2 v$に近い性質を持つ&lt;/li&gt;
&lt;li&gt;コメントの式変形が示す通り、以下の近似が成り立つ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\log_2 h_1 - \log_2 h_0 \approx \text{shifts(H0)} - \text{shifts(h1)}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;右辺には&lt;code&gt;log&lt;/code&gt;計算が一切登場せず、整数の左シフト回数を数えるだけなので、組み込み機器でも高速に計算できる&lt;/li&gt;
&lt;li&gt;誤差として捨てているのは、最上位ビットより下の桁（コメント中の$b_0$・$b_1$）の情報だが、これは$0$以上$1$未満の小さい値で、H0側とH1側で平均的に打ち消し合うため、実用上は無視できるとされている&lt;/li&gt;
&lt;li&gt;&lt;code&gt;h0_test&lt;/code&gt;または&lt;code&gt;h1_test&lt;/code&gt;が0（アンダーフロー）の場合は、シフト数を最大値&lt;code&gt;31&lt;/code&gt;にして「その仮説の尤度はほぼ0（=強く否定される）」ことを表現している&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="モデルは学習後も動き続けるオンライン適応"&gt;モデルは学習後も動き続ける（オンライン適応）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一般的なGMM分類器は、事前に学習したパラメータを固定して使うことが多い&lt;/li&gt;
&lt;li&gt;しかしWebRTC VADは、noise用・speech用それぞれのGMMの平均を、判定結果に応じてフレームごとに更新し続ける
&lt;ul&gt;
&lt;li&gt;そのフレームがspeechと判定されれば、speech側GMMの平均をそのフレームの特徴量に近づける&lt;/li&gt;
&lt;li&gt;noiseと判定されれば、同様にnoise側GMMの平均を近づける&lt;/li&gt;
&lt;li&gt;更新の強さは&lt;code&gt;kSpeechUpdateConst&lt;/code&gt;・&lt;code&gt;kNoiseUpdateConst&lt;/code&gt;という定数で決まっていて、急激に動きすぎないよう抑えられている&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;さらに、noise側の平均には長期的な補正もかかる
&lt;ul&gt;
&lt;li&gt;チャンネルごとに、直近の特徴量（log-energy）の中で特に小さかった値を16個、経過フレーム数（最大100フレーム）つきで保持し続ける仕組みがあり（&lt;code&gt;vad_sp.c&lt;/code&gt;の&lt;code&gt;WebRtcVad_FindMinimum&lt;/code&gt;）、そこから長期的な最小値（ノイズの床）を推定する&lt;/li&gt;
&lt;li&gt;noise側の平均は、その推定値の方向へ&lt;code&gt;kBackEta&lt;/code&gt;という定数の分だけゆっくり引き寄せられる（該当コード: &lt;code&gt;nmk3 = nmk2 + (int16_t)((ndelt * kBackEta) &amp;gt;&amp;gt; 9);&lt;/code&gt;、コメントには&amp;quot;Long term correction of the noise mean&amp;quot;とある）&lt;/li&gt;
&lt;li&gt;これにより、周囲のノイズレベル自体が時間とともに変化しても（エアコンが止まる、環境音が変わる等）、noiseモデルがその変化に追従できる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;つまり、事前学習したパラメータはあくまでスタート地点であり、実際に動かしている環境に合わせてモデル自体が少しずつ適応し続ける仕組みになっている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="aggressivenessによる閾値の違い"&gt;aggressivenessによる閾値の違い&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aggressiveness&lt;/code&gt;（0〜3）は、ローカル判定・グローバル判定それぞれの閾値のセットを切り替えるパラメータ&lt;/li&gt;
&lt;li&gt;例えば、10ms（80サンプル）フレームでの閾値は以下のように変わる&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;aggressiveness&lt;/th&gt;
&lt;th&gt;ローカル閾値&lt;/th&gt;
&lt;th&gt;グローバル閾値&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0（Quality）&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3（Very Aggressive）&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;数字が大きいほど、speechと判定するためにより強い証拠（尤度比の差）が必要になり、non-speech寄りに判定が倒れやすくなる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="判定のちらつきを抑える仕組みhangover"&gt;判定のちらつきを抑える仕組み（hangover）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;フレーム単位の判定をそのまま出力すると、speechの終わり際などで判定がON/OFFを細かく繰り返してしまう&lt;/li&gt;
&lt;li&gt;WebRTC VADは、これを抑えるためにhangover（一種の余韻）という仕組みを持つ
&lt;ul&gt;
&lt;li&gt;一度speechと判定されると、その後しばらくnoise判定が続いてもspeechのまま扱う&lt;/li&gt;
&lt;li&gt;直近でspeechが長く続いていたかどうかに応じて、hangoverの長さを切り替える&lt;/li&gt;
&lt;li&gt;hangoverの長さも&lt;code&gt;aggressiveness&lt;/code&gt;によって変わる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="webrtc-vadの特徴と限界"&gt;WebRTC VADの特徴と限界&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;特徴量が6帯域のlog-energyだけで、log-melやMFCCのようなメル尺度ベースの特徴量は使っていないため、計算が軽く高速&lt;/li&gt;
&lt;li&gt;ディープラーニング以前の手法のため、ノイズや残響で音質が劣化した音声には弱く、拾い漏れ（recallの低下）が起きやすい&lt;/li&gt;
&lt;li&gt;話者性・方向・距離といった情報は一切使っていないため、「誰が喋っているか」「どの方向の声か」は区別できない
&lt;ul&gt;
&lt;li&gt;カフェなど雑音の多い環境で、目的の話者以外の声（雑談など）を除外したい場合は、VAD単体では対応できない&lt;/li&gt;
&lt;li&gt;その用途には、話者のembeddingを使ったtarget speaker extractionや、ビームフォーミングなど別の技術が必要になる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="まとめ"&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;WebRTC VADは、GMMと尤度比という古典的な統計モデルでspeech/non-speechを判定している&lt;/li&gt;
&lt;li&gt;GMMは、複数の正規分布を重みつきで混ぜ合わせて、複雑な分布を表現するモデル&lt;/li&gt;
&lt;li&gt;尤度は「あるモデルからそのデータが出てくる確からしさ」で、クラスごとにGMMを学習して尤度を比較すれば、そのまま分類器として使える&lt;/li&gt;
&lt;li&gt;対数尤度（尤度の対数）を使うと、独立なデータの同時尤度の掛け算が足し算になり、計算が軽く数値的にも安定する。WebRTC VADが各帯域の尤度比を足し算で合算しているのもこの性質による&lt;/li&gt;
&lt;li&gt;dB・dBFS・log-mel・MFCCは、いずれも音声処理でよく出てくる基礎的な特徴量表現だが、WebRTC VADはそのどれでもなく、オールパスフィルタで再帰的に分割した6帯域のlog-energyというシンプルな特徴量を使っている&lt;/li&gt;
&lt;li&gt;実際のソースコードを確認すると、以下のようなことが分かった
&lt;ul&gt;
&lt;li&gt;特徴量は6つの周波数帯域（80-250, 250-500, 500-1000, 1000-2000, 2000-3000, 3000-4000 Hz）ごとのlog-energy&lt;/li&gt;
&lt;li&gt;帯域ごとに独立したGMMがあり、ローカル判定（帯域単体）とグローバル判定（重みつき合算）を組み合わせている&lt;/li&gt;
&lt;li&gt;これはH0: ノイズ／H1: 音声という尤度比検定（LRT）そのものの形になっている&lt;/li&gt;
&lt;li&gt;対数尤度比は&lt;code&gt;log&lt;/code&gt;関数を使わず、32bit整数の正規化に必要なビットシフト数の差だけで近似計算されている&lt;/li&gt;
&lt;li&gt;GMMのパラメータは事前学習した値で固定ではなく、動作中もフレームごとにオンラインで適応し続けている（ノイズの床の長期的なドリフトにも追従する）&lt;/li&gt;
&lt;li&gt;hangoverという仕組みで、判定のちらつきを抑えている&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WebRTC VADは軽量・高速な反面、話者を区別する仕組みがなく、ノイズへの頑健性もニューラルVADに比べて劣る&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考文献"&gt;参考文献&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/wiseman/py-webrtcvad" target="_blank" rel="noopener"
&gt;wiseman/py-webrtcvad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>