DeepOD入門: ディープラーニングによる異常検知の実践

システム運用や製造現場のデータ分析において、異常検知(Anomaly Detection:通常とは異なるデータを見つけ出す手法)は欠かせない技術です。ですが、データが複雑になるにつれて、従来の統計的な手法では十分な精度を出すのが難しくなっています。そこで強力な選択肢となるのが、深層学習(Deep Learning)の活用です。

DeepODは、深層学習を用いた異常検知を非常に簡単に実行できるオープンソースのPythonライブラリです。scikit-learnPyODと同様のAPIスタイルを採用しているため、様々な最新アルゴリズムをわずか数行のコードで実装できるのが大きな特徴です。

現在、表形式データ(Tabular data)と時系列データ(Time-series data)の両方に対応しており、27種類もの高度なモデルをサポートしています。今回は、異常検知の基礎概念からDeepODの実践的な使い方について解説します。

1. 異常検知の基礎概念

異常検知(Anomaly Detection)や外れ値検知(Outlier Detection)は、データセット全体の中から、大部分のデータとは異なる振る舞いやパターンを持つデータを見つけ出す技術です。DeepODはこの両方をターゲットとしており、実務で直面する様々な課題に対応できるよう設計されています。

学習のアプローチには、大きく分けて以下の2つのパラダイムがあります。

  • 教師なし学習(Unsupervised): 異常ラベルが一切ない状態でデータの構造を学習し、そこから外れたものを異常とみなします。
  • 弱教師あり学習(Weakly-supervised): 少量の「既知の異常ラベル」と、大量の「ラベルなしデータ」を併用して学習を実施します。DeepODでは、既知の異常を「1」、それ以外を「0」として学習を進めることで、より高精度な検知を目指せます。

また、対象とする「データの種類」によって、最適なディープラーニングの構造も変わります。

  • 表形式データ(Tabular data): 各サンプルが独立した特徴を持つデータです。DeepSVDDDIFなどのアルゴリズムが効果を発揮します。
  • 時系列データ(Time-series data): 時間の流れに伴って変化するデータです。データの順序や周期性を捉える必要があるため、LSTMTransformerTCN(Temporal Convolutional Network)といった時間的な特徴を扱えるネットワーク構造が活用されます。

どの手法を選んでも、共通してデータごとに「異常スコア(Anomaly Score)」が算出されます。このスコアが高いほど、そのデータは異常である可能性が高いと判断される仕組みです。

2. DeepODの概要

DeepODは、深層学習ベースの異常検知・外れ値検知に特化したオープンソースのPythonライブラリです。様々な最新アルゴリズムを、scikit-learnPyODと同じ感覚で実装できる点が大きなメリットです。

主な特徴として、以下の4点が挙げられます。

  • 27種類の最新モデルを統合: 再構成ベース、表現学習ベース、自己教師あり学習ベースなど、最新の最先端(SOTA)モデルが実装されています。
  • 統一されたAPI: すべてのモデルで fitpredictdecision_function といった共通のメソッドが使用可能です。
  • 柔軟なネットワーク構造: 時系列データに対し、TransformerTCNLSTMGRUなどをプラグインとして自由に入れ替えて試すことができます。
  • 包括的なテストベッド: 標準的なベンチマークデータセットを用いて、モデルのテストや精度評価を直接実施できる機能を備えています。

表形式データへの適用

ModelYearTypePaper
Deep SVDD2018unsupervisedDeep One-Class Classification
REPEN2018unsupervisedLearning Representations of Ultrahigh-dimensional Data for Random Distance-based Outlier Detection
RDP2020unsupervisedUnsupervised Representation Learning by Predicting Random Distances
RCA2021unsupervisedRCA: A Deep Collaborative Autoencoder Approach for Anomaly Detection
GOAD2020unsupervisedClassification-Based Anomaly Detection for General Data
NeuTraL2021unsupervisedNeural Transformation Learning for Deep Anomaly Detection Beyond Images
ICL2022unsupervisedAnomaly Detection for Tabular Data with Internal Contrastive Learning
DIF2023unsupervisedDeep Isolation Forest for Anomaly Detection
SLAD2023unsupervisedFascinating Supervisory Signals and Where to Find Them: Deep Anomaly Detection with Scale Learning
DevNet2019weakly-supervisedDeep Anomaly Detection with Deviation Networks
PReNet2023weakly-supervisedDeep Weakly-supervised Anomaly Detection
Deep SAD2020weakly-supervisedDeep Semi-Supervised Anomaly Detection
FeaWAD2021weakly-supervisedFeature Encoding with AutoEncoders for Weakly-supervised Anomaly Detection
RoSAS2023weakly-supervisedRoSAS: Deep semi-supervised anomaly detection with contamination-resilient continuous supervision

