支持向量機
分隔超平面
如下圖是一組線性可分的二維點,我們可以通過我們可以使用梯度下降算法找到將測試數據進行區分的直線,由於測試數據區分度很高,因此我們可能找到不止一條合適的分割線即多組權值((w0,w1,w2),(w0’,w1’,w2’)….),這個時候我們如何判定最合適的分割線?同理擴展到3維,n維都存在這個問題。
因此我們引入分隔超平面的概念,通過確認一個分隔訓練集的平面,使得所有訓練集合數據儘量的遠離該平面,這個平面稱之爲分隔超平面,對於二維數據超平面爲一維的一條直線,對於三維數據超平面爲二維平面,對於n維數據超平面爲n-1維的面。確定超平面後對於給定數據點如果離超平面的距離越遠則最終得到的預測結果越可信。
通過利用支持向量來確定分隔超平面,支持向量指離分隔超平面最近的點,使得支持向量到分隔面的距離最大化,就能找到最優的分隔超平面。
分隔超平面最優化問題
依據前兩章的做法定義超平面表達式
當P大於0.5時,特徵屬於y=1的分類,反之屬於y=0的分類。在支持向量機中我們將logistics做了一個變形,定義
對於超平面
間隔函數
需要求數據點到超平面的距離,我們定義
公式中
上面的公式比較難於理解,這裏有另外一種理解方式:定義兩個平面
圖片來源
我們要實現的目標是使得H1、H2到超平面的距離儘量遠即:
這裏的式(1)和式(2)爲了方便計算可以統一寫成
拉格朗日方法
爲了求解我們的約束方程,需要引入拉格朗日函數,拉格朗日函數是用於求解多元函數在收到一個或多個約束條件時的極值問題的方法。使用拉格朗日函數可將一個n個變量和k個約束條件的最優化問題轉換爲n+k個變量的n+k個變量方程組。比如:要求
拉格朗日乘法所得的極值會包含原問題的所有極值點,但並不保證每個極值點都是原問題的極值點。
使用拉格朗日方法求解我們的方程得到:
要求解
我們先求
推導公式可以參見 參考鏈接
關於離羣問題
很多情況下給定的訓練數據不是完全線性可分的,可能存在離羣點,即y=-1的點對應的特徵值很接近超平面甚至超出了超平面進入到y=1的類別中,這些點會影響超平面的移動甚至會導致超平面最終無解。我們引入了一個鬆弛變量
我們的超平面轉變爲:
最優值:
這裏的C表示離羣點對結果影響的權重值(當C=
最終得到:
這裏可以看到增加鬆弛變量和懲罰係數後我們的解表達式不變,只是
==這裏的推導詳見底部的引用鏈接 smo.pdf 文檔==
優化過程
這裏的目的是在代碼實現前,先看一下算法的優化問題和迴歸問題。
超平面
算法實現
支持向量機的實現都是參見<<機器學習實戰>>裏面的代碼。測試數據詳見參考裏的 svnTestSet.txt
簡單版本的實現代碼:
'''
Created on Nov 4, 2010
Chapter 5 source file for Machine Learing in Action
@author: Peter
'''
from numpy import *
from time import sleep
def loadDataSet(fileName):
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = line.strip().split('\t')
dataMat.append([float(lineArr[0]), float(lineArr[1])])
labelMat.append(float(lineArr[2]))
return dataMat,labelMat
def selectJrand(i,m):
j=i #we want to select any J not equal to i
while (j==i):
j = int(random.uniform(0,m))
return j
def clipAlpha(aj,H,L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
dataMatrix = mat(dataMatIn); labelMat = mat(classLabels).transpose()
b = 0; m,n = shape(dataMatrix)
alphas = mat(zeros((m,1)))
iter = 0
while (iter < maxIter):
alphaPairsChanged = 0
for i in range(m):
#下面兩行源自公式 aixiyi x + b - y >= 0 使得點能夠在H1 和 H2之外
fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
Ei = fXi - float(labelMat[i])#if checks if an example violates KKT conditions
#判斷是否在誤差區間,如果超過誤差區間則需要作調整
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
j = selectJrand(i,m)
fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
Ej = fXj - float(labelMat[j])
alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
## Two Lagrange Multiplier 問題 smo.pdf 裏面也有詳細解釋
if (labelMat[i] != labelMat[j]):
L = max(0, alphas[j] - alphas[i])
H = min(C, C + alphas[j] - alphas[i])
else:
L = max(0, alphas[j] + alphas[i] - C)
H = min(C, alphas[j] + alphas[i])
if L==H: print "L==H"; continue
# (x1 - x2)^2
eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - dataMatrix[j,:]*dataMatrix[j,:].T
if eta == 0: print "eta>=0"; continue
alphas[j] -= labelMat[j]*(Ei - Ej)/eta
alphas[j] = clipAlpha(alphas[j],H,L)
if (abs(alphas[j] - alphaJold) < 0.00001): print "j not moving enough"; continue
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])#update i by the same amount as j
#the update is in the oppostie direction
b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
if (0 < alphas[i]) and (C > alphas[i]): b = b1
elif (0 < alphas[j]) and (C > alphas[j]): b = b2
else: b = (b1 + b2)/2.0
alphaPairsChanged += 1
print "iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged)
if (alphaPairsChanged == 0): iter += 1
else: iter = 0
print "iteration number: %d" % iter
return b,alphas
def calcWs(alphas,dataArr,classLabels):
X = mat(dataArr); labelMat = mat(classLabels).transpose()
m,n = shape(X)
w = zeros((n,1))
for i in range(m):
w += multiply(alphas[i]*labelMat[i],X[i,:].T)
return w
dataMat, labelMat = loadDataSet("svnTestSet.txt")
b,result = smoSimple(dataMat, labelMat, 0.6, 0.001, 40)
result:
>>> b
matrix([[-3.73850318]])
>>> alphas[alphas>0]
matrix([[ 0.1516457 , 0.14693547, 0.06334441, 0.00565379, 0.35627179]])
#這裏可以看到,一共找到五個支持向量
核函數
考慮如下分佈圖,在這種case下我們無法通過上面的鬆弛變量和懲罰參數來進行優化,這個時候我們就需要將這種無法區分的訓練數據通過我們核函數(kernel函數)將數據轉換爲易於分類器理解的形式。通過核函數我們將我們特徵數據從一個特徵空間轉換到另外一個特徵空間,在新的空間下我們能很方便的利用smo算法對數據進行處理(另外一種理解是從低維度轉換到高緯度)。
當一個不可分的低維數據通過Kernel轉到到高維之後的視角:
==徑向基核函數==是目前使用最廣泛的Kernel函數,當然還有其他類型的Kernel函數(也可以自己指定自己的核函數)例如:多項式核函數,線性核函數
徑向基核函數
徑向基函數能夠基於變量距離運算輸出一個標量,這個距離可以是<0,0>向量與其他向量距離,徑向基函數的高斯版本:
這個公式可以理解爲將原始空間映射爲無窮維空間。
核函數代碼:
def kernelTrans(X, A, kTup): #calc the kernel or transform data to a higher dimensional space
m,n = shape(X)
K = mat(zeros((m,1)))
if kTup[0]=='lin': K = X * A.T #linear kernel
elif kTup[0]=='rbf':
for j in range(m):
deltaRow = X[j,:] - A
K[j] = deltaRow*deltaRow.T
K = exp(K/(-1*kTup[1]**2)) #divide in NumPy is element-wise not matrix like Matlab
else: raise NameError('Houston We Have a Problem -- \
That Kernel is not recognized')
return K
class optStruct:
def __init__(self,dataMatIn, classLabels, C, toler, kTup): # Initialize the structure with the parameters
self.X = dataMatIn
self.labelMat = classLabels
self.C = C
self.tol = toler
self.m = shape(dataMatIn)[0]
self.alphas = mat(zeros((self.m,1)))
self.b = 0
self.eCache = mat(zeros((self.m,2))) #first column is valid flag
self.K = mat(zeros((self.m,self.m)))
for i in range(self.m):
self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)
參考:
http://blog.csdn.net/v_july_v/article/details/7624837
http://blog.pluskid.org/?p=685
http://blog.sina.com.cn/s/blog_4298002e010144k8.html
https://zh.wikipedia.org/wiki/%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E4%B9%98%E6%95%B0
ftp://www.ai.mit.edu/pub/users/tlp/projects/svm/svm-smo/smo.pdf 或 http://oe7d0gss7.bkt.clouddn.com/smo.pdf
http://cs229.stanford.edu/notes/cs229-notes3.pdf
http://oe7d0gss7.bkt.clouddn.com/svnTestSet.txt