make_circlesのデータセットを用いてSVMを実施する【グリッドサーチ,交差検証】
この記事のポイント
  • RBFカーネルを使ったSVMによる非線形分離を実施.
  • make_circleのnoiseの異なるデータで検証.
  • グリッドサーチと交差検証(クロスバリデーション)も扱う.

こんにちは.けんゆー(@kenyu0501_)です.

https://twitter.com/kenyu0501_/status/1167323135542063105

やっぱりRBFカーネルでSVMを試すときには,こういった線形分離不可能なデータの方が見栄えが良いですよね.
闇雲にやってて分かりにくい対象を使っていましたが,反省しています.

この記事では,線形分離不可能なデータを使ってSVMでクラス分類を解きます.
使用するデータは,Scikit-learnのmake_circlesのデータです.
(参考:Scikit-learnのデータセットについて【make_blobs, make_moons, make_circles】)

クラスの異なるデータ群が,円状に配置されているデータですね.
外側と内側に分布しているので,直線での分離ができないわけです.

この記事では,rbfカーネルを使用します.
さらに,グリッドサーチ,クロスバリデーションも行なって,ハイパーパラメータ(γとC)のチューニングも実施します.

プログラムについて

先に,pythonによるプログラムを公開しておきます.

"""
Created on Sun Aug 25 14:36:27 2019

@author: ueharakenyuu
"""
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.datasets import make_circles

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

from sklearn.model_selection import cross_val_score

#------データの読み込みと使うデータの選択-----------#
X, y = make_circles(n_samples=200,
                  shuffle = True,
                  noise = 0.1,
                  random_state=0,
                  factor = 0.5)

a0, b0 = X[y==0,0], X[y==0,1]
a1, b1 = X[y==1,0], X[y==1,1]

plt.figure(figsize=(8, 7))

fig, ax = plt.subplots()
plt.scatter(a0, b0, marker='o', c="red", s=25, label="y = 0")
plt.scatter(a1, b1, marker='o', c="blue", s=25, label="y = 1")
plt.legend()
plt.xlabel("a")
plt.ylabel("b")
#plt.show()
plt.title("n_samples=200, noise = 0.1, random_state=0")
fig.savefig('output_circle.png',dpi=900)


#------データの読み込みと使うデータの選択-----------#
scaler = StandardScaler()
X = scaler.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2,random_state = 0)
X_train_val, X_val, y_train_val, y_val = train_test_split(X_train, y_train, test_size = 0.2,random_state = 0)
#------グリッドサーチとクロスバリデーションを使ったハイパーパラメータチューニング-----------#
best_score = float(0.0)
gammas = np.linspace(0.01, 10, 30)
Cs = np.linspace(0.01, 10, 30)
best_param_gamma = 0.0
best_param_C = 0.0
scores = pd.DataFrame()
for gamma in gammas:
    for C in Cs:
        svm = SVC(kernel = 'rbf', gamma=gamma, C=C)
        
        scores = scores.append(
                {
                    'gamma': gamma,
                    'C': C,
                    'accuracy': np.mean(cross_val_score(svm, X_train_val, y_train_val, cv=5))
                },
                ignore_index=True)
                
        if best_score < np.mean(cross_val_score(svm, X_train_val, y_train_val, cv=5)):
            best_score = np.mean(cross_val_score(svm, X_train_val, y_train_val, cv=5))
            best_param_gamma = gamma
            best_param_C = C
            
print("ベストスコア:", round(best_score,2))
print('g:%s c:%s' %(round(best_param_gamma,2),round(best_param_C,2)))

fig, ax = plt.subplots()
ax.set_xlabel('gamma')
ax.set_ylabel('C')
ax = ax.scatter(scores.gamma, scores.C, c=100.0*scores.accuracy, cmap='spring')
fig.colorbar(ax)
plt.title('Best:{}, gamma:{}, C:{}'.format(100*round(best_score,3), round(best_param_gamma,2),round(best_param_C,2)))
fig.savefig('output_gs.png',dpi=900)

#------グリッドサーチとクロスバリデーションを使ったハイパーパラメータチューニング-----------#
#------グリッドサーチで求めた最適なパラメータでSVMを回す-----------#
gamma = best_param_gamma
C = best_param_C

svm = SVC(kernel = 'rbf', gamma=gamma, C=C)

svm.fit(X_train, y_train)
R = svm.predict(X_test)
total = len(X_test)
success = sum(R == y_test)
print("Accuracy is:{}".format(100*success/total))
#------グリッドサーチで求めた最適なパラメータでSVMを回す-----------#
#----------グラフ描写用の関数---------------#
def plot_decision_function(model):
    _x0 = np.linspace(-3.0, 3.0, 100)
    _x1 = np.linspace(-3.0, 3.0, 100)
    x0, x1 = np.meshgrid(_x0, _x1)
    X = np.c_[x0.ravel(), x1.ravel()]
    y_pred = model.predict(X).reshape(x0.shape)
    y_decision = model.decision_function(X).reshape(x0.shape)
    plt.contourf(x0, x1, y_pred, cmap='winter_r', alpha=0.4)
    plt.contourf(x0, x1, y_decision,levels=10, alpha=0.2)

def plot_dataset(X,y):
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "ro", ms=5)
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bo", ms=5)
    plt.xlabel("a")
    plt.ylabel("b")
#---------------------------------------#

#--グラフを描写するor保存する(1行2列で)------#

plt.figure(figsize=(12,5))
plt.subplot(121)
plot_decision_function(svm)
plot_dataset(X_train,y_train)
plt.title('Training, C={:.2f},gamma={:.2f}'.format(C,gamma))

plt.subplot(122)
plot_decision_function(svm)
plot_dataset(X_test,y_test)
plt.title('Test, Accuracy:{:.2f}'.format(100.0*success/total))
plt.savefig('svm_output.png',dpi=900)
#plt.show()
#--------------------------------------#

データの切り分け

SVMを実施するときには,トレーニングデータ(80%)とテストデータ(20%)に分けます.
今回は,パラメータのチューニングも行うので,トレーニングデータからさらに20%をバリデーション(評価)データとして分けます.

データ全体の構図(データセットから解析まで)

扱うデータを,先ほど示したような感じでデータを切り分け,トレーニングデータとバリデーションデータを使って,ハイパーパラメータのチューニングを行います.
ハイパーパラメータのチューニングは,グリッドサーチとクロスバリデーションです.
(関連:rbfカーネルのハイパーパラメータをグリッドサーチとベイズ最適化で探す【irisデータセット】)

make_circlesのnoiseを色々と変えてSVMの実装

make_circleのデータセットにノイズを適当に入れて,SVMの結果がどのようになるのかを可視化しようと思います.

noise = 0.1

noiseの混入が少ないので,円状のデータセットはまだ形状を保っていますね.
この時のSVMの結果を見ると,かなり汎化性能が高いです.
グリッドサーチに関しても,どこのパラメータをとっても結果が良いことがわかります.

noise = 0.2

noise = 0.2にすると,内側の青いデータ群と,外側の赤いデータ群がちょっと入り組んだような形になります.
この時は,汎化性能が90%になりました.
それでもそこそこ機能しています.

noise = 0.3

noise = 0.3 になると,かなり複雑ですね.
グリッドサーチの結果も,全体的に,かなり汎化性能が低くなっていることがわかります.
一番良い箇所で,77.5%でした.

ペンのすけ

興味のある人はやってみてねー!

サポートベクターマシンの解説動画

ペンのすけ

参考にしてみてくださいー!