工場のポンプ、サーバー群のCPU・メモリ、配水網の流量計。こうした設備は数十〜百を超えるセンサーを同時に出力し続けます。問題は「どのセンサーも単体ではいつも通りなのに、組み合わせとして異常」という故障が起きることです。たとえば流量は正常範囲、圧力も正常範囲、しかし「この流量でこの圧力はありえない」——こうした多変量・文脈依存の異常は、しきい値ルールでは捉えられません。
オートエンコーダによる異常検知は有力な手段ですが、ひとつ弱点があります。オートエンコーダ(AE)は「入力をできるだけ忠実に再構成する」よう学習するため、正常にわずかに紛れた異常まで上手く再構成してしまい、見逃すのです。一方でGAN系の手法は「これは本物か?」を判定する識別器を持つので微妙な異常に強い反面、学習が不安定(モード崩壊・非収束)で、本番運用に乗せにくい。
USAD (UnSupervised Anomaly Detection on multivariate time series) は、この2つの「いいとこ取り」を狙った手法です。Orange社の研究者がKDD 2020で発表しました(Audibert et al., 2020)。アイデアは明快で、1つの共有エンコーダと2つのデコーダを「敵対的」に訓練することで、AEの安定性を保ったまま、わずかな異常の再構成誤差を増幅して検出します。しかも訓練はOmniAnomalyの平均547倍速い。
USADを理解すると、次のような場面で武器になります。
- 産業設備の予知保全: 水処理・配電・製造ラインの多変量センサーから故障予兆を検出
- ITインフラ監視 (AIOps): サーバー群のメトリクスやマイクロサービスのトレースから障害を早期発見
- 多変量機器の健全性監視: 多数のセンサー系列から微小な異常挙動を発見
本記事の内容
- USADの直感 — なぜ「敵対的なAE」が必要なのか
- 多変量時系列のデータの入れ方(窓化 $K \times m$、ダウンサンプリング)
- 共有エンコーダ+2デコーダのアーキテクチャと2フェーズ敵対訓練の数理(損失式の導出)
- 推論時の異常スコアと $\alpha,\beta$ による感度チューニング
- サーベイ: SWaT/WADI/SMD/SMAP/MSL の仕様と、F1・訓練時間・アブレーションの実験結果
- PyTorchによるスクラッチ実装と、合成多変量時系列での再現実験
前提知識
この記事を読む前に、以下を読んでおくと理解が深まります。



USADとは — 「再構成」と「識別」の合わせ技

まずイメージから入りましょう。USADには2人の登場人物がいます。
ひとりはまじめな模写職人(AE₁)。入力された時系列の窓を、できるだけそっくりに描き直します。もうひとりは鑑定士(AE₂)。職人の模写を見て「これは本物のデータか、それとも模写か」を見抜こうとします。
訓練では、職人は鑑定士を騙そうとし(模写を本物に近づける)、鑑定士は見破ろうとします(本物と模写の差を強調する)。この緊張関係を正常データだけで繰り返すと、職人は「正常な入力なら鑑定士を完璧に騙せる」状態に到達します。ところが異常な入力が来ると、それは訓練で一度も見ていないので職人は騙しきれず、鑑定士が大きな差を検出します。これが「異常の再構成誤差を増幅する」というUSADの核心です。
ここで重要なのは、鑑定士(AE₂)が独立した識別器ネットワークではなく、職人と同じエンコーダを共有するオートエンコーダだという点です。だからGANのように学習が崩壊せず、AE特有の安定性を保てます。「敵対的だが、中身はAE」——これがUSADの正体です。
では、そもそも多変量時系列をどうやってネットワークに入れるのか。データの入れ方から見ていきましょう。
データの入れ方 — 多変量時系列を「窓」に切る
USADへの入力は、生の時系列そのものではありません。時間的な文脈を保つために、長さ $K$ の窓(window) に区切ります。
時刻 $t$ における長さ $K$ の窓を次のように定義します。
$$ \begin{equation} W_t = \{x_{t-K+1}, \dots, x_{t-1}, x_t\} \end{equation} $$
各 $x \in \mathbb{R}^m$ は $m$ 変量(センサー数)のベクトルです。つまり1つの窓は $K \times m$ の行列で、これを1本のベクトルに平坦化してオートエンコーダに入れます。元の時系列 $\mathcal{T}$ を窓の列 $\mathcal{W} = \{W_1, \dots, W_T\}$ に変換し、これを訓練入力とします。

