サポートベクターマシンも、決定木と同様に、分類問題・回帰問題を解くことが可能なモデルです。過学習を抑制するためのパラメータが導入されているので、大変人気が高い人工知能の構築手法となります。一般に、決定木よりも性能が高いモデルを構築しやすく、ニューラルネットワークよりも安定した性能を出しやすい傾向があります。ただし、決定木のように、なぜそのクラスと判定されるのか、ということはわかりにくいです。
import numpy as np
np.random.seed(1) # 擬似乱数シード: 毎回同じ乱数を出す
# Class1: 成績上位群
mu = [7, 7]
sigma = [[0.5, 0.05], [0.05, 0.5]]
Dat01 = np.random.multivariate_normal(mu, sigma, 100)
Label01=[]
for i in range(0, len(Dat01)):
Label01.append("Good")
# Class 2: 成績中位群
mu = [5.5, 4]
sigma = [[0.8, -0.5], [-0.5, 0.8]]
Dat02 = np.random.multivariate_normal(mu, sigma, 100)
Label02=[]
for i in range(0, len(Dat02)):
Label02.append("Middle")
# Class 3: 成績下位群
mu = [2, 5]
sigma = [[0.3, 0.0], [0.0, 3]]
Dat03 = np.random.multivariate_normal(mu, sigma, 100)
Label03=[]
for i in range(0, len(Dat01)):
Label03.append("Bad")
続いて、散布図でデータを可視化してみます。これも決定木と同じなので、説明は省略します。
import matplotlib.pyplot as plt
x = [Dat01[:,0], Dat02[:,0], Dat03[:,0]]
y = [Dat01[:,1], Dat02[:,1], Dat03[:,1]]
plt.figure(figsize=(5, 4)) # figureの縦横の大きさ
# Goodの散布図
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
# Middleの散布図
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
# Badの散布図
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("Relationship: X1, X2 and Score") # タイトル
plt.xlabel("X1: Studying Time") # 軸名
plt.ylabel("X2: Understanding") #軸名
plt.grid(True) #グリッド線(True:引く、False:引かない)
plt.xlim(0, 10) # 横軸最小最大
plt.ylim(0, 10) # 縦軸最小最大
plt.legend(["Good", "Middle", "Bad"], loc="upper right") # 凡例
plt.show() # グラフの表示
これも決定木のときと同じです。Xの1列目には勉強時間、2列目には理解傾向が格納されています。そして、YにはGood, Middle, Badのラベルが導入されています。それぞれ100人分なので、合計300人分の勉強の仕方と学力が入っているデータセットになります。
X=np.concatenate([Dat01, Dat02, Dat03]) # 問題
Y=np.concatenate([Label01, Label02, Label03]) # 正解ラベル
それでは、XからYを推定するモデルを構築してみます。SVMが有する主要なパラメータは以下の4点です。
kernel: 'linear' or 'rbf'(他にもありますがとりあえずこの2つで)
C: 正の実数
gamma: 正の実数
class_weight: None or 'balanced'
線形カーネルを使用したSVMを線形SVMと言います。まずはこれを構築してみます。一つは誤差を許容するモデル、もう一つは誤差を許容しないモデルです。
from sklearn import svm
# モデル構造の決定
SVMModel_Lin01 = svm.SVC(kernel='linear', C=1)
SVMModel_Lin02 = svm.SVC(kernel='linear', C=10000)
# 学習
SVMModel_Lin01 = SVMModel_Lin01.fit(X,Y)
SVMModel_Lin02 = SVMModel_Lin02.fit(X,Y)
続いて、学習したモデルで新しいデータを推定してみます。勉強時間を5、理解傾向を6としてみます。
InputDat = [[5, 6]]
# Cが低いモデル
OutputDat = SVMModel_Lin01.predict(InputDat)
OutputScore = SVMModel_Lin01.decision_function(InputDat)
print("Cが低いモデル:", OutputDat, OutputScore)
# Cが高いモデル
OutputDat = SVMModel_Lin02.predict(InputDat)
OutputScore = SVMModel_Lin02.decision_function(InputDat)
print("Cが高いモデル:", OutputDat, OutputScore)
predictは判定クラスをそのまま返してきます。decision_functionは実数値の出力です。クラスはソートされているので、Bad, Good, Middleに並び替えられており、それらの確信度が出力されるわけです。Cが低いモデルは、Middleクラスの確信度が高いこと、Cが高いモデルはBadクラスの確信度が高いことがわかります(実際に判定クラスはそうなっていますね)。異なる出力になりました。モデルが違うので、推定結果が違う、ということですね。どういう識別境界が得られているのか、可視化してみます。コードの説明は、決定木と同じなので省略します。
# メッシュデータ生成
Xmin, Ymin, Xmax, Ymax = 0, 0, 10, 10 # 空間の最小最大値
resolution = 0.1 # 細かさ
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
# メッシュデータの推定
z_lin01 = SVMModel_Lin01.predict(MeshDat) # モデル1
z_lin02 = SVMModel_Lin02.predict(MeshDat) # モデル2
# データ整形
z_lin01 = np.reshape(z_lin01, (len(x_mesh), len(y_mesh)))
z_lin02 = np.reshape(z_lin02, (len(x_mesh), len(y_mesh)))
# 可視化
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 4))
# *** lin01 ***
plt.subplot(1,2,1)
# SVM出力
plt.scatter(x_mesh[z_lin01=='Bad'], y_mesh[z_lin01=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_lin01=='Middle'], y_mesh[z_lin01=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_lin01=='Good'], y_mesh[z_lin01=='Good'], s=5, alpha=0.3, c='blue')
# 教師データ(Bad, Middle, Good)
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("Feature space by SVM Lin 01")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** lin02 ***
plt.subplot(1,2,2)
# SVM出力
plt.scatter(x_mesh[z_lin02=='Bad'], y_mesh[z_lin02=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_lin02=='Middle'], y_mesh[z_lin02=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_lin02=='Good'], y_mesh[z_lin02=='Good'], s=5, alpha=0.3, c='blue')
# 教師データ(Bad, Middle, Good)
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("Feature space by SVM Lin 02")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
plt.legend(["Bad", "Middle", "Good",
"Bad (TrainData)", "Middle (TrainData)", "Good (TrainData)"],
loc="upper right", bbox_to_anchor=(1.5, 1))
左はCが低いモデルなので、教師データの誤差がある程度許容されています。右はCが高いモデルなので、教師データの誤差が許されないモデルです。また、いずれも線形カーネルを使用しているので、識別境界が直線です。ほとんど似ていますが、X1=4, X2=2付近のBadを分類しようとしている、そうではない、という部分に違いがありますね。一般的に、Cを高めると過学習を起こしますが、線形カーネルは過学習を起こしにくいという特性があります。そのため、Cが高い場合でも、過学習が起きていない空間に見えます。
続いてrbfカーネルを使用した、識別境界を曲げることのできるモデルを構築してみます。このようなSVMを非線形SVMと言います。rbfカーネルを用いた場合、Cとgammaを設定する必要があります。この影響を調べるため、
の4つの推定モデルを構築してみます。
# モデル構造の決定
SVMModel_RBF01 = svm.SVC(kernel='rbf', C=0.1, gamma=0.1)
SVMModel_RBF02 = svm.SVC(kernel='rbf', C=0.1, gamma=10)
SVMModel_RBF03 = svm.SVC(kernel='rbf', C=10, gamma=0.1)
SVMModel_RBF04 = svm.SVC(kernel='rbf', C=10, gamma=10)
# 学習
SVMModel_RBF01 = SVMModel_RBF01.fit(X,Y)
SVMModel_RBF02 = SVMModel_RBF02.fit(X,Y)
SVMModel_RBF03 = SVMModel_RBF03.fit(X,Y)
SVMModel_RBF04 = SVMModel_RBF04.fit(X,Y)
続いて空間を可視化してみます。
# メッシュデータ生成
Xmin, Ymin, Xmax, Ymax = 0, 0, 10, 10 # 空間の最小最大値
resolution = 0.1 # 細かさ
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
# メッシュデータの推定
z_RBF01 = SVMModel_RBF01.predict(MeshDat) # モデル1
z_RBF02 = SVMModel_RBF02.predict(MeshDat) # モデル2
z_RBF03 = SVMModel_RBF03.predict(MeshDat) # モデル3
z_RBF04 = SVMModel_RBF04.predict(MeshDat) # モデル4
# データ整形
z_RBF01 = np.reshape(z_RBF01, (len(x_mesh), len(y_mesh)))
z_RBF02 = np.reshape(z_RBF02, (len(x_mesh), len(y_mesh)))
z_RBF03 = np.reshape(z_RBF03, (len(x_mesh), len(y_mesh)))
z_RBF04 = np.reshape(z_RBF04, (len(x_mesh), len(y_mesh)))
# 可視化
import matplotlib.pyplot as plt
fig2 = plt.figure(figsize=(12, 12))
# *** RBF01 ***
plt.subplot(2,2,1)
# SVM出力
plt.scatter(x_mesh[z_RBF01=='Bad'], y_mesh[z_RBF01=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF01=='Middle'], y_mesh[z_RBF01=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF01=='Good'], y_mesh[z_RBF01=='Good'], s=5, alpha=0.3, c='blue')
# 教師データ(Bad, Middle, Good)
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("C=Low, gamma=Low")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** RBF02 ***
plt.subplot(2,2,2)
# SVM出力
plt.scatter(x_mesh[z_RBF02=='Bad'], y_mesh[z_RBF02=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF02=='Middle'], y_mesh[z_RBF02=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF02=='Good'], y_mesh[z_RBF02=='Good'], s=5, alpha=0.3, c='blue')
# 教師データ(Bad, Middle, Good)
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("C=Low, gamma=High")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** RBF03 ***
plt.subplot(2,2,3)
# SVM出力
plt.scatter(x_mesh[z_RBF03=='Bad'], y_mesh[z_RBF03=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF03=='Middle'], y_mesh[z_RBF03=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF03=='Good'], y_mesh[z_RBF03=='Good'], s=5, alpha=0.3, c='blue')
# 教師データ(Bad, Middle, Good)
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("C=High, gamma=Low")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** RBF04 ***
plt.subplot(2,2,4)
# SVM出力
plt.scatter(x_mesh[z_RBF04=='Bad'], y_mesh[z_RBF04=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF04=='Middle'], y_mesh[z_RBF04=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF04=='Good'], y_mesh[z_RBF04=='Good'], s=5, alpha=0.3, c='blue')
# 教師データ(Bad, Middle, Good)
plt.scatter(Dat03[:,0], Dat03[:,1], s=50, c='red', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat02[:,0], Dat02[:,1], s=50, c='orange', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.scatter(Dat01[:,0], Dat01[:,1], s=50, c='blue', marker='s',
alpha=0.8, linewidths=0.5, edgecolors='black')
plt.title("C=High, gamma=High")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
ちょっとわかりにくいので、教師データを載せないでみます。
fig2 = plt.figure(figsize=(12, 12))
# *** RBF01 ***
plt.subplot(2,2,1)
# SVM出力
plt.scatter(x_mesh[z_RBF01=='Bad'], y_mesh[z_RBF01=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF01=='Middle'], y_mesh[z_RBF01=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF01=='Good'], y_mesh[z_RBF01=='Good'], s=5, alpha=0.3, c='blue')
plt.title("C=Low, gamma=Low")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** RBF02 ***
plt.subplot(2,2,2)
# SVM出力
plt.scatter(x_mesh[z_RBF02=='Bad'], y_mesh[z_RBF02=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF02=='Middle'], y_mesh[z_RBF02=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF02=='Good'], y_mesh[z_RBF02=='Good'], s=5, alpha=0.3, c='blue')
plt.title("C=Low, gamma=High")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** RBF03 ***
plt.subplot(2,2,3)
# SVM出力
plt.scatter(x_mesh[z_RBF03=='Bad'], y_mesh[z_RBF03=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF03=='Middle'], y_mesh[z_RBF03=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF03=='Good'], y_mesh[z_RBF03=='Good'], s=5, alpha=0.3, c='blue')
plt.title("C=High, gamma=Low")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
# *** RBF04 ***
plt.subplot(2,2,4)
# SVM出力
plt.scatter(x_mesh[z_RBF04=='Bad'], y_mesh[z_RBF04=='Bad'], s=5, alpha=0.3, c='red')
plt.scatter(x_mesh[z_RBF04=='Middle'], y_mesh[z_RBF04=='Middle'], s=5, alpha=0.3, c='yellow')
plt.scatter(x_mesh[z_RBF04=='Good'], y_mesh[z_RBF04=='Good'], s=5, alpha=0.3, c='blue')
plt.title("C=High, gamma=High")
plt.xlabel("X1: Studying Time")
plt.ylabel("X2: Understanding")
plt.grid(True)
plt.xlim(0, 10)
plt.ylim(0, 10)
plt.show()
青がGood, 黄がMiddle, 赤がBadとSVMが推定した領域であることを意味します。右側、gammaが高いモデルはかなりダメダメであることがわかります。一方、左側、gammaが低いモデルはまあまあ良さそうなモデルです。好みによりますが、どちらかといえば、左上の方が解釈しやすいでしょうか。このように、非線形SVMは複雑な識別境界を生成することができますが、そのせいでかなりアレな知能が形成されてしまう場合もあります。非線形SVMが線形SVMよりも安定しないと言われる所以ですね。なお、今回は偶然gammaが高い方が悪い結果になっただけで、gammaを高めた方が性能が良くなる場合もあります。トライアンドエラーでいい値を探しましょう。
SVMは決定木と同様に、入力変数から実数値を判断する回帰問題も解くことができます。回帰問題を解くSVMを、SVRと区別する人もいます。Rはregression(回帰)から取ってきている模様です。
import numpy as np
from sklearn import svm
import matplotlib.pyplot as plt
# xからyを推定するモデル
x=np.zeros([100,1])
for i in range(0,100):
x[i,0]=i*0.2
y = np.sin(x).ravel() # 配列を1次元に変換
# モデル構築 (回帰の場合は、SVRを使用)
SVR_rbf = svm.SVR(kernel='rbf', C=10, gamma=10)
SVR_lin = svm.SVR(kernel='linear', C=1)
SVR_rbf = SVR_rbf.fit(x, y)
SVR_lin = SVR_lin.fit(x, y)
# # 推定
y_rbf = SVR_rbf.predict(x)
y_lin = SVR_lin.predict(x)
plt.scatter(x, y, c='black', label='Train data')
plt.plot(x, y_rbf, c='red', label='RBF')
plt.plot(x, y_lin, c='blue', label='Linear')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Support Vector Regression')
plt.legend()
plt.show()
xからyを回帰するモデルです。教師データが黒プロット、赤がRBFカーネルによる推定、青が線形カーネルによる推定です。今回はsin波形を回帰させており、直線ではないので、線形カーネルではうまく学習ができていないことがわかります。今回は1入力1出力の回帰モデルを扱いましたが、多入力1出力のモデルも構築可能です。その場合は、xの列成分に変数を増やしていってください。