机器学习笔记——支持向量机SMO算法完整版代码(核函数)

机器学习笔记——支持向量机SMO算法完整版代码(核函数)

\qquad 前面的代码中(机器学习笔记——支持向量机SMO算法完整版代码分析)使用的“间隔”为线性的间隔计算方法,即向量內积( K i j = ( x i , x j ) K_{ij}=(x_i,x_j) Kij=(xi,xj))。

\qquad 但是在样本数据线性不可分时,就需要通过核函数来进行转换,将低维数据映射到更高位当中进行分类,那么就要将算法中的代码进行修改。

\qquad 由于修改了“间隔”计算方法,也就是说在代码中涉及到 K i j K_{ij} Kij的计算都要修改,有 η 、 b 、 u i \eta、b、u_i ηbui,添加了 k e r n e l T r a n s kernelTrans kernelTrans核转换函数,这里只添加了高斯核,需要使用其他核函数可自行添加,下面为修改后的代码:

# -*- coding: utf-8 -*-
"""
Created on Thu Aug 22 15:07:35 2019

@author:wangtao_zuel

E-mail:[email protected]

"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def svmSmo(dataPath,outerPath,C=0.6,toler=0.001,maxIters=40,kTup=('lin',0)):
    """
    主函数、外循环
        kTup为核函数参数,第一个元素表示核函数类型,第二个元素表示核函数所需要的参数
            'lin':线性
            'rbf':高斯核
    """
    # 读取数据,此处用的为xlsx类型数据,其他类型的数据进行相应的改动
    dataMat,labelMat = loadData(dataPath)
    # 将数据放在参数类中,并定义一些后面需要用到参数
    oS = parameter(dataMat,labelMat,C,toler,kTup)
    # 统计大循环次数
    iters = 0
    # 用于切换边界、非边界情况
    entireSet = True
    # 统计在边界、非边界情况下是否进行了优化,若当前没有不再有优化则进行切换
    alphaPairsChanged =0
    # 循环结束条件:达到最大迭代次数或迭代无法提高精度(非边界、边界情况下都无法再进行优化)
    while (iters < maxIters) and ((alphaPairsChanged > 0) or (entireSet)):
        # 每次循环重新统计
        alphaPairsChanged =0
        # 最初将所有的alpha都定义为0,所以先遍历整个训练集
        if entireSet:
            for i in range(oS.m):
                alphaPairsChanged += innerL(oS,i)
            print("fullSet. iters:%d. alphaPairsChanged:%d"%(iters,alphaPairsChanged))
            iters += 1
        else:
            # 用nonzero方法筛选出非边界情况:即alpha!=0oralpha!=C的情况
            nonBond = np.nonzero((oS.alpha.A > 0)*(oS.alpha.A < oS.C))[0]
            for i in nonBond:
                alphaPairsChanged += innerL(oS,i)
            print("nonBond. iters:%d. alphaPairsChanged:%d"%(iters,alphaPairsChanged))
            iters += 1
        # 切换边界、非边界操作,同时结合着大循环的结束判断来理解
        # 若第一次循环,则为遍历整个数据集;第一次循环完成后则先遍历非边界情况,再遍历边界情况,所以第一次True后则将其转换为False
        if entireSet:
            entireSet = False
        # 将非边界情况的迭代结束条件设置为不再有精度提升,这时要考虑边界情况,则再次将entireSet设置为True,利用这种方法进行边界、非边界情况的切换
        elif alphaPairsChanged == 0:
            entireSet = True
    print("迭代优化完成!")
#    print(oS.alpha)
    # 计算参数
    w = calcW(oS)
#    print(w)
#    print(oS.b)
    # 可视化,只适用于二维数据
#    draw(oS,w)
    # 敏感性分析,只适用于二维数据
#    parameterAnalyze(oS,w)
    # 训练集外数据预测
    predict(w,oS.b,outerPath)
#    return w[0,0],w[1,0],oS.b[0,0]

def loadData(datapath):
    """
    数据读取
    """
    data = pd.read_excel(datapath)
    # 将训练集数据的特征和分类分开
    features = np.mat(data.iloc[:,:-1])
    labels = np.mat(data.iloc[:,-1]).T
    
    return features,labels

class parameter:
    """
    参数定义:toler为可容忍的误差或说精度;C为惩罚因子;eCache用于存储Ei,在选择最优j的时候要用到
    """
    def __init__(self,dataMat,labelMat,C,toler,kTup):
        self.x = dataMat
        self.y = labelMat
        self.m = dataMat.shape[0]
        self.alpha = np.mat(np.zeros((self.m,1)))
        self.b = 0
        self.C = C
        self.toler = toler 
        self.eCache = np.mat(np.zeros((self.m,2)))
        self.K = np.mat(np.zeros((self.m,self.m)))
        for ii in range(self.m):
            self.K[:,ii] = kernelTrans(self.x,self.x[ii,:],kTup)

def kernelTrans(X,A,kTup):
    """
    核转换函数:kTup表示核函数类型('lin':线性,'rbf':高斯核)
    """
    m,n = X.shape
    K = np.mat(np.zeros((m,1)))
    # 线性
    if kTup[0] == 'lin':
        K = X*A.T
    # 高斯核
    elif kTup[0] == 'rbf':
        for jj in range(m):
            deltaRow = X[jj,:] - A
            K[jj,0] = deltaRow*deltaRow.T
        K = np.exp(K/(-2*kTup[1]**2))
    else:
        raise NameError('未定义该核函数!')
    
    return K

def innerL(oS,i):
    """
    迭代优化部分,成功优化则返回1;满足KKT条件、无法优化返回0
    """
    Ei = calcEi(oS,i)
    # 判断是否满足KKT条件,若不满足则进入优化
    if ((oS.alpha[i,0] < oS.C) and (oS.y[i,0]*Ei < -oS.toler)) or ((oS.alpha[i,0] > 0) and (oS.y[i,0]*Ei > oS.toler)):
        # 寻找最大步长的j
        j,Ej = selectJ(oS,i,Ei)
        # 保存一下上一步的alpha,在新alpha计算中需要用到
        alphaIOld = oS.alpha[i,0].copy()
        alphaJOld = oS.alpha[j,0].copy()
        # 判断alpha上下界
        if oS.y[i,0] != oS.y[j,0]:
            L = max(0,oS.alpha[j,0]-oS.alpha[i,0])
            H = min(oS.C,oS.C+oS.alpha[j,0]-oS.alpha[i,0])
        else:
            L = max(0,oS.alpha[j,0]+oS.alpha[i,0]-oS.C)
            H = min(oS.C,oS.alpha[j,0]+oS.alpha[i,0])
        # 若L=H,则alpha必定在边界上,没有优化的空间,可直接返回0值
        if L == H:
            return 0
        eta = oS.K[i,i] + oS.K[j,j] - 2*oS.K[i,j]
        # 若eta为0,则返回0,因为分母不能为0,其实eta并不会为负数
        if eta == 0:
            return 0
        # 求新的参数,要注意符号问题,尤其是在结果中出现alpha全为0时,可能出现了符号问题
        oS.alpha[j,0] += oS.y[j,0]*(Ei-Ej)/eta
        # 将新参数与上下界进行比较
        oS.alpha[j,0] = clipAlpha(oS.alpha[j,0],H,L)
        # 更新eChache
        updateEi(oS,j)
        # 若优化精度提高较小,则返回0
        if abs(oS.alpha[j,0]-alphaJOld) < 0.00001:
#            print("优化提高不大,放弃此次优化!")
            return 0
        # 更新alpha_i,根据alpha_i*y_i和alpha_j*y_j的变动程度相同但方向相反来计算
        oS.alpha[i,0] += oS.y[i,0]*oS.y[j,0]*(alphaJOld-oS.alpha[j,0])
        updateEi(oS,i)
        # 更新参数b
        bi = oS.b - Ei - oS.y[i,0]*(oS.alpha[i,0]-alphaIOld)*oS.K[i,i] - oS.y[j,0]*(oS.alpha[j,0]-alphaJOld)*oS.K[i,j]
        bj = oS.b - Ej - oS.y[i,0]*(oS.alpha[i,0]-alphaIOld)*oS.K[i,j] - oS.y[j,0]*(oS.alpha[j,0]-alphaJOld)*oS.K[j,j]
        # 判断b,且注意这里b值返回的不再是数值型数据
        if (oS.alpha[i,0] > 0) and (oS.alpha[i,0] < oS.C):
            oS.b = bi
        elif (oS.alpha[j,0] > 0) and (oS.alpha[j,0] < oS.C):
            oS.b = bj
        else:
            oS.b = (bi+bj)/2
        # 所有参数都更新了则返回1
        return 1
    # 若满足KKT条件,则返回0
    else:
        return 0

def updateEi(oS,i):
    """
    更新eChache
    """
    Ei = calcEi(oS,i)
    oS.eCache[i,:] = [1,Ei]
        
def clipAlpha(alpha,H,L):
    """
    alpha与上下界比较
    """
    if alpha < L:
        alpha = L
    elif alpha > H:
        alpha = H
    
    return alpha        

def selectJ(oS,i,Ei):
    """
    寻找和i对应最大步长的j
    """
    maxJ = 0
    maxdeltaE = 0
    oS.eCache[i,:] = [1,Ei]
    validEcacheList = np.nonzero(oS.eCache[:,0].A)[0]
    # 在有效的j中寻找最大步长的j
    if len(validEcacheList) > 1:
        for j in validEcacheList:
            if j == i:
                continue
            Ej = calcEi(oS,j)
            deltaE = abs(Ei-Ej)
            if deltaE > maxdeltaE:
                maxJ = j
                maxdeltaE = deltaE
                best_Ej = Ej
        
        return maxJ,best_Ej
    # 若不存在有效的j,则随机选取一个作为j
    else:
        j = randomJ(i,oS.m)
        Ej = calcEi(oS,j)
        
        return j,Ej

def randomJ(i,m):
    """
    随机选取j
    """
    j = i
    while j == i:
        j = np.random.randint(0,m+1)
    
    return j

def calcEi(oS,i):
    """
    计算Ei,根据最大步长来选择最优的j
    """
    ui = float(np.multiply(oS.alpha,oS.y).T*oS.K[:,i]+oS.b)
    Ei = ui - float(oS.y[i,0])
    
    return Ei

def calcW(oS):   
    """
    计算参数w
    """
    w = oS.x.T*np.multiply(oS.alpha,oS.y)
    
    return w

def draw(oS,w):
    """
    拟合结果可视化:注意这里只适用于两特征的二维情况
    """
    x1 = []
    y1 = []
    x2 = []
    y2 = []
    for i in range(oS.m):
        if oS.y[i,0] == -1:
            x1.append(oS.x[i,0])
            y1.append(oS.x[i,1])
        else:
            x2.append(oS.x[i,0])
            y2.append(oS.x[i,1])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(x1,y1,marker='*')
    ax.scatter(x2,y2)
    x = np.arange(3,22,0.5)
    y = -(w[0,0]*x+oS.b[0,0])/w[1,0]
    ax.plot(x,y)
    plt.show()
    print(w)
    print(oS.b)

def parameterAnalyze(k,b,c):
    """
    参数分析:只适用于两特征二维样本
    """
    fig = plt.figure()
    ax1 = fig.add_subplot(211)
    ax1.plot(c,k)
    ax2 = fig.add_subplot(212)
    ax2.plot(c,b)
    plt.tight_layout()
    plt.show()

def predict(w,b,outerPath):
    """
    训练集外数据分类
    """
    data = pd.read_excel(outerPath)
    # 转换为矩阵形式
    dataMat = np.mat(data)
    result = dataMat*w + b
    # 计算结果大于0则分类为1,小于0则分类为-1类
    result[result>0] = 1
    result[result<0] = -1
    # 数据保存
    data['classLabel'] = result
    data.to_excel(outerPath,index=False)
    print("分类完成!")

你可能感兴趣的:(机器学习笔记)