上図のように、5本のセンサー系列から長さ $K$ の区間を切り出し、その $K \times m$ の値をまとめて1つの入力にします。点ではなく窓を入れることで、「いつもこの順序で上がって下がる」といった時間方向のパターンを学習できます。窓を1ステップずつずらせば、テスト系列の各時刻に異常スコアを与えられます。
前処理ではもう2つ大事な操作があります。
- 正規化: 各変量を訓練データの統計で標準化(またはmin-max)します。スケールの大きいセンサーが誤差を支配しないようにするためです。
- ダウンサンプリング: 連続する数点の中央値を取って系列を間引きます。論文では全データセットでレート5(5点を1点に)を採用。ノイズ除去と訓練高速化(時間が1/5)の効果があり、性能はレートに対してほぼ不感だと報告されています。
窓のサイズ $K$ は「どれだけ短い異常を、どれだけ速く検出したいか」を決めます。$K$ が小さいほど各観測の影響が大きく速く検出できますが、長く続く異常は捉えにくい。論文ではSWaTで $K \in \{5,10,20,50,100\}$ を試し、$K=10$ が最良でした。
データの形が決まったところで、「なぜ単純なAEではダメなのか」をもう一段深く見ていきます。
なぜ単純なAEでは不十分なのか
オートエンコーダによる異常検知は、再構成誤差 $\|X – AE(X)\|_2$ を異常スコアにします。正常で訓練したAEは、正常をよく再構成し(誤差小)、未知の異常はうまく再構成できない(誤差大)——というのが建前です。
ところが落とし穴があります。AEの訓練目的は「入力をできるだけ忠実に再構成すること」です。だから、正常データにわずかに近い異常(mild anomaly)が来ると、AEはそれもそこそこ上手く再構成してしまい、誤差が小さく、見逃してしまうのです。

左図のように、単純AEは正常域に近い異常をほぼそのまま再構成し、誤差(塗りつぶし)がごく小さくなります。理想は「入力が異常を含まないと確信できたときだけ上手に再構成し、そうでなければわざと外す」という挙動ですが、ふつうのAEにはその判断力がありません。
この「入力が正常かどうかを見分ける」能力こそ、GANの識別器が持つものです。右図のように、USADは敵対訓練を通じてAE₂が異常付近の再構成誤差を増幅するため、mild anomalyでもスコアが跳ね上がり検出できます。GANの判別力とAEの安定性を両立させる——これがUSADの設計思想です。
具体的にどんなネットワークでこれを実現するのか、アーキテクチャを見ましょう。
USADのアーキテクチャ — 共有エンコーダと2つのデコーダ
USADは3つの部品でできています。1つのエンコーダ $E$ と、2つのデコーダ $D_1, D_2$ です。これらを組み合わせ、エンコーダを共有する2つのオートエンコーダ $AE_1, AE_2$ を構成します。
$$ \begin{equation} AE_1(W) = D_1(E(W)), \qquad AE_2(W) = D_2(E(W)) \end{equation} $$

ポイントは、敵対訓練と推論で使うもう1つの経路です。$AE_1$ が再構成した出力 $AE_1(W)$ を、もう一度エンコーダ $E$ に通して $D_2$ で復元します。
$$ \begin{equation} AE_2(AE_1(W)) = D_2\big(E(D_1(E(W)))\big) \end{equation} $$
この「再構成をもう一度再構成する」二重経路が、本物 $W$ と模写 $AE_1(W)$ の食い違いを測るための仕掛けです。実装上のネットワークは論文ではシンプルなMLPで、エンコーダは入力次元($K \times m$)を $1/2 \to 1/4 \to$ 潜在次元へ絞るReLU多層、デコーダは対称に広げて最後にSigmoidをかけます。
論文では、この情報の流れを学習時と検出時の2枚組で示しています。下図はそれを再現したものです。

論文 Fig.1 を参考に作図。USADはarXiv版が無くKDD2020論文図の再配布可否が不明なため、同内容を自作で再現した。
左の学習では、共有エンコーダ $E$ が窓 $W$ を潜在 $Z$ に圧縮し、$D_1, D_2$ がそれぞれ復元します。青い矢印(Phase 1)は素直な再構成 $AE_1(W), AE_2(W)$、緑の矢印(Phase 2)が敵対経路で、$AE_1$ の出力をもう一度 $E \to D_2$ に通して $AE_2(AE_1(W))$ を作ります。右の検出では、$AE_1(W)$ と $AE_2(AE_1(W))$ の2つの誤差を $\alpha, \beta$ で重み付けして異常スコアにします。注目すべきは、学習にも検出にも同じ二重経路が使われ、新たな識別器ネットワークを一切足していないことです。$AE_2$ はGANの識別器と同じ「本物か再構成か」を見抜く役を担いながら、その実体はエンコーダを共有するオートエンコーダのまま——この一枚に、USADの新規性が凝縮されています。
この3部品をどう訓練すれば「正常なら騙せて、異常なら騙せない」状態になるのか。ここがUSADで最も美しい部分です。
2フェーズ敵対訓練の数理
USADの訓練は2つのフェーズからなります。

