UCR Anomaly Archive を実波形で読む — 「正しく」評価できる時系列異常検知ベンチマーク

時系列異常検知の論文は、発表のたびに「既存手法を大きく上回った」と主張します。ところが、その「上回った」がベンチマークの欠陥に由来していたとしたらどうでしょう。実は2021年、Eamonn Keogh らは多くの定番ベンチマーク(Yahoo・NAB・NASA など)を 「設計が壊れている(flawed)」 と痛烈に批判し、その代わりとなる UCR Anomaly Archive を公開しました。

このアーカイブは一風変わっています。1本の系列に、異常をちょうど1個だけ埋め込む のです。アルゴリズムの仕事は「最も異常な場所を1点指す」こと。当たったか外れたかで評価する――ただそれだけ。この潔さが、評価の水増しを生まない頑健なベンチマークを成立させています。

UCR Anomaly Archive を理解すると、次の2つがはっきり見えてきます。

  • 「良い異常検知ベンチマーク」とは何か — 異常密度・ラベル品質・評価指標のどこに落とし穴があるのか
  • 手法を比べるときの目の付け所 — なぜ「単純な最近傍距離」が多くの深層モデルに匹敵してしまうのか

本記事では、ローカルに展開した実データ(TimeEval 版の名称は KDD-TSAD、250系列)から 本物の波形 を描きながら、データ構造・統計・評価ルールを順に読み解きます。最後に最近傍距離ベースラインを Python で実装し、「単純な手法でもちゃんと異常を指す」ことを実データで確かめます。

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

UCR Anomaly Archive とは

まず全体像をイメージしましょう。UCR Anomaly Archive は、1次元の長い時系列が250本集まったデータセットです。各系列は2つのファイルに分かれています。

  • train:正常だけが含まれる前半の区間。アルゴリズムは「正常とはどういうものか」をここで学びます(ラベルは使わない=教師なし)。
  • test:大半は正常ですが、どこか1箇所にだけ異常が埋め込まれている 後半の区間。

UCR Anomaly Archiveの構造:train正常で学び、testの異常1個を当てる

この図が UCR の設計思想を凝縮しています。学習に使ってよいのは「正常しかない」と保証された区間だけ。そしてテスト側に現れる異常はちょうど1区間。アルゴリズムは「ここが一番おかしい」と1点を指し、それが埋め込まれた区間に入っていれば正解です。

なぜこんなにシンプルにするのでしょうか。それは、評価のごまかしようがない からです。異常が何十個もあって、しかも全体の数十%を占めるようなデータでは、「とりあえずたくさん異常と言っておけば当たる」戦略が通ってしまいます。異常が1個だけなら、その手は使えません。狙い撃ちできた手法だけが報われます。

ここで自然な疑問が湧きます。「正常だけで学ぶ」とは具体的にどういう状況なのでしょうか。実際の波形で確かめてみましょう。

データの構造:正常で学び、異常はテストに一度だけ

実データの1系列(生体信号、血圧系)について、train 区間と test 区間をそのままつなげて描いてみます。

train正常区間とtest区間を連結。異常はtest側に一度だけ現れる

緑が train(正常のみ)、青が test です。波形はずっと似たリズムを刻んでいますが、test の途中(赤い帯)でリズムが乱れています。これが埋め込まれた異常です。学習段階ではこの赤い区間を一切見られません。つまり「正常のパターンを覚え、初めて見る形を異常として弾く」という、教師なし異常検知の素直な設定になっています。

この構造は SMAP/MSL や SMD のような従来の多変量ベンチマークと違い、1次元 である点が重要です。次元が1つなら、波形をそのまま目で見て「どこが異常か」を人間が確認できます。ベンチマークの透明性が高いのです。

では、その「異常」は波形のうえでどう見えるのか。最も馴染みのある心電図(ECG)で詳しく見ていきましょう。

実波形を見る:心電図に埋め込まれた異常

下は ECG 系列の全体像です。3万点にわたって規則正しい拍動が続き、赤い帯のところだけ様子が違います。

ECG系列の全体と、埋め込まれた異常区間

全体を引いて見ると、異常区間はごく短い一瞬にすぎないことがわかります。3万点中わずか300点。「干し草の山から1本の針を探す」 という比喩がぴったりです。これだけ正常が多いと、ほんの少しでも誤検知の癖があるアルゴリズムは、針より先に山のあちこちを「異常だ」と言ってしまいます。

異常区間を拡大すると、何が起きているかがはっきりします。

ECG異常区間の拡大:拍動のリズムが乱れている

