Machine Learning: サポートベクターマシン — 最大マージン分類とカーネルトリック

最終更新:2026-08-26

SVMは「最も広い道」を見つけます。つまり、決定境界を両方のクラスからできるだけ遠ざけることで、自然とロバストなモデルになるのです。

1. 学習内容


2. マーケットアナリストの実体験ストーリー

(1) 課題:顧客の価値階層の境界があいまい

Charlieは欧州市場の顧客セグメンテーションを担当しており、顧客を高/中/低の価値階層に分けてサービスリソースを配分しています。しかし、高と中の境界を引くのは簡単ではありません。単一の指標(例:支出額)だけで階層分けすると、境界付近の顧客の20%が頻繁に誤分類されてしまいます。線形の境界では、顧客の多次元な複雑な関係を捉えきれないのです。

(2) SVMのカーネルトリックによる解決策

SVMはカーネルトリックを使ってデータを高次元空間に写像し、以前は分離できなかったデータから明確な境界を見つけ出します。

PYTHON
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# RBF kernel maps to high-dimensional space
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("svm", SVC(kernel="rbf", C=1.0, gamma="scale")),
])
pipe.fit(X_train, y_train)
print(f"Accuracy: {pipe.score(X_test, y_test):.4f}")

(3) 結果:境界付近の顧客の精度が65%から89%に向上

線形の階層分けをSVM-RBFに置き換えたところ、Charlieは境界付近の顧客の分類精度が65%から89%に跳ね上がるのを確認し、サービスリソースの誤配分を40%削減しました。


3. 最大マージン分類器

(1) SVMの核心的な考え方

SVMはマージンを最大化する分離超平面を見つけます。分類結果を決定するのは、マージン境界上にあるサンプル(サポートベクター)だけです。

100%
graph TB
    INPUT[Input Data] --> MAP[Kernel Mapping<br/>Low-D → High-D]
    MAP --> HYPER[Find Max-Margin<br/>Hyperplane]
    HYPER --> SV[Support Vectors<br/>Define the boundary]
    SV --> MARGIN[Margin Width<br/>Larger = More Robust]
    MARGIN --> CLASSIFY[Classification<br/>Decision Function]

▶ サンプル:線形SVMの可視化

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

X, y = make_blobs(n_samples=100, centers=2, random_state=42, cluster_std=1.5)

# Train SVM with linear kernel
svm = SVC(kernel="linear", C=1.0)
svm.fit(X, y)

fig, ax = plt.subplots(figsize=(8, 6))

# Plot data points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr", s=30, edgecolors="black")

# Plot decision boundary and margins
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = svm.decision_function(xy).reshape(XX.shape)

ax.contour(XX, YY, Z, colors="k", levels=[-1, 0, 1], alpha=0.5, linestyles=["--", "-", "--"])

# Highlight support vectors
ax.scatter(svm.support_vectors_[:, 0], svm.support_vectors_[:, 1],
           s=100, facecolors="none", edgecolors="k", linewidths=2)
ax.set_title(f"Linear SVM (C={svm.C})\nSupport vectors: {len(svm.support_vectors_)}")
plt.tight_layout()
plt.savefig("svm_linear.png", dpi=150)

出力:

TEXT 📖 参照専用
# Executed successfully

(2) ハードマージンとソフトマージン(Cパラメータ)

Cの値 マージン 誤分類の許容度 過学習のリスク ユースケース
大(100以上) 狭い ほぼなし 高い ほぼ線形分離可能なデータ
中(1) 中程度 ある程度許容 低い 一般的なケース
小(0.01) 広い 高く許容 低い ノイズの多い/重なり合ったデータ

▶ サンプル:Cパラメータの効果の比較

PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
from sklearn.model_selection import cross_val_score
import numpy as np

X, y = make_blobs(n_samples=200, centers=2, random_state=42, cluster_std=2.5)

for C in [0.01, 0.1, 1.0, 10.0, 100.0]:
    svm = SVC(kernel="rbf", C=C, gamma="scale")
    scores = cross_val_score(svm, X, y, cv=5, scoring="accuracy")
    svm.fit(X, y)
    print(f"C={C:6.2f}: Accuracy={scores.mean():.3f} +/- {scores.std():.3f}, "
          f"Support vectors={len(svm.support_vectors_)}")

出力:

TEXT 📖 参照専用
# Executed successfully

4. カーネルトリック