フェーズ1:オートエンコーダ訓練
まず、両方のAEに「正常をそっくり再現する」ことを覚えさせます。入力 $W$ をエンコーダ $E$ で潜在 $Z$ に圧縮し、各デコーダで復元します。目的は素直な再構成誤差の最小化です。
$$ \begin{equation} L_{AE_1} = \|W – AE_1(W)\|_2, \qquad L_{AE_2} = \|W – AE_2(W)\|_2 \end{equation} $$
ここで $\|\cdot\|_2$ はL2ノルムです。この段階は普通のAEと同じで、敵対訓練を始める前に重みを「正常データの良い再構成器」へと方向づける役割があります。
フェーズ2:敵対訓練
次が本番です。$AE_2$ に「本物の $W$ と、$AE_1$ が作った再構成を見分ける」ことを、$AE_1$ に「$AE_2$ を騙す」ことを学ばせます。$AE_1$ の出力を再び $E \to D_2$ に通した $AE_2(AE_1(W))$ について、次のミニマックスを解きます。
$$ \begin{equation} \min_{AE_1}\ \max_{AE_2}\ \ \|W – AE_2(AE_1(W))\|_2 \end{equation} $$
意味を言葉にすると、$AE_1$ は $AE_2(AE_1(W))$ を本物 $W$ に近づけたい(差を最小化=騙す)、$AE_2$ はその差を大きくしたい(差を最大化=見破る)。それぞれの損失は次のとおりです。
$$ \begin{equation} L_{AE_1} = +\|W – AE_2(AE_1(W))\|_2, \qquad L_{AE_2} = -\|W – AE_2(AE_1(W))\|_2 \end{equation} $$
正常データだけでこの綱引きを続けると、$AE_1$ は「正常入力なら $AE_2$ を騙せる(差が小さい)」状態に達します。逆に未知の異常では騙しきれず差が大きく出る——これが異常スコアの源になります。
2つのフェーズを1つに統合する
実際には2フェーズを別々に回すのではなく、エポックとともに比率を変える1つの目的関数にまとめます。式(4)と式(6)を、エポック $n$ で重み付けして合成します。
$$ \begin{equation} L_{AE_1} = \frac{1}{n}\|W – AE_1(W)\|_2 + \left(1 – \frac{1}{n}\right)\|W – AE_2(AE_1(W))\|_2 \end{equation} $$
$$ \begin{equation} L_{AE_2} = \frac{1}{n}\|W – AE_2(W)\|_2 – \left(1 – \frac{1}{n}\right)\|W – AE_2(AE_1(W))\|_2 \end{equation} $$
ここで $n$ は訓練エポック番号です。式の読み方を順に追いましょう。まず第1項(係数 $1/n$)は素直な再構成項。第2項(係数 $1 – 1/n$)が敵対項で、$AE_1$ では $+$(差を縮める=騙す)、$AE_2$ では $-$(差を広げる=見破る)で符号が逆になっています。
そして係数の $1/n$ がカギです。$n=1$ では $1/n = 1$ なのでほぼ再構成だけを学び、エポックが進むと $1/n \to 0$ で敵対項が支配的になります。

上図の通り、序盤は再構成項(青)が大きく「まず正常を再現できる土台」を作り、終盤は敵対項(赤)が大きく「誤差増幅の能力」を磨きます。先にフェーズ1で重みを良い場所に置いてからフェーズ2に移る、というカリキュラム学習になっているわけです。実際このスケジュールのおかげで、GANにありがちな崩壊を起こさず安定して収束します(後述の実装で確認します)。
訓練が終われば、あとは未知の窓にスコアを付けるだけです。
推論:異常スコアと α/β による感度チューニング
検出時、未知の窓 $\hat{W}$ の異常スコアは2つの誤差の重み付き和で定義します。
$$ \begin{equation} \mathcal{A}(\hat{W}) = \alpha\,\|\hat{W} – AE_1(\hat{W})\|_2 + \beta\,\|\hat{W} – AE_2(AE_1(\hat{W}))\|_2, \qquad \alpha + \beta = 1 \end{equation} $$
第1項は $AE_1$ の純粋な再構成誤差、第2項は敵対経路の誤差です。$\mathcal{A}(\hat{W})$ がしきい値 $\lambda$ を超えたら異常と判定します。
ここで $\alpha, \beta$ が偽陽性(FP)と真陽性(TP)のトレードオフを操る「つまみ」になります。
- $\alpha > \beta$(再構成重視): 低感度。FPは減るが、見逃しも増える
- $\alpha < \beta$(敵対重視): 高感度。見逃しは減るが、FPが増える
しかも重要なのは、1つの訓練済みモデルから、再訓練なしに $\alpha,\beta$ を変えるだけで複数の感度を取り出せる点です。運用現場で「管理者は誤報を嫌う(低感度)、現場技術者は取りこぼしを嫌う(高感度)」のような要求の違いに、モデルを1つ持つだけで応えられます。

