【Python】SVM（サポートベクターマシン）を実装してみる

# はじめに
本記事ではPythonでSVM（サポートベクターマシン）の実装を行います。データはscikit-learnのサンプルで登録されていてる乳がんのデータセット「load_breast_cancer」を使用しています。

# SVMとは
SVMとは機械学習モデルの一種で強力なアルゴリズムです。教師あり学習のひとつであり、主に分類（データがどのグループに属するかを分類する手法）で使用します。マージン最大化といった考え方を使用し、クラス分けを行います。

## マージン最大化について
マージン最大化とは、2つのサポートベクトル間の距離（マージン）が最大になるように決定境界を引くことです。
- `サポートベクトル`
決定境界から最も近い点のこと
- `決定境界`
機械学習の分類において、クラス分けの境目となる直線や曲線のこと

マージン最大化のイメージは以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/852f4c82-ed49-ce31-04b6-9eea0c5aece6.png)

# Python実行環境
python 3.10.4
Windows 11 Home

# 使用するデータの構造
使用するデータの説明変数と目的変数は以下の通りです。

- 説明変数
`load_breast_cancer`の`dataset.data`を使用します。
データの中身は以下で確認することができます。（乳房塊の微細針吸引物のデジタル画像に含まれる細胞核の特徴が入っている模様）

    ```python:説明変数に使うデータの確認
    import pandas as pd
    from sklearn.datasets import load_breast_cancer
    
    dataset = load_breast_cancer()
    
    # 1.データの行数、列数を確認する
    print(dataset.data.shape)
    
    # 2.データのカラムを確認する
    print(dataset.feature_names)
    
    # 3.データのサンプルを表示する
    sanple_df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
    print(sanple_df.head(10))
    ```

    ```:実行結果
    「1.データの行数、列数を確認する」の実行結果（569行30列）
    (569, 30)
    
    「2.データのカラムを確認する」の実行結果
    ['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']
    
    「3.説明変数データのサンプルを表示する」の実行結果
    mean radius  mean texture  ...  worst symmetry  worst fractal dimension
    0        17.99         10.38  ...          0.4601                  0.11890
    1        20.57         17.77  ...          0.2750                  0.08902
    2        19.69         21.25  ...          0.3613                  0.08758
    3        11.42         20.38  ...          0.6638                  0.17300
    4        20.29         14.34  ...          0.2364                  0.07678
    5        12.45         15.70  ...          0.3985                  0.12440
    6        18.25         19.98  ...          0.3063                  0.08368
    7        13.71         20.83  ...          0.3196                  0.11510
    8        13.00         21.82  ...          0.4378                  0.10720
    9        12.46         24.04  ...          0.4366                  0.20750
    
    ```
　
- 目的変数
`load_breast_cancer`の`dataset.target`を使用します。
データの中身は以下で確認することができます。（悪性か良性かの情報が入っている）

    ```python:目的変数に使うデータの確認
    import pandas as pd
    from sklearn.datasets import load_breast_cancer
    
    dataset = load_breast_cancer()
    
    # 1.データの行数、列数を確認する
    print(dataset.target.shape)
    
    # 2.データの対応を確認する
    print(dataset.target_names)
    
    # 3.データのサンプルを表示する(先頭10件は全て0だったため、末尾を表示)
    sanple_df = pd.DataFrame(dataset.target, columns=['乳がん診断結果'])
    print(sanple_df.tail(10))
    ```
    
    ```:実行結果
    「1.データの行数、列数を確認する」の実行結果（569行1列）
    (569,)
    
    「2.データの対応を確認する」の実行結果（0が悪性、1が良性）
    ['malignant' 'benign']
    
    「3.データのサンプルを表示する」の実行結果
         乳がん診断結果
    559        1
    560        1
    561        1
    562        0
    563        0
    564        0
    565        0
    566        0
    567        0
    568        1
    ```

# SVMの実装
PythonでSVMを実装したプログラムは以下になります。

```python
from sklearn.datasets import load_breast_cancer 
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt
import umap.umap_ as umap

def main():
    x_train, x_test, y_train, y_test = split_data_set()
    calculate_accuracy(x_train, x_test, y_train, y_test)
    check_group_2d(x_test, y_test)

#乳がんのデータを学習用とテスト用に分割する
def split_data_set():
    dataset = load_breast_cancer()
    x = dataset.data #説明変数
    y = dataset.target #目的変数

    #学習用データ（train）を8割、テストデータ（test）を2割に分ける
    #stratify = y 悪性と良性の割合が訓練データとテストデータで同じになるようにする
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, stratify = y)
    return x_train, x_test, y_train, y_test 

#SVMを使用し、テストデータの正解率を算出する
def calculate_accuracy(x_train, x_test, y_train, y_test):
    #グループ分けがうまくいかない場合はkernelを
    #'rbf'、'poly'、'sigmoid'等の別のものに変える
    model = SVC(kernel='linear')
    model.fit(x_train, y_train)
    pred = model.predict(x_test) 
    score = accuracy_score(y_test, pred)
    print("テストデータの正解率：" + str(score))

#説明変数を2次元に次元削減し、グループ分けの結果を出力する
def check_group_2d(x_test, y_test):
    #2次元に次元削減する
    model_umap = umap.UMAP(n_components=2,n_neighbors=100)
    vecs_list = model_umap.fit_transform(x_test)
    u_X = vecs_list[:,0]
    u_Y = vecs_list[:,1]

    #グラフを描写する
    sns.set(font="Hiragino Maru Gothic Pro",context="talk")
    plt.figure(figsize=(8, 8))
    color_codes = {0:"red",1:"blue"}
    colors = [color_codes[x] for x in y_test]
    plt.scatter(u_X,u_Y,color=colors)
    plt.show()

if __name__ == '__main__':
    main()
```

# 実行結果

- テストデータの正解率
    ```
    テストデータの正解率：0.9649122807017544
    ```
    約96％は正解（悪性のデータを正しく悪性、良性のデータを正しく良性と算出）となりました。
　
 
- 説明変数を2次元に次元削減後、グループ分けした結果

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/02490aeb-1964-d5ed-56d4-d5fe3289a637.png)
    一部例外データはありますが、線形でグループ分けできています。

※`train_test_split`で作られる学習用データとテストデータは毎回変わるため、正解率とグラフも毎回別のものになります

# 終わりに
今回はPythonでSVMを実装しました。
SVMは参考書でさらっと読んだだけであったため、本記事を作成していく中で理解が深まってよかったです。
最後まで読んでくださりありがとうございました。

# 参考
この記事は以下の動画・記事を参考にして執筆しました。

・[PythonでSVM(サポートベクターマシン)を作ってみよう【Python機械学習#7】](https://www.youtube.com/watch?v=BRXxvoQUMbI)

・[scikit-learnのbreast_cancerデータセットで機械学習の基礎を振り返る](https://ensekitt.hatenablog.com/entry/2018/08/22/200000)

・[サポートベクターマシン（SVM）とは？特徴やメリットと活用事例](https://aismiley.co.jp/ai_news/svm/)

・[scikit-learnのdatasetsにはどんなのが入っているのか調べてみた話【Breast Cancer編】](https://note.com/aki_5502/n/n694d702a50df)