(1) カーネル関数の選択

▶ サンプル:異なるカーネル関数の比較

PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np

# Non-linear data (moons)
X_moons, y_moons = make_moons(n_samples=500, noise=0.2, random_state=42)

kernels = {
    "Linear": SVC(kernel="linear", C=1),
    "Polynomial (deg=3)": SVC(kernel="poly", degree=3, C=1),
    "RBF": SVC(kernel="rbf", C=1, gamma="scale"),
}

for name, svm in kernels.items():
    pipe = Pipeline([("scaler", StandardScaler()), ("svm", svm)])
    scores = cross_val_score(pipe, X_moons, y_moons, cv=5, scoring="accuracy")
    print(f"{name:20s}: Accuracy={scores.mean():.3f}")

出力:

TEXT 📖 参照専用
# Executed successfully
カーネル 数式 ユースケース 主なパラメータ
線形(Linear) $\langle x, x' \rangle$ 高次元で線形分離可能 C
RBF $e^{-\gamma|x-x'|^2}$ 汎用的、非線形 C, gamma
多項式(Polynomial) $(\langle x, x' \rangle + c)^d$ 特徴量の相互作用 C, degree, coef0
シグモイド(Sigmoid) $\tanh(\gamma\langle x, x' \rangle + c)$ ニューラルネットワーク的 C, gamma, coef0

(2) gammaパラメータ

▶ サンプル:RBFカーネルにおけるgammaの効果

PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_moons
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

X, y = make_moons(n_samples=500, noise=0.15, random_state=42)

for gamma in [0.01, 0.1, 1.0, 10.0, "scale", "auto"]:
    pipe = Pipeline([
        ("scaler", StandardScaler()),
        ("svm", SVC(kernel="rbf", C=1, gamma=gamma)),
    ])
    scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
    print(f"gamma={str(gamma):6s}: Accuracy={scores.mean():.3f}")

出力:

TEXT 📖 参照専用
# Executed successfully
gamma 決定境界 効果
非常に小さい(0.01) 滑らか 学習不足
中程度(1) 適度に曲がる 多くの場合最適
非常に大きい(10以上) 非常に不規則 過学習
"scale" 1/(n_features * X.var()) sklearnのデフォルト
"auto" 1/n_features 従来のデフォルト

5. スケーリングが重要な理由とSVR

(1) SVMの特徴量スケールへの敏感さ

▶ サンプル:スケーリングがSVMに与える影響

PYTHON
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)

# Without scaling
svm_raw = SVC(kernel="rbf", C=1, gamma="scale")
scores_raw = cross_val_score(svm_raw, X, y, cv=5, scoring="accuracy")

# With scaling
pipe = Pipeline([("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", C=1, gamma="scale"))])
scores_scaled = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")

print(f"Without scaling: {scores_raw.mean():.4f}")
print(f"With scaling:    {scores_scaled.mean():.4f}")

出力:

TEXT 📖 参照専用
# Executed successfully
⚠️ 注意: SVMは距離尺度を使ってマージンを計算します。特徴量のスケールが大きく異なる場合(例:年齢が0〜100に対して収入が0〜1,000,000)、スケールの大きい特徴量がマージンの計算を支配してしまいます。SVMには必ずStandardScalerを組み合わせましょう。

(2) SVR回帰

▶ サンプル:SVRによる売上予測

PYTHON
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
import numpy as np

rng = np.random.default_rng(42)
X = rng.uniform(0, 100, (200, 3))
y = 50 + 0.8 * X[:, 0] + rng.normal(0, 5, 200)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# SVR with RBF kernel
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("svr", SVR(kernel="rbf", C=100, epsilon=5)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)

print(f"SVR R²: {r2_score(y_test, y_pred):.4f}")
print(f"SVR MAE: {mean_absolute_error(y_test, y_pred):.2f}")

出力:

TEXT 📖 参照専用
# Executed successfully
パラメータ 意味 効果
C 正則化の強さ Cが大きい → 誤差の許容が小さい → 過学習の可能性
epsilon(ε) 不感応帯域の幅 εが大きい → より多くの点が無視される → より滑らかなフィット
カーネル カーネル関数の種類 分類用SVMと同じ

6. Charlieの欧州顧客価値セグメンテーション

▶ サンプル:完全なSVM分類プロジェクト

PYTHON
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import classification_report
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 2000
df = pd.DataFrame({
    "annual_spending_eur": rng.exponential(5000, n),
    "purchase_frequency": rng.poisson(8, n),
    "avg_order_value_eur": rng.exponential(150, n),
    "tenure_months": rng.integers(1, 60, n),
    "support_tickets": rng.poisson(3, n),
})

# Value tier logic (EUR-based)
score = (df["annual_spending_eur"] / 5000 * 0.3
         + df["purchase_frequency"] / 20 * 0.25
         + df["avg_order_value_eur"] / 200 * 0.2
         + df["tenure_months"] / 60 * 0.15
         + rng.normal(0, 0.1, n))

df["tier"] = pd.cut(score, bins=[0, 0.3, 0.6, 1.5], labels=["Low", "Medium", "High"])

X = df.drop(columns=["tier"])
y = df["tier"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# Grid search for best SVM parameters
pipe = Pipeline([("scaler", StandardScaler()), ("svm", SVC())])
param_grid = {
    "svm__kernel": ["linear", "rbf"],
    "svm__C": [0.1, 1, 10],
    "svm__gamma": ["scale", 0.1, 1],
}

grid = GridSearchCV(pipe, param_grid, cv=3, scoring="accuracy", n_jobs=-1)
grid.fit(X_train, y_train)

print(f"Best params: {grid.best_params_}")
print(f"Best CV accuracy: {grid.best_score_:.4f}")
print(f"\nTest report:\n{classification_report(y_test, grid.predict(X_test))}")

出力:

TEXT 📖 参照専用
# Executed successfully

❓ よくある質問

Q SVMのデータはなぜスケーリングが必要なのですか?
A SVMは距離尺度に基づいてマージンを計算するためです。特徴量Aの範囲が0〜1で特徴量Bの範囲が0〜1,000,000の場合、Bがマージンの計算を完全に支配してしまいます。StandardScalerを使うことで、すべての特徴量が均等に寄与するようになります。
Q RBFカーネルではgammaとCのどちらがより重要ですか?
A どちらも重要で、互いに影響し合います。GridSearchCVを使って両方を同時に探索してください。典型的な探索範囲はC=[0.1, 1, 10, 100]、gamma=[0.001, 0.01, 0.1, 1, "scale"]です。
Q SVMは大規模データセットに向いていますか?
A あまり向いていません。SVMの学習計算量はO(n²)からO(n³)で、サンプル数が10万件を超えると非常に遅くなります。大規模データセットには、LinearSVC(線形カーネル)やRandom Forest/XGBoostを使ってください。
Q SVMは確率を出力できますか?
A デフォルトではできません。probability=Trueを設定すると、追加でPlatt Scalingモデルを学習して確率を出力しますが、学習は遅くなり、確率の較正もロジスティック回帰ほど良くありません。
Q 線形カーネルとロジスティック回帰の違いは何ですか?
A どちらも線形分類器です。違いは、SVMがマージンを最大化する(境界上のサンプルだけに注目する)のに対し、ロジスティック回帰は尤度を最大化する(すべてのサンプルを考慮する)点です。SVMは境界付近でよりロバストで、ロジスティック回帰はより適切に較正された確率を出力します。
Q SVRのepsilonパラメータはどのように選べばよいですか?
A epsilonは不感応帯域の幅で、εより小さい誤差は損失にカウントされません。典型的な値は、目的変数のノイズレベルの見積もりです。例えば、売上予測の誤差が±5千米ドル以内であれば、ε=5と設定します。

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):SVC(カーネル="rbf")を使ってmake_moonsデータセットを分類してください。StandardScalerを使う場合と使わない場合の精度を比較しましょう。ヒント:Pipelineと直接fitの比較。
  2. 中級(難易度 ⭐⭐):GridSearchCVを使ってIrisデータセットで最適なSVMパラメータ(カーネル、C、gamma)を探索し、最適な組み合わせを見つけてください。ヒント:Pipeline内のSVC + param_grid。
  3. 挑戦(難易度 ⭐⭐⭐):Charlieの顧客セグメンテーションを再現してください。3クラスの顧客データを生成し、SVM/LogisticRegression/RandomForestの分類レポートを比較して、境界があいまいな多クラスシナリオでどのモデルが最も良く機能するかを分析しましょう。ヒント:classification_reportを使ってprecision/再現率/f1を比較する。

← 前へ:決定木とランダムフォレスト | 次へ:KNNとクラスタリング →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%