このエントリは機械学習の数理 Advent Calendar 2018の7日目の記事になります。ハードマージン線形SVMを一部ソルバのちからを借りつつPythonで実装します。
数学的な準備
超平面について
p次元の分離超平面は、
w1x1+w2x2+⋯+wpxp+b=0
という式で表されます。
ベクトル
wx=(w1,w2,…,wp)T=(x1,x2,…,xp)T
を用いて
wx+b=0
と表されます。
また、超平面を実数倍しても、超平面自体は変わりません。
rwx+rb=0
超平面と点の距離
高校生の時に、1次関数(w1x1+w2x2+b=0)と点(x1′,x2′)の距離の公式を
d=w12+w22∣w1x1′+w2x2′+b∣
と習ったと思います。
これを一般化すると、
d=w12+w22+⋯+wp2∣w1x1′+w2x2′+,…,+wpxp′+b∣=∣∣w∣∣∣wTx′+b∣
となります。
ラグランジュ理論
不等式制約つき最適化問題
w,bminf(w)subject to gi(w)≤0 (i=1,…,n)
について
L(w,α)=f(w)+i=1∑nαigi(w)
をラグランジュ関数と定めたとき、次の4つの条件を満たすwとαを見つける問題に帰着される。
(1)(2)(3)(4)∂wj∂L(w,α)=∂wj∂f(w)+i=1∑nαj∂wj∂gi(w)=0∂αi∂L(w,α)=gi(w)≤0αi≥0αigi(w)=0
これらの条件のことをKKT条件(カルーシュ・クーン・タッカー条件)という。詳細については略。
ハードマージン線形SVM
2次計画問題への落し込み
線形分離可能とは
とした時に、
yi(wTxi+b)>0 (i=1,…,n)
を満たすことを言う。
この時、超平面から最も近いデータをサポートベクターと呼び、そのサポートベクターと超平面の距離dを最大化することを考えます。(ここで超平面と点の距離の公式を使います。)
w,bmaxd subject to ∣∣w∣∣yi(wTxi+b)≥d (i=1,…,n)
しかし、最大化問題は難しい…
そこで、先ほどの制約条件式の両辺をdで割って、
yi(d∣∣w∣∣1wTxi+d∣∣w∣∣1b)≥1 (i=1,…,n)
超平面を実数倍しても超平面自体は変わらないので、スケールを変換して、
yi(w′Txi′+b)≥1 (i=1,…,n)
という式が出てきますが、こちらが制約条件式になります。
この時、サポートベクターについては次式が成り立つ。
yi(w′Txi′+b)=1
yiは1or−1なので、サポートベクターと超平面の距離d′は
d′=∣∣w′∣∣∣w′Tx′+b∣=∣∣w′∣∣1
となります。
以降は′を外して説明します。
距離dを最大化するという問題を解きやすくするために逆数の2乗の最小化問題に置き換えます。
こうすることによって2次計画問題(解が一意に決定する。)に落としこむことができました。
w,bmin21∣∣w∣∣2 subject to yi(wTxi+b)≥1 (i=1,…,n)
主問題から双対問題への落し込み
この不等式制約つき最適化問題をラグランジュ理論を基に双対問題へ落し込む。制約条件式を変形して、
yi(wTxi+b)−{yi(wTxi+b)−1}≥1 (i=1,…,n)≤0 (i=1,…,n)
ラグランジュ未定乗数 α=(α1,α2,…,αn)T (αi≥0)を定義すると、ラグランジュ関数は、
L(w,b,α)=21∣∣w∣∣2−i=1∑nαi{yi(wTxi+b)−1}
と表され、偏微分すると、
∂b∂L(w,b,α)=−i=1∑nαiyi=0↔i=1∑nαiyi=0
∂w∂L(w,b,α)=w−i=1∑nαiyixi=0↔w=i=1∑nαiyixi
∑i=1nαiyi=0とw=∑i=1nαiyixiをL(w,b,α)に代入して計算すると、
LD(α)=21∣∣w∣∣2−i=1∑nαi{yi(wTxi+b)−1}=21i=1∑nαiαjyiyjxiTxj−i=1∑n{αiyi(wTxi+b)−αi}=21i=1∑nαiαjyiyjxiTxj−i=1∑nαiyiwTxi−bi=1∑nαiyi+i=1∑nαi=i=1∑nαi−21i=1∑nj=1∑nαiαjyiyjxiTxj (∵i=1∑nαiyi=0)
となる。
従って得られた双対問題は、
αmaxLD(α) subject to i=1∑nαiyi=0 αi≥0 (i=1,…,n)
これを2次計画問題のソルバーを使って解を得ることを考える。Pythonにはcvxopt
という数理最適化用ライブラリがあるのでこれの中にあるcvxopt.solvers.qp
を用いる。先程得られた双対問題をこれに合わせて変形することを考える。
一般的な凸2次計画問題の最適条件の定式は
αmin 21αTQα+pα
subject to Gα≤h Aα=b
と表される。一方、最適化したい問題は
αmin21i=1∑nj=1∑nαiαjyiyjxiTxj−i=1∑nαi
subject to αi≥0 (i=1,…,n) i=1∑nαiyi=0
なのでこれを式変形して
αmin21(α1,α2,⋯,αn)⎝⎜⎜⎜⎜⎛y1y1x1Tx1y2y1x2Tx1⋮yny1xnTx1y1y2x1Tx2y2y2x2Tx2⋮yny2xnTx2⋯⋯⋱⋯y1ynx1Txny2ynx2Txn⋮ynynxnTxn⎠⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎛α1α2⋮αn⎠⎟⎟⎟⎟⎞+(−1,−1,⋯,−1)⎝⎜⎜⎜⎜⎛α1α2⋮αn⎠⎟⎟⎟⎟⎞=αmin21αTQ⎝⎜⎜⎜⎜⎛y1y1x1Tx1y2y1x2Tx1⋮yny1xnTx1y1y2x1Tx2y2y2x2Tx2⋮yny2xnTx2⋯⋯⋱⋯y1ynx1Txny2ynx2Txn⋮ynynxnTxn⎠⎟⎟⎟⎟⎞α+p(−1)Tα
subject to αi≥0 (i=1,…,n)
⎝⎜⎜⎜⎜⎛α1α2⋮αn⎠⎟⎟⎟⎟⎞≥⎝⎜⎜⎜⎜⎛00⋮0⎠⎟⎟⎟⎟⎞
⎝⎜⎜⎜⎜⎛10⋮001⋮0⋯⋯⋱⋯0001⎠⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎛α1α2⋮αn⎠⎟⎟⎟⎟⎞∴G−⎝⎜⎜⎜⎜⎛10⋮001⋮0⋯⋯⋱⋯0001⎠⎟⎟⎟⎟⎞α≥⎝⎜⎜⎜⎜⎛00⋮0⎠⎟⎟⎟⎟⎞≤h⎝⎜⎜⎜⎜⎛00⋮0⎠⎟⎟⎟⎟⎞
また、もう一つの条件式についても
i=1∑nαiyi=0
(y1,y2,⋯,yn)⎝⎜⎜⎜⎜⎛α1α2⋮αn⎠⎟⎟⎟⎟⎞=0
A(y1,y2,⋯,yn)α=b0
つまるところ、
Q=⎝⎜⎜⎜⎜⎛y1y1x1Tx1y2y1x2Tx1⋮yny1xnTx1y1y2x1Tx2y2y2x2Tx2⋮yny2xnTx2⋯⋯⋱⋯y1ynx1Txny2ynx2Txn⋮ynynxnTxn⎠⎟⎟⎟⎟⎞,
p=(−1)T,
G=−⎝⎜⎜⎜⎜⎛10⋮001⋮0⋯⋯⋱⋯0001⎠⎟⎟⎟⎟⎞,
h=⎝⎜⎜⎜⎜⎛00⋮0⎠⎟⎟⎟⎟⎞,
A=(y1,y2,⋯,yn),
b=0.
として2次計画問題を解けば良いことになる。ただし、このときAは1行n列の行列として扱う。
これにより得られた解α=(α1,α2,⋯,αn)とすると、4番目のKKT条件から
αigi(w)=0
αi(yi(wTxi+b)−1)=0
を満たすことになるが、サポートベクターについてはwTxi+b=1 or −1となるため、αiがどのような値であっても条件を満たすが、それ以外についてはyi(wTxi+b)−1が0にならないため、必然的にαiが0になる必要がある。このため、wを求めるには非ゼロのαiについてのみ考えれば良く、
w=i∈S∑αiyixiwhere S is the set of indices of support vectors.
と求めることができる。一方バイアスbについて考える。サポートベクターについては、
yi(wTxi+b)=1
が成り立つので、正例のサポートベクターをx+、負例のサポートベクターをx−とすると、
wTx++b=1wTx−+b=−1↔wTx++wTx−+2b=0↔b=−21(wTx++wTx−)
Pythonで実装すると
import numpy as np
import cvxopt
import matplotlib.pyplot as plt
import sklearn.datasets
from typing import List
N = 100
positive: List[np.ndarray] = []
negative: List[np.ndarray] = []
mean1 = [-2, 2]
mean2 = [2, -2]
cov = [[1.0, 0.3], [0.3, 1.0]]
positive.extend(np.random.multivariate_normal(mean1, cov, N//2))
negative.extend(np.random.multivariate_normal(mean2, cov, N//2))
X = np.vstack((positive, negative))
y = []
for i in range(N//2):
y.append(1.0)
for i in range(N//2):
y.append(-1.0)
y = np.array(y)
_Q = np.zeros((N, N))
for i in range(N):
for j in range(N):
_Q[i, j] = y[i]*y[j]*np.dot(X[i], X[j])
Q = cvxopt.matrix(_Q)
p = cvxopt.matrix(-np.ones(N))
G = cvxopt.matrix(-np.eye(N))
h = cvxopt.matrix(np.zeros(N))
A = cvxopt.matrix(y[np.newaxis], (1, N), 'd')
b = cvxopt.matrix(0.0)
solution = cvxopt.solvers.qp(Q, p, G, h, A, b)
alpha = np.array(solution['x']).flatten()
top2_sv_indices = alpha.argsort()[-2:]
sv_indices = alpha > 1e-6
W = np.dot(alpha[sv_indices] * y[sv_indices], X[sv_indices])
bias = - np.dot(X[top2_sv_indices], W).mean()
xs = np.array([X.min(), X.max()])
ys = np.array([(-W[0]*xs[0]-bias)/W[1], (-W[0]*xs[1]-bias)/W[1]])
plt.scatter(X[:, 0][y == 1], X[:, 1][y == 1], label="positive")
plt.scatter(X[:, 0][y == -1], X[:, 1][y == -1], label="negative")
for coordinate in X[sv_indices]:
plt.annotate('sv', coordinate)
plt.plot(xs, ys)
plt.legend()
plt.grid()
plt.show()
実行した出力
pcost dcost gap pres dres
0: -7.4843e+00 -1.2390e+01 3e+02 2e+01 2e+00
1: -4.4900e+00 -1.3805e+00 3e+01 2e+00 2e-01
2: -1.0676e-01 -4.8913e-01 8e-01 2e-02 2e-03
3: -1.8470e-01 -2.8934e-01 1e-01 3e-03 3e-04
4: -2.4603e-01 -2.7739e-01 3e-02 6e-05 6e-06
5: -2.6911e-01 -2.7311e-01 4e-03 6e-06 7e-07
6: -2.7247e-01 -2.7268e-01 2e-04 2e-07 2e-08
7: -2.7266e-01 -2.7266e-01 2e-06 2e-09 2e-10
8: -2.7266e-01 -2.7266e-01 2e-08 2e-11 2e-12
Optimal solution found.
おわりに
今回は完全に線形分離可能な問題下におけるハードマージン線形SVMについて扱いました。線形分離可能でない場合については、今回解いた2次計画問題の設定では解にたどり着けないのでスラック変数というのを導入するのですが、それに関してはまた次回以降。
参考