正常な拍動(青)は一定の間隔で尖ったピークを繰り返します。ところが赤の区間では、ピークの間隔と高さのパターンが崩れ、いつもの「形」から逸脱しています。重要なのは、個々の点の値そのものは正常範囲に収まっていることです。つまり「1点だけ見れば正常、でも前後の文脈の中では異常」という、時系列ならではの異常になっています。単純な閾値(値が大きすぎる/小さすぎる)では捕まえられません。

ECG は分かりやすい例ですが、UCR の真価は 分野の多様性 にあります。次はそれを一望してみましょう。

多分野ギャラリー:同じ「異常1個当て」でも見た目はまるで違う

UCR の250系列は、医療・電力・気象・行動計測・昆虫生理など、まったく異なる分野から集められています。代表的な6系列を並べてみます。

UCR多分野ギャラリー:ECG/電力需要/歩行/気温/生体信号/昆虫EPG

赤い帯がそれぞれの埋め込み異常です。6枚を見比べると、「異常」という言葉が分野ごとにまったく違う見た目をしている ことがわかります。電力需要では日周期のパターンが一瞬乱れ、歩行(加速度)では歩調の繰り返しが途切れ、気温では鋭いスパイクが立ち、昆虫の摂食電位(EPG)では細かな振動の様子が変わります。

この多様性こそが UCR をベンチマークとして強くしています。特定の分野の癖に過適合したアルゴリズムは、ここでは平均点を取れません。本当に「正常からの逸脱」を一般的に捉えられる手法だけが、250系列を通して高い成績を出せます。1つや2つのデータセットでチャンピオンを名乗ることが、いかに当てにならないかが分かります。

見た目の違いをもう一段抽象化すると、異常は大きく3つの「型」に整理できます。

異常の型:点・形状・リズム

異常の型:点的なスパイク/形状の崩れ/リズムの乱れ

UCR に含まれる異常は、おおよそ次の3つに分類できます。

  1. 点的な異常:気温の例のように、1点(あるいは数点)が周囲から鋭く飛び出す。値の大きさそのものが手がかりになる、最も素直な異常。
  2. 形状の異常:ECG の例のように、各点の値は正常範囲でも、部分波形の「形」が正常パターンと一致しない。文脈を見ないと検出できない。
  3. リズムの異常:歩行の例のように、繰り返しの 周期やタイミングが乱れる。周波数領域や自己相関の崩れとして現れる。

この3類型を意識すると、なぜ「単一の万能手法」が存在しにくいかが見えてきます。点的な異常は単純な統計量(zスコア)で十分ですが、形状やリズムの異常には部分系列の比較や周期構造のモデル化が要ります。UCR は3つの型をまんべんなく含むことで、手法の総合力を試している のです。

ここまで個々の波形を見てきました。では250系列全体としては、どんな「素性」を持つデータの集まりなのでしょうか。統計で俯瞰しましょう。

250系列の素性:長く・異常はごくわずか

各系列のメタデータを集計すると、UCR の設計方針が数字に表れます。

250系列の統計:系列長/異常割合/異常区間長の分布

3つのヒストグラムから読み取れることは明快です。

  • 系列長は長い:中央値で約3万点。短いもので数千点、長いものは90万点に達します。「正常を十分に観測してから異常が来る」という現実的な設定です。
  • 異常の割合は極端に小さい:中央値で わずか0.3%。最大でも5%程度。これは「異常は稀である」という現実を反映しており、数十%が異常という旧来ベンチとは対照的です。
  • 異常区間は短い:多くが100点前後の短い区間。長く間延びした異常で「当てやすく」していません。

この「異常0.3%」という数字が、UCR の評価がなぜ厳しく、なぜ信頼できるのかの核心です。次に、それと表裏一体の 「壊れたベンチマーク」批判 を整理しましょう。

なぜ旧来ベンチでは不十分だったのか

UCR が生まれた背景には、Wu と Keogh による有名な批判論文があります(Renjie Wu & Eamonn J. Keogh, Current Time Series Anomaly Detection Benchmarks are Flawed: A Critical Evaluation of Anomaly Detection Algorithms, IEEE TKDE 2022)。彼らは当時主流だったベンチマークに、次のような欠陥を指摘しました。

旧来ベンチの欠陥とUCRの設計思想の対応

  • 異常密度が非現実的に高い:データの数十%が異常というデータセットすら存在し、「異常は稀」という前提が崩れていた。
  • ラベルの誤り・重複:人手アノテーションが粗く、明らかな誤ラベルや重複が混入していた。
  • 自明すぎる異常:「最初の数百点を見るだけ」「一番大きい値を指すだけ」で当たってしまう、学習を要しない異常が多かった。
  • 甘い評価指標:後述する point-adjust により、ランダムな検出器ですら高いスコアを得られた。

