サポートベクターマシン(SVM)のPython実装

目次

はじめに

この記事では、サポートベクターマシン(SVM)の実装方法を詳しく解説します。SVMは、機械学習の教師あり学習の一手法で、主に分類問題に用いられます。

SVMは、データ点を高次元空間に写像し、そこで最適な分離直線(または超平面)を見つけることで分類を行います。この分離直線(または超平面)は、マージン(直線(または超平面)と最も近いデータ点との距離)が最大となるように選ばれます。マージンを最大化することで、SVMは汎化性能の高いモデルを構築することができます。

この実装例では、SVMモデルの構築に必要な手順と考え方を理解することができます。特に、データの前処理とSVMのハイパーパラメータの選択は、モデルの性能に大きな影響を与えます。また、モデルの評価では、数値的な指標だけでなく、決定境界の可視化も重要な役割を果たします。

この記事が、SVMの理解を深め、実際の問題へ適用する際の一助となれば幸いです。

実装方法

1. ライブラリのインポート

SVMモデルを構築するには、様々なライブラリやモジュールが必要になります。ここでは以下のライブラリをインポートしています。

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn import datasets
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
Pythonコード詳細
  • matplotlibはデータの可視化に使用します。SVMの決定境界を可視化する際に重要な役割を果たします。
  • numpyは数値計算に使用する基本的なライブラリです。SVMの計算には多くの線形代数の操作が必要となるため、numpyは欠かせません。
  • pandasはデータ処理に使用します。DataFrameを使ってデータを構造化することで、データの理解と前処理が容易になります。
  • sklearnはPythonで機械学習モデルを構築するためのライブラリです。
    • datasetsモジュールからサンプルデータをロードできます。SVMの性能を評価する際によく使われる標準データセットが含まれています。
    • metricsモジュールからSVMの性能評価指標を計算するための関数が提供されています。
    • model_selectionモジュールからデータをトレーニングセットとテストセットに分割する関数が提供されています。これはSVMの汎化性能を正しく評価するために重要です。
    • preprocessingモジュールからデータの標準化などの前処理手法が提供されています。SVMは特徴量のスケールに敏感なため、前処理は欠かせません。
    • そして最も重要なのがsvmモジュールです。ここからSVMの実装であるSVCクラスをインポートしています。

2. データセットのインポート

SVMの性能を評価するには、適切なデータセットを選ぶ必要があります。ここでは、sklearn.datasetsから乳がんデータセットをロードしています。

dataset = datasets.load_breast_cancer()

print('columns:', dataset.feature_names)
print('ラベルの種類:', np.unique(dataset.target))
Pythonコード詳細

乳がんデータセットは、乳がんの診断データをまとめたデータセットで、30個の特徴量と2つのクラスラベル(悪性/良性)からなる二値分類問題です。SVMはこのような高次元かつ線形分離可能なデータセットに対して高い性能を発揮することが知られています。

  • .dataには30個の特徴量の値が格納されています。SVMはこれらの特徴量を使って最適な分離直線(または超平面)を見つけます。
  • .targetには目的変数の正解ラベル(0 or 1)が格納されています。SVMはこのラベルを使って学習を行います。

出力

columns: ['mean radius' 'mean texture' 'mean perimeter' 'mean area'
 'mean smoothness' 'mean compactness' 'mean concavity'
 'mean concave points' 'mean symmetry' 'mean fractal dimension'
 'radius error' 'texture error' 'perimeter error' 'area error'
 'smoothness error' 'compactness error' 'concavity error'
 'concave points error' 'symmetry error' 'fractal dimension error'
 'worst radius' 'worst texture' 'worst perimeter' 'worst area'
 'worst smoothness' 'worst compactness' 'worst concavity'
 'worst concave points' 'worst symmetry' 'worst fractal dimension']
ラベルの種類: [0 1]

最初に.feature_names.targetの中身を確認しています。これにより、SVMが扱うデータの概要を把握できます。

3. データセットの確認

SVMモデルを構築する前に、データセットの詳細を確認し、理解を深めることが重要です。ここではpandasのDataFrameを使ってデータを視覚的に確認できる形に変換しています。

