机器学习实战——SVM

17028203:

什么是SVM

  • 支持向量机(Support Vector Machines, SVM)
    • 支持向量(Support Vector)就是离分隔超平面最近的那些点。( “最大间隔” maximum margin )
    • 机(Machine)就是表示一种算法。

基于最大间隔分隔数据

基本概念

  • Separating Plane分隔超平面:将数据集分隔开来的超平面
    • 二维:separating line 分隔直线
    • N维:N-1维的对象即:hyperplane 超平面
  • 间隔Margin:数据点到分隔面的距离
  • Maximum 尽可能大 → \rightarrow 分类器更为健壮
  • 支持向量:离分隔超平面最近的点points with the smallest margin
    • 最大化支持向量到分隔面的距离(优化求解)find the points with the smallest margin and maximize that margin

寻找最大间隔

  1. 超平面方程:

    • 二维:y=ax+b
      将x轴写成x1,y轴写成 x 2 → x 2 = a x 1 + b → a x 1 − x 2 + b = 0 x2\rightarrow x2=ax1+b \rightarrow ax1-x2+b=0 x2x2=ax1+bax1x2+b=0, 向量化:
      [ a − 1 ] [ x 1 x 2 ] + b = 0 \begin{bmatrix}{a}&{-1}\end{bmatrix}\begin{bmatrix}{x_1}\\{x_2}\end{bmatrix} + b = 0 [a1][x1x2]+b=0
      用w列向量和x列向量进一步向量化:
      ω T x + b = 0 , ω = [ ω 1 , ω 2 ] T , x = [ x 1 , x 2 ] T \omega^Tx+b=0,\omega = [\omega_1,\omega_2]^T,x = [x_1,x_2]^T ωTx+b=0,ω=[ω1,ω2]T,x=[x1,x2]T
    • 二维 → \rightarrow n维:
      w T x + b = 0 , ω = [ ω 1 , ω 2 , ⋯   , ω n ] T , x = [ x 1 , x 2 , ⋯   , x n ] T w^Tx+b=0,\omega = [\omega_1,\omega_2,\cdots,\omega_n]^T,x = [x_1,x_2,\cdots,x_n]^T wTx+b=0,ω=[ω1,ω2,,ωn]T,x=[x1,x2,,xn]T
  2. “分类间隔”方程

    • 间隔大小:支持向量对应的样本点到决策面的距离的二倍

