One-Class SVMの理論と実装 — カーネル法による異常検知

異常検知では「正常データの境界を学習する」ことが基本的なアプローチです。しかし、現実の問題ではほとんどの場合、正常データは大量に手に入る一方で、異常データはごくわずかしか集まらないか、まったく存在しないことも珍しくありません。たとえば、工場の製造ラインでは正常品は毎日数千個と生産されるのに、不良品は数か月に1個しか出ないかもしれません。このように「片方のクラスのデータしかない」状況で、正常データを囲む「最もタイトな境界」を見つけるにはどうすればよいでしょうか。

One-Class SVM(Scholkopf et al., 2001)は、この問いに対する理論的に美しい解答です。通常のSVMが「2つのクラスの間にマージン最大の超平面を引く」のに対し、One-Class SVMは正常データをカーネル特徴空間で原点からできるだけ離れた側に押しやる超平面を見つけることで、正常データの「境界」を学習します。この超平面よりも原点側にあるデータが異常と判定されます。

イメージとしては、「データを高次元の世界に飛ばして、そこで原点(=何もない空間の代表)から正常データをできるだけ遠ざける壁を立てる」と考えるとわかりやすいでしょう。壁の手前(原点側)に来てしまうデータは、正常データの集団から外れているため「異常」と判定されます。

通常のSVMが2クラスの分離に超平面を使うのに対し、One-Class SVMは「正常データ vs 原点」の1クラス分離を行います。カーネルトリックにより非線形な境界を学習でき、正則化パラメータ $\nu$ で異常の割合を直接制御できる理論的に美しい手法です。

One-Class SVMを理解すると、以下のような場面で活用できます。

  • 新規性検知: 訓練データにない新しいパターンの検出。たとえば、未知のマルウェアの検出にも応用されます
  • 品質管理: 正常品のデータのみから不良品の検出基準を学習。製造業において不良品のサンプルを事前に集めることなく検査基準を構築できます
  • ネットワーク侵入検知: 正常なトラフィックパターンからの逸脱を検出。新種の攻撃にも対応可能です
  • 医療診断: 健常者のデータから異常な検査値のパターンを検出。希少疾患の早期発見に寄与します
  • 金融不正検出: 正常な取引パターンを学習し、不正取引を検出。クレジットカードの不正使用検出などに活用されます

本記事の内容

  • One-Class SVMの最適化問題の定式化
  • $\nu$パラメータの理論的解釈
  • カーネル特徴空間での決定境界
  • 双対問題とSMOアルゴリズム
  • Pythonでの実装と可視化

前提知識

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

SVMから One-Class SVMへ

One-Class SVMの概念

One-Class SVMは、正常データ(青)を囲む境界を学び、その外に出た点(赤)を異常とみなします。確率分布を仮定せず、境界の形をデータから直接学べるのが特徴です。

通常のSVMの復習

通常のSVM(2クラス分類)は、2つのクラスを分離する超平面 $\bm{w}^T\bm{x} – b = 0$ のうち、マージン(最も近いデータ点との距離)を最大化するものを見つけます。

$$ \min_{\bm{w}, b} \frac{1}{2}\|\bm{w}\|^2 \quad \text{subject to} \quad y_i(\bm{w}^T\bm{x}_i – b) \geq 1 \quad \forall i $$

この最適化問題は「できるだけ幅の広い道を2つのクラスの間に通す」というイメージです。$\|\bm{w}\|^2$ を最小化することでマージン $2 / \|\bm{w}\|$ が最大化され、汎化性能が高くなります。制約条件は「全てのデータ点がマージンの外側にある」ことを保証しています。

しかし、この定式化には根本的な前提があります。それは「正例と負例の両方のデータが手に入る」ということです。異常検知のように、正常データしか手に入らない場合はどうすればよいでしょうか。ここでOne-Class SVMの出番です。

One-Classへの拡張

One-Class SVMでは、正のクラス(正常データ)しかありません。Scholkopf et al.は、「原点からデータをできるだけ離す超平面」を見つけるという巧みな定式化を提案しました。

One-Class SVMのアイデア

特徴空間において、正常データを原点から最大マージンで分離する超平面を引き、正常データ側を「正常」、原点側を「異常」とみなします。「正常が固まっている領域を、できるだけきつく囲い込む」操作です。