pd.set_option('display.max_columns', None)
df = pd.DataFrame(dataset.data, columns=dataset.feature_names)

df['target'] = dataset.target
df.head()
Pythonコード詳細
  1. pd.set_option('display.max_columns', None)
    • pandasの表示オプションを変更し、出力時にすべての列を表示するようにしています。
  2. df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
    • DataFrameを作成し、特徴量(dataset.data)を割り当てています。列名にはdataset.feature_namesを使用しています。
  3. df['target'] = dataset.target
    • 目的変数(dataset.target)を新しい列として追加しています。
  4. df.head()
    • 作成したDataFrameの先頭5行を表示することで、データの中身を確認できます。

出力

このようにDataFrameに変換することで、データをよりわかりやすい形で扱えるようになります。SVMに限らず、機械学習モデルを構築する際は、データの理解が非常に重要です。

具体的には、以下のようなことを確認します。

  • 欠損値の有無: SVMは欠損値を扱えないため、欠損値がある場合は何らかの処理が必要です。
  • データの型: 特徴量は数値型である必要があります。カテゴリ型の特徴量がある場合は、数値型に変換する必要があります。
  • 外れ値の有無: 外れ値はSVMの学習を大きく歪めてしまう可能性があります。
  • 特徴量のスケール: SVMは特徴量のスケールに敏感なため、スケールが大きく異なる特徴量がある場合は標準化などの前処理が必要です。
  • クラスの割合: 不均衡データ(あるクラスのデータが極端に少ない場合)では、SVMの学習が適切に行われない可能性があります。

次の手順では、このデータに対して相関分析を行い、SVMの性能向上につながる特徴量を選択します。特徴量選択は、SVMの汎化性能を高める上で重要なステップとなります。

4. 相関関係の確認

SVMは特徴量の選択に大きな影響を受けます。冗長な特徴量や不要な特徴量を含めてしまうと、かえってSVMの性能が下がってしまう可能性があります。そこで、特徴量と目的変数の間の相関関係を確認し、SVMの性能向上に寄与する特徴量を選択することが重要です。

corr_matrix = df.corr() 

sorted_columns = corr_matrix.abs().sort_values('target', ascending=False).index

sorted_corr_matrix = corr_matrix[sorted_columns].loc[sorted_columns]
sorted_corr_matrix
Pythonコード詳細
  1. corr_matrix = df.corr()
    • DataFrameの.corr()メソッドで相関行列を計算しています。
  2. sorted_columns = corr_matrix.abs().sort_values('target', ascending=False).index
    • 目的変数'target'との絶対値の相関が高い順に特徴量を並び替えています。
  3. sorted_corr_matrix = corr_matrix.loc[sorted_columns, sorted_columns]
    • 並び替えた列順で相関行列を作り直しています。
  4. print(sorted_corr_matrix)
    • 並び替えた相関行列を出力しています。

出力

これにより、目的変数との相関が高い特徴量がわかります。SVMでは、この情報を使って分離直線(または超平面)の構築に寄与する特徴量を選択することができます。

相関の高い特徴量を選択することで、SVMの汎化性能が向上する可能性があります。一方で、相関が高くても冗長な特徴量を含めてしまうと、計算量が増大し、かえって性能が下がってしまうこともあります。そのため、ドメイン知識と組み合わせながら、慎重に特徴量を選択する必要があります。

5. 説明変数と目的変数に分割

前の手順で確認した相関関係に基づき、SVMの学習に使用する特徴量を選択します。そして、特徴量(X)と目的変数(y)に分離させます。

select_features = ['worst perimeter', 'worst concave points']
X = df.loc[:, select_features].values
y = df.loc[:, 'target'].values
Pythonコード詳細
  1. select_features = ['worst perimeter', 'worst concave points']
    • 4.の相関分析で相関が高かった2つの特徴量を選択しています。
  2. X = df.loc[:, select_features].values
    • DataFrameから選択した特徴量の列を抽出し、numpyの配列に変換しています。
  3. y = df.loc[:, 'target'].values
    • 同様に目的変数の値を配列に変換しています。