時系列データへの適用

ModelYearTypePaper
DCdetector2023unsupervisedDCdetector: Dual Attention Contrastive Representation Learning for Time Series Anomaly Detection
TimesNet2023unsupervisedTIMESNET: Temporal 2D-Variation Modeling for General Time Series Analysis
AnomalyTransformer2022unsupervisedAnomaly Transformer: Time Series Anomaly Detection with Association Discrepancy
NCAD2022unsupervisedNeural Contextual Anomaly Detection for Time Series
TranAD2022unsupervisedTranAD: Deep Transformer Networks for Anomaly Detection in Multivariate Time Series Data
COUTA2024unsupervisedCalibrated One-class Classification for Unsupervised Time Series Anomaly Detection
DIF2023unsupervisedDeep Isolation Forest for Anomaly Detection
TcnED2021unsupervisedAn Evaluation of Anomaly Detection and Diagnosis in Multivariate Time Series
Deep SVDD (TS)2018unsupervisedDeep One-Class Classification
DevNet (TS)2019weakly-supervisedDeep Anomaly Detection with Deviation Networks
PReNet (TS)2023weakly-supervisedDeep Weakly-supervised Anomaly Detection
Deep SAD (TS)2020weakly-supervisedDeep Semi-Supervised Anomaly Detection

3. Pythonによる実装例

DeepODの最大の魅力は、様々な深層学習モデルをscikit-learnPyODと同じようなシンプルなコードで扱える点にあります。まずは、ライブラリをインストールしましょう。Python 3.73.10の環境で、以下のコマンドを実行します。

# 事前に利用したいバージョンの PyTorch をインストールしておくことを推奨
# (DeepOD が想定している PyTorchのバージョン:torch>=1.10.0,<1.13.1)
$ pip install torch==1.13.0+cu116 torchvision==0.14.0+cu116 torchaudio==0.13.0 --extra-index-url https://download.pytorch.org/whl/cu116

# Numpyが2.0以上のものがインストールされている場合、上記のPyTorchのバージョンと互換性がないので、再インストールをしておく
$ pip install "numpy<2"

# DeepOD 本体のインストール
$ pip install deepod==0.4.1

表形式データに対する実装例

表形式データに対して、教師なし学習(DeepSVDD)の異常検知を実施する例は次の通りです。

import numpy as np
from deepod.models.tabular import DeepSVDD
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split