上図は論文のSWaTでの実測値です。$\alpha$ を $0.0$ から $0.9$ に上げると、FPは $604 \to 299$ と約50%削減される一方、TPの減少は $35{,}616 \to 34{,}028$ とわずか3%に抑えられています。つまり「少しの取りこぼしと引き換えに、誤報を大きく減らせる」という、運用上きわめて扱いやすい挙動です。
ここまでが手法の全体像です。では実際にどんなデータで、どれだけの性能が出るのか——論文の実験をサーベイとしてまとめます。
サーベイ①:ベンチマークデータセット
USADは公開されている5つの多変量時系列データセット(+Orange社の社内データ)で評価されました。異常検知の研究で繰り返し使われる定番ばかりなので、データの素性を押さえておくと他手法との比較も読めるようになります。
| データセット | 訓練長 | テスト長 | 次元 $m$ | 異常率(%) | 内容 |
|---|---|---|---|---|---|
| SWaT | 496,800 | 449,919 | 51 | 11.98 | 実水処理プラントの縮小版。7日正常+4日攻撃シナリオ |
| WADI | 1,048,571 | 172,801 | 123 | 5.99 | 配水テストベッド(SWaTの拡張、センサ数2倍超) |
| SMD | 708,405 | 708,420 | 38 (×28台) | 4.16 | サーバー機28台のリソース使用率(5週間) |
| SMAP | 135,183 | 427,617 | 25 (×55体) | 13.13 | NASA衛星(土壌水分観測)のテレメトリ |
| MSL | 58,317 | 73,729 | 55 (×27体) | 10.72 | NASA火星探査機のセンサ・アクチュエータ |
| Orange | 2,781,000 | 5,081,000 | 33 | 33.72 | 社内・広告網の技術/業務指標(実用性評価) |
SWaT・WADIはサイバー物理システム(CPS)のセキュリティ研究用で、実際に攻撃を仕掛けたときのセンサー応答が異常としてラベルされています。SMD・SMAP・MSLはIT/宇宙機の運用監視系で、SMD/SMAP/MSLは複数の機体・エンティティの集合体です(各エンティティを個別に訓練・評価して平均します)。
USADのデータセット別ハイパーパラメータも公開されています。窓長 $K$・潜在次元 $m$ が、次元数や異常の長さに応じて調整されているのが分かります。
| データセット | 窓長 $K$ | エポック | 潜在次元 | ダウンサンプリング |
|---|---|---|---|---|
| SWaT | 12 | 70 | 40 | 5 |
| WADI | 10 | 70 | 100 | 5 |
| SMD | 5 | 250 | 38 | 5 |
| SMAP | 5 | 250 | 55 | 5 |
| MSL | 5 | 250 | 33 | 5 |
次元の大きいWADI(123次元)では潜在次元100、複数エンティティを個別学習するSMD/SMAP/MSLでは小さい窓($K=5$)とエポック250、というように設計されています。
では、これらのパラメータを変えると性能はどう動くのか。論文はSWaTで、ダウンサンプリング率・窓長 $K$・潜在次元 $Z$・訓練データの異常混入率の4つを振ってPrecision/Recall/F1を測りました。

論文 Fig.2 を参考に作図(数値は論文の実測値)。USADはarXiv版が無いため自作で再現した。
読み取れることは2つあります。第一に、A〜CのパラメータにUSADはほぼ不感です。ダウンサンプリング率を1〜50、窓長を5〜100、潜在次元を5〜100と桁で動かしても、F1は0.77〜0.79のごく狭い帯に収まります。チューニングに神経質にならずに済むのは、本番運用での大きな利点です(精度は $K=10$・潜在次元40・ダウンサンプリング5あたりがわずかに最良)。第二に、唯一はっきり効くのがD)訓練データの異常混入率です。混入5%まではPrecision 0.98台・F1 0.77を保ちますが、10%でPrecisionが0.91に落ち、30%では0.60まで崩れます。USADは「正常で学ぶ」前提なので、訓練期間に大きなインシデントを含めないことが性能維持の鍵だと、この図がはっきり示しています。
データの性質を押さえたうえで、肝心の検出性能を見ます。
サーベイ②:検出性能(F1)の比較
論文では、Isolation Forest(IF)・素のAE・LSTM-VAE・DAGMM・OmniAnomalyという代表的な教師なし手法とF1で比較しています。評価は先行研究にならいpoint-adjust(後述)を適用したF1です。太字が各列の最良値です。
| 手法 | SWaT | WADI | SMD | SMAP | MSL |
|---|---|---|---|---|---|
| Isolation Forest | 0.8311 | 0.6198 | 0.5866 | 0.4671 | 0.5984 |
| AE(素のオートエンコーダ) | 0.8233 | 0.3556 | 0.8280 | 0.7776 | 0.8792 |
| LSTM-VAE | 0.8051 | 0.3799 | 0.8083 | 0.7555 | 0.8537 |
| DAGMM | 0.7971 | 0.2094 | 0.7231 | 0.7124 | 0.8112 |
| OmniAnomaly | 0.8328 | 0.4174 | 0.9441 | 0.8054 | 0.8952 |
| USAD | 0.8460 | 0.4296 | 0.9382 | 0.8186 | 0.9109 |
USADはSWaT・SMAP・MSLで最良、SMDでOmniAnomalyに次ぐ2位です。全データセット平均でもトップで、論文は当時の最先端(OmniAnomaly)を平均で上回ったと報告しています。
| 手法 | 平均 P | 平均 R | 平均 F1 |
|---|---|---|---|
| AE | 0.77 | 0.76 | 0.73 |
| Isolation Forest | 0.64 | 0.68 | 0.62 |
| LSTM-VAE | 0.72 | 0.80 | 0.75 |
| DAGMM | 0.62 | 0.76 | 0.65 |
| OmniAnomaly | 0.73 | 0.95 | 0.78 |
| USAD | 0.84 | 0.80 | 0.79 |
注目したいのは、IFやDAGMMが時系列で軒並み低いことです。これらは観測間の時間依存を使わないため、系列データでは不利になります。USADは窓を入力にすることで時間的文脈を保持し、かつ敵対訓練でmild anomalyを増幅するので、再構成系(AE/LSTM-VAE)もOmniAnomalyも上回れた、という構図です。
ただし注意点があります。WADIでIsolation Forestが最良(0.62)なのは、point-adjustとWADIの異常が長く続く性質の相互作用による「見かけ」です。point-adjustは「異常区間の1点でも当てれば、その区間全体を正解扱い」するため、点単位で判定するIFが過大評価されやすい。WADIで全手法のF1が低いことからも、この設定の難しさが分かります。なおpoint-adjustなしで測ると、USADのSWaT F1は0.7917(適合率0.9851・再現率0.6618)で、こちらが素の実力に近い数字です。
point-adjustの読み方: 近年の研究では「point-adjustはスコアを大きく水増しし、ランダムスコアでも高いF1が出る」と批判されています。論文間でF1を比較するときは、同じ評価規約かを必ず確認してください。本記事のサーベイ表もすべてpoint-adjust基準で揃えています。
性能だけでなく「速さ」もUSADの売りでした。
サーベイ③:訓練速度とアブレーション
USADの設計動機の一つは、Orange社の「省エネ・大規模運用」要件です。最も近い性能のOmniAnomalyと、1エポックあたりの訓練時間(分)を比べた結果が次表です。
| データセット | OmniAnomaly | USAD | 高速化倍率 |
|---|---|---|---|
| SWaT | 13 | 0.06 | ×216 |
| WADI | 31 | 0.12 | ×258 |
| SMD | 87 | 0.06 | ×1331 |
| SMAP | 48 | 0.08 | ×581 |
| MSL | 11 | 0.03 | ×349 |
平均で約547倍高速です。USADはシンプルなMLPベースのAEで、確率的RNNや正規化フローを持つOmniAnomalyに比べて計算が軽いためです。同等の精度を桁違いに速く出せることが、本番運用での大きな利点になります。
では、2フェーズ訓練は本当に効いているのか。論文はSMD/SMAP/MSLで、(a)2フェーズ統合(USAD)、(b)フェーズ1のみ(=素のAE)、(c)フェーズ2のみ(=敵対のみ)の3条件でアブレーションを行いました。