特徴量Xと目的変数yに分離することで、SVMの学習に必要なデータの形式が整います。

SVMは教師あり学習のアルゴリズムで、特徴量Xと目的変数yのペアを使って学習を行います。学習の目的は、特徴量空間内で目的変数のクラスを最もよく分離する直線(または超平面)を見つけることです。この直線(または超平面)は、サポートベクターと呼ばれる重要なデータ点のみに依存して決まります。

6. 訓練データとテストデータに分割

次に、特徴量Xと目的変数yのデータを訓練データとテストデータに分割します。この分割は、SVMの汎化性能を正しく評価するために非常に重要です。

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=0, stratify=y)
Pythonコード詳細

sklearn.model_selection.train_test_split関数を使って分割しています。

  • test_size=0.3で、全体の30%をテストデータとして割り当てています。
  • random_state=0で乱数シードを固定しています。これにより、分割の再現性が確保されます。
  • stratify=yを指定することで、訓練データとテストデータの目的変数の割合が元データと同じになるように層化抽出を行います。

訓練データはSVMの学習に使用され、テストデータはSVMの汎化性能の評価に使用されます。

テストデータはSVMの学習には一切使用せず、学習済みのSVMモデルの性能評価にのみ使用することが重要です。テストデータを学習に使ってしまうと、テストデータに対して過剰に適合したモデルになってしまい、真の汎化性能を測ることができなくなります。

また、層化抽出(stratify)を使うことで、クラス不均衡データに対しても適切に分割を行うことができます。SVMはクラスの割合が大きく異なるデータに対しては性能が出にくいため、この点は重要です。

通常、テストデータの割合は20~30%程度が一般的ですが、データ数が少ない場合はより多めのテストデータを確保する必要があります。一方で、テストデータが多すぎると、SVMの学習に使えるデータが減ってしまうため、バランスを考えて設定する必要があります。

以上の手順で、SVMの学習と評価に適した形にデータを分割することができました。次は、このデータを使ってSVMモデルを構築していきます。

7. 特徴量のスケーリング

SVMは特徴量のスケールに非常に敏感なアルゴリズムです。異なるスケールの特徴量がある場合、大きな値を持つ特徴量が支配的になり、小さな値を持つ特徴量はほとんど無視されてしまう可能性があります。これは、SVMが特徴量空間内の距離に基づいて最適な分離直線(または超平面)を決定するためです。

そこで、特徴量のスケールを揃えるために、標準化を行います。

sc = StandardScaler()

sc.fit(X_train)

X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
Pythonコード詳細
  1. sc = StandardScaler()
    • scikit-learnのStandardScalerクラスのインスタンスを作成します。
  2. sc.fit(X_train)
    • 訓練データX_trainを使って、特徴量ごとの平均と標準偏差を計算します。
  3. X_train_std = sc.transform(X_train)
    • 計算した平均と標準偏差を使って、訓練データを標準化します。
  4. X_test_std = sc.transform(X_test)
    • 訓練データから計算した平均と標準偏差を使って、テストデータも標準化します。

標準化により、全ての特徴量が平均0、分散1に変換されます。これにより、特徴量間のスケールの差が解消され、SVMは各特徴量を公平に扱うことができるようになります。

訓練データとテストデータに対して、別々のスケーリングを適用してはいけません。テストデータは未知のデータを想定しているため、訓練データから得られたスケーリングのルールを適用する必要があります。

また、スケーリングの方法には、標準化の他にも最小最大スケーリングなどがあります。データの性質によって適切な方法を選択する必要があります。SVMでは一般的に標準化が用いられますが、特徴量の分布が一様である場合などは、他のスケーリング方法が適している場合もあります。

8. 訓練データによるモデルの学習

スケーリングされた訓練データを使って、SVMモデルを学習します。ここでは、線形SVMを使用しています。