直感的には、特徴空間で原点を「異常の代表」と見なし、正常データと原点を分離する超平面を求めます。日常的なアナロジーで説明すると、2クラスSVMが「りんごとみかんの間に仕切りを立てる」のに対し、One-Class SVMは「りんごだけが入っている箱の中で、りんごを壁のそばに押しやり、壁の向こう側(何もない空間)を異常領域とする」イメージです。

なぜ「原点」が異常の代表として機能するのでしょうか。カーネル特徴空間 $\Phi(\bm{x})$ はデータを非線形に変換するため、原点は入力空間での「何もない空間」に対応し、正常データの密集領域から離れた場所になります。具体的に、RBFカーネルを使った場合を考えてみましょう。RBFカーネルの特徴写像 $\Phi(\bm{x})$ は $\|\Phi(\bm{x})\| = \sqrt{K(\bm{x}, \bm{x})} = 1$ という性質を持ちます。つまり、全てのデータ点は特徴空間の単位球面上に写像されます。原点は球の中心にあるため、データ点から離れた場所に位置します。

原点を基準にする理由は、カーネル特徴空間での $\Phi(\bm{x})$ は一般に原点の近くにはない(RBFカーネルでは $\|\Phi(\bm{x})\| = 1$)ため、原点が自然な「外側」の基準点になるからです。この着想を数学的に定式化すると、次に述べる最適化問題が得られます。

最適化問題の定式化

主問題

前セクションで「正常データを原点からできるだけ離す超平面を見つける」という直感を説明しました。ここでは、この直感を数学的に厳密な最適化問題として定式化します。

$n$ 個の正常データ $\bm{x}_1, \ldots, \bm{x}_n$ に対して、One-Class SVMの主問題は以下のように定式化されます。

$$ \begin{equation} \min_{\bm{w}, \rho, \bm{\xi}} \frac{1}{2}\|\bm{w}\|^2 – \rho + \frac{1}{\nu n}\sum_{i=1}^{n}\xi_i \end{equation} $$

$$ \text{subject to} \quad \bm{w}^T\Phi(\bm{x}_i) \geq \rho – \xi_i, \quad \xi_i \geq 0 $$

各項の意味を解説します。

  • $\frac{1}{2}\|\bm{w}\|^2$: 超平面の「滑らかさ」を制御する正則化項。小さいほどマージンが大きい
  • $\rho$: 超平面と原点の距離を $\|\bm{w}\|$ で割ったもの。これを最大化したい(符号を反転して最小化)
  • $\xi_i$: スラック変数。一部のデータが超平面の「内側」(原点側)にあることを許容
  • $\frac{1}{\nu n}$: スラック変数のペナルティ係数。$\nu$ が小さいほどペナルティが大きく、データを超平面の外側に強く押す

制約条件 $\bm{w}^T\Phi(\bm{x}_i) \geq \rho – \xi_i$ は「データ点 $\bm{x}_i$ が超平面 $\bm{w}^T\Phi(\bm{x}) = \rho$ の外側(正常側)にあるか、スラック変数 $\xi_i$ 以内で内側にある」ことを意味します。

この定式化を通常のSVMと対比すると構造がよく見えます。通常のSVMでは「2つのクラスの間」にマージンを設けますが、One-Class SVMでは「正常データと原点の間」にマージンを設けます。$\rho$ が原点からの距離に相当し、これをできるだけ大きくすることで正常データと原点の分離を促進します。一方、$\frac{1}{2}\|\bm{w}\|^2$ の正則化は境界の複雑さを抑制し、$\xi_i$ のペナルティは一部のデータが境界の内側に入ることを許容します。この3つのバランスが $\nu$ によって制御されます。

$\nu$パラメータの理論的解釈

$\nu \in (0, 1]$ はOne-Class SVMの最も重要なハイパーパラメータで、以下の2つの意味を持ちます。

  1. 異常の割合の上界: $\nu$ は、訓練データのうち超平面の内側(原点側)に分類される割合の上界です。つまり、$\nu = 0.1$ ならば最大10%の訓練データが「異常側」に分類されます

  2. サポートベクターの割合の下界: $\nu$ はサポートベクター(超平面上または内側のデータ点)の割合の下界です

Scholkopf et al.(2001)の $\nu$-property定理によれば、最適解において

$$ \frac{|\{i : \xi_i > 0\}|}{n} \leq \nu \leq \frac{|\{i : \alpha_i > 0\}|}{n} $$

