\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 η、b、ui,添加了 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("分类完成!")