UCR はこれらを一つひとつ潰す設計になっています(図の右側)。異常は1区間だけに絞り、手作業で丁寧に確定し、正常で学んだあとに初めて異常が出るようにし、評価は素直な「1点当て」にしました。ベンチマークの欠陥は、そのまま手法の見かけ上の優劣を歪めます。だからこそ、何で測るかは何を作るかと同じくらい重要なのです。

最後の「甘い評価指標」は特に根が深いので、節を改めて見ておきましょう。

評価の罠:異常1個 × point-adjust

時系列異常検知で長く使われてきた point-adjust(PA) という評価の調整があります。発想は「異常区間のどこか1点でも検出できたら、その区間全体を検出成功とみなす」というもの。一見もっともらしいのですが、これが評価を大きく水増しします。

point-adjustによる評価水増し:ランダム検出でも区間全体が正解に膨らむ

図の真ん中は、まったくのランダムに点を「異常」と打っただけの検出器です。本来ならほぼ無意味ですが、たまたま異常区間(上段)に1点でも入ると、point-adjust は 区間全体を検出成功に塗り替えます(下段)。異常区間が長いほど、この「ご褒美」は大きくなります。結果として、ランダム検出器でも高い適合率・再現率・F値が出てしまうのです。これは Kim ら(AAAI 2022)が定量的に示した、よく知られた落とし穴です。

UCR はこの問題を構造から回避しています。異常を1区間に絞り、アルゴリズムには「最も異常な1点」だけを出させる。その1点が区間内なら正解、外なら不正解。閾値調整も point-adjust も介在する余地がありません。評価指標の設計に踏み込みたい方は、区間ベースで頑健に測る VUS(Volume Under the Surface) の記事もあわせてどうぞ。

理屈は分かりました。では実際に手を動かして、「単純な手法でもちゃんと異常を指せるのか」を確かめてみましょう。

Pythonで触る:データの読み込み

まず1系列を読み込みます。train(正常)と test(value と is_anomaly の2列)を取り出すだけです。

import numpy as np
import os

DATA = os.path.expanduser(
    "~/workspace/dataset/timeeval-datasets/univariate/KDD-TSAD")

def load_ucr(base):
    """base 例: 011_UCR_Anomaly_DISTORTEDECG1"""
    tr = np.loadtxt(f"{DATA}/{base}.train.csv", delimiter=",", skiprows=1)
    te = np.loadtxt(f"{DATA}/{base}.test.csv", delimiter=",", skiprows=1)
    train_value = tr[:, 1]            # 正常区間の値
    test_value  = te[:, 1]            # テスト区間の値
    is_anomaly  = te[:, 2].astype(int)  # 0/1 ラベル(評価用、学習には使わない)
    return train_value, test_value, is_anomaly

tr, te, lab = load_ucr("011_UCR_Anomaly_DISTORTEDECG1")
idx = np.where(lab == 1)[0]
print("train長:", len(tr), " test長:", len(te))
print("異常区間:", (idx.min(), idx.max()), " 異常割合: %.3f%%" % (100*lab.mean()))

出力は次のようになります。

train長: 10000  test長: 30000
異常区間: (11800, 12099)  異常割合: 1.000%

train が1万点、test が3万点。異常区間は test の中央付近にわずか300点だけ。ラベル(is_anomaly)はあくまで採点用 で、アルゴリズムには渡しません。データの形が掴めたところで、検出器を組み立てます。

Pythonで触る:最近傍距離ベースライン

Wu と Keogh が繰り返し強調したのは、「単純な最近傍距離(discord)が、多くの凝った深層モデルに匹敵する」 という事実でした。その単純な手法を自分で実装してみます。アイデアはこうです。

  1. 正常な train を、長さ $w$ の短い窓(部分系列)に細かく刻む。これが「正常の見本帳」になります。
  2. test も同じ長さの窓に刻む。
  3. test の各窓について、見本帳の中で一番似ている窓との距離 を測る。正常な箇所なら似た見本が必ずあるので距離は小さく、異常な箇所なら似た見本が無いので距離が大きくなります。

窓どうしの比較は、振幅や基線の違いに惑わされないよう zスコア正規化(平均0・分散1にそろえる)してから行います。

