機器學習05-支持向量機_1

支持向量機

分隔超平面

如下圖是一組線性可分的二維點,我們可以通過我們可以使用梯度下降算法找到將測試數據進行區分的直線,由於測試數據區分度很高,因此我們可能找到不止一條合適的分割線即多組權值((w0,w1,w2),(w0’,w1’,w2’)….),這個時候我們如何判定最合適的分割線?同理擴展到3維,n維都存在這個問題。

因此我們引入分隔超平面的概念,通過確認一個分隔訓練集的平面,使得所有訓練集合數據儘量的遠離該平面,這個平面稱之爲分隔超平面,對於二維數據超平面爲一維的一條直線,對於三維數據超平面爲二維平面,對於n維數據超平面爲n-1維的面。確定超平面後對於給定數據點如果離超平面的距離越遠則最終得到的預測結果越可信。
通過利用支持向量來確定分隔超平面,支持向量指離分隔超平面最近的點,使得支持向量到分隔面的距離最大化,就能找到最優的分隔超平面。

分隔超平面最優化問題

依據前兩章的做法定義超平面表達式 wTx+b ,其中b相當於w0,要計算點A到超平面的距離,就需要知道平面的法線,超平面的法線值爲 |wT+b|/||w|| 。在梯度下降中的logistics迴歸我們使用了Sigmoid函數定義我們的二分類算法 hθ(x)=g(θTx)=11+eθTx 用來表示函數的特徵屬於y=1的概率:

P(y=1|x;θ)=hθ(x) P(y=0|x;θ)=1hθ(x)

當P大於0.5時,特徵屬於y=1的分類,反之屬於y=0的分類。在支持向量機中我們將logistics做了一個變形,定義hw,b(x)=g(wTx+b) 並且定義:

g(z)={1,1,z >= 0z<0

對於超平面wTx+b 當>0時表示數據點在超平面上方分類爲1,當<0時表示數據點在超平面下方表示分類爲-1,當=0時表示數據點在超平面上這是我們可以默認此時分類爲1。

間隔函數

需要求數據點到超平面的距離,我們定義 y(wTx+b) 爲我們的間隔函數,其中y 表示目標分類 -1 或 1,這樣我們的間隔函數值就恆大於等於0。函數的目標是爲了找出分類器的參數 wTxb ,我們需要先確定我們的支持向量(也叫最小間隔),然後使得該最小間隔最大,可以寫成:

argmaxw,b{minn(y(wTx+b)1||w||)}

公式中minn(y(wTx+b)1||w||) 表示的是超平面的支持向量,argmaxw,b 使得支持向量距離最大,令所有支持向量label(wTx+b) 值爲1,方程就轉換爲求 ||w||1 的最小值。我們又知道只有那些離超平面最近的點的距離纔等於1,而其它點都大於1,因此在求值過程中我們需要給函數一個約束條件最終該函數轉換爲:
{min||w||2,st.yi(wTxi+b)1>=0(1)

上面的公式比較難於理解,這裏有另外一種理解方式:定義兩個平面

{wTx+b=1,wTx+b=1,for y=1for y=-1
這兩個平面分別表示類別y = 1 和 -1時,離超平面最近的點所在的平面。式子統一轉換爲y(wT+b)>=1 如下圖中的H1平面 H2平面:

圖片來源
我們要實現的目標是使得H1、H2到超平面的距離儘量遠即:Margin=2||w|| 的最大值,於是就能構造出求解方程組:
{min||w||2,st.yi(wTxi+b)1>=0(2)

這裏的式(1)和式(2)爲了方便計算可以統一寫成min||w||22

拉格朗日方法

爲了求解我們的約束方程,需要引入拉格朗日函數,拉格朗日函數是用於求解多元函數在收到一個或多個約束條件時的極值問題的方法。使用拉格朗日函數可將一個n個變量和k個約束條件的最優化問題轉換爲n+k個變量的n+k個變量方程組。比如:要求f(x,y)g(x,y)=c 時的最大值時,我們可以引入新變量拉格朗日乘數α ,這是我們只需要下列拉格朗日函數的極值:

L(x,y,α)=f(x,y)+α(g(x,y)c)

拉格朗日乘法所得的極值會包含原問題的所有極值點,但並不保證每個極值點都是原問題的極值點。

使用拉格朗日方法求解我們的方程得到:

L(w,b,α)=min12||w||2i=0nαi(yi(wTxi+b)1))

要求解L(w,b,α) 的最小值並且使之等價於min||w||2 , 又因爲yi(wTxi+b)>=1 ,所有我們需要先求解maxα>=0L(w,b,α) ,因此原始方程解變成求解:
minw,bmaxαi>=0L(w,b,α)=maxαi>=0minw,bL(w,b,α)

我們先求minw,bL(w,b,α) ,先對w,b分別求偏導,令其等於0:

dL(w,b,α)dw=||w||i=0nαiyixi

dL(w,b,α)db=i=0nαiyi
得到:
||w||=i=0nαiyixi,i=0nαiyi=0
帶入L 得:
L=niαi12ni,jyiyjαiαj<xixj>,st.αi>=0,st.ni=0αiyi=0(2)

推導公式可以參見 參考鏈接

關於離羣問題

很多情況下給定的訓練數據不是完全線性可分的,可能存在離羣點,即y=-1的點對應的特徵值很接近超平面甚至超出了超平面進入到y=1的類別中,這些點會影響超平面的移動甚至會導致超平面最終無解。我們引入了一個鬆弛變量ξ 來表示點的偏移程度,目的是我們允許H1 和 H2之間存在數據點。
我們的超平面轉變爲:subjecttoyi(wxib1+ξ)>=0 並且 ξ>=0
最優值:minw,b,ξ12wTw+Ciξi
這裏的C表示離羣點對結果影響的權重值(當C= 時退化到原來的理想線性可分場景),對上面的公式採用之前的拉格朗日方法推導得到:

L(w,b,ξ,α,u)=12wTw+Cni=1ξini=1αi[yi(wTxib)+ξi1]ni=1uiξist.αi(yi(wTxib)+ξi1)=0st.uiξi=0

最終得到:

L=niαi12ni,jyiyjαiαj<xixj>st.0<=αi<=Cst.ni=0αiyi=0

這裏可以看到增加鬆弛變量和懲罰係數後我們的解表達式不變,只是αi 的取值範圍有原來的 0<=αi< 變成了 0<=αi<=C

==這裏的推導詳見底部的引用鏈接 smo.pdf 文檔==

優化過程

這裏的目的是在代碼實現前,先看一下算法的優化問題和迴歸問題。
超平面wTx+b= ,算法的最終目的是獲取最優的wTb ,通過前面的推導公式 w=ni=0αiyixi 可以轉而得到 w 因此我們的目標轉而獲取 αib , 在優化過程中我們每次選兩個α1α2 求最優化,然後再根據α1alpha2 優化我們的 b , 這裏只寫一下回歸公式結果,具體推導過程都在==參考的smo.pdf裏面寫的很詳細==。

η=||x2x1||2w=Ni=1αiyixiEold2=xT2wold+boldy2αnew2=αold2+y2(Eold2Eold1)ηαnew1=αold1+y1y2Δα2Δb=E(x,y)old+Δα1y1xT1x+Δα2y2xT2x

算法實現

支持向量機的實現都是參見<<機器學習實戰>>裏面的代碼。測試數據詳見參考裏的 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>向量與其他向量距離,徑向基函數的高斯版本:

k(x,y)=exp(||xy||22σ)

這個公式可以理解爲將原始空間映射爲無窮維空間。

核函數代碼:

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.pdfhttp://oe7d0gss7.bkt.clouddn.com/smo.pdf
http://cs229.stanford.edu/notes/cs229-notes3.pdf
http://oe7d0gss7.bkt.clouddn.com/svnTestSet.txt

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章