いい加減時代の潮流に乗ろうということで機械学習を学びたいと思う。またRはともかくとしてPythonは未経験であるため、丁度良い書籍として
を用いることにする。
前回
8. サポートベクトルマシン
8.2 最適化の理論
主問題、双対問題の最適値をそれぞれ
とおくと、に対して
であるから、
が成立する。以下、この不等式の等式が成立していると仮定する*1。
次にが凸関数でで微分可能だとする。以下の条件を条件という。
条件 の下でがを最小にすることと、
であって
を満足するようなが存在することは同値である。
が成立する。このことから、条件の下でについて
が成立し、が最適解であることが分かる。
() が主問題の最適解であれば、の解が存在することを意味するため、
でなければならない。仮定から
が成り立つ。すなわち2つの不等式はすべて等式となり、
が成立する。またはの最小値を取ることで得られたものだから、
も成り立つ。 )
8.3 サポートベクトルマシンの解
条件から以下の7つの式が得られる。
の双対問題は
と書ける。更にで微分してとおいた式を用いると
で得られる。以上から係数だったを入力とする関数
を構成できる。は
を満たすような範囲で動く。はの中に亜h含まれていないものの、がと共に上記のとして反映されている。
ここで双対問題を解くに当たり、が以下の3つの場合に分類できることに注意する。
に対する条件
を順番に用いることで
が得られる。のとき、
を用いることでを得る。更により
である。
のときおよびより
が成り立つ。更により
である。他方で、
を用いることで
が成立する。 )
このような性質を利用して、からを求める方法を検討する。
まずであるようなが少なくとも1つ存在することを示す。上記の命題から、かつを意味する。この場合、
の最初の2項は小さく出来るから、が最適であることに矛盾する。
次にの下ではを、はを意味する。そこでとおき、各をに、をに置き換えてもとなるから、
の等号が成立し、がに置き換わるのみであるから、7つの条件は成立する。しかしは第3項がだけ減少するから、上記の命題から、少なくとも1つのについてを得る。
具体的にとなるようなを集めて、の算術平均を取ればよい。
双対問題
を二次計画法の解ソルバーで解く。これには
となるようなを指定する必要がある。
8.4 カーネルを用いた拡張
サポートベクトルマシンを双対問題で解く必要があるのは、が内積を用いて
と書ける。を内積が定義されたベクトル空間だとして、を用いると、内積をに置き換えることができる。この場合、から非線形の分類規則が得られる。
成分がの内積であるような行列によってカーネルを構成する。実際、任意のに対して、二次形式
が非負定値で対称であるから、狭義のカーネルである。
あるを用いて、をすべてに置き換えるものとする。したがってはであり、の定義の中の内積はとの間の内積、すなわちカーネルとなる。
8.5 Rによるシミュレーション
8.5.1 サポートベクトルマシンの例
library("quadprog") #二次計画法を解くパッケージ svm_1 <- function(X, y, C){ eps <- 10^(-3) n <- nrow(X) meq <- 1 Dmat <- matrix(nrow = n, ncol = n) for(i in 1:n){ for(j in 1:n){ Dmat[i,j] <- sum(X[i,] * X[j,]) * y[i] * y[j] } } Dmat <- Dmat + eps * diag(n) # 対角成分に微小な値を加えることで正則にする dvec <- rep(1:n) Amat <- matrix(nrow = (2*n +1), ncol = n) Amat[1,] <- y Amat[2:(n+1), 1:n] <- -diag(n) Amat[(n+2):(2*n+1),1:n] <- diag(n) Amat <- t(Amat) bvec <- c(0, rep(-C,n), rep(0,n)) alpha <- solve.QP(Dmat, dvec, Amat, bvec = bvec, meq=1)$solution beta <- drop((alpha * y) %*% X) index <- (1:n)[eps < alpha & alpha < C - eps] beta_0 <- mean(y[index] - X[index, ] %*% beta) return(list(beta = beta, beta_0 = beta_0)) } #### library("ggplot2") set.seed(1) a <- rnorm(1) b <- rnorm(1) n <- 300 X <- matrix(rnorm(n*2), nrow = n, ncol = 2) y <- sign(a * X[,1] + b * X[,2] + 0.1*rnorm(n)) df_test <- data.frame(X,y) colnames(df_test) <- c("x1","x2","y") qq <- svm_1(X,y,10) # グラフ g <- ggplot(df_test,aes(x = x1, y = x2, fill = y, colour = y)) g <- g + geom_point() + geom_abline(intercept = -qq$beta_0/qq$beta[1],slope = -qq$beta[1]/qq$beta[2]) g <- g + labs(fill = "",color="",x="x1", y = "x2") g <- g + theme_classic() g <- g + theme(plot.title = element_text(hjust = 0.5),legend.position = "none", legend.title=element_text(size = 12), legend.text=element_text(size = 12)) g <- g + scale_x_continuous(limits = c(-3,3),labels = seq(-3,3,1),breaks = seq(-3,3,1)) g <- g + scale_y_continuous(limits = c(-3,3),labels = seq(-3,3,1),breaks = seq(-3,3,1)) g <- g + geom_vline(xintercept = 0, linetype = 1) g <- g + geom_vline(xintercept = seq(-3,3,1), linetype = 3) g <- g + geom_hline(yintercept = 0, linetype = 1) g <- g + geom_hline(yintercept = seq(-3,3,1), linetype = 3) plot(g)
8.5.2 カーネルを用いたサポートベクトルマシンの拡張
svm_2 <- function(X, y, C, K) { eps <- 10^(-4) n <- nrow(X) Dmat <- matrix(nrow = n, ncol = n) for (i in 1:n){ for(j in 1:n){ Dmat[i, j] <- K(X[i, ], X[j, ]) * y[i] * y[j] } } Dmat <- Dmat + eps * diag(n) dvec <- rep(1, n) Amat <- matrix(nrow = (2 * n + 1), ncol = n) Amat[1, ] <- y Amat[2:(n + 1), 1:n] <- -diag(n) Amat[(n + 2):(2 * n + 1), 1:n] <- diag(n) Amat <- t(Amat) bvec <- c(0, -C * rep(1, n), rep(0, n)) meq <- 1 alpha <- solve.QP(Dmat, dvec, Amat, bvec = bvec, meq = 1)$solution index <- (1:n)[eps < alpha & alpha < C - eps] beta <- drop((alpha * y) %*% X) beta_0 <- mean(y[index] - X[index, ] %*% beta) return(list(alpha = alpha, beta_0 = beta_0)) } K_linear <- function(x, y) { return(t(x) %*% y) } K_poly <- function(x, y) { return((1 + t(x) %*% y) ^ 2) } plot_kernel <- function(K, lty) { # 引数ltyで,線の種類を指定する qq <- svm_2(X, y, 1, K) alpha <- qq$alpha beta_0 <- qq$beta_0 f <- function(u, v) { x <- c(u, v) S <- beta_0 for (i in 1:n) S <- S + alpha[i] * y[i] * K(X[i, ], x) return(S) } # fは(x, y)でのz方向の高さを求める。これから輪郭を求めることができる。 u <- seq(-2, 2, 0.1) v <- seq(-2, 2, 0.1) w <- array(dim = c(41, 41)) for (i in 1:41) for (j in 1:41) w[i, j] <- f(u[i], v[j]) contour(u, v, w, level = 0, add = TRUE, lty = lty) } # 実行 a <- 3 b <- -1 n <- 200 X <- matrix(rnorm(n * 2), ncol = 2, nrow = n) y <- sign(a * X[, 1] + b * X[, 2] ^ 2 + 0.3 * rnorm(n)) plot(-3:3, -3:3, xlab = "X[, 1]", ylab = "X[, 2]", type = "n") for (i in 1:n) { if (y[i] == 1) { points(X[i, 1], X[i, 2], col = "red") } else { points(X[i, 1], X[i, 2], col = "blue") } } plot_kernel(K_linear, 1) plot_kernel(K_poly, 2)
8.6 Pythonによるシミュレーション
8.6.1 サポートベクトルマシンの例
import numpy as np import matplotlib.pyplot as plt %matplotlib inline import japanize_matplotlib import scipy from scipy import stats from numpy.random import randn #正規乱数 import cvxopt from cvxopt import matrix a = randn(1) b = randn(1) n = 100 X = randn(n,2) y = np.sign(a * X[:,0] + b * X[:,1] + 0.1*randn(n)) y = y.reshape(-1,1) # def svm_1(X, y, C): eps = 0.0001 n = X.shape[0] P = np.zeros((n,n)) for i in range(n): for j in range(n): P[i,j] = np.dot(X[i,:], X[j,:]) * y[i] * y[j] P = matrix(P + np.eye(n) * eps) A = matrix(-y.T.astype(np.float)) b = matrix(np.array([0]).astype(np.float)) h = matrix(np.array([C]*n+[0]*n).reshape(-1,1).astype(np.float)) G = matrix(np.concatenate([np.diag(np.ones(n)),np.diag(-np.ones(n))])) q = matrix(np.array([-1]*n).astype(np.float)) # ソルバーの実行 res = cvxopt.solvers.qp(P,q,A=A, b=b,G=G, h=h) alpha = np.array(res['x']) beta = ((alpha*y).T@X).reshape(2,1) index = (eps < alpha[:, 0]) & (alpha[:, 0] < C - eps) beta_0 = np.mean(y[index]-X[index,:]@beta) return {'beta':beta, 'beta_0':beta_0} # テスト a=randn(1);b=randn(1) n=100 X=randn(n,2) y=np.sign(a*X[:,0]+b*X[:,1]+0.1*randn(n)) y=y.reshape(-1,1) for i in range(n): if y[i]==1: plt.scatter(X[i,0],X[i,1],c="red") else : plt.scatter(X[i,0],X[i,1],c="blue") res=svm_1(X,y,C=10) def f(x): return -res['beta_0']/res['beta'][1]-x*res['beta'][0]/res['beta'][1] x_seq=np.arange(-3,3,0.5) plt.plot(x_seq,f(x_seq)) res
8.6.2 カーネルを用いたサポートベクトルマシンの拡張
import numpy as np import matplotlib.pyplot as plt %matplotlib inline import japanize_matplotlib import scipy from scipy import stats from numpy.random import randn #正規乱数 import cvxopt from cvxopt import matrix # def K_linear(x,y): return x.T@y def K_poly(x,y): return (1+x.T@y)**2 def svm_2(X,y,C,K): eps = 0.0001 n = X.shape[0] P = np.zeros((n,n)) for i in range(n): for j in range(n): P[i,j] = K(X[i,:],X[j,:]) * y[i] * y[j] #パッケージにあるmatrix関数を使って指定する必要がある P = matrix(P+np.eye(n)*eps) A = matrix(-y.T.astype(np.float)) b = matrix(np.array([0]).astype(np.float)) h = matrix(np.array([C]*n+[0]*n).reshape(-1,1).astype(np.float)) G = matrix(np.concatenate([np.diag(np.ones(n)),np.diag(-np.ones(n))])) q = matrix(np.array([-1]*n).astype(np.float)) res = cvxopt.solvers.qp(P,q, A=A, b=b,G=G, h=h) alpha = np.array(res['x']) #xが本文中のalphaに対応 beta = ((alpha*y).T@X).reshape(2,1) index = (eps < alpha[:, 0]) & (alpha[:, 0] < C - eps) beta_0 = np.mean(y[index]-X[index,:]@beta) return {'alpha':alpha, 'beta':beta, 'beta_0':beta_0} # テスト a = 3 b = -1 n = 200 X = randn(n,2) y = np.sign(a * X[:,0] + b * X[:,1]**2 + 0.3 * randn(n)) y = y.reshape(-1,1) def plot_kernel(K,line): res = svm_2(X,y,1,K) alpha = res['alpha'][:,0] beta_0 = res['beta_0'] def f(u,v): S = beta_0 for i in range(X.shape[0]): S = S + alpha[i] * y[i] * K(X[i,:],[u,v]) return S[0] uu = np.arange(-2,2,0.1) vv = np.arange(-2,2,0.1) ww = [] for v in vv: w = [] for u in uu: w.append(f(u,v)) ww.append(w) plt.contour(uu,vv,ww,levels=0,linestyles=line) for i in range(n): if y[i]==1: plt.scatter(X[i,0],X[i,1],c="red") else: plt.scatter(X[i,0],X[i,1],c="blue") plot_kernel(K_poly,line="dashed") plot_kernel(K_linear,line="solid")
*1:サポートベクトルマシンはこの仮定を満たしている。