d = ∣ A x 0 + B y 0 + C A 2 + B 2 ∣ d = |\frac {Ax_0+By_0+C}{\sqrt{A^2+B^2}}| d=A2+B2 Ax0+By0+C

  • 将直线方程扩展到多维,求得我们现在的超平面方程,对公式进行如下变形:
    d = ∣ ω ⃗ x ⃗ + b ∣ ∣ ∣ ω ∣ ∣ d = \frac {|\vec\omega\vec x+b|}{||\omega||} d=∣∣ω∣∣ω x +b
    分类器的好坏的评定依据是分类间隔W=2d的大小,即分类间隔W越大,认为这个超平面的分类效果越好。求解超平面的问题就变成了求解分类间隔W最大化问题,也就是d最大化问题。
  1. 约束条件:优化的变量d的取值范围受到了限制和约束。
    每个样本点 x i x_i xi加上一个类别标签 l a b e l i label_i labeli
    约束条件如下:
    l a b e l ∗ ( ω T + b ) ⩽ 1        ∀ x label*(\omega^T+b)\leqslant 1 ~~~~~~ \forall x label(ωT+b)1      x
    标签设为1和-1,方便将约束条件变成一个约束方程,从而方便计算
  2. 线性SVM优化问题基本描述
  • 目标函数: m a x ( d = ∣ ω ⃗ x ⃗ + b ∣ ∣ ∣ ω ∣ ∣ ) max(d = \frac {|\vec\omega\vec x+b|}{||\omega||}) max(d=∣∣ω∣∣ω x +b)
  • 利用支持向量上的样本点求解d的最大化问题。支持向量上的样本点满足: ∣ ω T x i + b ∣ = 1 |\omega^Tx_i+b| = 1 ωTxi+b=1
  • 目标函数进一步简化为: d = 1 ∣ ∣ ω ∣ ∣ d = \frac 1{||\omega||} d=∣∣ω∣∣1
  • 目标函数等效为: m i n ( 1 2 ∣ ∣ ω ∣ ∣ 2 ) min(\frac 12||\omega||^2) min(21∣∣ω2)
  • 加上约束即为: m i n ( 1 2 ∣ ∣ ω ∣ ∣ 2 )                         s . t . y i ( ω T x i + b ) ⩾ 1 ,    i = 1 , 2 ⋯   , n min(\frac 12||\omega||^2)\\~~~~~~~~~~~~~~~~~~~~~~~ s.t. y_i(\omega^Tx_i+b)\geqslant 1,~~i=1,2\cdots,n min(21∣∣ω2)                       s.t.yi(ωTxi+b)1,  i=1,2,n
  1. 最优化问题求解
  • 费马大定理(Fermat),即使用求取函数f(x)的导数,然后令其为零,可以求得候选最优值,再在这些候选值中验证;如果是凸函数,可以保证是最优解。
  • 拉格朗日乘子法(Lagrange Multiplier) ,即把等式约束hi(x)用一个系数与f(x)写为一个式子,称为拉格朗日函数,而系数称为拉格朗日乘子。通过拉格朗日函数对各个变量求导,令其为零,可以求得候选值集合,然后验证求得最优值
  • 常使用的方法就是KKT条件。同样地,把所有的等式、不等式约束与f(x)写为一个式子,也叫拉格朗日函数Lagrange function ,系数也称拉格朗日乘子Lagrange multiplier,通过一些条件,可以求出最优值的必要条件,这个条件称为KKT条件

1)拉格朗日函数:将约束条件放到目标函数中,从而将有约束优化问题转换为无约束优化问题

  • 拉格朗日对偶问题(dual problem):使用拉格朗日获得的函数,使用求导的方法求解依然困难。进而,需要对问题再进行一次转换,即:使用的一个数学技巧。
  • 将有约束的原始目标函数转换为无约束的新构造的拉格朗日目标函数
    L ( ω , b , α ) = 1 2 ∣ ∣ ω ∣ ∣ 2 − ∑ i = 1 n α i ( y i ( ω T x i + b ) − 1 ) L(\omega,b,\alpha) = \frac 1 2||\omega||^2 - \sum_{i=1}^{n}\alpha_i(y_i(\omega^Tx_i+b) - 1) L(ω,b,α)=21∣∣ω2i=1nαi(yi(ωTxi+b)1)
    • 其中αi是拉格朗日乘子,αi大于等于0。
  • 使用拉格朗日对偶性,将不易求解的优化问题转化为易求解的优化(p==d,需满足凸优化和KKT条件)

2)KKT条件:
最优值条件必须满足以下条件:

  • 条件一:经过拉格朗日函数处理之后的新目标函数L(w,b,α)对w,b求导为零:
  • 条件二: h j ( x ) = 0 h_j(x) = 0 hj(x)=0
  • 条件三: α ∗ g k ( k ) = 0 \alpha*g_k(k) = 0 αgk(k)=0
    凸优化问题和KKT都满足了,问题转换成了对偶问题