が成り立ちます。ここで $\alpha_i$ は双対変数(サポートベクターに対応する非零の変数)です。

この性質により、$\nu$ は異常の「許容割合」として直感的に解釈でき、ドメイン知識に基づいて設定できます。例えば「異常は全データの5%程度」と予想される場合、$\nu = 0.05$ と設定します。

$\nu$ の設定は実務的にも重要です。仮に $\nu$ を大きくしすぎると、正常データの多くが異常側に分類されてしまい(偽陽性の増加)、逆に小さくしすぎると真の異常を見逃してしまいます(偽陰性の増加)。ドメイン知識で「正常データの中にも含まれうる異常の割合」を見積もり、それに応じて $\nu$ を設定するのが基本戦略です。

この $\nu$-property は One-Class SVM の大きな利点であり、Isolation Forest や LOF などの他の異常検知手法では、しきい値を別途調整する必要がありますが、One-Class SVM では $\nu$ がその役割を理論的に担ってくれます。

主問題の直感を把握したところで、次にこの問題をカーネルトリックが適用できる形に変換するため、双対問題を導出しましょう。

双対問題

ラグランジュ乗数 $\alpha_i \geq 0$(制約 $\bm{w}^T\Phi(\bm{x}_i) \geq \rho – \xi_i$ に対応)と $\beta_i \geq 0$(制約 $\xi_i \geq 0$ に対応)を導入して双対問題を導出します。主問題のラグランジアンは

$$ \mathcal{L} = \frac{1}{2}\|\bm{w}\|^2 – \rho + \frac{1}{\nu n}\sum_i \xi_i – \sum_i \alpha_i(\bm{w}^T\Phi(\bm{x}_i) – \rho + \xi_i) – \sum_i \beta_i \xi_i $$

最適性の必要条件として、$\bm{w}, \rho, \xi_i$ について偏微分を0とおきます。

$\bm{w}$ について偏微分すると、$\frac{\partial \mathcal{L}}{\partial \bm{w}} = \bm{w} – \sum_i \alpha_i \Phi(\bm{x}_i) = 0$ より

$$ \bm{w} = \sum_i \alpha_i \Phi(\bm{x}_i) $$

これは通常のSVMと同様に、最適な超平面の法線ベクトルがサポートベクターの線形結合で表されることを意味します。

$\rho$ について偏微分すると、$\frac{\partial \mathcal{L}}{\partial \rho} = -1 + \sum_i \alpha_i = 0$ より

$$ \sum_i \alpha_i = 1 $$

$\xi_i$ について偏微分すると、$\frac{\partial \mathcal{L}}{\partial \xi_i} = \frac{1}{\nu n} – \alpha_i – \beta_i = 0$ より、$\beta_i \geq 0$ の条件を合わせて

$$ 0 \leq \alpha_i \leq \frac{1}{\nu n} $$

これら3つの条件をラグランジアンに代入すると、$\bm{w}$ に関する項は $\frac{1}{2}\|\bm{w}\|^2 – \bm{w}^T\sum_i \alpha_i \Phi(\bm{x}_i) = -\frac{1}{2}\sum_{i,j}\alpha_i\alpha_j \Phi(\bm{x}_i)^T\Phi(\bm{x}_j)$ となり、$\rho$ の項と $\xi_i$ の項は相殺されます。結果として双対問題が得られます。

$$ \begin{equation} \min_{\bm{\alpha}} \frac{1}{2}\sum_{i,j}\alpha_i \alpha_j K(\bm{x}_i, \bm{x}_j) \end{equation} $$

$$ \text{subject to} \quad \sum_i \alpha_i = 1, \quad 0 \leq \alpha_i \leq \frac{1}{\nu n} $$

ここで $K(\bm{x}_i, \bm{x}_j) = \Phi(\bm{x}_i)^T\Phi(\bm{x}_j)$ はカーネル関数です。カーネルトリックにより、特徴空間の内積を入力空間のカーネル計算で置き換えられるため、高次元(または無限次元)の特徴空間を明示的に計算する必要がありません。

この双対問題の構造を見ると、目的関数は $\alpha_i$ と $\alpha_j$ に関する二次形式であり、制約は線形です。したがって、これは凸二次計画問題(QP: Quadratic Programming)であり、大域最適解が一意に存在します。通常のSVMの双対問題と非常に似た構造をしていますが、One-Class SVMでは制約に $\sum_i \alpha_i = 1$ が含まれる点が特徴的です。