論文 Fig.3 を参考に作図(数値は論文の実測値)。USADはarXiv版が無いため自作で再現した。
平均すると次のようにまとめられます。
- フェーズ2(敵対)を入れると、フェーズ1のみ(=素のAE)より F1で+5.88%
- フェーズ1(再構成)を入れると、フェーズ2のみより F1で+24.09%
図を見ると、その内訳がよく分かります。3データセットすべてで2フェーズ統合(青)が最高です。フェーズ1のみ(素のAE、橙)はそこそこ健闘しますが、敵対増幅がない分どれも統合版に届きません。最も劇的なのはフェーズ2のみ(敵対のみ、緑)のSMDで0.386まで崩落することです。再構成で重みを良い場所に置く下地なしにいきなり敵対だけを回すと、学習が安定せず大きく劣化するのです。一方SMAP/MSLでは敵対のみでも0.75〜0.80を保っており、データセットによって崩れ方が違う点も読み取れます。いずれにせよどちらのフェーズも欠かせない——再構成だけ(素のAE)ではmild anomalyを増幅できず、敵対だけでは土台が崩れる。両者の組み合わせがUSADの強さの源だと確認できます。
最後に、USADが発表から数年後の比較でもなお強いベースラインであることを示しておきます。Transformer型のTranAD(Tuli et al., VLDB 2022)のベンチマーク(全データで訓練、F1)では次のとおりです。
| 手法 | SWaT | SMAP | MSL | SMD |
|---|---|---|---|---|
| DAGMM | 0.8128 | 0.8888 | 0.8482 | 0.9491 |
| OmniAnomaly | 0.8131 | 0.8728 | 0.8765 | 0.9401 |
| USAD | 0.8143 | 0.8419 | 0.8822 | 0.9495 |
| TranAD | 0.8151 | 0.8915 | 0.9494 | 0.9605 |
新しいTransformer型に最高値は譲るものの、USADはSMDで首位、他でも上位に踏みとどまっています。軽量で速く、それでいて十分強い——だからこそ今も標準的な比較対象として使われ続けています。
数字を眺めたら、次は手を動かしましょう。合成データでUSADをスクラッチ実装して、これまでの主張を自分の目で確かめます。
Pythonでのスクラッチ実装
ここからは、PyTorchでUSADを実装し、相関を持つ合成多変量時系列(5変量)で再現実験します。確かめたいのは3点です。(1) 訓練が安定収束するか、(2) 異常スコアが本当に異常区間で跳ねるか、(3) 素のAEより mild anomaly を捉えられるか。
合成データの生成と窓化
2つの潜在因子が5チャネルを駆動する正常系列を作り、テスト系列には正常に近い異常(軽い振幅変化・相関破壊・センサ固着・小さな点異常)を埋め込みます。これがUSADの狙うタイプの異常です。
import numpy as np
import torch
import torch.nn as nn
from sklearn.metrics import roc_auc_score, average_precision_score, precision_recall_curve
np.random.seed(7); torch.manual_seed(7)
m, K, LAT, EPOCHS = 5, 12, 16, 100 # 変量数・窓長・潜在次元・エポック
def gen_series(T, anomalies=False, seed=0):
rng = np.random.default_rng(seed); t = np.arange(T)
f1 = np.sin(0.05*t); f2 = np.sin(0.017*t + 1.0) # 2つの潜在因子
base = np.vstack([f1 + 0.3*f2, 0.8*f1 - 0.2*f2, 0.5*f1 + 0.6*f2,
np.sin(0.11*t), 0.4*f2 + 0.3*np.sin(0.07*t)])
X = base + 0.06*rng.standard_normal((m, T)); label = np.zeros(T, dtype=int)
if anomalies: # 正常に近いmild anomaly中心
for s0 in [200, 700]: # 軽い振幅変化(文脈異常)
ln = 24; X[:, s0:s0+ln] *= 1.22; label[s0:s0+ln] = 1
for s0, ch in [(360, 2), (880, 1)]: # 相関破壊(1ch符号反転)
ln = 22; X[ch, s0:s0+ln] = -X[ch, s0:s0+ln]; label[s0:s0+ln] = 1
s0 = 1050; ln = 28; X[:, s0:s0+ln] = X[:, s0:s0+1]; label[s0:s0+ln] = 1 # 固着
for c0 in [520, 1180]: # 小さな点異常
ch = rng.integers(0, m); X[ch, c0:c0+2] += rng.choice([-1, 1])*0.7
label[c0:c0+2] = 1
return X.T, label
Xtr, _ = gen_series(2500, anomalies=False, seed=11)
Xte, lab_te = gen_series(1300, anomalies=True, seed=29)
mu, sd = Xtr.mean(0), Xtr.std(0) + 1e-8 # 訓練統計で正規化
Xtr, Xte = (Xtr - mu)/sd, (Xte - mu)/sd
def windows(X, lab=None):
T = len(X); W = np.stack([X[i:i+K].reshape(-1) for i in range(T-K+1)])
if lab is None: return W.astype(np.float32)
y = np.array([lab[i:i+K].max() for i in range(T-K+1)]) # 窓内に異常があれば1
return W.astype(np.float32), y
Wtr = windows(Xtr); Wte, yte = windows(Xte, lab_te)
INP = K*m
Wtr_t, Wte_t = torch.tensor(Wtr), torch.tensor(Wte)
print("入力次元 =", INP, " 訓練窓数 =", len(Wtr), " 異常率 =", round(yte.mean(), 3))
ラベルは「窓内に1点でも異常があれば異常窓」とします。これで各窓を入力次元 $K \times m = 60$ のベクトルとして扱えます。異常率は約0.16で、現実的な不均衡データになっています。
モデル:共有エンコーダ + 2デコーダ
論文の構成にならい、エンコーダは入力を $1/2 \to 1/4 \to$ 潜在へ絞り、デコーダは対称に広げて最後にSigmoidをかけます。
class Enc(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(INP, INP//2), nn.ReLU(),
nn.Linear(INP//2, INP//4), nn.ReLU(),
nn.Linear(INP//4, LAT), nn.ReLU())
def forward(self, x): return self.net(x)
class Dec(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(LAT, INP//4), nn.ReLU(),
nn.Linear(INP//4, INP//2), nn.ReLU(),
nn.Linear(INP//2, INP), nn.Sigmoid())
def forward(self, z): return self.net(z)
エンコーダ $E$ は2つのオートエンコーダで共有し、デコーダ $D_1, D_2$ は同じ構造で別パラメータにします。Sigmoid出力に合わせ、入力は後で $[0,1]$ にスケールします。
2フェーズ敵対訓練
式(7)(8)をそのまま実装します。エポック $n$ で $1/n$ と $1-1/n$ の重みを動かすのがポイントです。比較のため、mode で「2フェーズ統合 / フェーズ1のみ(素のAE) / フェーズ2のみ」を切り替えられるようにします。
def train_usad(mode="combined", epochs=EPOCHS, seed=7):
torch.manual_seed(seed)
E, D1, D2 = Enc(), Dec(), Dec()
opt1 = torch.optim.Adam(list(E.parameters()) + list(D1.parameters()), lr=1e-3)
opt2 = torch.optim.Adam(list(E.parameters()) + list(D2.parameters()), lr=1e-3)
lo, hi = Wtr_t.min(0).values, Wtr_t.max(0).values # 入力を[0,1]へ
nrm = lambda x: (x - lo)/(hi - lo + 1e-8)
Xn = nrm(Wtr_t); hist = {"L1": [], "L2": []}
for ep in range(1, epochs+1):
perm = torch.randperm(len(Xn)); e1s, e2s = [], []
for i in range(0, len(Xn), 64):
w = Xn[perm[i:i+64]]
z = E(w); w1 = D1(z); w2 = D2(z); w2_ = D2(E(w1))
rec1 = ((w-w1)**2).mean(); rec2 = ((w-w2)**2).mean(); adv = ((w-w2_)**2).mean()
if mode == "combined": # 式(7)(8)
L1 = (1/ep)*rec1 + (1-1/ep)*adv
L2 = (1/ep)*rec2 - (1-1/ep)*adv
elif mode == "phase1": # 再構成のみ=素のAE
L1, L2 = rec1, rec2
elif mode == "phase2": # 敵対のみ
L1, L2 = adv, -adv
opt1.zero_grad(); opt2.zero_grad()
L1.backward(retain_graph=True); L2.backward() # 共有Eを両方で更新
opt1.step(); opt2.step()
e1s.append(float(L1)); e2s.append(float(L2))
hist["L1"].append(np.mean(e1s)); hist["L2"].append(np.mean(e2s))
return E, D1, D2, nrm, hist
E, D1, D2, nrm, hist = train_usad("combined")
注意点として、エンコーダ $E$ を2つのオプティマイザが共有するため、両方の損失を逆伝播してからまとめてパラメータ更新します(片方を先に更新するとグラフが壊れます)。訓練損失の推移を見てみましょう。
import matplotlib.pyplot as plt
ep = np.arange(1, EPOCHS+1)
plt.figure(figsize=(8, 4))
plt.plot(ep, hist["L1"], label="L_AE1"); plt.plot(ep, hist["L2"], label="L_AE2")
plt.axhline(0, ls="--", c="gray"); plt.xlabel("エポック"); plt.ylabel("損失")
plt.legend(); plt.title("USADの訓練損失"); plt.show()

訓練損失は崩壊せず滑らかに収束します。$L_{AE_2}$ は敵対項が負で寄与するため負値で安定し、GANにありがちな発振やモード崩壊は起きていません。「敵対的なのに安定」というUSADの主張が、ここに表れています。
異常スコアと検出
式(9)に従い、$\alpha=\beta=0.5$ で異常スコアを計算します。
def scores(E, D1, D2, nrm, Wt, alpha=0.5, beta=0.5):
with torch.no_grad():
w = nrm(Wt); w1 = D1(E(w)); w2_ = D2(E(w1))
s1 = ((w-w1)**2).mean(1).numpy() # 再構成誤差
s2 = ((w-w2_)**2).mean(1).numpy() # 敵対経路の誤差
return alpha*s1 + beta*s2, s1, s2
def best_f1(y, sc):
p, r, _ = precision_recall_curve(y, sc)
f1 = 2*p*r/(p+r+1e-12); return float(np.nanmax(f1))
sc_usad, s1, s2 = scores(E, D1, D2, nrm, Wte_t, 0.5, 0.5)
print("USAD AUC =", round(roc_auc_score(yte, sc_usad), 3),
" 最良F1 =", round(best_f1(yte, sc_usad), 3))
実行すると USAD AUC = 0.73 最良F1 = 0.601 が得られます。スコアを時系列で可視化すると、異常区間でしっかり跳ねているのが分かります。
plt.figure(figsize=(11, 4))
plt.plot(sc_usad, lw=1, label="USAD 異常スコア")
in_a = False
for i, v in enumerate(yte):
if v and not in_a: s0 = i; in_a = True
if (not v or i == len(yte)-1) and in_a: plt.axvspan(s0, i, color="red", alpha=0.15); in_a = False
plt.legend(); plt.xlabel("窓インデックス"); plt.ylabel("異常スコア"); plt.show()

赤帯が真の異常区間です。振幅変化・相関破壊・固着・点異常のいずれでもスコアが上昇し、適切な閾値で検出できます。次に、正常窓と異常窓で再構成を比べます。

正常窓では $AE_1$ も敵対経路 $AE_2(AE_1)$ もよく一致しますが、異常窓では敵対経路の誤差が大きく跳ね上がります。これが「異常の再構成誤差を増幅する」効果の正体です。
素のAEとの比較、そしてアブレーション
最後に、USADが本当に素のAEより mild anomaly に強いか、そして両フェーズが効いているかを確かめます。
# 素のAE(フェーズ1のみ)と敵対のみ(フェーズ2のみ)を訓練
Ea, D1a, D2a, na, _ = train_usad("phase1")
sc_ae, _, _ = scores(Ea, D1a, D2a, na, Wte_t, 1.0, 0.0) # 純粋な再構成スコア
Ep, D1p, D2p, npn, _ = train_usad("phase2")
sc_p2, _, _ = scores(Ep, D1p, D2p, npn, Wte_t, 0.5, 0.5)
print("USAD AUC=%.3f AP=%.3f F1=%.3f" % (
roc_auc_score(yte, sc_usad), average_precision_score(yte, sc_usad), best_f1(yte, sc_usad)))
print("素のAE AUC=%.3f AP=%.3f F1=%.3f" % (
roc_auc_score(yte, sc_ae), average_precision_score(yte, sc_ae), best_f1(yte, sc_ae)))
print("敵対のみ F1=%.3f" % best_f1(yte, sc_p2))
結果は次のとおりです(乱数seed固定)。
| 構成 | ROC-AUC | PR-AUC(AP) | 最良F1 |
|---|---|---|---|
| USAD(2フェーズ統合) | 0.730 | 0.639 | 0.601 |
| 素のAE(フェーズ1のみ) | 0.720 | 0.561 | 0.517 |
| 敵対のみ(フェーズ2のみ) | — | — | 0.323 |

USADは素のAEをROC-AUC・PR-AUC・F1のすべてで上回りました。特にPR-AUC(0.639 vs 0.561)の差が大きく、不均衡データでの実用性能で効いています。アブレーションを棒グラフでまとめます。

2フェーズ統合(0.601)が最良で、フェーズ1のみ(0.517)・フェーズ2のみ(0.323)はいずれも劣ります。論文と同じ結論——どちらのフェーズも欠かせないことが、自前の合成データでも再現できました。なお、ここで見た改善幅は論文のSWaT(+2.3%)などと同様に控えめですが、これはmild anomaly検出という難しい設定での本質的な伸びしろを反映したものです。
実務での使い方と限界
USADを現場で使うときの勘所をまとめます。
- 訓練データは「ほぼ正常」が前提: USADは正常データで学びます。論文の検証では、訓練データに5%程度のノイズ(異常混入)までは頑健ですが、10%を超えると適合率が落ち始め(誤報が増え)、30%では大きく劣化します。訓練期間は大きなインシデントを含まない期間を選ぶのが鉄則です。
- 窓長 $K$ は検出速度と検出対象のトレードオフ: 小さい $K$ は短い異常を速く検出、大きい $K$ は長い異常を捉える。SWaTでは $K=10$ 前後が良好でした。
- 潜在次元は中庸に: 小さすぎると情報が落ちて再構成不能、大きすぎると訓練データを丸暗記して性能低下。論文のFig.2でも中間レンジが安定でした。
- $\alpha,\beta$ で運用後に感度調整: 再訓練なしで誤報・取りこぼしのバランスを変えられるのは、本番運用での大きな利点です。
- 評価規約に注意: point-adjustはF1を大きく押し上げます。手法間比較や自分の数字を報告するときは、同じ評価規約で揃えること。可能ならpoint-adjustなしの数字も併記しましょう。
弱点としては、(1) MLPベースで長期依存や複雑な空間相関の表現力はTransformer型(TranAD等)に劣ること、(2) ダウンサンプリングや窓化のハイパーパラメータ調整が必要なこと、が挙げられます。それでも「軽量・高速・安定・調整しやすい」という総合力で、USADは今も最初に試す価値のある標準的ベースラインです。
まとめ
本記事では、USAD(UnSupervised Anomaly Detection on multivariate time series)を理論から実装、論文のサーベイまで通して解説しました。
- 核心: 共有エンコーダ+2デコーダを「敵対的」に訓練し、AEの安定性を保ったまま、わずかな異常の再構成誤差を増幅して検出する
- データの入れ方: 多変量時系列を長さ $K$ の窓に切り、$K \times m$ を平坦化。正規化と中央値ダウンサンプリングが前処理の定番
- 2フェーズ訓練: フェーズ1で再構成、フェーズ2で敵対。$1/n$ のスケジュールで滑らかに移行し、GANと違い崩壊しない
- 推論: スコア $\mathcal{A} = \alpha\|\hat{W}-AE_1\| + \beta\|\hat{W}-AE_2(AE_1)\|$。$\alpha,\beta$ で再訓練なしに感度を調整
- 実験: SWaT/SMAP/MSLで最良F1、平均でも当時のSOTAを上回り、訓練は平均547倍高速。アブレーションで両フェーズの必要性を確認
- 再現: 合成多変量時系列でも、USADが素のAEをAUC・AP・F1で上回り、両フェーズが効くことを確認
次のステップとして、以下の記事も参考にしてください。



参考文献
- J. Audibert, P. Michiardi, F. Guyard, S. Marti, M. A. Zuluaga. “USAD: UnSupervised Anomaly Detection on Multivariate Time Series.” Proc. 26th ACM SIGKDD (KDD ’20), 2020, pp. 3395–3404.
- S. Tuli, G. Casale, N. R. Jennings. “TranAD: Deep Transformer Networks for Anomaly Detection in Multivariate Time Series Data.” Proc. VLDB Endowment, 15(6), 2022.
- Y. Su et al. “Robust Anomaly Detection for Multivariate Time Series through Stochastic Recurrent Neural Network (OmniAnomaly).” KDD ’19, 2019.
さらに深く ― 関連手法とベンチマーク
USADは多くの後発手法でベースラインとして比較されます。手法の系譜・評価・ベンチマークの全体像は以下から辿れます。