【初学者向け】サポートベクターマシン(SVM)についてわかりやすく解説

Python

こんにちは!ぼりたそです!

今回はサポートベクターマシン(SVM)について初学者の方でも理解しやすいように解説してみました。

この記事は以下のポイントでまとめています。

Point
  • サポートベクターマシン(SVM)とは
     
  • サポートベクターマシン(SVM)のアルゴリズム
     
  • PythonでSVMを実行

それでは順に解説していきます。

スポンサーリンク

サポートベクターマシンとは?

サポートベクターマシン(SVM)は、機械学習のアルゴリズムの一種で、主に二値分類問題に使われます。かなりざっくり言うと、SVMはデータを分ける「境界線」を探し出す方法です。

SVMは、異なるクラスのデータ点から最も離れた線(高次元の場合は超平面)を見つけることで、データを分類します。この「距離」をマージンと呼び、SVMはマージンが最大になるような決定境界を探します。これにより、新しいデータがどちらのクラスに属するかを予測しやすくなります。

ちなみに、「サポートベクター」とは、決定境界に最も近いデータ点のことを指します。これらの点が決定境界を定める役割を果たしており、境界を調整するために重要なポイントとなります。

また、以下にSVMのメリット、デメリットを記載します。

■メリット

  1. 高い分類性能
    SVMは、マージンを最大化する決定境界を求めるため、分類性能が高いです。特に、データの分類が非線形の場合でもきちんと分類できるというのが大きな特徴です。
  2. 過学習のリスクが低い
    マージンの最大化を目的としているため、データに対する一般化能力が高く、過学習のリスクが低いです。のちに説明しますが、正則化パラメータを設定することで、モデルの複雑さと誤分類のバランスを調整できます。
  3. 高次元データに強い
    SVMは高次元空間でも効果的に動作します。特徴量の数が多いデータセット(テキストデータや画像データなど)でも高い精度を保つことができます。

■デメリット

  1. 大規模データには不向き
    計算量が多く、データセットが非常に大きい場合やサンプル数が多い場合には、学習時間が長くなる可能性があります。
     
  2. 出力が確率的ではない
    SVMは分類確率を直接出力するわけではありません。分類の結果はクラスラベル(+1, -1など)で出力されるため、確率的な情報が必要な場合には追加の手法を用いて確率に変換する必要があります。

SVMのアルゴリズム

次にSVMのアルゴリズムについて説明していきます。

ざっくりと言えば、SVMは決定境界とマージンを決めてしまえば終わりです。

それを踏まえると大きく2つ〜3つのプロセスに分けることができます。

  • 決定境界とマージンの定義
     
  • スラッグ変数の導入
     
  • 最適化関数の導入

それぞれ順に説明していきます。

決定境界とマージンの定義

まずは決定境界とマージンについて定義していきます。

下に例として変数が2つでクラス+1(●)に属するグループとクラス-1(✖︎)に属するグループがあったとします。

この時、クラス+1と-1を適切に分ける決定境界は以下の式で表すことができます。

$$f(x_i) = a_1 \cdot x_1 + a_2 \cdot x_2 + b = 0$$

$a_i$ は各説明変数の重み

$b$ は定数項

を表しています。この $a_i$ , $b$ を求めることがSVMの命題となります。

また、決定境界から最も近いサンプルの距離が同じになるように線を引きます。この時、各直線は以下のような式で表すことができます。この時、bを定数倍することで直線を並行移動しています。

$$a_1 \cdot x_1 + a_2 \cdot x_2 + b = 1$$

$$a_1 \cdot x_1 + a_2 \cdot x_2 + b = -1$$

また、上記2つの直線の距離をマージンと呼び、以下の式で表すことができます。

$$\text{マージンの幅} = 2 \times \frac{|a_1 \cdot x_1 + a_2 \cdot x_2 + b|}{\sqrt{{{a_1}^2}+{a_2}^2}} = \frac{2}{\sqrt{{{a_1}^2}+{a_2}^2}} = \frac{2}{|a|}$$

$a_1 \cdot x_1 + a_2 \cdot x_2 + b$ が+1, -1を通ることから $|a_1 \cdot x_1 + a_2 \cdot x_2 + b| = 1$ として計算しています。

このマージンを最大化するように $a_i$ , $b$ を求めればいいいのですが、実際は正しく判別できないサンプルもあります。その場合は損失関数としてスラッグ関数を導入する必要があります。