# 線形SVMのインスタンスを生成
classifier = SVC(kernel='linear', C=1.0, random_state=1)
# 訓練データをモデルに適合させる
classifier.fit(X_train_std, y_train)
Pythonコード詳細
  1. classifier = SVC(kernel='linear', C=1.0, random_state=1)
    • SVCクラスのインスタンスを作成します。
    • kernel='linear'で線形SVMを指定しています。
    • C=1.0はコストパラメータです。大きな値は誤分類を許容しにくく、小さな値は誤分類を許容しやすくなります。
    • random_state=1は乱数シードを指定しています。再現性の確保のために使用します。
  2. classifier.fit(X_train_std, y_train)
    • 訓練データX_train_stdと目的変数y_trainを使ってSVMモデルを学習します。

SVMの学習は、特徴量空間内で目的変数のクラスを最もよく分離する直線(または超平面)を見つける最適化問題として定式化されます。線形SVMの場合、この直線(または超平面)は特徴量の線形結合で表現されます。

学習の過程では、訓練データの一部がサポートベクターとして選択されます。これらのサポートベクターのみが、分離直線(または超平面)の決定に寄与します。他の訓練データは無視されます。これがSVMの特徴の一つであり、高次元でもデータ数が少ない場合に有効に働きます。

マージンの大きさは、コストパラメータCによって制御されます。Cが大きいほどマージンは狭くなり、訓練データへの適合が強くなります。ただし、Cが大きすぎると過学習が起こる可能性があります。逆にCが小さすぎると、未学習になってしまいます。

9. 新しいデータセットで予測

学習済みのSVMモデルを使って、新しいデータポイントに対する予測を行うことができます。

# 新しいデータポイントで予測
new_data = [[-1,1]]
pred_label = classifier.predict(sc.transform(new_data))[0]
print(f'予測したクラス:{pred_label}({dataset.target_names[pred_label]})')
Pythonコード詳細
  1. new_data = [[-1,1]]
    • 新しいデータポイントの特徴量の値をリストで定義しています。
  2. classifier.predict(sc.transform(new_data))
    • predictメソッドを使って、新しいデータポイントのクラスラベルを予測します。
    • sc.transform(new_data)で、新しいデータポイントに対して訓練データと同じスケーリングを適用しています。これは重要なステップです。
  3. [0]
    • predictメソッドは予測結果をリストで返すため、[0]で最初の要素を取り出しています。
  4. dataset.target_names[pred_label]
    • 予測されたラベルに対応する実際のクラス名を取得しています。

出力

予測したクラス:0(malignant) # 0:悪性

SVMによる予測は、新しいデータポイントがどちらのクラスに属するかを決定する分類問題です。具体的には、学習された分離直線(または超平面)に対して、新しいデータポイントがどちら側に位置するかを計算することで、クラスラベルを決定します。

この予測は、SVMモデルの実用的な使用方法の一つです。ただし、予測の信頼性は、モデルの汎化性能に大きく依存します。そのため、次の手順でテストデータを使ってモデルの性能を評価します。

10. テストデータで予測

学習済みのSVMモデルの真の性能を評価するために、モデルの学習に使用していないテストデータに対して予測を行います。

y_pred = classifier.predict(X_test)
Pythonコード詳細
  1. classifier.predict(X_test_std)
    • predictメソッドに標準化済みのテストデータX_test_stdを渡して、テストデータの各データポイントに対する予測を行います。
  2. y_pred = ...
    • 予測結果をy_predという変数に格納しています。

ここで得られたy_predは、テストデータの各データポイントに対するSVMモデルの予測クラスラベルを表しています。

SVMモデルは、訓練データから学習された分離直線(または超平面)を使って、新しいデータポイントのクラスを予測します。テストデータは、モデルにとって未知のデータであるため、テストデータに対する予測性能は、モデルの汎化能力を反映しています。

汎化能力とは、未知のデータに対してどの程度正確に予測できるかを表す尺度です。これは機械学習モデルの最も重要な性質の一つです。汎化能力が高いモデルは、過学習を避け、未知のデータに対しても robust な予測を行うことができます。

次の手順では、この予測結果y_predと実際の正解ラベルy_testを比較することで、SVMモデルの性能を定量的に評価します。

11. モデルの性能評価

予測結果と実際の正解ラベルを比較することで、SVMモデルの性能を評価します。ここでは、正解率(Accuracy)を使って評価しています。