3)对偶问题的求解
首先固定α,要让L(w,b,α)关于w和b最小化,分别对w和b偏导数,令其等于0,即:
∂ L ∂ ω = 0 ⇒ ω = ∑ i = 1 n α i y i x i ∂ L ∂ b = 0 ⇒ ∑ i = 1 n α i y i = 0 \frac {\partial L}{\partial \omega} = 0 \Rightarrow\omega = \sum_{i=1}^n\alpha_iy_ix_i\\\frac {\partial L}{\partial b} = 0 \Rightarrow \sum_{i=1}^n\alpha_iy_i = 0 ωL=0ω=i=1nαiyixibL=0i=1nαiyi=0
代回 L ( ω , b , α ) L(\omega,b,\alpha) L(ω,b,α)
L ( ω , b , α ) = ∑ i = 1 n α i − 1 2 ∑ i , j = 1 n α i α j y i y j x i T x j L(\omega,b,\alpha) = \sum_{i = 1}^n\alpha_i - \frac 12 \sum_{i,j=1}^n\alpha_i\alpha_jy_iy_jx_i^Tx_j L(ω,b,α)=i=1nαi21i,j=1nαiαjyiyjxiTxj
SVM的问题满足使用拉格朗日乘子法的条件优化问题可表示如下:
m i n 1 2 ∑ i , j = 1 n α i α j y i y j x i T x j − ∑ i = 1 n α i s . t .     α i ⩾ 0 , i = 1 , 2 , ⋯   , n ∑ i = 0 n α i y i = 0 min \frac 12 \sum_{i,j=1}^n\alpha_i\alpha_jy_iy_jx_i^Tx_j - \sum_{i = 1}^n\alpha_i\\s.t. ~~~\alpha_i \geqslant 0 ,i = 1,2,\cdots,n\\\sum_{i=0}^n \alpha_iy_i = 0 min21i,j=1nαiαjyiyjxiTxji=1nαis.t.   αi0,i=1,2,,ni=0nαiyi=0
当数据集并不是严格线性可分时,即满足绝大部分样本点是线性可分,点w是一个异常点,导致无法找到一个合适的超平面 → \rightarrow 为了解决这个问题,引入松弛变量(slackvariable)ξ。

  • 约束条件变成:
    m i n 1 2 ∑ i , j = 1 n α i α j y i y j x i T x j − ∑ i = 1 n α i s . t .     0 ⩽ α i ⩽ C , i = 1 , 2 , ⋯   , n ∑ i = 0 n α i y i = 0 α i ( 1 − y i ( ∑ j = 1 n α j y j < x j , y j > + b ) ) = 0 , i = 1 , 2 , ⋯   , n min \frac 12 \sum_{i,j=1}^n\alpha_i\alpha_jy_iy_jx_i^Tx_j - \sum_{i = 1}^n\alpha_i\\s.t. ~~~0\leqslant \alpha_i \leqslant C ,i = 1,2,\cdots,n\\\sum_{i=0}^n \alpha_iy_i = 0\\ \alpha_i(1 - y_i(\sum_{j=1}^n\alpha_jy_j<x_j,y_j>+b)) = 0,i = 1,2,\cdots,n min21i,j=1nαiαjyiyjxiTxji=1nαis.t.   0αiC,i=1,2,,ni=0nαiyi=0αi(1yi(j=1nαjyj<xj,yj>+b))=0,i=1,2,,n
    通过引入松弛变量来允许数据点可以处于分隔面错误的一侧, C用于控制“最
    大化间隔”和“保证大部分点的函数间隔小于1.0” 这两个目标的权重

SMO高效优化算法

  • SVM有很多种实现,最流行的一种实现是: 序列最小优化(Sequential Minimal Optimization, SMO)算法

    • 用途:用于训练SVM
    • 目标:求出一系列alpha 和b,一旦求出alpha,就很容易计算出权重向量w 并得到分隔超平面。
    • 思想:是将大优化问题分解为多个小优化问题来求解的
    • 原理:每次循环选择两个alpha 进行优化处理,一旦找出一对合适的alpha,那么就增大一个同时减少一个。
  • alpha要符合一定的条件

    • 符合KKT条件
    • 之所以要同时改变2个 alpha;原因是我们有一个约束条件:
      ∑ i = 0 n α i y i = 0 \sum_{i=0}^n \alpha_iy_i = 0 i=0nαiyi=0
  • 如果只修改一个alpha,很可能导致约束条件失效
    伪代码:

创建一个alpha向量并将其初始化为0向量
当迭代次数小于最大迭代次数时(外循环)
    对数据集中的每个数据向量(内循环):
        如果该数据向量可以被优化
            随机选择另一个数据向量
            同时优化这两个向量
            如果两个向量都不能被优化,推出内循环
    如果所有向量都没有被优化,增加迭代数目,继续下一次循环

简化版SMO python代码:

import numpy as np

# 导入数据
from matplotlib import pyplot as plt


def loadDataSet(filename):
    """
    读取数据
    :param filename:文件名
    :return:
        dataMat:数据矩阵
        labelMat:数据标签
    """
    dataMat = []
    labelMat = []
    fr = open(filename)
    for line in fr.readlines():
        lineArr = line.strip().split(' ')
        dataMat.append([float(lineArr[0]), float(lineArr[1])])
        labelMat.append(float(lineArr[2]))
    return dataMat, labelMat


def selectJrand(i, m):
    """
    随机选择alpha
    :param i:第一个alpha的下标
    :param m:所有alpha数目
    :return:
        j
    """
    j = i
    while j == i:
        j = int(np.random.uniform(0, m))
    return j


def clipAlpha(aj, H, L):
    """
    函数说明:修剪alpha
    :param aj: alpha值
    :param H: alpha上限
    :param L: alpha下限
    :return:
        aj:alpha值
    """
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj


def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    """
    函数说明:简化版SMO算法
    :param dataMatIn:数据矩阵
    :param classLabels:数据标签
    :param C:松弛变量
    :param toler:容错率
    :param maxIter:最大迭代次数
    :return:
    """
    # 将列表形式转为矩阵或向量形式
    dataMatrix = np.mat(dataMatIn)
    labelMatrix = np.mat(classLabels).transpose()
    # 初始化b = 0,获取矩阵行列
    b = 0
    col, row = np.shape(dataMatrix)
    # 新建一个行数与数据行数相同,列数为1的向量
    alphas = np.mat(np.zeros((col, 1)))
    # 初始化迭代次数为0
    iter = 0
    while iter < maxIter:
        # 改变的alpha的对数
        alphaPairxChanged = 0
        # 遍历样本集样本
        for i in range(col):
            # 计算支持向量机算法的预测值
            fxi = float(np.multiply(alphas, labelMatrix).T * (dataMatrix * dataMatrix[i, :].T) + b)
            # 计算预测值与实际值的误差
            Ei = fxi - float(labelMatrix[i])
            # 如果不满足KKT条件,即labelMatrix[i]*fxi<1(labelMatrix[i]*fxi - 1<-toler)
            # and alpha < C,or labelMatrix[i]*fxi>1(labelMatrix[i]*fxi - 1>toler) and alpha > 0
            if ((labelMatrix * Ei < -toler) and (alphas < C)) or ((labelMatrix[i] * Ei > toler) and (alphas[i] > 0)):
                # 随机选择第二个变量alphaj
                j = selectJrand(i, col)
                fxj = float(np.multiply(alphas, labelMatrix).T * (dataMatrix * dataMatrix[j, :].T)) + b
                Ej = fxj - float(labelMatrix[j])
                # 记录alphai和alphaj的原始值,便于后续的比较
                alphaIold = alphas[i].copy()
                alphaJold = alphas[j].copy()
                # 如果将两个alpha对应严样本的标签不相同
                if labelMatrix[i] != labelMatrix[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
                # 根据公式计算未经剪辑的alphaj
                eta = 2.0 * dataMatrix[i, :] * dataMatrix[i, :].T - dataMatrix[j, :] * dataMatrix[j, :].T
                # 如果eta >= 0,则跳出本次循环
                if eta >= 0:
                    print("eta >= 0")
                    continue
                alphas[i] = labelMatrix[j] * (Ei - Ej) / eta
                alphas[j] = clipAlpha(alphas[j], H, L)
                # 如果改变后的alphaj值变换不大,跳出本次循环
                if abs(alphas[j] - alphaJold < 0.00001):
                    print("j not moving enough")
                    continue
                # 否则,计算相应的alphai值
                alphas[i] += labelMatrix[j] * labelMatrix[i] * (alphaJold - alphas[i])
                # 再分别计算两个alpha情况下对于b值
                b1 = b - Ei - labelMatrix[i] * (alphas[i] - alphaIold) * dataMatrix[i, :] * dataMatrix[i, :].T - \
                     labelMatrix[j] * (alphas[j] - alphaJold) * dataMatrix[i, :] * dataMatrix[j, :].T
                b2 = b - Ej - labelMatrix[i] * (alphas[i] - alphaIold) * dataMatrix[i, :] * dataMatrix[j, :].T - \
                     labelMatrix[j] * (alphas[j] - alphaJold) * dataMatrix[j, :] * dataMatrix[j, :].T
                # 如果0 < alphai < C,那么b = b1
                if (0 < alphas[i]) and (C > alphas[i]):
                    b = b1
                # 否则,如果0 < alphaj < C,那么b = b2
                elif (0 < alphas[j]) and (C > alphas[j]):
                    b = b2
                # 否则,b为b1,b2的平均值
                else:
                    b = (b1 + b2) / 2.0
                alphaPairxChanged += 1
                print("iter:%d i:%d,paird changed %d" % (iter, i, alphaPairxChanged))
        if alphaPairxChanged == 0:
            iter += 1
        else:
            iter = 0
        print("iteration number: %f" % iter)



def showClassifer(dataMat, labelMat, alphas, w, b):
    """
    分类结果可视化
    :param dataMat:数据矩阵
    :param labelMat:数据标签
    :param alphas:
    :param w:直线法向量
    :param b:直线截距
    :return:
    """
    # 新建两个空列表分别存放正样本和负样本
    data_posi = []
    data_nega = []
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_posi.append(dataMat[i])
        else:
            data_nega.append(dataMat[i])
    # 转换成numpy矩阵
    data_posi_np = np.array(data_posi)
    data_nega_np = np.array(data_nega)
    plt.scatter(np.transpose(data_posi_np)[0], np.transpose(data_posi_np)[1], s=30, alpha=0.7)
    plt.scatter(np.transpose(data_nega_np)[0], np.transpose(data_nega_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 = (-b - a1 * x1) / a2
    y2 = (-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()


def get_w(dataMat, labelMat, alphas):
    """
    计算w
    :param dataMat:数据矩阵
    :param labelMat:数据标签
    :param alphas:alphas值
    :Returns:
    """
    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()

利用完整版Platt SMO算法加速优化

不同点:选择alpha的方式

  • 启发式方法(提速,对于大规模的数据集而言,简化SMO收敛速度非常慢)
    • 启发式选取alpha变量的SMO算法:
  • 启发式的SMO算法通过一个外循环来选择第一个alpha值,并且其选择过程会在下面两种方法之间进行交替*:
    (1)在所有数据集上进行单遍扫描
    (2)另一种方法是在非边界alpha中单遍扫描,所谓非边界alpha 即:不等于边界0或者C的alpha值(对应支持向量点)
  • 在选择第一个alpha值后,算法会通过一个内循环来选择第二个值,在
    优化的过程中依据alpha的更新公式 α n e w , u n c = a o l d + l a b e l ∗ ( E i − E j ) / η \alpha^{new,unc}=a^{old}+label*(E_i-E_j)/\eta αnew,unc=aold+label(EiEj)/η,alpha值的变化程度与 E i − E j E_i-E_j EiEj的差值成正比,为了使alpha有足够大的变化,选择使 E i − E j E_i-E_j EiEj最大的alpha值作为另外一个alpha。

完整版Platt SMO python代码

import numpy as np
import SMO_simple

"""
数据结构,维护所有需要操作的值
Parameters:
    dataMatIn:数据矩阵
    classLabels:数据标签
    C:松弛变量
    toler:容错率
"""


class optStruct:
    def __init__(self, dataMatIn, classLabels, C, toler):
        self.X = dataMatIn
        self.labelMat = classLabels
        self.C = C
        self.tol = toler
        self.m = np.shape(dataMatIn)[0]
        self.alphas = np.mat(np.zeros((self.m, 1)))  # 根据矩阵行数初始化alpha
        self.b = 0
        self.eCache = np.mat(np.zeros((self.m, 2)))  # 初始化误差缓存,第一列为是否有效的标志位,第二列为实际的误差E的值。


def calcEk(oS, k):
    """
    计算误差E
    :param oS: 数据结构
    :param k: 标号为k的数据
    :return:
        Ek:标号为k的数据误差
    """
    fXk = float(np.multiply(oS.alphas, oS.labelMat).T * (oS.X * oS.X[k, :].T)) + oS.b
    Ek = fXk - float(oS.labelMat[k])
    return Ek


def selectJ(i, oS, Ei):
    """
    选择alpha_j的值
    :param i: 标号为i的数据的索引值
    :param oS: 数据结构
    :param Ei: 标号为i的数据误差
    :return:
        j,maxK:标号为j或maxK的数据的索引值
        Ej:标号为j的数据误差
    """
    maxK = -1
    maxDeltaE = 0
    Ej = 0
    oS.eCache[i] = [1, Ei]  # 根据Ei更新误差缓存
    validEcacheList = np.nonzero(oS.eCache[:, 0].A)[0]  # 返回误差不为0的数据的索引值
    if (len(validEcacheList)) > 1:  # 判断误差列表有不为0的误差
        for k in validEcacheList:  # 循环遍历不为0的误差
            if k == i:  # 此种情况不做计算
                continue
            Ek = calcEk(oS, k)  # 计算Ek
            deltaE = abs(Ei - Ek)  # 计算|Ei-Ek|
            if deltaE > maxDeltaE:  # 选择具有最大步长的alpha的索引j
                maxK = k
                maxDeltaE = deltaE
                Ej = Ek
        return maxK, Ej
    else:
        j = SMO_simple.selectJrand(i, oS.m)
        Ej = calcEk(oS, j)
    return j, Ej


def updateEk(oS, k):
    """
    计算Ek,并更新误差缓存
    :param oS: 数据结构
    :param k: 标号为k的数据的索引值
    :return:
    """
    Ek = calcEk(oS, k)
    oS.eCache[k] = [1, Ek]


def innerL(i, oS):
    """
    优化SMO算法来选择第二个alpha
    :param i: 标号为i的数据的索引值
    :param oS: 数据结构
    :return:
        1:有任意一对alpha值发生变化
        0:没有任意一对alpha值发生变化或变化太小
    """
    # 步骤1: 计算误差Ei
    Ei = calcEk(oS, i)
    if ((oS.labelMat[i] * Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or (
            (oS.labelMat[i] * Ei > oS.tol) and (oS.alphas[i] > 0)):
        # 使用内循环启发方式2选择alpha_j,并计算Ej
        j, Ej = selectJ(i, oS, Ei)
        # 保存更新前的aplpha值,使用深拷贝
        alphaIold = oS.alphas[i].copy()
        alphaJold = oS.alphas[j].copy()
        # 步骤2:计算上下界L和H
        if oS.labelMat[i] != oS.labelMat[j]:
            L = max(0, oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
        else:
            L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
            H = min(oS.C, oS.alphas[j] + oS.alphas[i])
        if L == H:
            print("L==H")
            return 0
        # 步骤3:计算学习率eta
        eta = 2.0 * oS.X[i, :] * oS.X[j, :].T - oS.X[i, :] * oS.X[i, :].T - oS.X[j, :] * oS.X[j, :].T
        if eta >= 0:
            print("eta>=0")
            return 0
        # 步骤4:更新alpha_j
        oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
        # 步骤5:修剪alpha_j
        oS.alphas[j] = SMO_simple.clipAlpha(oS.alphas[j], H, L)
        # 更新Ej到误差缓存
        updateEk(oS, j)
        if abs(oS.alphas[j] - alphaJold) < 0.00001:
            print("J not moving enough")
            return 0
        # 步骤6: 更新alpha_i
        oS.alphas[i] += oS.labelMat[j] * oS.labelMat[i] * (alphaJold - oS.alphas[j])
        updateEk(oS, i)
        # 步骤7:更新b_1和b_2
        b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.X[i, :] * oS.X[i, :].T - oS.labelMat[j] * (
                oS.alphas[j] - alphaJold) * oS.X[i, :] * oS.X[j, :].T
        b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.X[i, :] * oS.X[j, :].T - oS.labelMat[j] * (
                oS.alphas[j] - alphaJold) * oS.X[j, :] * oS.X[j, :].T
        # 步骤8:根据b_1和b_2更新b
        if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]):
            oS.b = b1
        elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]):
            oS.b = b2
        else:
            oS.b = (b1 + b2) / 2.0
        return 1
    else:
        return 0


def smoP(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)):
    """
    完整版的线性SMO算法
    :param dataMatIn: 数据矩阵
    :param classLabels: 数据标签
    :param C: 松弛变量
    :param toler: 容错率
    :param maxIter: 最大迭代次数
    :param kTup:
    :return:
        oS.b:SMO算法计算的b
        oS.alphas:SMO算法计算的alphas
    """
    # 初始化数据结构、迭代次数、
    oS = optStruct(np.mat(dataMatIn), np.mat(classLabels).transpose(), C, toler)
    Iter = 0
    entireSet = True
    alphaPairsChanged = 0
    # 当超过最大迭代次数、或者遍历整个数据集任意一个alpha值都没有更新,则退出循环
    while (Iter < maxIter) and ((alphaPairsChanged > 0) or entireSet):
        alphaPairsChanged = 0
        if entireSet:  # 遍历整个数据集
            for i in range(oS.m):
                alphaPairsChanged += innerL(i, oS)  # 使用优化的SMO算法,选择第二个alpha
                print("全样本遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (Iter, i, alphaPairsChanged))
            Iter += 1
        else:
            nonBoundIs = np.nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]  # 遍历不在边界0和C的alpha值
            for i in nonBoundIs:
                alphaPairsChanged += innerL(i, oS)
                print("非边界遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (Iter, i, alphaPairsChanged))
            Iter += 1
        if entireSet:  # 遍历一次后改为非边界遍历
            entireSet = False
        elif alphaPairsChanged == 0:  # 如果alpha没有更新,计算全样本遍历
            entireSet = True
            print("iteration number: %d" % Iter)
    return oS.b, oS.alphas  # 返回SMO算法计算的b和alphas


def calWs(alphas, dataArr, classLabels):
    """
    计算w
    :param alphas: alphas值
    :param dataArr:数据矩阵
    :param classLabels:数据标签
    :return:
        w
    """
    X = np.mat(dataArr)
    labelMat = np.mat(classLabels).transpose()
    m, n = np.shape(X)
    w = np.zeros((n, 1))
    for i in range(m):
        w += np.multiply(alphas[i] * labelMat[i], X[i, :].T)
    return w

在复杂数据上应用核函数

解决非线性分类问题,通过核技巧将低维的非线性特征转化为高维的线性特征,从而可以通过线性模型来解决非线性的分类问题
在这里插入图片描述
此时数据集线性不可分,无法用一个超平面来将两种样本分隔开;我们希望将这些数据进行转化,转化之后的数据就能够通过一个线性超平面将不同类别的样本分开——核函数!

核函数的目的主要是为了解决非线性分类问题,通过核技巧将低维的非线性特征转化为高维的线性特征,从而可以通过线性模型来解决非线性的分类问题

常用的核函数:

  • 线性: K ( v 1 , v 2 ) = < v 1 , v 2 > K(v_1,v_2) = <v_1,v_2> K(v1,v2)=<v1,v2>
  • 多项式: K ( v 1 , v 2 ) = ( γ < v 1 , v 2 > + c ) n K(v_1,v_2) = (\gamma<v_1,v_2>+c)^n K(v1,v2)=(γ<v1,v2>+c)n
  • Radial basis function: K ( v 1 , v 2 ) = e x p ( − γ ∣ ∣ v 1 − v 2 ∣ ∣ 2 ) K(v_1,v_2) = exp(-\gamma||v_1-v_2||^2) K(v1,v2)=exp(γ∣∣v1v22)
  • Sigmoid: K ( v 1 , v 2 ) = t a n h ( γ < v 1 , v 2 > + c ) K(v_1,v_2) = tanh(\gamma<v_1,v_2>+c) K(v1,v2)=tanh(γ<v1,v2>+c)

基于SVM的手写数字识别

import time

import sklearn.svm as svm
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV

if __name__ == "__main__":
    digits = datasets.load_digits()
    x = digits.data
    y = digits.target
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=0)

    # 网格搜索法GridSearch
    # 把要调整的参数以及其候选值 列出来;
    T1 = time.perf_counter()
    param_grid = {
    
    "kernel": ['linear', 'poly', 'rbf', 'sigmoid'],
                  "gamma": ['auto', 0.001, 0.01, 0.1, 1, 10, 100],
                  "C": [0.001, 0.01, 0.1, 1, 10, 100],
                  "degree": [2, 3, 4, 5, 6]}

    # 实例化一个GridSearchCV类
    grid_search = GridSearchCV(svm.SVC(), param_grid)
    grid_search.fit(x_train, y_train)  # 训练,找到最优的参数
    print("最好的训练集分数:{:.2f}".format(grid_search.score(x_test, y_test)))
    print("best_parameters:{}".format(grid_search.best_params_))
    print("最好模型在测试集上的:{:.2f}".format(grid_search.best_score_))
    T2 = time.perf_counter()
    print('SVM算法运行时间:%s秒' % (T2 - T1))

总结

  • 支持向量机学习策略:间隔最大化,形式化为通过求解凸二次规划问题来解决分类问题的算法,具有较低的泛化错误率。而SMO算法可以通过每次只优化两个alpha值来加快SVM的训练速度
  • 核技巧是将数据由低维空间映射到高维空间,可以将一个低维空间中的非线性问题转换为高维空间下的线性问题来求解。而径向基核函数是一个常用的度量两个向量距离的核函数
  • 支持向量机的优缺点:
    • 优点:泛化错误率低,计算开销不大
    • 对参数调节和核函数的选择敏感,适用于二类分类。
  • SVM核支持向量机是非常强大的模型,在各种数据集上的表现都很好
  • SVM允许决策边界很复杂,即时数据只有几个特征
  • 缺点:预处理数据和调参都要非常小心。(现如今很多应用还是采用基
    于树的模型,如随机森林或梯度上升 ⩽ \leqslant 需要很少的预处理、甚至不要)
  • SVM模型很难检查、很难解释为什么这么预测
  • SVM适合于当所有特征的测量单位相似(如像素密度)而且范围差不多
  • 核SVM的重要参数:正则化参数C、核的选择以及核相关的参数。
    • RBF核:Gamma-高斯核宽度的倒数。
    • Gamma和C都是控制模型的复杂度,较大值对应更为复杂的模型。这两个参数设定通常是强烈相关的、应当同时调节

猜你喜欢

转载自blog.csdn.net/m0_55818687/article/details/127539968