if __name__ == "__main__":
    # ----------------------------------
    # 1. ダミーデータの生成
    # ----------------------------------
    # 異常検知の性能を検証するために、ダミーデータを生成します。
    # 正常データは多次元ガウス分布から生成し、異常データは一様分布から生成します。

    # シードを固定して再現性を確保
    np.random.seed(42)

    # 正常データの生成
    # 平均0、共分散行列が単位行列の2次元ガウス分布から1000個のデータを生成
    X_normal = np.random.randn(1000, 2)
    y_normal = np.zeros(1000)  # ラベルは0とする

    # 異常データの生成
    # -5から5の範囲の一様分布から100個のデータを生成
    # 正常データの分布から外れた値を取るように範囲を設定
    X_anomaly = np.random.uniform(low=-5.0, high=5.0, size=(100, 2))
    y_anomaly = np.ones(100)  # ラベルは1とする

    # 正常データと異常データを結合
    X = np.vstack([X_normal, X_anomaly])
    y = np.hstack([y_normal, y_anomaly])

    # ----------------------------------
    # 2. データの分割
    # ----------------------------------
    # Deep SVDDは、主に正常データのみを用いて学習する教師なし学習モデルです。
    # そのため、学習データには正常データのみを使用します。
    # テストデータには正常データと異常データの両方を含め、モデルの汎化性能を評価します。

    # 全データのうち、正常データのみを抽出
    X_normal_data = X[y == 0]
    y_normal_data = y[y == 0]

    # 正常データを学習用とテスト用に分割 (学習: 80%, テスト: 20%)
    X_train, X_test_normal, y_train, y_test_normal = train_test_split(
        X_normal_data, y_normal_data, test_size=0.2, random_state=42
    )

    # テストデータに異常データを追加
    X_test = np.vstack([X_test_normal, X_anomaly])
    y_test = np.hstack([y_test_normal, y_anomaly])

    # ----------------------------------
    # 3. モデルの学習と評価
    # ----------------------------------
    # Deep SVDDモデルを初期化し、学習データで訓練します。
    # その後、テストデータを用いてモデルの性能を評価します。

    # Deep SVDDモデルのインスタンスを作成
    # epochs: 学習のエポック数
    # device: 'cuda' or 'cpu'
    clf = DeepSVDD(epochs=20, device="cpu", random_state=42)

    # モデルの学習
    # Deep SVDDは正常データのみを使って、データの特徴をコンパクトな超球にマッピングすることを学習します。
    clf.fit(X_train, y=None)

    # 異常スコアの算出
    # 学習したモデルを使い、テストデータがどれだけ「正常らしくないか」をスコアとして算出します。
    # スコアが高いほど異常である可能性が高いと判断されます。
    test_scores = clf.decision_function(X_test)

    # ----------------------------------
    # 4. 性能評価
    # ----------------------------------
    # ROC-AUC (Receiver Operating Characteristic - Area Under the Curve) を用いて評価します。
    # ROC-AUCは、異常検知の分野で広く用いられる評価指標で、
    # 1に近いほどモデルの性能が高いことを示します。
    auc = roc_auc_score(y_test, test_scores)
    print(f"Test ROC-AUC: {auc:.4f}")

    # ----------------------------------
    # 5. 結果の確認 (オプション)
    # ----------------------------------
    # 上位5件の異常スコアとその真のラベルを確認してみます。
    print("\n--- Top 5 Anomaly Scores ---")
    # スコアを降順にソートするためのインデックスを取得
    sorted_indices = np.argsort(test_scores)[::-1]
    for i in range(5):
        idx = sorted_indices[i]
        print(f"Sample {idx:3d}: Score = {test_scores[idx]:.4f}, True Label = {int(y_test[idx])}")

時系列データに対する実装例

時系列データに対して、異常検知を実施する例は次の通りです。

import matplotlib.pyplot as plt
import numpy as np
from deepod.metrics import point_adjustment, ts_metrics
from deepod.models.time_series import TimesNet
from matplotlib.lines import Line2D

# ----------------------------------
# 1. ダミーの時系列データ生成
# ----------------------------------
# 異常検知の性能を検証するために、サイン波に基づいたダミーの時系列データを生成します。
# 学習データは正常な時系列データのみを使用し、
# テストデータには正常なデータに加えて、意図的に異常値を注入します。

# シードを固定して再現性を確保
np.random.seed(42)

# データ全体の長さ
n_total = 1000
# 学習データの長さ
n_train = 500
# 時系列データの特徴量の次元数 (今回は1次元)
n_features = 1
# モデルが一度に観測するデータ点数(ウィンドウサイズ)
n_window = 64

# 正常な時系列データ(サイン波 + 小さなノイズ)を生成
t = np.linspace(0, 200, n_total)
X_series = np.sin(t) + np.random.normal(0, 0.1, n_total)

# 異常を注入
anomalies_indices = [600, 800]  # 異常を発生させる時点
y_true = np.zeros(n_total)
for idx in anomalies_indices:
    # 異常箇所では、値を大きく変動させる
    X_series[idx : idx + 5] = X_series[idx : idx + 5] + np.random.normal(2, 0.5, 5)
    # 異常区間のラベルを1に設定
    y_true[idx : idx + 5] = 1

# ----------------------------------
# 2. データを学習用とテスト用に分割
# ----------------------------------
# deepodの時系列モデルは、(時系列長, 特徴量数) の2次元配列を入力として受け取ります。
# 連続した時系列データをそのままモデルに渡します。

