機器學習實戰刻意練習 —— Task 03. 支持向量機

機器學習實戰刻意練習

第 1 周任務
  分類問題:K-鄰近算法
  分類問題:決策樹

第 2 周任務
  分類問題:樸素貝葉斯
  分類問題:邏輯迴歸

第 3 周任務
  分類問題:支持向量機

第 4 周任務
  分類問題:AdaBoost

第 5 周任務
  迴歸問題:線性迴歸、嶺迴歸、套索方法、逐步迴歸等
  迴歸問題:樹迴歸

第 6 周任務
  聚類問題:K均值聚類
  相關問題:Apriori

第 7 周任務
  相關問題:FP-Growth

第 8 周任務
  簡化數據:PCA主成分分析
  簡化數據:SVD奇異值分解
    



支持向量機



1.簡介

支持向量機(Support Vector Machine, SVM)是一類按監督學習(supervised learning)方式對數據進行二元分類的廣義線性分類器(generalized linear classifier),其決策邊界是對學習樣本求解的最大邊距超平面(maximum-margin hyperplane)。
SVM使用鉸鏈損失函數(hinge loss)計算經驗風險(empirical risk)並在求解系統中加入了正則化項以優化結構風險(structural risk),是一個具有稀疏性和穩健性的分類器 。SVM可以通過核方法(kernel method)進行非線性分類,是常見的核學習(kernel learning)方法之一 。
SVM被提出於1964年,在二十世紀90年代後得到快速發展並衍生出一系列改進和擴展算法,在人像識別、文本分類等模式識別(pattern recognition)問題中有得到應用。

  支持向量機(Suport Vector Machine,常簡稱爲SVM),是一個監督式學習的方式。支持向量機屬於一般化線性分類器,這類分類器的特點是能夠同時最小化經驗誤差與最大化幾何邊緣區,因此支持向量機機也被稱爲最大邊緣區分類器。
SVM
max margin
  藍色和紅色的線圈出來的點就是所謂的支持向量,離分界線最近的點,如果去掉這些點,直線多半要改變位置。Classifier Boundary就是決策函數f(x),在兩個類的中間。紅色和藍色之間的間隙就是我們要的最大化分類的間隙。


1.1. 拉格朗日乘子法

  有拉格朗日乘子法的地方,必然是一個組合優化問題。比如
    minf=2x12+3x22+7x32min f=2x_1^{2}+3x_2^{2}+7x_3^{2}
       s.t.s.t.2x1+x2=12x_1+x_2=1
          2x2+3x3=22x_2+3x_3=2
  這是一個帶等式約束的優化問題,有目標值,有約束條件,不能直接求導。可以使用拉格朗日方法,把這個約束乘以一個係數加到目標函數中去,這樣相當與既考慮了原目標函數,也考慮了約束條件。然後分別對x求導等於0,
fx1=4x1+2α1=0x1=0.5α1\frac{\partial f}{\partial x_1} = 4x_1+2α_1=0\Longrightarrow x_1=-0.5α_1

fx2=6x2+α1+2α2=0x2=α1+2α26\frac{\partial f}{\partial x_2} = 6x_2+α_1+2α_2=0\Longrightarrow x_2=-\frac{α_1+2α_2}{6}

fx3=14x3+3α2=0x3=3α214\frac{\partial f}{\partial x_3} = 14x_3+3α_2=0\Longrightarrow x_3=-\frac{3α_2}{14}

  把它帶點菜約束條件中去,可以看到,2個變量兩個等式,最終可再帶回去求x就可以了。更高一層,帶有不等式的約束問題怎麼辦?需要用更一般化的拉格朗日乘子法,即KKT條件,來求解這個問題。


1.2. KKT條件

  任何原始問題約束條件無非最多三種,等式約束,大於號約束,小於號約束,而這三種最終通過將約束方程簡化成兩類:約束方程等於0和約束方程小於0。

  假設原始問題約束條件爲下例所示:
    minf=x122x1+1+x22+4x2+4min f=x_1^{2}-2x_1+1+x_2^{2}+4x_2+4
       s.t.s.t.x1+10x2>10x_1+10x_2>10
         10x1+10x2<1010x_1+10x_2<10
  那麼把約束條件變個樣子
       s.t.s.t.10x110x2<010-x_1-10x_2<0
         10x110x210<010x_1-10x_2-10<0
  現在拿到目標函數中去變成
    L(x,α)=f(x)+α1g1(x)+α2g2(x)L(x,α)=f(x)+α_1g_1(x)+α_2g_2(x)
        =x12+2x1+11+x22+4x2+4+α1(10x110x2)+α2(10x110x210)=x_1^{2}+2x_1+11+x_2^{2}+4x_2+4+α_1(10-x_1-10x_2)+α_2(10x_1-10x_2-10)
  那麼KKT條件的定理是什麼呢?就是如果一個優化問題在轉變成
