文章目錄
Linear SVM
perceptron model
感知機模型是二分類線性判別模型,將所有誤分樣本的幾何間隔作爲損失函數,使用隨機梯度下降法求解. 所有樣本點正確分類時,模型學習完成,最終超平面與初始參數、梯度更新次序有關.
樣本離超平面的距離,可近似模型對樣本分類的可靠程度. 對於所有滿足條件的超平面,距最近樣本點距離最大的超平面爲最優超平面. 基於此建立的線性可分模型爲凸優化模型
,解唯一.
distance to hyperplane
對於超平面,則空間中任意點到的距離爲
證明: 若是中的任意一點,到的距離爲到法向量的投影,即
distance to separating hyperplane
若樣本標籤,樣本的預測標籤爲,當超平面將正確劃分,則始終滿足,因此正確劃分的到的距離等價於
margin of special separating hyperplane
縮放可調整到的函數間隔,爲便於計算,令最小函數間隔爲1,則最小函數間隔和幾何間隔分別表示爲
基於**最大化最小几何間隔(最大化邊界)**建立模型,優化問題爲
最小函數間隔爲1,等價於所有樣本的函數間隔大於等於1,優化問題等價於
上述問題爲凸二次規劃問題,有閉式最優解.
Linear SVM of Soft Interval Maximization
SVM Model and Hinge Loss
令函數間隔,鬆弛變量
與合頁損失函數
即,; ,.
對於線性不可分問題,以合頁函數作爲損失函數並加入正則項,模型表示爲(可用梯度下降法求解)
令,則上述優化問題等價於(推導略)
最小化目標函數的意義是使間隔儘量大,同時使誤分類點數儘量少. 目標函數第一項爲結構風險,第二項爲經驗風險. 懲罰參數越大,模型誤分類的代價就越大.
LR使用交叉熵損失,關注全局實例,輸出具有自然概率意義.
Dual Representation
原始約束極值問題,構造拉格朗日函數
原問題內部極大化是不等式約束的等價形式:
- 若存在不滿足約束的或,令或,則;
- 若任意滿足約束,則;
i.
求的偏導並令其爲0,得
ii.
帶入上述結果,最優化問題轉換爲
消去得
基於SMO算法求解約束方程解,.
令表示的集合,則,模型參數
實例點計算僅以內積形式出現,可自然引入核函數.
KKT Condition and Support Vector
KKT條件(最優解必要條件)
對應的實例爲支持向量,由KKT條件知支持向量滿足
- ,,樣本位於邊界之外(分類正確);
- ,,樣本位於邊界上(分類正確);
- ,,樣本位於超平面與邊界之間(分類正確);,樣本位於超平面上;,樣本位於邊界之外(分類錯誤);
Non-linear SVM Based on Kernel function
對於有限維數據,一定存在高維特徵空間使樣本線性可分. 對於非線性分類問題,首先需將原空間數據變換至新的特徵空間(一般爲高維),然後在新空間中使用線性分類方法學習分類模型.
SVM中引入核函數,得SVM的決策函數
Kernel Trick
設是一個從到的映射,對所有的滿足
定義核函數使得學習隱式地在特徵空間中進行,不需要顯式定義新的特徵空間和映射函數,避免在新的高維空間中做內積運算.
對於映射,則
在低維空間定義的運算等價於中完成高維空間的內積運算,二維空間等價於三維空間中.
Positive Definite Kernel
核函數的條件:K對稱,K對應的Gram矩陣半正定.
Radial Basis Function Kernel, RBF
泰勒展開得
RBF核等價於在無窮維空間中做內積,容易過擬合.
Sigmoid kernel
決策函數
等價於僅含有一層隱藏層的神經網絡,隱藏層神經元個數等於支持向量數,激活函數爲tanh函數.
Sequential Minimal Optimization, SMO
引入核函數的線性SVM的對偶問題
SMO算法啓發式求解,若所有解滿足KKT條件知,則所得解爲最優解. SMO算法每次迭代只優化兩個變量(等式約束限制,實際僅優化一個自由變量),固定其它變量,構建二次規劃逐漸逼近最優解.
Unclipped Solving
假定優化變量,則最優化問題等價於
爲自由變量,替換,令對的偏導爲0,得
令迭代前的預測偏差,因此未剪短的最優解爲
Clipped Solving
由約束條件知,最優解位於平行於邊長爲C的正方形的對角線的線段上,如下所示
當,最優解的界限
當,最優解的界限
因此剪短之後的最優解
Variable Selection
初始,迭代時變量選擇
- 選擇違反KKT條件最嚴重的變量,具體做法是的不滿足KKT條件的實例,若均滿足則在餘下實例中查找;
- 選擇更新後變化較大的變量,更新後變化程度正比於,爲簡化計算,選擇具有較大的變量;
如何檢驗是否滿足KKT條件(誤差範圍內檢驗)?
綜上,不滿足KKT條件等價於,當,,或當,.
如何更新值?
利用間隔邊界上的點或更新值,假定位於間隔邊界(任一間隔邊界上的點均可),,則
若和均位於間隔邊界,則分別計算取均值作爲新的值. 可見,保存並更新預測誤差能加速計算.
如何更新誤差值?
若每次更新和兩個變量,比較更新前後誤差值
因此,誤差的更新公式爲
SVC Implementation
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
from numpy import *
class Kernel:
@staticmethod
def linear():
return lambda x, y: float(inner(x, y))
@staticmethod
def gaussian(sigma):
return lambda x, y: exp(
float(linalg.norm(x - y)) ** 2 / (-2 * sigma ** 2))
@staticmethod
def _polykernel(dimension, offset):
return lambda x, y: (offset + float(inner(x, y))) ** dimension
@classmethod
def inhomogenous_polynomial(cls, dimension):
return cls._polykernel(dimension=dimension, offset=1.0)
class SVC:
def __init__(self, kernel, C=0.5, max_iter=1000, eps=1e-3):
"""
構造函數
:param kernel: 核函數指針
:param C: 懲罰參數
:param max_iter: 無任何變量改變時的最大迭代次數
:param eps: KKT條件檢驗範圍(容錯率)
"""
self.C = C
self.kernel = kernel
self.max_iter = max_iter
self.eps = eps
def fit(self, X, y):
"""
訓練模型
:param X: 輸入特徵集, 樣本數*特徵數
:param y: 輸入標籤集, 1*樣本數, 類別爲+1或-1
:return: self
"""
self._X = mat(X, dtype=float64)
self._Y = mat(y, int8).T
n_samples, n_features = X.shape
# 初始化alpha、gram矩陣、誤差矩陣
self._K = self.__gram_matrix(self._X)
self._E = mat(-self._Y, dtype=float64)
self._alphas = mat(zeros((n_samples, 1)))
self.b = 0
# 是否遍歷全部變量
entire = True
# 內循環有效更改變量次數
pair_changed = 0
for _ in range(self.max_iter):
print('iter:', _)
# 若已遍歷全部變量, 變量未有效更新, 則終止循環
if not entire and pair_changed == 0:
break
pair_changed = 0
if entire:
for i in range(n_samples):
pair_changed += self.__inner_loop(i)
else:
for i in where((self._alphas > 0) & (self._alphas < self.C))[0]:
pair_changed += self.__inner_loop(i)
# 若已遍歷全部變量, 則下次一定遍歷邊界變量;
# 若已遍歷邊界變量, 變量得到有效更新,則下次仍遍歷邊界變量;
entire = False if entire else pair_changed == 0
# 計算模型
sv = where(self._alphas > 0)[0]
self.sv_X = self._X[sv]
self.sv_Y = self._Y[sv]
self.sv_alphas = self._alphas[sv]
self.w = (multiply(self.sv_alphas, self.sv_Y).T * self.sv_X).T
return self
def __inner_loop(self, i):
"""內循環"""
# 臨時變量, 用於減少訪問, 加速計算
alphas, b, C, E, K = self._alphas, self.b, self.C, self._E, self._K
alphaIold, Yi, Ei = alphas[i, 0], self._Y[i, 0], E[i, 0]
# 滿足KKT條件, 則跳出本次循環
if not (
alphaIold < C and Yi * Ei < -self.eps or alphaIold > 0 and Yi
* Ei > self.eps):
return 0
# 選擇第二個變量
j = self.__select_j(i)
alphaJold, Yj, Ej = alphas[j, 0], self._Y[j, 0], E[j, 0]
# 計算剪輯邊界
if Yi == Yj:
L = max(0, alphaJold + alphaIold - C)
H = min(C, alphaJold + alphaIold)
else:
L = max(0, alphaJold - alphaIold)
H = min(C, C + alphaJold - alphaIold)
if L == H:
return 0
eta = K[i, i] + K[j, j] - 2.0 * K[i, j]
if eta == 0:
return 0
# 更新第二個變量
unc = alphaJold + Yj * (Ei - Ej) / eta
alphas[j, 0] = H if unc > H else L if unc < L else unc
deltaJ = Yj * (alphas[j, 0] - alphaJold)
# 更新第一個變量
alphas[i, 0] -= Yi * deltaJ
# 更新b值
deltaI = Yi * (alphas[i, 0] - alphaIold)
b1 = b - Ei - deltaI * K[i, i] - deltaJ * K[i, j]
b2 = b - Ej - deltaI * K[i, j] - deltaJ * K[j, j]
if 0 < alphas[i, 0] < C:
self.b = b1
elif 0 < alphas[j, 0] < C:
self.b = b2
else:
self.b = 0.5 * (b1 + b2)
# 更新誤差矩陣
E += ([deltaI, deltaJ] * K[[i, j]]).T + (self.b - b)
return 1 if abs(deltaJ) > 0.00001 else 0
def score(self, X, y):
"""
計算模型預測正確率
:param X: 輸入特徵集, m*n
:param y: 輸入標籤集, 1*m
:return: 0~1
"""
y_ptd = self.predict(X)
error_nums = len(where(y_ptd != mat(y).T)[0])
return 1 - error_nums / len(X)
def predict(self, X):
"""
預測類別
:param X: 輸入特徵集, m*n
:return: 各樣本類別, m*1
"""
X = mat(X)
kernel = self.kernel
sv_X = self.sv_X
K = mat([[kernel(x, xi) for xi in sv_X] for x in X])
y = mat([1] * len(X)).T
y[K * multiply(self.sv_alphas, self.sv_Y) + self.b < 0] = -1
return y
def __gram_matrix(self, X):
"""
計算gram矩陣, 用於加速計算
:param X: 輸入特徵集
:return: gram矩陣
"""
n_samples, n_features = X.shape
K = mat(zeros((n_samples, n_samples)))
# 利用核函數計算內積
for i, x_i in enumerate(X):
for j, x_j in enumerate(X[:i + 1]):
K[i, j] = K[j, i] = self.kernel(x_i, x_j)
return K
def __select_j(self, i):
"""
通過最大化步長的方式來獲取第二個alpha值的索引.
:param i: 第一個變量編號
:return: 第二個變量編號
"""
j, E = i, self._E
# 查找最小誤差的變量編號
if E[i] > 0:
min_error = inf
for k in where((self._alphas > 0) & (self._alphas < self.C))[0]:
if k != i and E[k] < min_error:
j, min_error = k, E[k]
# 查找最大誤差的變量編號
else:
max_error = -inf
for k in where((self._alphas > 0) & (self._alphas < self.C))[0]:
if k != i and E[k] > max_error:
j, max_error = k, E[k]
while j == i:
j = random.randint(0, self._X.shape[0])
return j
if __name__ == '__main__':
def load_data(filename):
"""讀取數據"""
X, y = [], []
with open(filename) as f:
for line in f.read().strip().split('\n'):
line_array = line.split('\t')
X.append(line_array[:-1])
y.append(line_array[-1])
return array(X, float64), array(y, float64)
def plot_2Dsvm(X, y, w, b, alphas):
"""顯示二維SVM"""
X, y = array(X), array(y)
fig = plt.figure()
ax = fig.add_subplot(111)
# 繪製樣本散點圖
colors = array(['g'] * X.shape[0])
colors[y > 0] = 'b'
ax.scatter(X[:, 0], X[:, 1], s=30, c=colors, alpha=0.5)
# w1x+w2y+b=0, 取兩點繪製超平面及間隔
x_min = X[where(X[:, 0] == X[:, 0].min())[0], 0]
x_max = X[where(X[:, 0] == X[:, 0].max())[0], 0]
x = array([x_min, x_max])
y = (- b - w[0, 0] * x) / w[1, 0]
y1 = (- b - linalg.norm(w, 2) - w[0, 0] * x) / w[1, 0]
y2 = (- b + linalg.norm(w, 2) - w[0, 0] * x) / w[1, 0]
ax.plot(x, y, 'r')
ax.plot(x, y1, 'r--', alpha=0.2)
ax.plot(x, y2, 'r--', alpha=0.2)
for k in where(alphas > 0)[0]:
plt.scatter(X[k, 0], X[k, 1], color='', edgecolors='r', marker='o',
s=150)
plt.show()
X, y = load_data(r"D:\testSet.txt")
svc = SVC(kernel=Kernel.linear(), C=2)
# svc = SVC(kernel=Kernel.gaussian(0.6), C=20)
svc.fit(X, y)
print(svc.score(X, y))
plot_2Dsvm(X, y, svc.w, svc.b, svc._alphas)
數據集百度雲鏈接:https://pan.baidu.com/s/1BmphBTCQLUV-djorG8JdwQ 提取碼:krs9
線性可分SVM | 線性不可分SVM |
非線性SVM-1 | 非線性SVM-2 |