マルチモード監視の王道、混合ガウス(FGMM)には2つの課題が残っていました。

ひとつは「モード数 $K$ をどう決めるか」。前記事ではBICで外から選びましたが、もっと原理的に自動決定したい。もうひとつは「各モードで何を監視するか」。前記事はセンサ値の密度(マハラノビス距離)を見ましたが、これだと各変数は正常範囲なのに変数間の関係だけが崩れる異常を見逃します。
この2つを解くのが、本記事の 変分ベイズ混合ガウス(VBGMM)+正準相関分析(CCA) です(多モードプロセス監視に変分ベイズGMMとCCAを組み合わせる研究、IEEE系、2019 を念頭に置きます)。

- VBGMM がモード数を自動決定(不要な成分の重みを0へ剪定)
- CCA がモードごとに入出力(操作量とセンサ応答)の関係を学習し、関係の破れを残差で捉える
この手法が効く場面は明確です。
- モード数が事前に分からない系:いくつの運用モードがあるか自動で知りたい
- 入力(操作量)と出力(センサ)の関係が重要な系:個々の値より相関が壊れる異常を見たい
- 冗長なセンサを持つプロセス:変数間の整合性監視
この記事の内容
- VBGMM — 変分ベイズによるモード数の自動決定(成分の剪定)
- CCA — 入力ブロックと出力ブロックの関係を捉える
- モードごとのCCA監視 — 関係の破れを残差で検出
- FGMM との違い
- scikit-learn実装 — 周辺マハラノビス(AUC0.41)が見逃す関係破れを、VBGMM+CCA(0.77)が捉える様子を実測
前提知識


1. VBGMM — モード数の自動決定
混合ガウスで成分数 $K$ を決めるのは厄介です。多すぎれば過剰適合、少なすぎればモードを取りこぼす。変分ベイズ混合ガウス(VBGMM)は、これを自動で解きます。
アイデアは、混合重み $\pi$ に疎性を促す事前分布(Dirichlet過程/弱い濃度パラメータ)を置くことです。多めの成分数(たとえば10)から始め、変分ベイズで学習すると、データが要らない成分の重みは自動的に0近くへ剪定され、実際に必要なモードだけが残ります。

上図では10成分から始めましたが、3成分だけが有効な重みを持ち、残りは0近くへ剪定されました。データの真のモード数(3)を、$K$ を指定せずに言い当てています。BICのようにモデルを何度も作り直して比較する必要がありません。

割当結果を見ると、各モードがきれいに分離されています。モードが識別できたら、次は「各モードで何を監視するか」です。ここでCCAが効いてきます。
2. CCA — 入出力の関係を捉える
前記事のFGMMは、各モードでセンサ値の密度(マハラノビス距離)を監視しました。しかしこれは、各変数が正常範囲に収まっている限り異常を見逃します。問題は、変数間の関係が壊れるタイプの異常です。
たとえば「操作量を上げたのにセンサ応答が伴わない」。操作量もセンサ値もそれぞれ正常範囲内なのに、両者の対応が崩れている。これを捉えるのが正準相関分析(CCA, Canonical Correlation Analysis)です。

CCAは、入力ブロック $X$(操作量・条件)と出力ブロック $Y$(センサ応答)を、それぞれ射影 $a^\top X$、$b^\top Y$ したときの相関が最大になる方向を見つけます。
$$ \begin{equation} (a^*, b^*) = \arg\max_{a, b} \ \mathrm{corr}\left(a^\top X,\ b^\top Y\right) \end{equation} $$
正常データで入出力の関係を学習しておけば、関係が崩れたとき、入力から予測される出力 $\hat{Y}$ と実際の出力 $Y$ の残差が大きくなります。これが異常スコアです。

モードごとに入出力関係は違うので、モードごとに専用のCCAモデルを持ちます。新しい点が来たら、VBGMMでモードを判定し、そのモードのCCAで残差を評価します。