実用的な求解には、SMO(Sequential Minimal Optimization)アルゴリズムやその変形が使われます。SMOは一度に2つの $\alpha_i$ だけを更新することで、制約条件を維持しながら効率的に最適化を行います。

双対問題の最適解 $\alpha^*$ が得られれば、新しいデータ点に対する判定が可能になります。次に、その判定を行う決定関数を見ていきましょう。

決定関数

学習後の決定関数は

$$ \begin{equation} f(\bm{x}) = \text{sign}\left(\sum_{i \in SV} \alpha_i K(\bm{x}_i, \bm{x}) – \rho\right) \end{equation} $$

$f(\bm{x}) = +1$ なら正常、$f(\bm{x}) = -1$ なら異常と判定します。

$\rho$ はサポートベクター $\bm{x}_j$($0 < \alpha_j < 1/\nu n$)を使って

$$ \rho = \sum_{i \in SV} \alpha_i K(\bm{x}_i, \bm{x}_j) $$

で計算できます。$0 < \alpha_j < 1/(\nu n)$ を満たすサポートベクターは決定境界上に位置するため、これらに対して $f(\bm{x}_j) = 0$(つまり $\sum_i \alpha_i K(\bm{x}_i, \bm{x}_j) = \rho$)が成り立ちます。数値的な安定性のため、複数のサポートベクターに対して $\rho$ を計算し、その平均をとる方法も実務ではよく使われます。

KKT条件(Karush-Kuhn-Tucker条件)から、$\alpha_i$ の値によってデータ点は3種類に分類されます。

  • $\alpha_i = 0$: 超平面の外側(正常側)にあるデータ点。決定関数の計算に寄与しない
  • $0 < \alpha_i < 1/(\nu n)$: 決定境界上にあるデータ点(狭義のサポートベクター)
  • $\alpha_i = 1/(\nu n)$: 超平面の内側(原点側)にあるデータ点。$\xi_i > 0$ であり、異常側に分類される

この分類は通常のSVMのKKT条件と対応しており、サポートベクターだけで決定関数が決まるというSVM系手法の大きな特長が、One-Class SVMでも維持されています。

決定関数の構造が明らかになったところで、次にカーネル関数の選択について詳しく見ていきましょう。カーネルの選択は決定境界の形状を決定するため、One-Class SVMの性能に大きな影響を与えます。

カーネルの選択

RBFカーネル(ガウスカーネル)

One-Class SVMで最も一般的に使われるのはRBFカーネル(Radial Basis Function)です。

$$ K(\bm{x}_i, \bm{x}_j) = \exp\left(-\gamma \|\bm{x}_i – \bm{x}_j\|^2\right) $$

$\gamma > 0$ はカーネルの幅を制御するパラメータです。

RBFカーネル

  • $\gamma$ が大きい: 局所的な特徴を重視し、複雑な境界を学習。過学習のリスク
  • $\gamma$ が小さい: 大域的な特徴を重視し、滑らかな境界を学習。未学習のリスク

RBFカーネルの特徴空間は無限次元であり、非常に柔軟な決定境界を学習できます。

γの効果

図のように、$\gamma$ が小さいと境界は滑らかに、大きいと各点に密着した複雑な境界になります。大きすぎると過学習し、汎化しません。

$\gamma$ の選択

$\gamma$ の適切な値は、データの特性に依存します。一般的な指針として

  • メディアンヒューリスティック: データ間のユークリッド距離のメディアン $d_m$ を使い、$\gamma = 1 / d_m^2$ と設定する
  • グリッドサーチ: $\gamma \in \{10^{-3}, 10^{-2}, \ldots, 10^2\}$ で交差検証する

異常検知では通常のクラス分類と異なり、異常データのラベルがないため、交差検証が困難です。そのため、メディアンヒューリスティックや、正常データの再構成/分類精度に基づく選択が使われます。

カーネル比較

カーネル 特性
線形 $\bm{x}_i^T\bm{x}_j$ 高速、線形境界のみ
多項式 $(\bm{x}_i^T\bm{x}_j + c)^d$ 非線形、次数 $d$ で制御
RBF $\exp(-\gamma\|\bm{x}_i – \bm{x}_j\|^2)$ 非線形、最も柔軟