L(x,α,β)=f(x)+αigi(x)+βihi(x)L(x,α,β)=f(x)+\sum_{}α_ig_i(x)+\sum_{}β_ih_i(x)

  其中g是不等式約束,h是等式約束。那麼KKT條件就是函數的最優值,它必定滿足下面條件:

  1. L對各個x求導爲0
  2. h(x)=0h(x)=0
  3. αigi(x)=0,αi0\sum_{}α_ig_i(x)=0,α_i\leq 0

  這三個等式很好理解,重點是第三個句子不好理解,因爲我們知道在約束條件變完或,所有的g(x)0g(x) \le 0,且求和還要爲0。那麼爲什麼KKT的條件是這樣的呢?
  某次的g(x)g(x)在爲最優解起作用,那麼它的係數值(可以)不爲0,如果某次g(x)g(x)沒有爲下一次的最優解起作用,那麼它的係數就必須爲0。


1.3. 對偶算法

  爲了求解線性可分支持向量機的最優化問題,將它作爲原始最優化問題,應用到拉格朗日對偶性,通過求解對偶問題得到原始問題的最優解,這就是線性可支持向量機的對偶算法(dual algorithm)。這樣做的優點,一是對偶問題往往根據容易求解;二是自然引入核函數,進而推廣到非線性可分類問題。

  首先構建拉格朗日函數(Lagrange function)。爲此,對每一個不等式約束引入拉格朗日乘子(Lagrange multiplier)αi0,i=1,2,...,Nα_i\geq 0,i=1,2,...,N定義拉格朗日函數:
L(ω,b,α)=12ω2i=1Nαiyi(ωxi+b)+i=1NαiL(\omega,b,α)=\frac{1}{2}||\omega||^{2}-\sum_{i=1}^Nα_iy_i(\omega*x_i+b)+\sum_{i=1}^Nα_i

  其中α=(α1,α2,,αN)T\alpha=(\alpha_1,\alpha_2,\dots,\alpha_N)^T爲拉格朗日乘子向量。
  根據拉格朗日對偶性,原始問題的對偶問題是極大極小問題maxαminω,bL(ω,b,α)\mathop{max}_{\alpha}\mathop{\min}_{\omega,b}L(\omega,b,\alpha)

  爲了得到對偶函數問題的解,需要先求L(ω,b,α)L(\omega,b,\alpha)ω,b\omega,b的極小,再求α\alpha的極大
  (1)求minω,bL(ω,b,α)\mathop{\min}_{\omega,b}L(\omega,b,\alpha)
  將拉格朗日函數L(ω,b,αL(\omega,b,\alpha)分別對ω,b\omega,b求偏導數並令其等於00
ωL(ω,b,α)=ωi=1Nαiyixi=0bL(ω,b,α)=sumi=1Nαiyi=0ω=i=1Nαiyixi=0 (1)i=1Nαiyi=0 (2) \begin{matrix}\bigtriangledown_{\omega}L(\omega,b,\alpha)=\omega-\sum_{i=1}^N\alpha_iy_ix_i=0 \\ \bigtriangledown_bL(\omega,b,\alpha) = -sum_{i=1}^N\alpha_iy_i=0 \\ \omega = \sum_{i=1}^N\alpha_iy_ix_i=0 \space(1) \\ \sum_{i=1}^N\alpha_iy_i=0 \space(2) \end{matrix}

  將(1)代入拉格朗日函數,並利用(2),即可得
L(ω,b,α)=12i=1Nj=1Nαiαjyiyj(xixj)i=1Nαiyi((j=1Nαjyjxj)xi+b)+i=1Nαi12i=1Nj=1Nαiαjyiyj(xixj)+i=1Nαi\begin{matrix}L(\omega,b,\alpha)=-\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_j(x_i*x_j)-\sum_{i=1}^N\alpha_iy_i((\sum_{j=1}^N\alpha_jy_jx_j)*x_i+b)+ \sum_{i=1}^N\alpha_i \\ -\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_j(x_i*x_j)+\sum_{i=1}^N\alpha_i \end{matrix}

  即
minω,b=12i=1Nj=1Nαiαjyiyj(xixj)+i=1Nαi\mathop{\min}_{\omega,b}=-\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_j(x_i*x_j)+\sum_{i=1}^N\alpha_i

  (2)求minω,b\mathop{\min}_{\omega,b}α\alpha的極,即對偶問題
maxω 12i=1Nj=1Nαiαjyiyj(xixj)+i=1Nαi (3)\mathop{max}_{\omega} \space -\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_j(x_i*x_j)+\sum_{i=1}^N\alpha_i \space(3)

s.t. i=1Nαiyi=0(αi0,i=1,2,,N)s.t. \space \sum_{i=1}^N\alpha_iy_i = 0(\alpha_i \ge 0 ,i=1,2,\dots,N)

  將公式(3)的目標函數由極大值轉換成求極小,就得到下面與之等價的對偶最優化問題
minω 12i=1Nj=1Nαiαjyiyj(xixj)+i=1Nαi (3)\mathop{min}_{\omega} \space \frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_j(x_i*x_j)+\sum_{i=1}^N\alpha_i \space(3)

s.t. i=1Nαiyi=0(αi0,i=1,2,,N) (4)s.t. \space \sum_{i=1}^N\alpha_iy_i = 0(\alpha_i \ge 0 ,i=1,2,\dots,N) \space(4)

  (3)解ω,b\omega^*,b^*
  假設α=(α1,α2,,αl)T\alpha^*=(\alpha_1^*,\alpha_2^*,\dots,\alpha_l^*)^T是對偶最優化問題的解,則存在下標使得αj>0\alpha_j^* > 0,並求按下式求得原始最優化的解ω,b\omega^*,b

  根據KKT條件成立,即得
ωL(ω,b,α)=ωi=1Nαiyixi=0bL(ω,b,α)=i=1Nαiyi=0αi(yi(ωxi+b)1)0,i=1,2,,Nyi(ωxi+b)10,i=1,2,,Nαi0,i=1,2,,N\begin{matrix}\bigtriangledown_{\omega}L(\omega^*,b^*,\alpha^*) = \omega^*-\sum_{i=1}^N\alpha_i^*y_ix_i = 0 \\ \bigtriangledown_b L(\omega^*,b^*,\alpha^*)=-\sum_{i=1}^N \alpha_i^*y_i =0 \\ \alpha_i^*(y_i(\omega^**x_i+b^*)-1) \ge 0,i=1,2,\dots,N \\ y_i(\omega^**x_i+b^*)-1 \ge 0,i =1,2,\dots,N \\ \alpha_i^* \ge 0 ,i =1,2,\dots,N \end{matrix}

  因此ω=i=1Nαiyixi (5)\omega^*=\sum_{i=1}^N \alpha_i^*y_ix_i\space(5)

  yj2=1y_j^2=1,且至少存在一個αj>0\alpha_j > 0,假設,α=0\alpha^* = 0,那麼ω=0\omega= 0不是原始問題的解,所以
b=yji=1Nαiyi(xixj) (6)b^{*}=y_j-\sum_{i=1}^N \alpha_i^*y_i(x_i*x_j) \space(6)

  那麼分離的超平面可以寫成i=1Nαiyi(xxi)+b=0 (7)\sum_{i=1}^N\alpha_i^*y_i(x*x_i)+b^*=0\space(7)

  決策函數可以寫成
f(x)=sign(i=1Nαiyi(xxi)+b) (8)f(x)=sign(\sum_{i=1}^N\alpha_i^*y_i(x*x_i)+b^*) \space (8)

  由此可以看出,分類決策函數只依賴於輸入x和訓練樣本輸入的內積,式(8)稱爲線性可分支持向量機的對偶形式。





2. 動手實戰

2.1. 項目案例:預測患疝氣病的馬的存活問題


項目概述
  我們先使用簡單的數據集進行測試,數據集下載

可視化數據集

import matplotlib.pyplot as plt
import numpy as np

"""
函數說明:讀取數據

Parameters:
    fileName - 文件名
Returns:
    dataMat - 數據矩陣
    labelMat - 數據標籤
"""
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

"""
函數說明:數據可視化

Parameters:
    dataMat - 數據矩陣
    labelMat - 數據標籤
Returns:
    無
"""
def showDataSet(dataMat, labelMat):
    data_plus = []                                  #正樣本
    data_minus = []                                 #負樣本
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    data_plus_np = np.array(data_plus)              #轉換爲numpy矩陣
    data_minus_np = np.array(data_minus)            #轉換爲numpy矩陣
    plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1])   #正樣本散點圖
    plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1]) #負樣本散點圖
    plt.show()

if __name__ == '__main__':
    dataMat, labelMat = loadDataSet('testSet.txt')
    showDataSet(dataMat, labelMat)

  運行結果如下:
在這裏插入圖片描述



簡化版SMO算法

from time import sleep
import matplotlib.pyplot as plt
import numpy as np
import random
import types