# 学習データ(正常データのみ)
# モデルが(サンプル数, 特徴量数)の形式を期待するため、(500,) -> (500, 1) にreshape
X_train = X_series[:n_train].reshape(-1, n_features)

# テストデータ(正常・異常混合)
X_test = X_series[n_train:].reshape(-1, n_features)
y_test = y_true[n_train:]  # ラベルもテストデータに合わせて調整

print(f"学習データ Shape: {X_train.shape}")
print(f"テストデータ Shape: {X_test.shape}")
print(f"テスト ラベル Shape: {y_test.shape}")

# ----------------------------------
# 3. モデルの学習と異常スコアの算出
# ----------------------------------
# 時系列異常検知モデル TimesNet を初期化し、学習データで訓練します。
# TimesNetは、時系列データから周期性を発見し、その周期性から外れるパターンを異常として検出するモデルです。

# TimesNetモデルのインスタンスを作成
# epochs: 学習のエポック数
# hidden_dims: 中間層の次元数
# device: 'cuda' or 'cpu'
clf = TimesNet(
    epochs=10,
    seq_len=n_window,
    device="cpu",
    random_state=42,
)

# モデルの学習
# 正常な時系列データのパターンのみを学習します。
clf.fit(X_train)

# 異常スコアの算出
# 学習したモデルを使い、テストデータがどれだけ「正常らしくないか」をスコアとして算出します。
# スコアが高いほど異常である可能性が高いと判断されます。
test_scores = clf.decision_function(X_test)

# ----------------------------------
# 4. 性能評価
# ----------------------------------
# 時系列異常検知では、異常が発生したタイミングを正確に捉えることが重要です。
# ここでは一般的な評価指標と、時系列特有の「点調整(Point Adjustment)」を適用した評価指標の両方を計算します。

# (1) 通常の評価
# 各データ点が異常か正常かを予測し、その結果を評価します。
print("\n--- Standard Evaluation ---")
auc_roc, auc_pr, f1, p, r = ts_metrics(y_test, test_scores)
print(f"AUC-ROC: {auc_roc:.4f}, AUC-PR: {auc_pr:.4f}")
print(f"F1: {f1:.4f}, Precision: {p:.4f}, Recall: {r:.4f}")

# (2) 点調整(Point Adjustment)を適用した評価
# 異常が発生した場合、その予兆や少し後のタイミングで検知できても正解とみなす評価方法です。
# これにより、より実用的なシナリオに近い形でモデルの性能を評価できます。
print("\n--- Point-Adjusted Evaluation ---")
adj_test_scores = point_adjustment(y_test, test_scores)
adj_auc_roc, adj_auc_pr, adj_f1, adj_p, adj_r = ts_metrics(y_test, adj_test_scores)
print(f"AUC-ROC: {adj_auc_roc:.4f}, AUC-PR: {adj_auc_pr:.4f}")
print(f"F1: {adj_f1:.4f}, Precision: {adj_p:.4f}, Recall: {adj_r:.4f}")


# ----------------------------------
# 5. 結果の可視化
# ----------------------------------
# テストデータ、真の異常箇所、そしてモデルが算出した異常スコアをグラフにプロットし、
# どの部分を異常と判断したかを視覚的に確認します。

plt.figure(figsize=(18, 6))

# 元の時系列データをプロット
# スコアと長さを合わせるため、ウィンドウサイズ分だけ切り詰める
plot_series = X_test.flatten()
plt.plot(plot_series, label="Time Series Data", color="blue")

# 異常スコアを重ねてプロット
plt.plot(test_scores, label="Anomaly Score", color="red", alpha=0.6)

# 真の異常箇所を背景色で示す
for i in range(len(y_test)):
    if y_test[i] == 1:
        plt.axvspan(i, i + 1, color="yellow", alpha=0.5, label="_nolegend_")

plt.title("Time Series Anomaly Detection with TimesNet")
plt.xlabel("Time Step")
plt.ylabel("Value / Anomaly Score")
# 凡例の重複を避けるための処理
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles, strict=True))
# 'True Anomaly' の凡例を手動で追加
by_label["True Anomaly"] = Line2D([0], [0], color="yellow", lw=4, alpha=0.5)
plt.legend(by_label.values(), by_label.keys())