# 正分類のデータ点の個数を表示
print(f'正分類のデータ点: {(y_test == y_pred).sum()}個/{len(y_test)}個' )

# 分類の正解率を表示
print(f'Accuracy(Test): {accuracy_score(y_test, y_pred):.3f}')
Pythonコード詳細
  1. (y_test == y_pred).sum()
    • 予測ラベルy_predと正解ラベルy_testを比較し、一致している(正しく分類できている)データポイントの数を計算しています。
  2. len(y_test)
    • テストデータの総数を表しています。
  3. accuracy_score(y_test, y_pred)
    • scikit-learnのaccuracy_score関数を使って、正解率を計算しています。正解率は、正しく分類できたデータポイントの割合を表します。

出力

正分類のデータ点: 155個/171個
Accuracy(Test): 0.906

出力結果から、テストデータ全体に対する正しい分類の数と割合(正解率)を知ることができます。

正解率は、分類問題における最も基本的な性能指標の一つです。しかし、クラス不均衡データの場合、正解率だけでは十分な評価ができない可能性があります。例えば、全体の90%がクラスAで10%がクラスBのデータセットでは、全てをクラスAと予測するモデルの正解率は90%になりますが、このモデルはクラスBを全く予測できていません。このような場合、適合率(Precision)、再現率(Recall)、F1スコアなどの他の指標も合わせて評価する必要があります。

SVMの性能は、ハイパーパラメータ(例えば、カーネルの種類やペナルティ係数Cなど)の選択に大きく依存します。最適なハイパーパラメータを見つけるために、グリッドサーチやランダムサーチなどの手法を用いて、ハイパーパラメータのチューニングを行うことが一般的です。

12. 性能評価の可視化

SVMモデルの予測性能を視覚的に理解するために、決定境界を可視化します。ここでは、訓練データとテストデータの両方に対して、決定境界がどのように引かれているかを示します。

# 訓練データとテストデータの最小値と最大値を計算
def calculate_bounds(X1, X2):
    X1_min, X1_max = X1.min() - (X1.max()-X1.min())/20, X1.max() + (X1.max()-X1.min())/20
    X2_min, X2_max = X2.min() - (X2.max()-X2.min())/20, X2.max() + (X2.max()-X2.min())/20
    return X1_min, X1_max, X2_min, X2_max
def plot_data(ax, X_set, y_set, X1, X2, Z, colors, kind, classifier):
    cmap = ListedColormap(colors[:len(np.unique(y_set))])
    ax.contourf(X1, X2, Z, alpha=0.3, cmap=cmap)
    for idx, feature in enumerate(np.unique(y_set)):
        ax.scatter(x=X_set[y_set == feature, 0], 
                   y=X_set[y_set == feature, 1],
                   alpha=0.5, 
                   color=colors[idx],
                   marker='o', 
                   label=dataset.target_names[feature], 
                   edgecolor='black')
    ax.set_xlabel(select_features[0])
    ax.set_ylabel(select_features[1])
    ax.set_title(f'{type(classifier).__name__} ({kind})')
    ax.legend(loc='best')
def plot_decision_regions(X_train_std, X_test_std, y_train, y_test, classifier):
    # マーカーとカラーマップの準備
    colors = ('red', 'blue')
    
    # スケーリング前の元のデータに変換
    X_train_set, y_train_set = sc.inverse_transform(X_train_std), y_train
    X_test_set, y_test_set = sc.inverse_transform(X_test_std), y_test
    
    # 訓練データとテストデータの範囲を統一
    X_combined = np.vstack((X_train_set, X_test_set))
    X1_min, X1_max, X2_min, X2_max = calculate_bounds(X_combined[:, 0], X_combined[:, 1])
    
    # グリッドポイントの生成
    X1, X2 = np.meshgrid(np.arange(X1_min, X1_max, step=(X1_max - X1_min) / 1000),
                         np.arange(X2_min, X2_max, step=(X2_max - X2_min) / 1000))
    # 各特徴を1次元配列に変換して予測を実行
    Z = classifier.predict(sc.transform(np.array([X1.ravel(), X2.ravel()]).T))
    # 予測結果を元のグリッドポイントのデータサイズに変換
    Z = Z.reshape(X1.shape)
    
    fig, ax = plt.subplots(1, 2, figsize=(8, 4))
    
    # 訓練データとテストデータのプロット
    plot_data(ax[0], X_train_set, y_train_set, X1, X2, Z, colors, "Training set", classifier)
    plot_data(ax[1], X_test_set, y_test_set, X1, X2, Z, colors, "Test set", classifier)
    
    plt.tight_layout()
    plt.show()
