サポートベクターマシーンを可視化する

サポートベクターマシーンは「マージンを最大化する直線を引く」と説明されます。
しかしソフトマージンの説明以降, 個人的に混乱したので
可視化して説明できるようにしました。

# データセットの用意
ライブラリの import
```python
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.svm import SVC
```

データセットの用意

今回はランダムなデータに直線を引いた data1 を用意しました。

data1 はランダムな$N(0, 1)$のノイズデータを生成し, ボーダー(border: $x_2 = f(x_1)$)の直線で2クラスに分けます。
マージンは2クラスを分ける直線からmergineの幅にあるデータを消します。
これは境界線付近のデータの有無でサポートベクターマシーンの結果がどう変わるか, 調査するためです。
ノイズを加えてボーダーを越境するデータを生成します(noise が倍率)。

```python
n = 200
np.random.seed(0)

x2 = lambda x1: 0.3*x1 + 0.2
def data1(border=x2, merging=0.3, noise=0):
    X = np.random.normal(size=(n, 2))
    y = np.where(border(X[:, 0]) > X[:, 1], np.zeros(X.shape[0]), np.ones(X.shape[0]))

    # merging
    a = (border(1)-border(0))
    merging_line_0 = lambda x: border(x) - merging*np.sqrt(a**2 + 1)
    merging_line_1 = lambda x: border(x) + merging*np.sqrt(a**2 + 1)
    del_loc = (np.logical_and(merging_line_0(X[:, 0]) < X[:, 1], X[:, 1] < merging_line_1(X[:, 0])))
    X = np.delete(X, del_loc, axis=0)
    y = np.delete(y, del_loc)

    # noise
    X += np.random.normal(scale=noise, size=X.shape)

    return X, y

X, y = data1()
```

1. border = lambda x1: x1 + 0.2
merging = 0
noise = 0
![Figure 29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/675d8ee3-cb4f-5a29-6452-2b0c3ce652ad.png)
$x_2=x_1+0.2$ でクラスが分けられます。

2. border = lambda x1: x1 + 0.2
merging = 0.3
noise = 0
![Figure 30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/d2cf2947-61f6-b6ca-2933-8d38132298d0.png)
$x_2=x_1+0.2$ でクラスが分けられ, 直線前後に 0.3 の幅のマージンが取られます。

3. border = lambda x1: x1 + 0.2
merging = 0.0
noise = 0.2
![Figure 33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/0a3dbf0e-9716-d0cc-7d33-1ee7082c81b5.png)
$x_2=x_1+0.2$ でクラスが分けられますが, そのデータに $N(0, \mathrm{noise})$ のノイズを加えてボーダーの越境を許します。

この3データでSVMの様子を確認します。

### マージンを算出する式
直線 $y = ax + b$ と距離 d 離れた直線は, $y = ax + b \pm d\sqrt{a^2 + 1}$ で得られます。
直線は $f(x)$ で与えられているので, $a = f(1) - f(0)$ で算出しています。

### 画像を表示するコード
```python
fig, ax = plt.subplots(figsize=(5, 5))
ax.scatter(X[y==0, 0], X[y==0, 1])
ax.scatter(X[y==1, 0], X[y==1, 1])
ax.set_xlabel('X1')
ax.set_ylabel('X2')
```

# モデル作成
```python
clf = SVC(C=0.1, kernel='linear')
clf.fit(X, y)

# 試しに適当な点の値を出してみる
X_eval = np.random.rand(1,2)
print(X_eval)
# [[0.20063767 0.92947519]]
print(clf.decision_function(X_eval))
# [0.61825635]
print((clf.coef_ @ X_eval.T)[0] + clf.intercept_)  # 同じ値が出力される
# [0.61825635]
```

SVC には coef_, intercept_ が属性として用意されている。
この値は decision_function(X) で取得できる予測値の計算に用いられる。

# decision_function の描画
coef_, intercept_ で描かれる平面を描画してみる。

![Figure 9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/3a433eed-9395-5e46-b6c4-257241af626b.png)
2つのクラスを1, -1としたときその点あたりを通る面が描ける。

この decision_function で描かれる面は, 境界線との距離を表す。

### 描画用コード
```python
test_x = np.linspace(-3, 3, 1201)
test_x1, test_x2 = np.meshgrid(test_x, test_x)
test_y = np.empty_like(test_x1)

for i, (x1, x2) in enumerate(zip(test_x1, test_x2)):
    test_y[i] = (clf.coef_ @ np.array([x1, x2]))[0] + clf.intercept_

fig = plt.figure()
ax = fig.add_subplot(projection='3d')

ax.plot_surface(test_x1, test_x2, test_y, color='orange', alpha=0.2)

items_0 = X[y==0]
items_1 = X[y==1]
ax.scatter(items_0[:, 0], items_0[:, 1], -1, marker='o', c='blue')
ax.scatter(items_1[:, 0], items_1[:, 1], 1, marker='o', c='red')

ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.set_zlabel('y')
ax.set_zlim(-3, 3)

plt.show()
```

# 境界線の描画
SVMの境界線の描画をした.
また, decision_functionを 1 より大きい部分は, -1 より小さい部分をクラスの値にした。

![Figure 13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/34299202-f70e-1713-f7b3-99bd55d9d518.png)