ほとんどの異常検知タスクではRBFカーネルが推奨されます。線形カーネルは計算が高速ですが、正常データが非凸な形状をしている場合(例えば半月形や環状のクラスタ)には対応できません。多項式カーネルは次数 $d$ を調整できますが、RBFカーネルのほうがパラメータのチューニングが容易で、より柔軟な境界を学習できます。

カーネルの比較

環状(リング状)の正常データに対し、線形カーネル(左)は直線でしか切れず非凸な領域を囲めませんが、RBFカーネル(右)は曲線でリングをぴったり囲んでいます。

理論を理解したところで、Pythonで実装してみましょう。まず勾配射影法による簡易的な実装で双対問題の構造を確認し、次に2次元データでの可視化を通じて決定境界の振る舞いを観察します。

Pythonでの実装

One-Class SVMの簡易実装

双対問題を二次計画法で解く完全な実装は複雑なため、ここでは勾配射影法による簡易的な実装を行います。勾配射影法は「勾配方向に進む→制約を満たすように射影する」を繰り返すアルゴリズムで、制約付き最適化の基本的なアプローチです。まず理論を確認するためのカーネル関数と決定関数を実装し、次にscikit-learnを使った実験を行います。

import numpy as np
import matplotlib.pyplot as plt

def rbf_kernel(X1, X2, gamma=1.0):
    """RBFカーネル行列の計算"""
    sq1 = np.sum(X1**2, axis=1, keepdims=True)
    sq2 = np.sum(X2**2, axis=1, keepdims=True)
    dist_sq = sq1 + sq2.T - 2 * X1 @ X2.T
    return np.exp(-gamma * dist_sq)

class SimpleOneClassSVM:
    """One-Class SVMの簡易実装(勾配射影法による最適化)"""
    def __init__(self, kernel='rbf', gamma=1.0, nu=0.1):
        self.gamma = gamma
        self.nu = nu

    def fit(self, X):
        """双対問題を勾配射影法で解く"""
        n = X.shape[0]
        self.X_train = X.copy()
        K = rbf_kernel(X, X, self.gamma)
        self.K = K

        # 双対変数の初期化(制約: sum=1, 0<=alpha<=1/(nu*n))
        alpha = np.ones(n) / n
        upper = 1.0 / (self.nu * n)
        lr = 0.1

        for iteration in range(500):
            # 勾配: d/d_alpha (0.5 * alpha^T K alpha) = K alpha
            grad = K @ alpha

            # 勾配降下
            alpha_new = alpha - lr * grad

            # 制約への射影: [0, upper]にクリップ
            alpha_new = np.clip(alpha_new, 0, upper)

            # sum=1の制約への射影
            if alpha_new.sum() > 0:
                alpha_new = alpha_new / alpha_new.sum()
            else:
                alpha_new = np.ones(n) / n

            alpha = alpha_new

        self.alpha = alpha

        # rhoの計算(サポートベクターから)
        sv_mask = (alpha > 1e-8) & (alpha < upper - 1e-8)
        if sv_mask.any():
            sv_idx = np.where(sv_mask)[0][0]
            self.rho = alpha @ K[:, sv_idx]
        else:
            # フォールバック: 全サンプルの決定関数値の中央値
            decision_vals = alpha @ K
            self.rho = np.median(decision_vals)

        self.sv_mask = alpha > 1e-8

    def decision_function(self, X):
        """決定関数: 正の値=正常、負の値=異常"""
        K = rbf_kernel(self.X_train, X, self.gamma)
        return self.alpha @ K - self.rho

    def predict(self, X):
        """予測: +1=正常, -1=異常"""
        return np.sign(self.decision_function(X))

上のコードでは、rbf_kernel 関数が $K(\bm{x}_i, \bm{x}_j) = \exp(-\gamma \|\bm{x}_i – \bm{x}_j\|^2)$ を計算しています。距離の2乗を効率的に計算するため、$\|\bm{x}_i – \bm{x}_j\|^2 = \|\bm{x}_i\|^2 + \|\bm{x}_j\|^2 – 2\bm{x}_i^T\bm{x}_j$ という展開を利用しています。SimpleOneClassSVM クラスの fit メソッドでは、勾配 $\nabla_\alpha \frac{1}{2}\bm{\alpha}^T K \bm{\alpha} = K\bm{\alpha}$ を計算した後、np.clip で $[0, 1/(\nu n)]$ にクリップし、さらに $\sum_i \alpha_i = 1$ を満たすように正規化しています。これは厳密な射影ではありませんが、簡易的な実装として動作します。