スポンサーリンク

スラッグ変数の導入

スラッグ変数 $\xi$ は損失関数(ペナルティ)であり、下図のように

正しく分類されマージンの境界上、外側にあるサンプルは $\xi = 0$

それ以外のサンプルは $\xi = |y_i – f(x_i)|$

と定義されます。

各サンプルのスラッグ変数の和である、

$$\sum_{j=1}^{N} \xi_j$$

として、これを最小化するように $a_i$ , $b$ を求める必要があります。

最適化関数の導入

以上からSVMはマージンを最大化し、スラッグ変数を最小化するように $a_i$ , $b$ を求めることで最適な決定境界を引くことができます。

そのために以下の最適化関数 $S$ を定義します。SVMではこの $S$ の最小化を目指します。

$$ S = \frac{1}{2} |a|^2 + C \sum_{i=1}^{N} \xi_i $$

第一項はマージンを表す項になっており、 $S$ を最小化問題にするためにマージン$\frac{2}{|a|}$ に対して逆数を取ります。また、詳細は省きますが、最適化のしやすさから $|a|$ を二乗しています。

第二項 はスラッグ変数を表しており、 $C$ はハイパーパラメータとなっています。 $C$ が大きければそれだけ誤分類が無いように最適化され、 $C$ が小さければマージンが大きくなるように最適化されます。

この最適化関数を解くにはラグランジュの未定乗数法を使用するのですが、今回は解法については割愛させていただいます。

以上の手法により、SVMは決定境界及びマージンを設定することができ、分類問題を解くことができます。

Pythonで実装

それでは実際にPythonでSVMを実行してみましょう。

今回は以下のような条件でSVMを実行しています。

条件説明
学習データランダム関数を使用して生成しています
説明変数説明変数の個数は2つとしています
目的変数1, -1の二値としています
ハイパーパラメータ $C$グリッドサーチで最適化しています

実際のコードが以下のようになっています。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import make_blobs

# データを生成
np.random.seed(42)
X, y = make_blobs(n_samples=100, centers=2, cluster_std=1.5, n_features=2, random_state=42)

# クラスラベルを-1, 1に変換
y = np.where(y == 0, -1, 1)

# 他のクラスに混ざるように外れ値を追加
n_outliers = 5
np.random.seed(0)
outliers = np.random.uniform(low=X.min(axis=0), high=X.max(axis=0), size=(n_outliers, 2))
outlier_labels = np.random.choice([-1, 1], size=n_outliers)
X = np.vstack([X, outliers])
y = np.concatenate([y, outlier_labels])

# ハイパーパラメータCのグリッドサーチ
param_grid = {'C': [0.01, 0.1, 1, 10, 100, 1000]}
grid_search = GridSearchCV(SVC(kernel='linear'), param_grid, cv=5)
grid_search.fit(X, y)

# 最適なモデルを取得
best_model = grid_search.best_estimator_

# サポートベクター、決定境界を描画
def plot_decision_boundary(clf, X, y):
    # 描画範囲の設定
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 500), np.linspace(y_min, y_max, 500))

    # モデルの予測値を取得
    Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    # 決定境界とマージンの描画
    plt.contour(xx, yy, Z, levels=[-1, 0, 1], linestyles=['--', '-', '--'], colors='k')
    plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100, facecolors='none', edgecolors='k')
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.bwr, edgecolors='k')

    plt.title(f'Soft Margin SVM with Linear Kernel (C = {clf.C})')
    plt.xlabel('Feature 1')
    plt.ylabel('Feature 2')
    plt.show()

# 最適なモデルを用いて決定境界をプロット
plot_decision_boundary(best_model, X, y)

# 最適なパラメータの出力
print(f"Best parameter (C): {grid_search.best_params_['C']}")

結果は以下のように出力されました。黒線が決定境界で点線がサポートベクター上の線、丸で囲っているサンプルはサポートベクターとなっています。

外れ値を忍ばしておきましたが、きちんとマージンを最大化しつつ決定境界を引けているようです。

以上がPythonで実際にSVMを実行した結果になります。

終わりに

SVMについてわかりやすく解説したつもりですが、いかがでしょうか?今回は導入として線形分離できる例を説明しましたが、カーネルという概念を導入すると非線形問題でも解くことができるので、またの機会に解説できればと考えています。少しでも皆様の理解の一助となれれば幸いです。

スポンサーリンク

タイトルとURLをコピーしました