```python
# 各点の値を算出
test_x = np.linspace(-3, 3, 1201)
test_x1, test_x2 = np.meshgrid(test_x, test_x)
test_y = np.empty_like(test_x1)

for i, (x1, x2) in enumerate(zip(test_x1, test_x2)):
    test_y[i] = (clf.coef_ @ np.array([x1, x2]))[0] + clf.intercept_

fig = plt.figure()
ax = fig.add_subplot(projection='3d')

test_y = np.where(test_y > 1, 1, test_y)
test_y = np.where(test_y < -1, -1, test_y)
ax.plot_surface(test_x1, test_x2, test_y, cmap=cm.jet, alpha=0.5)

items_0 = X[y==0]
items_1 = X[y==1]
ax.scatter(items_0[:, 0], items_0[:, 1], -1, marker='o', c='blue')
ax.scatter(items_1[:, 0], items_1[:, 1], 1, marker='o', c='red')

ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.set_zlabel('y')
ax.set_zlim(-1.1, 1.1)

border_line = (np.isclose(test_y, 0, rtol=0, atol=0.05))
ax.plot(test_x1[border_line], test_x2[border_line], 0, c='g', alpha=0.5)

plt.show()
```

# サポートベクターとマージンの描画

![Figure 15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/99606d6f-b5cf-b3c8-5fd2-634548a23ba8.png)

サポートベクターは decision_function との距離が1未満の点が該当する。

```python
# X のうちサポートベクターとなる番号を取得できる
print(clf.support_)

# 下の2つの値は一致する
print(clf.support_vectors_[0:5])
print(X[clf.support_[0:5]])
```

```python
# 各点の値を算出
test_x = np.linspace(-3, 3, 1201)
test_x1, test_x2 = np.meshgrid(test_x, test_x)
test_y = np.empty_like(test_x1)

for i, (x1, x2) in enumerate(zip(test_x1, test_x2)):
    test_y[i] = (clf.coef_ @ np.array([x1, x2]))[0] + clf.intercept_

fig = plt.figure()
ax = fig.add_subplot(projection='3d')

unsupport_X = np.delete(X, clf.support_, axis=0)
unsupport_y = np.delete(y, clf.support_)
items_0 = unsupport_X[unsupport_y==0]
items_1 = unsupport_X[unsupport_y==1]
ax.scatter(items_0[:, 0], items_0[:, 1], -1, marker='o', c='blue')
ax.scatter(items_1[:, 0], items_1[:, 1], 1, marker='o', c='red')

support_0 = clf.support_vectors_[y[clf.support_]==0]
support_1 = clf.support_vectors_[y[clf.support_]==1]
ax.scatter(support_0[:, 0], support_0[:, 1], -1, marker='*', c='midnightblue')
ax.scatter(support_1[:, 0], support_1[:, 1], 1, marker='*', c='darkred')

test_y = np.where(test_y > 1, 1, test_y)
test_y = np.where(test_y < -1, -1, test_y)
ax.plot_surface(test_x1, test_x2, test_y, cmap=cm.jet, alpha=0.2)

ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.set_zlabel('y')
ax.set_zlim(-1.1, 1.1)

border_line = (np.isclose(test_y, 0, rtol=0, atol=0.01))
ax.plot(test_x1[border_line], test_x2[border_line], 0, c='g', alpha=0.5)

merging_0 = (np.isclose(test_y, -0.98, rtol=0, atol=0.01))
ax.plot(test_x1[merging_0], test_x2[merging_0], -1, c='b', alpha=0.5)

merging_1 = (np.isclose(test_y, 0.98, rtol=0, atol=0.01))
ax.plot(test_x1[merging_1], test_x2[merging_1], 1, c='r', alpha=0.5)

plt.show()
```

# 2次元で描画
領域の色が decision_functionの勾配となる。

![Figure 8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662712/28689d87-5f4f-43f9-cf97-5715bfce7a57.png)

```python
# 2次元でプロット
fig2 = plt.figure(figsize=(5,5))
ax2 = fig2.add_subplot()

unsupport_X = np.delete(X, clf.support_, axis=0)
unsupport_y = np.delete(y, clf.support_)
items_0 = unsupport_X[unsupport_y==0]
items_1 = unsupport_X[unsupport_y==1]
ax2.scatter(items_0[:, 0], items_0[:, 1], marker='o', c='blue')
ax2.scatter(items_1[:, 0], items_1[:, 1], marker='o', c='red')

support_0 = clf.support_vectors_[y[clf.support_]==0]
support_1 = clf.support_vectors_[y[clf.support_]==1]
ax2.scatter(support_0[:, 0], support_0[:, 1], marker='*', c='midnightblue')
ax2.scatter(support_1[:, 0], support_1[:, 1], marker='*', c='darkred')

ax2.contourf(test_x1, test_x2, test_y, cmap=cm.jet, alpha=0.2)
ax2.set_xlabel('x1')
ax2.set_ylabel('x2')
ax2.set_xlim(-3, 3)
ax2.set_ylim(-3, 3)

border_line = (np.isclose(test_y, 0, rtol=0, atol=0.01))
ax2.plot(test_x1[border_line], test_x2[border_line], c='g', alpha=0.5)

merging_0 = (np.isclose(test_y, -0.98, rtol=0, atol=0.01))
ax2.plot(test_x1[merging_0], test_x2[merging_0], c='b', alpha=0.5)

merging_1 = (np.isclose(test_y, 0.98, rtol=0, atol=0.01))
ax2.plot(test_x1[merging_1], test_x2[merging_1], c='r', alpha=0.5)

plt.show()
```