サポートベクトルマシンについて丁寧に理解しようとした（その１：MakeMoonsを例にして多項式/RBFカーネルを試した）

#はじめに
　今回は、サポートベクトルマシンによる機械学習モデルをまとめたいと思います。
　概要は下記です。


+ サポートベクトルマシンの概念を理解する
+ MakeMoonsデータセットを元にして非線形ＳＶＭ分類器を試す
+ ガウスRBFカーネルを試してみる



##サポートベクトルマシンの考え方を理解する

　サポートベクトルマシン（Suppurt Vector Machine）は「マージン最大化」と呼ばれる基準でモデルの最適化を行う機械学習におけるアルゴリズムの一つです。
　基本的に分類や回帰問題に使用されます。これら分類・回帰問題を解決するアルゴリズムとして（私が認識している）代表的なアルゴリズムが以下のように挙げられます。


+ 決定木系（決定木/ランダムフォレスト/LightGBM等）　
+ ニューラルネットワーク系
+ **（今回）サポートベクトルマシン**
+ k近傍法
+ ロジスティク回帰

　ロジスティク回帰以外は非線形の表現が可能である特徴があります。今回のサポートベクトルマシンは実用的にも取り扱いやすいため、人気のアルゴリズム（のよう）です。


　訳としてサポート**ベクター**マシンと訳されるケースも見られます。これは、Vectorを"ベクトル"か"ベクター"と訳す違いによるものと思われます。

###サポートベクトル、マージン最大化とは

　さて、言葉の説明を行います。マージンとは、下記図に示すような識別を行う分離器（線形式:$w^Tx+b$）と最も距離が近い要素（△,▲）の距離のことをいいます。
　そして、この最も距離が近い要素をサポートベクトルと呼びます。

　このマージンの距離を最大化することが、このサポートベクトルマシン（以下ＳＶＭと略します）の最適化になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/db638995-5e7f-e9d9-bcd6-e01746cf1d23.png)






##MakeMoonsデータセットを元にして非線形ＳＶＭ分類器を試す

　線形の分類器は上記のような分けやすい特徴のデータでは効果を発揮しますが、実際の問題では非常に複雑な分割を必要とする場合が多いです。その場合、非線形な分類器を使用する必要があります。
　ＳＶＭを使用すれば容易に非線形な分類器を作成、使用することが可能です。

　ＳＶＭの解析を行う上での一例にMakeMoonsデータセットがあります。これは、下記に示すscikit learnのデータセットからインポートすることができるモジュールになります。
　いわゆる月の形のデータを描くことができます。これを非線形ＳＶＭで分類していきましょう。

```python:SVM.ipynb
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.15, random_state=42)

def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel(r"$x_1$", fontsize=20)
    plt.ylabel(r"$x_2$", fontsize=20, rotation=0)

plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()
```

![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/cb761f2f-72b6-959d-f8bd-c4fb7a3d8765.png)



###次元の異なる多項式カーネルによるＳＶＭ分類器

　ＳＶＭにおいてdegreeの値は次元を表しており、この次元値により分類器の性能が変わります。


```python:SVM.ipynb
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.svm import SVC

poly_kernel_svm_clf = Pipeline([
        ("scaler", StandardScaler()),
        ("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))
    ])
poly_kernel_svm_clf.fit(X, y)

poly100_kernel_svm_clf = Pipeline([
        ("scaler", StandardScaler()),
        ("svm_clf", SVC(kernel="poly", degree=12, coef0=100, C=5))
    ])
poly100_kernel_svm_clf.fit(X, y)

plt.figure(figsize=(11, 4))

plt.subplot(121)
plot_predictions(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r"$d=3, r=1, C=5$", fontsize=18)

plt.subplot(122)
plot_predictions(poly100_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r"$d=12, r=100, C=5$", fontsize=18)

plt.show()

```

![011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/ea0beee1-c4c3-5b14-4653-cca0bc0c4524.png)

上記の例では、左図が3次元、右図が12次元の例を示している。12次元の場合は過学習を起こしている状態になっています。従って、この場合はモデルの次数を下げる必要があります。逆に2次元まで下げると下記図のように分類できなくなることが分かります。

![012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/c66d3843-a906-853c-be01-9d2bd8f18eb7.png)