それでは、この実装を使って2次元データの決定境界を可視化してみましょう。

2次元データでの可視化

分布を仮定する手法(マハラノビス距離など単一ガウス)は、環状の正常領域を表せず、中央の空洞を「最も正常」と誤判定してしまいます。

単一ガウスの限界

One-Class SVMはRBFカーネルにより、この環状の正常領域を正しく囲みます。

決定境界

赤い線が学習された境界で、リング状の正常データをぴったり囲み、中央の空洞も「異常側」として正しく除外しています。

np.random.seed(42)

# 正常データ: 半月形
n_normal = 300
theta = np.random.uniform(0, np.pi, n_normal)
r = 2 + 0.3 * np.random.randn(n_normal)
X_normal = np.column_stack([r * np.cos(theta), r * np.sin(theta)])

# 異常データ
n_anomaly = 15
X_anomaly = np.random.uniform(-4, 4, (n_anomaly, 2))

X_train = X_normal[:250]
X_test = np.vstack([X_normal[250:], X_anomaly])
y_test = np.array([1]*50 + [-1]*n_anomaly)

# gamma の選択(メディアンヒューリスティック)
from scipy.spatial.distance import pdist
dists = pdist(X_train)
gamma = 1.0 / np.median(dists)**2

# One-Class SVM の学習
ocsvm = SimpleOneClassSVM(gamma=gamma, nu=0.1)
ocsvm.fit(X_train)

# 決定境界の可視化
x_min, x_max = -5, 5
y_min, y_max = -3, 5
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                      np.linspace(y_min, y_max, 200))
grid = np.column_stack([xx.ravel(), yy.ravel()])
Z = ocsvm.decision_function(grid).reshape(xx.shape)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# (a) 決定境界
ax = axes[0]
ax.contourf(xx, yy, Z, levels=np.linspace(Z.min(), Z.max(), 20),
            cmap="RdYlBu", alpha=0.5)
ax.contour(xx, yy, Z, levels=[0], colors="green", linewidths=2)
ax.scatter(X_train[:, 0], X_train[:, 1], c="blue", s=10, alpha=0.5,
           label="Training (normal)")
ax.scatter(X_test[y_test==1, 0], X_test[y_test==1, 1], c="cyan", s=20,
           alpha=0.7, marker="^", label="Test (normal)")
ax.scatter(X_test[y_test==-1, 0], X_test[y_test==-1, 1], c="red", s=40,
           marker="x", linewidth=2, label="Test (anomaly)")
# サポートベクターの表示
sv_points = X_train[ocsvm.sv_mask]
ax.scatter(sv_points[:, 0], sv_points[:, 1], s=60, facecolors="none",
           edgecolors="orange", linewidth=1.5, label="Support vectors")
ax.set_xlabel("$x_1$", fontsize=12)
ax.set_ylabel("$x_2$", fontsize=12)
ax.set_title("One-Class SVM Decision Boundary", fontsize=13)
ax.legend(fontsize=9, loc="lower left")
ax.grid(True, alpha=0.3)

# (b) 決定関数値の分布
ax = axes[1]
df_normal = ocsvm.decision_function(X_test[y_test==1])
df_anomaly = ocsvm.decision_function(X_test[y_test==-1])
ax.hist(df_normal, bins=20, alpha=0.7, color="blue", label="Normal", density=True)
ax.hist(df_anomaly, bins=10, alpha=0.7, color="red", label="Anomaly", density=True)
ax.axvline(0, color="green", linestyle="--", linewidth=2, label="Decision boundary (f=0)")
ax.set_xlabel("Decision Function Value $f(x)$", fontsize=12)
ax.set_ylabel("Density", fontsize=12)
ax.set_title("Decision Function Distribution", fontsize=13)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig("one_class_svm.png", dpi=150, bbox_inches="tight")
plt.show()

このグラフから、One-Class SVMの動作が理解できます。

  1. 左図(決定境界): 緑の等高線がOne-Class SVMの決定境界($f(\bm{x}) = 0$)です。半月形の正常データ(青い点)の周囲にタイトな境界が形成されており、カーネルによる非線形な境界学習が機能しています。オレンジの丸はサポートベクター(境界上またはその内側のデータ点)で、決定境界の形状を決定しています。赤いxマーク(異常データ)の多くが境界の外側にあることがわかります

  2. 右図(決定関数値の分布): 正常データ(青)の決定関数値は正の側に分布し、異常データ(赤)は負の側に分布しています。$f = 0$ の決定境界(緑の点線)で分離できていることがわかります。正常データの一部が0付近にある点は、$\nu = 0.1$ の設定により一定割合のデータが「異常側」に分類されることを許容していることを反映しています

