CATCH:周波数パッチ×チャネル認識による多変量時系列異常検知(ICLR 2025)

このシリーズの締めくくりに、運用・操作で動く多変量時系列の異常検知を、周波数領域チャネル相関の両面から捉える最新手法を取り上げます。CATCH(Channel-Aware Multivariate Time Series Anomaly Detection via Frequency Patching, ICLR 2025)です。

運用・操作で動くシステムの異常検知:サーベイ(親記事)
本記事はシリーズ最終回。深層・周波数領域の最新手法です。

これまでの手法の多くは、時間領域でセンサ値を直接モデル化してきました。しかし産業システムの異常には、時間領域では見えにくいが周波数領域でははっきり現れるものがあります。たとえば、同じ周波数成分なのに振幅バランスだけが崩れる、特定の周波数帯に微細な変化が出る、といった異常です。さらに多変量では、チャネル(変数)間の相関が壊れる異常もあります。

CATCHはこの2つに正面から取り組みます。

CATCHの概念図

  • 周波数パッチ化:周波数スペクトルを帯(パッチ)に分け、細かい周波数特性を捉える
  • Channel Fusion Module(CFM):マスク生成+マスク付き注意で、関連チャネルを束ね悪影響チャネルを切り離す

この手法が効く場面は明確です。

  • 周波数特性が重要な系:回転機械・電気系統など、スペクトルに異常が出る
  • チャネル相関が意味を持つ多変量系:変数間の関係が壊れる異常を見たい
  • 微細な周波数変化の検出:時間領域では埋もれる異常

この記事の内容

  • 周波数パッチ化 — スペクトルを帯に分ける意味
  • なぜ周波数領域か — 時間領域では見えない異常
  • CATCHのアーキテクチャ(論文図)
  • Channel Fusion Module — チャネル相関を活かす
  • NumPy実装 — 周波数パッチ・チャネル認識の効果を実測

前提知識

画像なし
変分ベイズGMM+CCAによるマルチモード監視
チャネル間の関係を監視する考え方。本記事の深層・周波数版です。
画像なし
フーリエ変換
周波数領域の基礎です。

1. 周波数パッチ化 — スペクトルを帯に分ける

時系列の周波数特性は、FFTで得られるスペクトルに現れます。CATCHの第一のアイデアは、このスペクトルを帯(パッチ)に分割することです。

周波数パッチ化

スペクトル全体を1つのベクトルとして扱うのではなく、周波数帯ごとのパッチに分けることで、どの周波数帯に異常があるかを細かく捉えられます。画像認識でのパッチ分割(ViT)が空間を細かく見るのと同じ発想を、周波数軸で行うわけです。これにより、特定帯域の微細な変化を見逃さなくなります。

ではなぜ、時間領域ではなく周波数領域なのでしょうか。


2. なぜ周波数領域か — 時間領域では見えない異常

決定的なのは、位相に対する不変性です。多くの産業信号は、同じ周波数成分を持ちながら、位相(タイミング)は窓ごとにバラバラです。時間領域で見ると、位相がずれた信号はまったく別の波形に見えるため、正常データが非常に高次元に散らばります。一方、周波数振幅は位相に依存しないので安定しています。

時間領域と周波数領域

上図がこれを示します。「同じ周波数成分だが、ある周波数の振幅だけが変わる」異常を考えます。時間領域(上段)では、位相がランダムなため正常も異常も多様な波形になり、見分けがつきません。しかし周波数領域(下段)では、振幅プロファイルの違いがはっきり現れます。位相のばらつきに埋もれず、周波数の構造だけを取り出せる——これが周波数領域の強みです。

この周波数表現を、チャネル相関まで考慮して再構成するのがCATCHの全体像です。


3. CATCHのアーキテクチャ

CATCHは3つのモジュールからなります。論文の全体図を見てみましょう。

CATCHのアーキテクチャ

出典: X. Wu, et al., “CATCH: Channel-Aware Multivariate Time Series Anomaly Detection via Frequency Patching,” ICLR 2025, Fig. 2.

論文の原図の通り、処理は次の流れです。(1) Forward Module が入力を正規化し、FFTで周波数領域に移してパッチ化(FFT & Patching)、線形射影する。(2) Channel Fusion Module(CFM) が、パッチごとにチャネル相関を発見し(Channel Correlation Discovering)、マスク生成(Mask Generator)とマスク付きTransformer(Channel-Masked Transformer)で関連チャネルを融合する。(3) 融合した表現から周波数スペクトルを再構成し、iFFTで時間領域に戻して再構成誤差を異常スコアにする。

再構成ベース検知