plt.grid(True)
plt.show()

モデルの保存と運用

DeepODの各モデルには標準で保存・読み込みの機能が備わっています。なお、標準の保存機能だけでなく、Python標準のpickleを直接使用してモデルを保存することも可能です。

# モデルの保存
path = 'my_model.pkl'
model.save_model(path)

# モデルの読み込み
from deepod.models.tabular import DeepSVDD
model = DeepSVDD.load_model(path)
new_scores = model.decision_function(X_new)

おわりに

DeepODは、最新の深層学習アルゴリズムと使い慣れたインターフェースを両立させた、MLエンジニアにとって非常に実用性の高いライブラリです。27種類におよぶ豊富なモデル群が実装されており、教師なし学習だけでなく、少量のラベルを活用した「弱い教師あり学習」にも対応しています。

大きな魅力は、scikit-learnやPyODと同様の統一されたAPIで、表形式データと時系列データの双方に適用できる点です。また、時系列データに対してTransformerやTCNといった様々なネットワーク構造を柔軟に入れ替えられるため、異常検知システムの開発効率を劇的に向上させることができます。

More Information

  • Ruff, Lukas, et al. “Deep one-class classification.” ICML. 2018.
  • Pang, Guansong, et al. “Learning representations of ultrahigh-dimensional data for random distance-based outlier detection”. KDD (pp. 2041-2050). 2018.
  • Wang, Hu, et al. “Unsupervised Representation Learning by Predicting Random Distances”. IJCAI (pp. 2950-2956). 2020.
  • Liu, Boyang, et al. “RCA: A Deep Collaborative Autoencoder Approach for Anomaly Detection”. IJCAI (pp. 1505-1511). 2021.
  • Bergman, Liron, and Yedid Hoshen. “Classification-Based Anomaly Detection for General Data”. ICLR. 2020.
  • Qiu, Chen, et al. “Neural Transformation Learning for Deep Anomaly Detection Beyond Images”. ICML. 2021.
  • Shenkar, Tom, et al. “Anomaly Detection for Tabular Data with Internal Contrastive Learning”. ICLR. 2022.
  • Pang, Guansong, et al. “Deep Anomaly Detection with Deviation Networks”. KDD. 2019.
  • Pang, Guansong, et al. “Deep Weakly-supervised Anomaly Detection”. KDD. 2023.
  • Ruff, Lukas, et al. “Deep Semi-Supervised Anomaly Detection”. ICLR. 2020.
  • Zhou, Yingjie, et al. “Feature Encoding with AutoEncoders for Weakly-supervised Anomaly Detection”. TNNLS. 2021.
  • Xu, Jiehui, et al. “Anomaly Transformer: Time Series Anomaly Detection with Association Discrepancy”. ICLR, 2022.
  • Wu, Haixu, et al. “TimesNet: Temporal 2D-Variation Modeling for General Time Series Analysis”. ICLR. 2023.
  • Yang, Yiyuan, et al. “DCdetector: Dual Attention Contrastive Representation Learning for Time Series Anomaly Detection”. KDD. 2023
  • Tuli, Shreshth, et al. “TranAD: Deep Transformer Networks for Anomaly Detection in Multivariate Time Series Data”. VLDB. 2022.
  • Carmona, Chris U., et al. “Neural Contextual Anomaly Detection for Time Series”. IJCAI. 2022.
  • Garg, Astha, et al. “An Evaluation of Anomaly Detection and Diagnosis in Multivariate Time Series”. TNNLS. 2021.
  • Xu, Hongzuo et al. “Calibrated One-class Classification for Unsupervised Time Series Anomaly Detection”. TKDE. 2024.
  • Xu, Hongzuo et al. “Deep Isolation Forest for Anomaly Detection”. TKDE. 2023.
  • Xu, Hongzuo et al. “Fascinating supervisory signals and where to find them: deep anomaly detection with scale learning”. ICML. 2023.
  • Xu, Hongzuo et al. “RoSAS: Deep semi-supervised anomaly detection with contamination-resilient continuous supervision”. IP&M. 2023.