非線形SVMについての掘り下げ

1: カーネル関数とは?

用語について

$$ カーネル関数とは、x_nとx_iの近さを表す関数 $$

$$k(\boldsymbol{x},\boldsymbol{x'}) = φ(\boldsymbol{x})^{\mathrm{T}}φ(\boldsymbol{x'}) $$

$$ = φ_1(\boldsymbol{x'})φ_1(\boldsymbol{x'})+φ_2(\boldsymbol{x'})φ_2(\boldsymbol{x'})+ ... +φ_n(\boldsymbol{x'})φ_n(\boldsymbol{x'}) で表される $$

$$ 特徴空間とは、入力ベクトル\boldsymbol{x}の写像 $$

カーネル関数の定義方法二種類

1、特徴空間への写像を考え、その内積をカーネル関数とする

$$k(\boldsymbol{x},\boldsymbol{x'}) = φ(\boldsymbol{x})^{\mathrm{T}}φ(\boldsymbol{x'}) $$

2、カーネル関数を直接定義する

$$k(\boldsymbol{x},\boldsymbol{z}) = (\boldsymbol{x}^{\mathrm{T}}\boldsymbol{z})^2 $$

$$二次元入力(x_1,x_2)を考え、展開すると$$

\begin{align} k(\boldsymbol{x},\boldsymbol{x'}) &= (x_1z_1+ x_2z_2)^2 \\ &= x_1^2z_1^2 + 2x_1z_1x_2z_2 + x_2^2z_2^2\\ &= (x_1^2,\sqrt{2}x_1x_2,x_2^2)(z_1^2,\sqrt{2}z_1z_2,z_2^2)\\ &= φ(\boldsymbol{x})^{\mathrm{T}}φ(\boldsymbol{z}) \end{align}

これは特徴ベクトルの積で表されたので、有効なカーネル関数 $$ちなみに、有効になるための必要十分条件は任意の{x_n}に対して要素がk(x_n,x_n)で与えられるグラム行列kが半正定値であること$$

2: Gaussian Kernal

\begin{align} e^{-γ|x_i-x_j|^2}\\ &=e^{-γ(x_i-x_j)^2}\\ &=e^{-γx_i^2+2γx_ix_j-γx_j^2}\\ &=e^{-γx_i^2-γx_j^2}e^{2γx_ix_j}\\ &=e^{-γx_i^2-γx_j^2}(1+\frac{2γx_ix_j}{1!}+\frac{(2γx_ix_j)^2}{2!}+...)\\ &=e^{-γx_i^2-γx_j^2}(1+\sqrt{\frac{2γ}{1!}}x_i\sqrt{\frac{2γ}{1!}x_j}+...)\\ &= φ(\boldsymbol{x_i})^{\mathrm{T}}φ(\boldsymbol{x_j}) \end{align}

なぜカーネル関数を使うとうまくいくか φという特徴空間への写像を自由に選べるから

例えばgaussian kernel だと、特徴空間の次元が無限になり、超平面(線形関数)で分割できるようになる。 また、カーネル関数を用いると、φがわからずとも内積を計算できる。

この前は、「線形分離がおおよそできる」という条件を満たす任意のφについて成り立つ式変形をしていた。

3: ソフトマージン

マージンの外側の領域を0、正しく分類されているがマージン内のものを0〜1、誤分類を1とする、スラック変数を定義する。 $$C\sum_{k=1}^{n} スラック変数 + \frac{1}{2}|\boldsymbol{w}|^2 \\ $$ Cの値を無限大にすれば、ハードマージンとなる

In [ ]:
# 非線形SVM(ソフトマージン)
# cvxoptのQuadratic Programmingを解く関数を使用

import numpy as np
from scipy.linalg import norm
import cvxopt
import cvxopt.solvers
from pylab import *

N = 200         # データ数
C = 0.5         # スラック変数を用いて表されるペナルティとマージンのトレードオフパラメータ
SIGMA = 0.3     # ガウスカーネルのパラメータ


# ガウスカーネル
def gaussian_kernel(x, y):
    return np.exp(-norm(x-y)**2 / (2 * (SIGMA ** 2)))

# どちらのカーネル関数を使うかここで指定
kernel = gaussian_kernel

def f(x, a, t, X, b):
    sum = 0.0
    for n in range(N):
        sum += a[n] * t[n] * kernel(x, X[n])
    return sum + b

if __name__ == "__main__":
    # 訓練データをロード
    data = np.genfromtxt("classification.txt")
    X = data[:,0:2]
    t = data[:,2] * 2 - 1.0  # 教師信号を-1 or 1に変換
    
    # ラグランジュ乗数を二次計画法(Quadratic Programming)で求める
    K = np.zeros((N, N))
    for i in range(N):
        for j in range(N):
            K[i, j] = t[i] * t[j] * kernel(X[i], X[j])
    
    Q = cvxopt.matrix(K)
    p = cvxopt.matrix(-np.ones(N))
    temp1 = np.diag([-1.0]*N)
    temp2 = np.identity(N)
    G = cvxopt.matrix(np.vstack((temp1, temp2)))
    temp1 = np.zeros(N)
    temp2 = np.ones(N) * C
    h = cvxopt.matrix(np.hstack((temp1, temp2)))
    A = cvxopt.matrix(t, (1,N))
    b = cvxopt.matrix(0.0)
    sol = cvxopt.solvers.qp(Q, p, G, h, A, b)
    a = array(sol['x']).reshape(N)
    print (a)
    
    # サポートベクトルのインデックスを抽出
    S = []
    M = []
    for n in range(len(a)):
        if 0 < a[n]:
            S.append(n)
        if 0 < a[n] < C:
            M.append(n)
    
    # bを計算
    sum = 0
    for n in M:
        temp = 0
        for m in S:
            temp += a[m] * t[m] * kernel(X[n], X[m])
        sum += (t[n] - temp)
    b = sum / len(M)
    
    print (S, b)
    
    # 訓練データを描画
    for n in range(N):
        if t[n] > 0:
            scatter(X[n,0], X[n,1], c='b', marker='o')
        else:
            scatter(X[n,0], X[n,1], c='r', marker='o')
    
    # サポートベクトルを描画
#    for n in S:
#        scatter(X[n,0], X[n,1], s=80, c='c', marker='o')
    
    # 識別境界を描画
    X1, X2 = meshgrid(linspace(-2,2,50), linspace(-2,2,50))
    w, h = X1.shape
    X1.resize(X1.size)
    X2.resize(X2.size)
    Z = array([f(array([x1, x2]), a, t, X, b) for (x1, x2) in zip(X1, X2)])
    X1.resize((w, h))
    X2.resize((w, h))
    Z.resize((w, h))
    CS = contour(X1, X2, Z, [0.0], colors='k', linewidths=1, origin='lower')
    
    for n in S:
        print (f(X[n], a, t, X, b))
    
    xlim(-2, 2)
    ylim(-2, 2)
    show()
In [ ]:
# 台湾大学資料 https://www.csie.ntu.edu.tw/~cjlin/talks/kuleuven_svm.pdf
http://aidiary.hatenablog.com/entry/20100503/1272889097