正常な周波数スペクトルを学習し、再構成誤差が大きい周波数・チャネルを異常とします。核心はCFMなので、次に詳しく見ます。


4. Channel Fusion Module — チャネル相関を活かす

多変量時系列では、チャネル(変数)をどう扱うかが難問です。すべてのチャネルを一様に混ぜると無関係なチャネルの悪影響を受け、独立に扱うとチャネル間の相関が壊れる異常を見逃します。

Channel Fusion Module

CATCHのCFMは、パッチごとに適切なチャネル相関を学習します。マスク生成器がチャネル間の関連度(マスク)を作り、マスク付き注意機構で関連チャネルだけを束ね、悪影響のあるチャネルは切り離す。このマスクは、二段階の多目的最適化(クラスタリング損失と正則化損失のバランス)で学習されます。関連チャネルをクラスタ化しつつ、互いに干渉するチャネルを分離するわけです。

これにより、前記事のCCAが捉えた「入出力関係の破れ」と同種の、チャネル相関が壊れる異常を深層・周波数領域で捉えられます。実装で2つの核を確かめます。


5. NumPy実装 — 周波数パッチとチャネル認識の効果

CATCHの2つの核(周波数パッチ・チャネル融合)を、決定的なPCA再構成で概念実証します。まずデータを作ります。正常信号は、共通の周波数成分(チャネル間で位相を共有=相関)を持ち、窓ごとに位相がランダムです。

import numpy as np
rng = np.random.default_rng(0)
C, L = 4, 128; t = np.arange(L)
FREQS = np.array([10, 14, 18, 22])             # 正常・異常で共通の周波数
NORMAL_MAG = np.array([1.0, 1.0, 1.0, 1.0])
ANOM_MAG   = np.array([1.0, 1.45, 0.7, 1.0])   # 同じ周波数だが振幅プロファイルが違う

def make_window(mags=NORMAL_MAG, broken=False):
    phases = rng.uniform(0, 2*np.pi, len(FREQS))           # 窓ごとランダム位相(全chで共有=相関)
    common = sum(mags[k]*np.sin(2*np.pi*FREQS[k]*t/L + phases[k]) for k in range(len(FREQS)))
    X = np.zeros((C, L))
    for c in range(C):
        X[c] = (0.8+0.4*rng.random())*common + rng.normal(0, 0.1, L)
    if broken:                                              # ch0だけ独立位相(相関破れ。振幅は正常)
        ph2 = rng.uniform(0, 2*np.pi, len(FREQS))
        X[0] = (0.8+0.4*rng.random())*sum(NORMAL_MAG[k]*np.sin(2*np.pi*FREQS[k]*t/L + ph2[k]) for k in range(len(FREQS))) + rng.normal(0,0.1,L)
    return X

n_tr, n_te = 800, 400
Xtr   = np.array([make_window() for _ in range(n_tr)])
Xte_n = np.array([make_window() for _ in range(n_te)])
Xan_f = np.array([make_window(mags=ANOM_MAG)  for _ in range(n_te)])   # 異常1: 振幅プロファイル
Xan_c = np.array([make_window(broken=True)    for _ in range(n_te)])   # 異常2: チャネル相関破れ

特徴量を定義します。時間領域は生の窓、周波数パッチは各チャネルのスペクトルを帯ごとに(ピーク保持のmaxで)まとめます。

def time_feat(W): return W.reshape(len(W), -1)
def freq_feat(W, n_band=32, channel_aware=True):
    mag = np.abs(np.fft.rfft(W, axis=2))                   # n×C×(L/2+1)
    edges = np.linspace(0, mag.shape[2], n_band+1).astype(int)
    patches = np.stack([mag[:,:,edges[b]:edges[b+1]].max(2) for b in range(n_band)], 2)  # パッチ内max
    return patches.reshape(len(W), -1) if channel_aware else patches

def pca_recon_error(Xtr, Xte, k):
    mu = Xtr.mean(0); U,S,Vt = np.linalg.svd(Xtr-mu, full_matrices=False); W = Vt[:k]
    return np.linalg.norm(Xte - (((Xte-mu)@W.T)@W + mu), axis=1)

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)

実験1(周波数パッチ):振幅プロファイル異常を、時間領域 vs 周波数パッチで検知します。

Xte1 = np.vstack([Xte_n, Xan_f]); y1 = np.concatenate([np.zeros(n_te), np.ones(n_te)])
s_time = pca_recon_error(time_feat(Xtr), time_feat(Xte1), 40)
s_freq = pca_recon_error(freq_feat(Xtr), freq_feat(Xte1), 20)
print(f"時間領域   AUC={auc(s_time, y1):.3f}")
print(f"周波数パッチ AUC={auc(s_freq, y1):.3f}")