$\nu$ と $\gamma$ の影響

One-Class SVMの性能は、$\nu$ と $\gamma$ の2つのハイパーパラメータに大きく依存します。これら2つのパラメータは独立に異なる役割を果たしますが、相互に影響し合うため、適切な組み合わせを見つけることが重要です。ここでは、それぞれのパラメータが決定境界にどのような影響を与えるかを詳しく見ていきます。

$\nu$ の影響

$\nu$ を変化させると、決定境界のタイトさが変わります。

νパラメータ

$\nu$ は「訓練データを異常と判定する割合の上限」かつ「サポートベクター割合の下限」という2つの意味を持ち、図のように訓練データの異常判定率は $\nu$ に比例します。

  • $\nu$ が小さい(例: 0.01): 境界がタイトで、ほぼ全ての訓練データを囲む。偽陽性が少ないが、微妙な異常を見逃す可能性
  • $\nu$ が大きい(例: 0.3): 境界が緩く、多くの訓練データが「異常側」に分類される。異常の検出力は高いが、偽陽性も多い

直感的には、$\nu$ は「どれくらい厳しく異常を判定するか」のノブです。$\nu = 0.01$ は「1%くらいしか異常はないはずだ」という事前知識を反映し、$\nu = 0.3$ は「30%くらいは怪しいデータがあるかもしれない」という判断を反映します。

実務では、最初にドメイン知識に基づいて $\nu$ の初期値を設定し、正常データのみでの交差検証(正常データがどれくらい異常と判定されるかを確認)で微調整する方法が一般的です。例えば、製造業の品質管理では不良率が1%未満であれば $\nu = 0.01$-$0.05$ が適切であり、不正検出では不正率に応じて $\nu = 0.05$-$0.15$ 程度に設定することが多いです。

$\gamma$ の影響

$\gamma$ はRBFカーネルの幅を制御し、決定境界の「滑らかさ」に直接影響します。

  • $\gamma$ が大きい: 各データ点の「影響範囲」が狭くなり、局所的な構造を重視します。結果として、データに密着した複雑な境界が形成されます。極端に大きくすると、各データ点の周りに小さな「島」ができてしまい、過学習のリスクがあります
  • $\gamma$ が小さい: 各データ点の「影響範囲」が広くなり、大域的な構造を重視します。結果として、滑らかな円形に近い境界が形成されます。極端に小さくすると、データの形状を捉えきれず、未学習(underfitting)になります

$\gamma$ の物理的な意味をもう少し掘り下げましょう。RBFカーネル $K(\bm{x}_i, \bm{x}_j) = \exp(-\gamma \|\bm{x}_i – \bm{x}_j\|^2)$ において、$\gamma = 1/(2\sigma^2)$ と書き換えると、$\sigma$ はガウス関数の標準偏差に対応します。つまり、$\gamma$ が大きいほど $\sigma$ が小さくなり、「近くのデータ点にしか影響を及ぼさない」局所的なカーネルになります。

$\nu$ と $\gamma$ の相互作用

$\nu$ と $\gamma$ のバランスが重要で、通常はグリッドサーチまたはメディアンヒューリスティック($\gamma$ 用)と事前知識($\nu$ 用)で設定します。

重要な点として、$\nu$ と $\gamma$ は独立に調整するだけでなく、相互の影響を考慮する必要があります。例えば、$\gamma$ が大きい場合は境界が複雑になるため、$\nu$ を大きくしても境界の外側に出るデータ点が少なくなることがあります。逆に、$\gamma$ が小さくて境界が滑らかな場合は、$\nu$ の影響がより直接的に現れます。

実務的なチューニング戦略として、以下のアプローチが推奨されます。

  1. まず $\gamma$ をメディアンヒューリスティックで初期設定する
  2. $\nu$ をドメイン知識に基づいて設定する
  3. 正常データの一部を検証用に取り分けて、正常データの「異常判定率」が $\nu$ に近い値になっているかを確認する
  4. 必要に応じて $\gamma$ を調整し、境界の形状が妥当かを可視化で確認する