"""
函數說明:讀取數據

Parameters:
    fileName - 文件名
Returns:
    dataMat - 數據矩陣
    labelMat - 數據標籤
"""
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


"""
函數說明:隨機選擇alpha

Parameters:
    i - alpha
    m - alpha參數個數
Returns:
    j -
"""
def selectJrand(i, m):
    j = i                                 #選擇一個不等於i的j
    while (j == i):
        j = int(random.uniform(0, m))
    return j

"""
函數說明:修剪alpha

Parameters:
    aj - alpha值
    H - alpha上限
    L - alpha下限
Returns:
    aj - alpah值
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Zhihu:
    https://www.zhihu.com/people/Jack--Cui/
Modify:
    2017-09-21
"""
def clipAlpha(aj,H,L):
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj

"""
函數說明:簡化版SMO算法

Parameters:
    dataMatIn - 數據矩陣
    classLabels - 數據標籤
    C - 鬆弛變量
    toler - 容錯率
    maxIter - 最大迭代次數
Returns:
    無
"""
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    #轉換爲numpy的mat存儲
    dataMatrix = np.mat(dataMatIn); labelMat = np.mat(classLabels).transpose()
    #初始化b參數,統計dataMatrix的維度
    b = 0; m,n = np.shape(dataMatrix)
    #初始化alpha參數,設爲0
    alphas = np.mat(np.zeros((m,1)))
    #初始化迭代次數
    iter_num = 0
    #最多迭代matIter次
    while (iter_num < maxIter):
        alphaPairsChanged = 0
        for i in range(m):
            #步驟1:計算誤差Ei
            fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
            Ei = fXi - float(labelMat[i])
            #優化alpha,更設定一定的容錯率。
            if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
                #隨機選擇另一個與alpha_i成對優化的alpha_j
                j = selectJrand(i,m)
                #步驟1:計算誤差Ej
                fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
                Ej = fXj - float(labelMat[j])
                #保存更新前的aplpha值,使用深拷貝
                alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
                #步驟2:計算上下界L和H
                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
                #步驟3:計算eta
                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
                #步驟4:更新alpha_j
                alphas[j] -= labelMat[j]*(Ei - Ej)/eta
                #步驟5:修剪alpha_j
                alphas[j] = clipAlpha(alphas[j],H,L)
                if (abs(alphas[j] - alphaJold) < 0.00001): print("alpha_j變化太小"); continue
                #步驟6:更新alpha_i
                alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
                #步驟7:更新b_1和b_2
                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
                #步驟8:根據b_1和b_2更新b
                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("第%d次迭代 樣本:%d, alpha優化次數:%d" % (iter_num,i,alphaPairsChanged))
        #更新迭代次數
        if (alphaPairsChanged == 0): iter_num += 1
        else: iter_num = 0
        print("迭代次數: %d" % iter_num)
    return b,alphas

"""
函數說明:分類結果可視化

Parameters:
    dataMat - 數據矩陣
    w - 直線法向量
    b - 直線解決
Returns:
    無
"""
def showClassifer(dataMat, w, b):
    #繪製樣本點
    data_plus = []                                  #正樣本
    data_minus = []                                 #負樣本
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    data_plus_np = np.array(data_plus)              #轉換爲numpy矩陣
    data_minus_np = np.array(data_minus)            #轉換爲numpy矩陣
    plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7)   #正樣本散點圖
    plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7) #負樣本散點圖
    #繪製直線
    x1 = max(dataMat)[0]
    x2 = min(dataMat)[0]
    a1, a2 = w
    b = float(b)
    a1 = float(a1[0])
    a2 = float(a2[0])
    y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
    plt.plot([x1, x2], [y1, y2])
    #找出支持向量點
    for i, alpha in enumerate(alphas):
        if abs(alpha) > 0:
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
    plt.show()


"""
函數說明:計算w

Parameters:
    dataMat - 數據矩陣
    labelMat - 數據標籤
    alphas - alphas值
Returns:
    無
"""
def get_w(dataMat, labelMat, alphas):
    alphas, dataMat, labelMat = np.array(alphas), np.array(dataMat), np.array(labelMat)
    w = np.dot((np.tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
    return w.tolist()


if __name__ == '__main__':
    dataMat, labelMat = loadDataSet('testSet.txt')
    b,alphas = smoSimple(dataMat, labelMat, 0.6, 0.001, 40)
    w = get_w(dataMat, labelMat, alphas)
    showClassifer(dataMat, w, b)

  運行結果如下:
在這裏插入圖片描述在這裏插入圖片描述


參考資料

  • https://blog.csdn.net/c406495762/article/details/78072313
  • https://www.jianshu.com/p/59ed6e68703d
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章