def windows(x, w, stride):
    starts = np.arange(0, len(x) - w, stride)
    W = np.stack([x[i:i+w] for i in starts])
    mu = W.mean(1, keepdims=True)
    sd = W.std(1, keepdims=True) + 1e-8
    return (W - mu) / sd, starts          # z正規化した窓と開始位置

def nn_distance_score(tr, te, w=128, stride=4):
    TR, _      = windows(tr, w, stride)   # 正常の見本帳
    TE, starts = windows(te, w, stride)   # 評価対象の窓
    rng = np.random.default_rng(0)
    if len(TR) > 1500:                    # 見本帳を間引いて計算量を抑える
        TR = TR[rng.choice(len(TR), 1500, replace=False)]
    scores = np.empty(len(TE))
    for i in range(len(TE)):              # 各窓 → 見本帳への最近傍距離
        d = np.sqrt(((TR - TE[i]) ** 2).sum(1))
        scores[i] = d.min()
    # 窓スコアを時刻ごとに展開(重なりは平均)
    full = np.zeros(len(te)); cnt = np.zeros(len(te))
    for s, i in zip(scores, starts):
        full[i:i+w] += s; cnt[i:i+w] += 1
    return full / np.maximum(cnt, 1)

score = nn_distance_score(tr, te, w=128, stride=4)
peak = int(np.argmax(score))
hit = idx.min() <= peak <= idx.max()
print("スコア最大点:", peak, " 真の異常区間内か:", hit)

実行すると、次のように スコアが最大になる点が、埋め込まれた異常区間の中 に収まります。

スコア最大点: 11932  真の異常区間内か: True

つまり、深層学習を一切使わない素朴な距離計算だけで、UCR の「1点当て」に正解できたわけです。スコアの波形を可視化してみましょう。

import matplotlib.pyplot as plt

fig, ax = plt.subplots(2, 1, figsize=(10, 5), sharex=True)
t = np.arange(len(te))
ax[0].plot(t, te, lw=0.5)
ax[0].axvspan(idx.min(), idx.max(), color="red", alpha=0.25)
ax[0].set_ylabel("値")
ax[1].plot(t, score, lw=0.8, color="purple")
ax[1].axvspan(idx.min(), idx.max(), color="red", alpha=0.25)
ax[1].plot(peak, score[peak], "v", color="red", ms=10)
ax[1].set_ylabel("異常スコア"); ax[1].set_xlabel("時刻インデックス")
plt.tight_layout(); plt.show()

最近傍距離スコアが異常区間で最大になる

下段のスコアを見ると、異常区間(赤帯)でスコアがはっきり跳ね上がり、全体の最大点(赤い三角)がそこに来ています。同時に、別の場所にも小さな山がいくつか見えます。これらは「正常だがやや珍しい」拍動で、もし「スコアがしきい値を超えたら異常」という運用をすると誤検知になりかねません。だからこそ UCR は「最大の1点だけを採点する」という運用を採り、誤検知の数え方をめぐる不毛な争いを避けているのです。

この実験から得られる教訓は2つあります。第一に、単純な最近傍距離は強力なベースライン であり、新しい手法はこれを明確に上回って初めて意味を持つということ。第二に、UCR の「1点当て」評価は、こうした単純手法と複雑手法を 同じ土俵で公平に比較できる ということです。新手法を提案するときは、まずこのベースラインに勝てるかを確認するのが筋といえます。

まとめ

本記事では、UCR Anomaly Archive(KDD-TSAD, 250系列)を実波形つきで読み解きました。

  • 構造:1次元の長い時系列に、異常をちょうど1区間だけ埋め込む。train は正常のみで、教師なしに正常を学ぶ。
  • 多様性:ECG・電力・歩行・気温・生体信号・昆虫EPGなど多分野にまたがり、点・形状・リズムの3型の異常を含む。特定分野への過適合を許さない。
  • 素性:系列長は中央値3万点、異常割合は中央値0.3%。「異常は稀」という現実を忠実に再現。
  • 設計思想:旧来ベンチの欠陥(高すぎる異常密度・誤ラベル・自明な異常・point-adjustの水増し)を、1区間・丁寧なラベル・1点当て評価で克服。
  • ベースライン:最近傍距離という素朴な手法でも異常を正しく指せる。新手法はこれを超えてこそ価値がある。

ベンチマークは、手法を測る「ものさし」です。ものさしが歪んでいれば、いくら精緻な手法を作っても評価は信頼できません。UCR Anomaly Archive は、その歪みを正すために生まれた現代の標準です。異常検知に取り組むなら、まず自分の手法をここで試してみることをおすすめします。

次のステップとして、以下の記事も参考にしてください。