実験2(チャネル融合):チャネル相関破れを、チャネル独立 vs チャネル認識で検知します。チャネル認識はチャネル間相関を特徴にします。

Xte2 = np.vstack([Xte_n, Xan_c]); y2 = np.concatenate([np.zeros(n_te), np.ones(n_te)])
# チャネル独立: 各chの周波数特徴で別々にPCA→ch最大誤差(ch0も周波数・振幅は正常→見逃す)
fi_tr = freq_feat(Xtr, channel_aware=False); fi_te = freq_feat(Xte2, channel_aware=False)
s_ind = np.zeros(len(Xte2))
for c in range(C):
    s_ind = np.maximum(s_ind, pca_recon_error(fi_tr[:,c,:], fi_te[:,c,:], 8))
# チャネル認識: チャネル間相関(上三角)を特徴に
def corr_feat(W):
    iu = np.triu_indices(C,1)
    return np.array([np.corrcoef(w)[iu] for w in W])
s_aware = pca_recon_error(corr_feat(Xtr), corr_feat(Xte2), 3)
print(f"チャネル独立 AUC={auc(s_ind, y2):.3f}")
print(f"チャネル認識 AUC={auc(s_aware, y2):.3f}")

実行結果(seed固定):

時間領域   AUC=0.498
周波数パッチ AUC=1.000
チャネル独立 AUC=0.496
チャネル認識 AUC=1.000

チャネル相関破れの可視化

実験1:周波数パッチの効果

実験2:チャネル認識の効果

スコア分布

数字の読み取りです(理想化した合成データで、各機構を切り出して示しています)。

  • 実験1:時間領域0.498 → 周波数パッチ1.000。振幅プロファイル異常は、同じ周波数・ランダム位相なので時間領域では正常と同じ部分空間に入り、まったく区別できません(フーリエ変換は線形なので、位相のばらつきがそのまま時間波形の多様性になる)。周波数振幅は位相不変なので、振幅の違いをくっきり捉えます。
  • 実験2:チャネル独立0.496 → チャネル認識1.000。ch0が他チャネルと非同期になる相関破れは、各チャネルを個別に見れば周波数も振幅も正常なので見逃します。チャネル間相関を見て初めて捉えられます。

CATCHは、この周波数パッチ(位相に強く細かい周波数特性)チャネル融合(相関を活かす)を、深層学習で同時に・適応的に行う手法です。本実証は線形PCAでの概念実証ですが、CATCHはこれをマスク付き注意と多目的最適化で学習し、実ベンチマーク(SWaT・WADI・SMD・MSL・SMAP・PSMなど)で高い精度を報告しています。


6. まとめ — シリーズ全体の締めくくり

CATCH(ICLR 2025)を見てきました。

  • 周波数パッチ化:スペクトルを帯に分け、位相に強く細かい周波数特性を捉える。
  • Channel Fusion Module:マスク生成+マスク付き注意で関連チャネルを融合、悪影響チャネルを分離。
  • 再構成ベース:周波数スペクトルの再構成誤差を異常スコアに。
  • 実証:時間領域(0.498)が見逃す振幅プロファイル異常を周波数パッチ(1.0)が、チャネル独立(0.496)が見逃す相関破れをチャネル認識(1.0)が捉える。

このシリーズでは、「運用・操作で動いた分を異常としない」という一つの原理を、条件付き(Song原型・残差・状態遷移・適合性・条件付きフロー・運転条件正規化)とマルチモード(FGMM・HMM・遷移・VBGMM+CCA)、そして本記事の周波数×チャネルまで、12本の記事で多面的に掘り下げてきました。手法は古典から最新まで幅広いですが、貫くのは「操作・モード・チャネル相関という文脈で説明できる変動を取り除き、残りで異常を測る」という同じ思想です。すべては親サーベイに地図としてまとまっています。

運用・操作で動くシステムの異常検知:サーベイ(親記事)
シリーズ全体の地図。全手法へのリンク。
画像なし
変分ベイズGMM+CCAによるマルチモード監視
チャネル関係を監視する古典的手法。

主な参考文献

  • X. Wu, et al., “CATCH: Channel-Aware Multivariate Time Series Anomaly Detection via Frequency Patching,” ICLR 2025, arXiv:2410.12261.
  • A. Vaswani, et al., “Attention is All You Need,” NeurIPS, 2017.
  • A. Dosovitskiy, et al., “An Image is Worth 16×16 Words (ViT),” ICLR, 2021.(パッチ化の発想)