### sckit learnのPipelineを理解する
　さらっと上記プログラムで出てきたPipelineというメソッドが出てきましたので、簡単に記します。機械学習におけるモデル予測を行う上では、欠損値処理や値の標準化等、細かい前処理が必要になります。この時、ある程度これら問題を解決できる手法がsklearn のPipelineになります。
　
今回のケースであれば、以下の処理を行っています。

```python

"scaler", StandardScaler()#値を標準化する（＝平均で引いて、分散値で割る）
"svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5)#ＳＶＭの分類器の設定をする
```

モデルとしてコンパイルしたい処理を入れこむものと理解しました、

##ガウスRBFカーネルを試してみる

　さて、ガウスＲＢＦカーネル法というＳＶＭの分類方法の一つを用います。言葉がどれも理解しにくかったため、それぞれ語句の意味を理解したいと思います。

###カーネル関数ってなんだ　

　まず、カーネル関数についてです。これは、先ほどのMakeMoonsの例でもさらっと使用していましたが非線形で表現するうえで非常に重要な関数です。
　概要としては、下記図のように**次元を増やして線形分離可能な平面（実際問題では3次元以上のため超平面と呼称します）**を持つことができる処理のことをカーネル関数と呼びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/ce161408-c84a-8892-38c2-34db5f04ee5f.png)



そして、このカーネル関数を使った回帰をカーネル回帰と呼びます。カーネル回帰を表す式は以下のような式になります。

```math
f({\bf x}) = \sum_{i=1}^{N} \alpha_i k({\bf x}^{(i)}, {\bf x})
```

$\alpha_i$が最適化したい係数で、 $k({\bf x}^{(i)}, {\bf x})$がカーネル関数と呼ばれるものです。このカーネル関数には何種類か使用されるものがあります。以下はその一つであるガウスカーネルと呼ばれるカーネル関数です。

```math
k({\bf x}, {\bf x}') = exp(-\beta \|{\bf x} - {\bf x}'\|^2)
```

$||x-x'||$はノルム：距離を示すものであります。$\beta$は$\beta>0$を満たす実数のハイパーパラメータであり、モデルを使用する人が決定します。


###ＲＢＦってなんだ
　Radial Basis Functionと呼ばれ、日本語では動径基底関数と呼びます。このＲＢＦとなる関数は様々あるようですが、最も使用されるのはガウス関数と呼ばれるものです。


```math
φ(\bf x)=(\bf x - \bf c)^T \frac {(\bf x - \bf c)}{2σ^2}
```


![001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/676dad70-b230-01fe-9b45-6ffe2c87a3ec.jpeg)


$\bf x$が入力関数、$\bf c$がガウス関数の中心になります。


###実装してみよう

```python:SVM.ipynb
from sklearn.svm import SVC

gamma1, gamma2 = 0.1, 5
C1, C2 = 0.001, 1000
hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2)

svm_clfs = []
for gamma, C in hyperparams:
    rbf_kernel_svm_clf = Pipeline([
            ("scaler", StandardScaler()),
            ("svm_clf", SVC(kernel="rbf", gamma=gamma, C=C))
        ])
    rbf_kernel_svm_clf.fit(X, y)
    svm_clfs.append(rbf_kernel_svm_clf)

plt.figure(figsize=(11, 11))

for i, svm_clf in enumerate(svm_clfs):
    plt.subplot(221 + i)
    plot_predictions(svm_clf, [-1.5, 2.5, -1, 1.5])
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    gamma, C = hyperparams[i]
    plt.title(r"$\gamma = {}, C = {}$".format(gamma, C), fontsize=16)

plt.show()
```

![013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/75f237ba-ae00-4b0b-08bd-255491fbf671.png)


ハイパーパラメータとしては、γとＣを両方決める必要がありますが、それぞれ値が大きすぎると過学習を起こしてしまうことが分かります。



#終わりに

　ＳＶＭは非常に使いやすいモデルである一方、その背景にある数学理論は非常に奥が深いです。


今回こちらの記事を参考にさせて頂きました。
>線形な手法とカーネル法（回帰分析）
https://qiita.com/wsuzume/items/09a59036c8944fd563ff


プログラム全文はこちらに格納しております。
https://github.com/Fumio-eisan/SVM_20200417