# 決定境界のプロット
plot_decision_regions(X_train_std, X_test_std, y_train, y_test, classifier)
Pythonコード詳細

この可視化は以下の手順で行われています。

  1. calculate_bounds関数で、プロットの範囲を計算します。特徴量の最小値と最大値から少し余裕を持たせた範囲を返します。
  2. plot_data関数で、実際のプロットを行います。
    • contourfで決定境界を等高線で描画します。
    • scatterで訓練データとテストデータの点をプロットします。
    • 軸ラベル、タイトル、凡例を設定します。
  3. plot_decision_regions関数で、訓練データとテストデータそれぞれに対してplot_data関数を呼び出し、2つのプロットを並べて表示します。
    • meshgridで、プロットする範囲内に等間隔の点を生成します。
    • 生成した点に対してpredictを行い、予測結果に基づいて色分けします。
  4. 最後にplot_decision_regions関数を呼び出して、実際にプロットを表示します。

出力

このプロットから、以下のことが読み取れます。

  • SVMモデルが訓練データに対してどの程度適合しているか(訓練データのプロット)
  • SVMモデルがテストデータに対してどの程度汎化しているか(テストデータのプロット)
  • 決定境界の形状(等高線)
  • 誤分類の傾向(異なる色の点が混ざっている領域)

SVMの決定境界は、サポートベクターと呼ばれる重要なデータ点のみに依存して決まります。線形SVMの場合、決定境界は特徴量空間内の直線(または超平面)になります。一方、カーネルトリックを使った非線形SVMの場合、特徴量空間内では非線形な決定境界になります。

可視化は、SVMモデルの振る舞いを直感的に理解するために非常に有益です。単に数値的な評価だけでなく、視覚的な情報からもモデルの性能や問題点を読み取ることができます。例えば、訓練データでは良好な決定境界が得られているのにテストデータではうまくいっていない場合は、過学習が疑われます。逆に、訓練データでもテストデータでも決定境界が適切でない場合は、特徴量の選択やハイパーパラメータの設定が不適切である可能性があります。

以上のように、可視化はSVMモデルの開発と評価において重要な役割を果たします。数値的な評価と視覚的な評価を組み合わせることで、SVMモデルの性能をより深く理解することができます。

まとめ

この記事では、scikit-learnを使ったSVMモデルの実装方法を詳細に解説しました。

SVMは、線形分離可能なデータに対して非常に効果的なアルゴリズムです。また、カーネルトリックを用いることで、非線形のデータにも適用可能です。ただし、SVMの性能は、ハイパーパラメータの選択に大きく依存します。適切なハイパーパラメータを見つけるために、グリッドサーチやランダムサーチなどの手法が用いられます。

また、SVMは二値分類問題だけでなく、多クラス分類問題にも拡張可能です。一対一法や一対他法などの手法を用いることで、多クラス問題をいくつかの二値分類問題に分解し、SVMを適用することができます。

この記事で示した実装方法は、SVMに限らず、他の機械学習アルゴリズムにも応用できる汎用的なものです。データの前処理、モデルの構築、評価という一連の流れは、機械学習プロジェクトに共通するものだからです。

機械学習のモデル開発では、これらの工程を繰り返し、試行錯誤することが重要です。この記事が、そのための基礎知識と実践的なスキルを提供できていれば幸いです。SVMは理論的にも実用的にも非常に重要なアルゴリズムです。ここで得た知見を活かし、さらなる探求を続けていただければと思います。

この記事が気に入ったら
フォローしてね!

この記事が参考になった方はシェアしてね!
  • URLをコピーしました!

本コンテンツへの意見や質問

コメントする

目次