FGMM(成分数を外から選び、密度監視)と比べ、VBGMM+CCA(モード数を自動剪定、関係監視)は2点で進化しています。実装で確かめます。
3. scikit-learn実装 — 関係の破れを捉える
3モードのデータを作ります。各モードで入力分布と入出力の線形関係($Y = M_{\text{mode}} X$)が異なります。
import numpy as np
from sklearn.mixture import BayesianGaussianMixture
from sklearn.cross_decomposition import CCA
rng = np.random.default_rng(0)
KM, din, dout = 3, 3, 3
in_means = np.array([[-3,-2,0],[2,3,-1],[3,-2,2.0]])
maps = [rng.normal(0,1,(dout,din)) for _ in range(KM)]
N = 3000; X=[]; Y=[]; z=[]
for _ in range(N):
m = rng.integers(KM)
xi = rng.normal(in_means[m], 0.7)
yi = maps[m] @ xi + rng.normal(0, 0.3, dout)
X.append(xi); Y.append(yi); z.append(m)
X=np.array(X); Y=np.array(Y); z=np.array(z)
n_tr=2000; Xtr,Ytr=X[:n_tr],Y[:n_tr]; Xte_n,Yte_n=X[n_tr:],Y[n_tr:]
異常は、入力はそのまま、出力を「同じモードの別サンプルの正常出力」に差し替えます。こうすると出力の周辺分布は正常(本物のモード出力)なのに、入力との対応=関係だけが破れます。これが周辺監視には見えないステルスな異常です。
ztest = z[n_tr:]; ztr = z[:n_tr]; Xan = X[n_tr:].copy(); Yan = []
for i in range(len(Xte_n)):
m = ztest[i]; pool = np.where(ztr == m)[0] # 同モードの正常出力プール
j = pool[rng.integers(len(pool))]
Yan.append(Ytr[j] + rng.normal(0, 0.05, dout)) # 別サンプルの出力で関係を断つ
Yan = np.array(Yan)
Xte = np.vstack([Xte_n, Xan]); Yte = np.vstack([Yte_n, Yan])
yte = np.concatenate([np.zeros(len(Xte_n)), np.ones(len(Xan))])
VBGMMでモード数を自動決定します(10成分から始める)。
XYtr = np.hstack([Xtr, Ytr])
vb = BayesianGaussianMixture(n_components=10, covariance_type="full",
weight_concentration_prior=1e-2, random_state=0, max_iter=300).fit(XYtr)
eff_K = int((vb.weights_ > 0.02).sum()) # 有効モード数
mtr = vb.predict(XYtr)
used = [k for k in np.unique(mtr) if (mtr==k).sum() > 30]
モードごとにCCAを学習し、入力から予測した出力との残差を異常スコアにします。比較は出力だけの周辺マハラノビスです。
ccas = {k: CCA(n_components=2).fit(Xtr[mtr==k], Ytr[mtr==k]) for k in used}
mte = vb.predict(np.hstack([Xte, Yte]))
s_cca = np.zeros(len(Xte))
for i in range(len(Xte)):
k = mte[i]
if k in ccas:
Ypred = ccas[k].predict(Xte[i:i+1])[0]
s_cca[i] = np.linalg.norm(Yte[i] - Ypred) # 入出力関係からの残差
# 比較: 出力だけの周辺マハラノビス(モードごと)
invs={}; mus={}
for k in used:
Yk = Ytr[mtr==k]; mus[k]=Yk.mean(0); invs[k]=np.linalg.inv(np.cov(Yk.T)+1e-3*np.eye(dout))
s_marg = np.array([ (Yte[i]-mus[mte[i]])@invs[mte[i]]@(Yte[i]-mus[mte[i]]) if mte[i] in invs else 0
for i in range(len(Xte)) ])
def auc(score, y):
order=np.argsort(score); ranks=np.empty(len(score)); ranks[order]=np.arange(1,len(score)+1)
npos=y.sum(); nneg=len(y)-npos
return (ranks[y==1].sum()-npos*(npos+1)/2)/(npos*nneg)
print(f"有効モード数={eff_K}")
print(f"周辺マハラノビス(出力のみ) AUC={auc(s_marg, yte):.3f}")
print(f"VBGMM+CCA(入出力関係) AUC={auc(s_cca, yte):.3f}")
実行結果(seed固定):
有効モード数=3
周辺マハラノビス(出力のみ) AUC=0.414
VBGMM+CCA(入出力関係) AUC=0.769


パリティ図が核心を示します。正常点は「CCA予測出力=実出力」の対角線に乗りますが、異常点(赤×)は対角から外れます。出力の値そのものは正常範囲なのに、入力から予測される出力と食い違う——これが関係の破れです。


数字の読み取りです。
- 有効モード数=3:VBGMMが $K$ を指定せず、10成分から正しく3モードに剪定しました。
- 周辺マハラノビス=0.414:ランダム(0.5)を下回っています。異常の出力は「本物のモード出力」なので周辺分布では正常そのもの。関係の破れには完全に盲目です。
- VBGMM+CCA=0.769:入力との関係から予測した出力との残差を見るので、関係が崩れた点を捉えます。
教訓は明快です。各変数を個別に見ているだけでは、変数間の関係が壊れる異常を見逃す。 VBGMMでモードを自動識別し、モードごとにCCAで入出力の関係を監視することで、周辺監視には見えないステルスな異常を捉えられます。
4. まとめ
VBGMM+CCAによるマルチモード監視を見てきました。
- VBGMM:混合重みに疎性事前分布を置き、不要成分を剪定してモード数を自動決定。
- CCA:入力ブロックと出力ブロックの相関を最大化し、関係の破れを残差で捉える。
- モードごと監視:VBGMMでモード判定 → そのモードのCCA残差で異常評価。
- 実証:周辺マハラノビス(0.414、ランダム以下)が見逃す関係破れを、VBGMM+CCA(0.769)が捉える。有効モード数も自動で3。
FGMM(密度監視)からVBGMM+CCA(関係監視+自動モード数)へ——マルチモード監視は「何を正常とみなすか」を精緻化してきました。次の記事では、深層学習で周波数とチャネルの関係まで捉える最新手法CATCH(ICLR2025)に進みます。


主な参考文献
- 多モードプロセス監視のための変分ベイズGMMと正準相関分析(VBGMM-CCA), IEEE系, 2019.
- C. M. Bishop, “Pattern Recognition and Machine Learning,” Springer, 2006.(変分ベイズ混合モデル)
- H. Hotelling, “Relations between two sets of variates,” Biometrika, 28, 321–377, 1936.(CCA)
- J. Yu, S. J. Qin, “Multimode process monitoring with Bayesian inference-based finite Gaussian mixture models,” AIChE Journal, 2008.