ハイパーパラメータの影響を把握したところで、次にOne-Class SVMの計算量と実用上の制限について確認しましょう。

計算量と実用上の制限

One-Class SVMの主な制限は計算量です。

  • 学習時: カーネル行列の計算に $O(n^2 d)$($n$: データ数、$d$: 次元数)、最適化に $O(n^2)$-$O(n^3)$
  • メモリ: カーネル行列全体を保持する場合 $O(n^2)$。$n = 50000$ でカーネル行列は約20GBになります
  • 推論時: 各サンプルに対してサポートベクターとのカーネル計算が必要 $O(n_{SV} \cdot d)$

$n > 10000$ 程度になると学習が困難になるため、大規模データにはIsolation ForestやAutoEncoderが適しています。

ただし、以下のようなカーネル近似手法を使えば、大規模データにも適用可能です。

  • Nystrom近似: カーネル行列のランク $k$ 近似を行い、計算量を $O(nk^2)$ に削減します。$k \ll n$ とすることで大幅な高速化が可能です
  • ランダムフーリエ特徴量(Random Fourier Features): Rahimi & Recht(2007)が提案した手法で、RBFカーネルを有限次元の特徴量で近似します。$D$ 次元の近似特徴量を計算するコストは $O(nDd)$ で、その後は線形SVMとして解けるため、非常に高速です

これらの近似手法は、精度をほとんど落とすことなく計算量を大幅に削減できるため、実務では積極的に活用されています。

他の異常検知手法との比較

One-Class SVMの位置づけを明確にするために、他の代表的な異常検知手法と比較してみましょう。

手法 計算量(学習) 大規模データ 非線形境界 パラメータの解釈性
One-Class SVM $O(n^2)$-$O(n^3)$ 困難 RBFカーネルで対応 $\nu$ が異常割合
Isolation Forest $O(n \log n)$ 容易 木構造で自然に対応 汚染率を指定
LOF $O(n^2)$ 中程度 密度ベースで対応 $k$近傍数を指定
AutoEncoder $O(n \cdot \text{epoch})$ GPU利用で容易 ネットワーク構造で対応 しきい値を別途設定

One-Class SVMは「理論的な裏付けの強さ」と「$\nu$ による異常割合の直接制御」が最大の強みです。一方、大規模データへのスケーラビリティでは Isolation Forest に劣ります。問題の性質やデータのサイズに応じて使い分けることが重要です。

関連手法として SVDD(Support Vector Data Description) があります。One-Class SVMが「原点から超平面で分離する」のに対し、SVDDは「正常データを囲む最小の超球を見つける」発想ですが、RBFカーネルのもとでは両者は数学的に等価です。

SVDD

実際に、環状の正常データで各手法をROCで比較すると、RBF One-Class SVMが非凸な正常領域を正しく囲んで圧倒的に高い性能を示します(線形カーネルや単一ガウスのマハラノビス距離は非凸領域を扱えず大きく劣る)。

ROC比較

まとめ

本記事では、One-Class SVMの理論と実装について解説しました。

  • One-Class SVMは、カーネル特徴空間で正常データと原点を分離する超平面を学習する。通常のSVMが「2クラスの分離」であるのに対し、「正常データ vs 原点」の1クラス分離を行う
  • $\nu$パラメータは異常の割合の上界であり、ドメイン知識に基づいて直感的に設定できる。これは$\nu$-property定理によって理論的に保証されている
  • 双対問題はカーネルトリックにより、高次元の特徴空間を明示的に計算せずに解ける。双対問題は凸二次計画問題であり、大域最適解が保証される
  • RBFカーネルが最も一般的で、非線形な決定境界を学習できる。$\gamma$ のチューニングにはメディアンヒューリスティックが有効
  • 計算量は $O(n^2)$-$O(n^3)$ のため、大規模データにはIsolation Forestが適している。Nystrom近似やランダムフーリエ特徴量を使えばスケーラビリティを改善できる

One-Class SVMは「正常データしか手に入らない」という現実の制約下で、理論的に裏付けのある異常検知を行える強力な手法です。特に、$\nu$ パラメータによる異常割合の直接制御と、カーネルによる柔軟な非線形境界の学習が最大の強みです。データサイズが中規模(数千件程度)であれば、まずOne-Class SVMを試してみることをお勧めします。

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