Support Vector Machine#
マージン#
データがクラス\(\{C_1, C_2\}\)のどちらに含まれるかを判断する2クラス識別問題について考える。 教師ラベルは\(y \in \{+1, -1\}\)であり、それぞれデータが\(C_1, C_2\)のどちらに含まれるかを示すとする。 係数ベクトルを\(w=(w_1, ..., w_d)^T\)、バイアス項を\(b\)、特徴量ベクトルを\(x=(x_1, ..., x_d)^T\)とおくと、線形識別関数は
と表すことができる。
識別境界(識別超平面)は\(f(x)=0\)となる位置に描かれるとし、クラス1を\(f(x) >= 0\)、クラス2を\(f(x) < 0\)で表現するように学習させるとする。例えば次の図のように、ある識別関数が存在したとする。
訓練データ中に存在しなかったノイズがテストデータに含まれていた場合、ノイズの分だけ識別を誤りやすくなる。 しかし、訓練データの点が識別超平面からある値\(h > 0\)よりも離れるように学習させれば、\(h\)より小さなノイズに対しては正しく識別できるようになる。
例えば、以下の図の(a)と(b)はいずれもサンプルをうまく分離できているものの、(a)よりも(b)のほうがデータ点と識別超平面の距離があり、ノイズに対してより頑健で望ましい分類器であると考えられる。
となれば、 「識別超平面が訓練データからもっとも離れるように(両クラスの中間になるように)学習させればよいのではないか」 という考えが湧く。
これがサポートベクターマシン(Support Vector Machine: SVM)の考え方である。
識別境界\(f(x) = 0\)と最も近い各クラスの訓練データの点を サポートベクトル (support vector)といい、サポートベクトルと識別境界との距離(識別境界と最も近いデータ点の距離)を マージン (margin)という。
ある識別関数に対してとれるマージンの大きさは、両クラスの学習データを識別関数の法線ベクトル上に射影した長さの最小値
の半分である。\(\rho(w)\)はクラス間マージンという。次の図中の2つの破線の間の距離が\(\rho(w)\)である
ハードマージンSVM#
学習データの集合を\(\mathcal{D}_L = \{(y_i, x_i)\}(i=1,...,N)\)とする。係数ベクトルはバイアス項\(b\)を外に出す形で、\(w=(w_1, ..., w_d)^T\)と表記する。特徴量ベクトルは\(x=(x_1,...,x_d)^T\)である。\(y_i=\{-1, +1\}\)は教師データで、学習データ\(x_i\in \mathbb{R}^d\)がどちらのクラスに属するかを示す。
線形識別関数のマージンを\(\kappa\)とすれば全ての学習データで
が成り立つ。
係数ベクトルとバイアス項をマージンで正規化(\(w^T x_i = -b\)を定数倍)したものをあらためて\(w, b\)とおけば
となり、まとめて表記すると
クラス間マージンは $\( \rho(w, b) = \min_{x \in C_{y=+1}} \frac{w^T x}{||w||} - \max_{x\in C_{y=-1}} \frac{w^T x}{||w||} \)$
第1項の分子は\(w^T x_i + b \geq +1\)の最小値が\(w^T x_i + b = 1\)であることから\(\min w^T x_i = 1 - b\)
第2項の分子は\(w^T x_i + b \leq -1\)の最大値が\(w^T x_i + b = -1\)であることから\(\max w^T x_i = -1 - b\)
であることを使えば
となる。
識別関数の最大マージンは最大クラス間マージンの半分であるため、\(\frac{1}{||w||}\)となる。
最適識別超平面#
最適な識別超平面は、「すべての訓練データを正しく識別できる」という制約条件
の下でマージン\(\frac{1}{\|w\|}\)を最大化した解として得られる。 マージンの最大化は\(\|w\|\)の最小化と等しいため、
として求めることができる。これは次の不等式制約条件つき最適化問題を解くことで得られる。
主問題
この問題はラグランジュの未定乗数法を用いて解かれ、次のラグランジュ関数として定式化される
ここで\(\alpha=(\alpha_1, ..., \alpha_N)^T\)、\(\alpha_i \geq 0\)であり、\(\alpha_i\)はラグランジュ未定乗数と呼ばれる。
この最適化問題の解\(w_*\)と\(b_*\)は以下のKKT(Karush-Kuhn-Tucker)条件を満たす解として知られている。
KKT条件
(1) \(\displaystyle \frac{\partial \tilde{L}_p(w, b, \alpha)}{\partial w}|_{w=w_*} = w_* - \sum^N_{i=1} \alpha_i y_i x_i = 0\)
(2) \(\displaystyle \frac{\partial \tilde{L}_p(w, b, \alpha)}{\partial b} = \sum^N_{i=1} \alpha_i y_i = 0\)
(3) \(y_i(w^T x_i + b) - 1 \geq 0\)
(4) \(\alpha_i \geq 0\)
(5) \(\alpha_i (y_i (w^T x_i + b) - 1) = 0\)
ラグランジュ関数の\(w\)を\(w_*\)に置き換えてKKT条件(1)と(2)を代入して整理すると
となり、ラグランジュ未定乗数のみの関数にすることができる
KKT条件(1)より最適解は\(w_* = \sum^N_{i=1} \alpha_i y_i x_i\)のようになることがわかっているので、最適な係数\(\alpha_i\)を求める問題に置き換えることができる。
双対問題
ここで
である。
双対問題のラグランジュ関数は、ラグランジュ未定乗数を\(\beta\)とすれば次の関数になる。
KKT条件(5)より\(\alpha_i (y_i (w^T x_i + b) - 1) = 0\)がすべての\(i\)で成り立てば良いため、
となる。\(\alpha_i > 0\)となる\(x_i\)をサポートベクトルという。
最適なバイアス\(b_*\)はサポートベクトルの一つ\(x_s\)を用いて
を解いて求めるか、それらの平均を用いる。
実装例(cvxpy)#
主問題をそのままソルバーに通すパターン
import cvxpy as cp
n = X.shape[0]
d = X.shape[1]
b = cp.Variable()
w = cp.Variable(d)
prob = cp.Problem(cp.Minimize( (1/2) * cp.norm(w, 2)**2 ),
[y[i] * (w.T @ X[i, :] + b) >= 1 for i in range(n)])
prob.solve()
print("The optimal value is", prob.value)
print("w is", w.value)
print("b is", b.value)
The optimal value is 0.9049773766503881
w is [0.90497738 0.99547511]
b is -8.920766380091388e-17
双対問題をソルバーに通すパターン#
双対問題
をcvxpyの二次計画問題のソルバーを使って解いてみる
# データ数が多くなるとソルバーが上手く動かないので一旦暫定的対処としてデータ数を絞る
y = y[3:6]
X = X[3:6]
# Hを作成
n = X.shape[0]
H = np.zeros(shape=(n, n))
for i in range(n):
for j in range(n):
H[i, j] = y[i] * y[i] * X[i] @ X[j]
ones = np.ones(shape=(n, ))
import cvxpy as cp
alpha = cp.Variable(n) # 長さnのベクトル
prob = cp.Problem(cp.Maximize( alpha.T @ ones - (1/2) * cp.quad_form(alpha, H) ),
[alpha.T @ y == 0])
prob.solve()
print("The optimal value is", prob.value)
print("alpha is", alpha.value)
a = alpha.value
w = sum([a[i] * y[i] * X[i] for i in range(n)])
print(f"w={w}")
The optimal value is 33.99999999999994
alpha is [34. 10. 24.]
w=[39.6 49. ]
実装例(scikit-learn)#
from sklearn import svm
clf = svm.SVC(random_state=0, kernel='linear', shrinking=False)
clf.fit(X, y)
print(f"b={clf.intercept_}, w={clf.coef_}")
b=[-0.15974441], w=[[0.76677316 0.83067093]]
ソフトマージンSVM#
C-SVM#
スラック変数と呼ばれる変数\(\xi_i\)を追加する。
以下のように書くこともできる
ここで\(f_{+}(x)\)はヒンジ(hinge)関数と呼ばれるもので
である
ソフトマージン識別器の主問題は以下のように定式化される。
主問題
すべての訓練データのスラック変数の和\(\sum \xi_i (\xi_i \geq 0)\)は誤識別数の上限を与える。 パラメータ\(C\)は誤識別数に対するペナルティの強さであり、\(C\)が大きいほど\(w\)のノルム最小化よりも誤識別数を小さくする方を優先することになる。
このSVMは\(C\)-SVMと呼ばれる。
ν-SVM#
上限サポートベクトル(マージン誤り\(\xi_i > 0\)のベクトルの数)の割合の上限を規定するハイパーパラメータ\(\nu\)が指定できるようになった
カーネルトリック#
カーネルモデル#
線形モデルをカーネルモデルに拡張することを考える。
\(x_i = (x_{i1}, \dots, x_{iD})\)
\(w=(x_1,\dots,x_D)^T\)
\(x_i = (x_{i1}, \dots, x_{iD})\)
\(w=(x_1,\dots,x_N)^T\)
ここで\(\phi(\cdot)\)は任意の関数で、\(\phi(\cdot)\)によって入力ベクトル\(x\)を高次元空間に写像し(2次元では線形分離不可能なものを3次元に写して線形分離不可にするイメージ)、高次元空間上の類似度を内積\(\phi(x_i)^T \phi(x)\)で表す。
カーネルモデルは\(N\)の和が入っているように、訓練データ数\(N\)が増えるとモデルの表現力は高まるが計算量が増える。
カーネルトリック#
\(\phi(x)\)上での内積計算を緩和するために 正定値関数 (positive definite function)を用いる。
正定値関数
関数 \(k(x_i, x_j)\) は次の条件を満たすとき正定値関数と呼ばれる。
(1) 対称性:\(k(x_i, x_j)=k(x_j, x_i)\)
(2) 正定値性:デー夕点 \(\mathrm{x}_1, \mathrm{x}_2, \ldots, \mathrm{x}_N\) に対する以下の グラム行列 (Gram matrix) \(K\) が半正定値である。
すなわち、任意の \(N\) 次元のベクトル \(z\) に対し \(z^{\top} K z \geq 0\) が成り立つ。
正定値関数 \(k(x_i, x_j)\) は 再生核ヒルベルト空間 \(\mathcal{H}_k\)への写像 \(\phi(x_i), \phi(x_j) \in \mathcal{H}_k\)の内積に対応する。
これを用いて高次元空間上での内積をより単純な2変数関数の計算\(k(\cdot, \cdot)\)に置き換える事ができる。この正定値関数\(k(\cdot, \cdot)\)のことを カーネル関数 (kernel function)、グラム行列\(K\)を カーネル行列 (kernel matrix) と呼ぶ。
カーネル関数とカーネル行列を用いると、カーネルモデルは以下のように表現できる
ここで\(K_{i:}\)はカーネル行列の\(i\)行目の行ベクトルを表す。
カーネル関数の例#
入力をそのまま出力する写像関数\(\phi(x)=x^T\)に対応する
カーネルモデルを用いたソフトマージン最大化問題(主問題)
カーネルモデルを用いたソフトマージン最大化のラグランジュ双対問題
# TODO: コードかく
参考#
八谷大岳. (2020). ゼロからつくるPython機械学習プログラミング入門.
# TODO
from sklearn import datasets
X, y = datasets.load_iris(return_X_y=True, as_frame=False)
X = X[:, 0:2]
fig, ax = plt.subplots()
for c, label in enumerate(['setosa', 'versicolor', 'virginica']):
is_c = y == c
ax.scatter(X[is_c, 0], X[is_c, 1], label=label)
ax.legend()
<matplotlib.legend.Legend at 0x7f4cca912d10>
# setosaとvirginicaを1つにまとめて2クラスにする
y[y == 2] = 0
fig, ax = plt.subplots()
for c, label in enumerate(['setosa', 'versicolor', 'virginica']):
is_c = y == c
ax.scatter(X[is_c, 0], X[is_c, 1], label=label)
ax.legend()
<matplotlib.legend.Legend at 0x7f4cd4ba76d0>
GaussianKernel (RBF kernel)#
一般的には $\( k(\mathbf{x},\mathbf{x}^{\prime}) = \exp \left( \frac{-\| \mathbf{x} - \mathbf{x}^{\prime} \|^{2}}{2\sigma^{2}} \right) \tag{2} \)$
David Duvenaud (2014). “The Kernel Cookbook: Advice on Covariance functions”.
x1 = np.array([[1], [2]])
x2 = np.array([[3], [4]])
x1
array([[1],
[2]])
def rbf_kernel(x1, x2, sigma=1.0) -> float:
return - np.linalg.norm(x1 - x2)**2 / (2 * sigma**2)
rbf_kernel(x1, x2)
-4.000000000000001
class GaussianKernel:
def fit(self, X):
self.alpha = 0.001
self.mu = np.mean(X, axis=0)
self.sigma = np.cov(X, rowvar=False, bias=False)
def transform(self, X) -> np.array:
return np.array([self._transform(x) for x in X])
def _transform(self, x) -> float:
return np.exp( - self.alpha * (x - self.mu) @ np.linalg.inv(self.sigma) @ (x - self.mu) )
def __repr__(self) -> str:
return f"<{self.__class__.__name__} alpha={self.alpha}, mu={self.mu}, sigma={self.sigma}>"
Memo 4#
RBFカーネルが無限次元になるのは指数関数の冪級数による定義が無限和であるため
ref: https://ja.wikipedia.org/wiki/指数関数#厳密な定義a
TODO: もっと図とか入れたい
のように
Support vector machine classifier with (\ell_1)-regularization — CVXPY 1.3 documentation