前言
此文介绍了 S M O SMO SMO算法,以及前面我们介绍了支持向量机的理论,下面我们就该通过代码来实现了。
由于 S M O SMO SMO算法不易于理解,为了让大家正确理解它的工作流程,我们先从简化版的 S M O SMO SMO算法开始讨论。
【数据集以及代码】链接:https://pan.baidu.com/s/1mGQNPA4xmgUEANo1o8rHxg 密码:oq2g
应用简化版 S M O SMO SMO算法处理小规模数据集
通过之前的学习,我们知道 S M O SMO SMO算法中的外循环确定要优化的 α \alpha α,而简化版的会跳过这一部分,首先在训练数据集上遍历每一个 α \alpha α,然后在剩下的 α \alpha α集合中随机选取另一个 α \alpha α,从而构建 α \alpha α对。
这里有一点非常重要,之前我们也提到过,这里再说明一下:
我们需要同时改变两个变量 α \alpha α,因为约束条件:
α 1 y 1 + α 2 y 2 = − ∑ i = 3 N α i y i = ς \alpha_1y_1+\alpha_2y_2 = -\sum_{i=3}^N\alpha_iy_i=\varsigma α1y1+α2y2=−i=3∑Nαiyi=ς
其中, ς \varsigma ς为常数
为此,我们先构建一个辅助函数,用于在某个区间范围内随机选择一个整数;同时我们也需要另一个辅助函数,用于在数值太大时,对其进行调整;还有一个函数是加载数据集的。
代码如下:
import random
from numpy import *
# 加载数据集
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): # i为第一个alpha下标,m是所有alpha对应的数目
j = i
while (j == i):
j = int(random.uniform(0, m))
return j
# 用于调整大于H或小于L的alpha值
def clipAlpha(aj, H, L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
# 测试
dataArr, labelArr = loadDataSet('testSet.txt')
print(labelArr)
运行结果1:
[-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0,...
可以看出,这里采用的类别标签是-1和+1
简化版 S M O SMO SMO算法
伪代码如下:
创建一个alpha向量并将其初始化为0向量
当迭代次数小于最大迭代次数时(外循环):
对数据集中的每个数据向量(内循环):
如果该数据向量可以被优化:
随机选择另一个数据向量
同时优化这两个向量
如果两个向量都不能被优化,退出内循环
如果所有向量都没有被优化,增加迭代次数,继续下一次循环
python3 代码:
# 简化版SMO算法
def smoSimple(dataMatIn, classLabels, C, toler, maxIter): # 数据集、类别标签、常数C、容错率和退出前最大的循环次数
dataMatrix = mat(dataMatIn) # 将数据集用mat函数转换为矩阵
labelMat = mat(classLabels).transpose() # 转置类别标签(使得类别标签向量的每行元素都和数据矩阵中的每一行一一对应)
b = 0
m, n = shape(dataMatrix) # 通过矩阵的shape属性得到常数m,n
alphas = mat(zeros((m, 1))) # 构建一个alpha列矩阵,初始化为0
iter = 0 # 建立一个iter变量(该变量存储在没有任何alpha改变的情况下遍历数据集的次数,当该变量达到输入值maxIter时,函数结束)
while (iter < maxIter): # 遍历数据集的次数小于输入的maxIter
alphaPairsChanged = 0 # 用于记录alpha是否已经进行优化,先设为0
for i in range(m): # 顺序遍历整个集合
fXi = float(multiply(alphas, labelMat).T * (dataMatrix * dataMatrix[i, :].T)) + b # fXi可以计算出来,是我们预测的类别
Ei = fXi - float(labelMat[i]) # 计算误差,基于这个实例的预测结果和真实结果的比对
if ((labelMat[i] * Ei < -toler) and (alphas[i] < C)) or (
(labelMat[i] * Ei > toler) and (alphas[i] > 0)): # 如果误差过大,那么对该数据实例对应的alpha值进行优化
j = selectJrand(i, m) # 随机选取第二个alpha,调用辅助函数selectJrand
fXj = float(multiply(alphas, labelMat).T * (dataMatrix * dataMatrix[j, :].T)) + b
Ej = fXj - float(labelMat[j]) # 计算误差(同第一个alpha值的误差计算方法)
alphaIold = alphas[i].copy() # 浅复制,两对象互不影响,为了稍后对新旧alpha值进行比较
alphaJold = alphas[j].copy()
if (labelMat[i] != labelMat[j]): # 保证alpha值在0与C之间
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 # 本次循环结束,进入下一次for循环
eta = 2.0 * dataMatrix[i, :] * dataMatrix[j, :].T - \
dataMatrix[i, :] * dataMatrix[i, :].T - dataMatrix[j, :] * dataMatrix[j, :].T # alpha值的最优修改值
if eta >= 0:
print("eta >= 0")
continue
alphas[j] -= labelMat[j] * (Ei - Ej) / eta # 计算出新的alpha[j]值
alphas[j] = clipAlpha(alphas[j], H, L) # 调用clipAlpha辅助函数以及L,H值对其进行调整
if (abs(alphas[j] - alphaJold) < 0.00001): # 检查alpha[j]是否有轻微改变,如果是的话,就退出for循环
print("J not moving enough!")
continue
alphas[i] += labelMat[j] * labelMat[i] * (alphaJold - alphas[j]) # 对alpha[i]进行同样的改变,改变大小一样方向相反
# 给alpha[i]和alpha[j]设置一个常数项b,等同于上篇博客中计算阀值b部分,可以对照看
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 # 当for循环运行到这一行,说明已经成功的改变了一对alpha值,同时让alphaPairsChanged加1
print("iter:%d i:%d,pairs changed %d" % (iter, i, alphaPairsChanged))
# for循环结束后,检查alpha值是否做了更新
if (alphaPairsChanged == 0): # 如果没有,iter加1
iter += 1 # 下面后回到while判断
else: # 如果有更新则iter设为0后继续运行程序
iter = 0
print("iteration number: %d" % iter)
return b, alphas # (只有在所有数据集上遍历maxIter次,且不再发生任何alpha值修改之后,程序才会停止,并退出while循环)
# 测试
b, alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40)
print(b)
print(alphas[alphas > 0]) # 观察元素大于0的
shape(alphas[alphas > 0]) # 得到支持向量的个数
for i in range(100):
if alphas[i] > 0.0:
print("简单版支持向量为:", dataArr[i], labelArr[i])
运行结果2:
为了画出图形来,我们还需要计算w的值,如下:
python3 代码:
# 计算w的值
# 该程序最重要的是for循环,for循环中实现的仅仅是多个数的乘积,前面我们计算出的alpha值,大部分是为0
# 虽然遍历数据集中的所有数据,但是起作用的只有支持向量,其他对计算w毫无作用
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
ws = calcWs(alphas, dataArr, labelArr)
print(ws)
运行结果3:
画图代码:
将上面程序运行的结果输入下面代码:
from numpy import *
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
xcord0 = []
ycord0 = []
xcord1 = []
ycord1 = []
markers = []
colors = []
fr = open('testSet.txt')
for line in fr.readlines():
lineSplit = line.strip().split('\t')
xPt = float(lineSplit[0])
yPt = float(lineSplit[1])
label = int(lineSplit[2])
if (label == -1):
xcord0.append(xPt)
ycord0.append(yPt)
else:
xcord1.append(xPt)
ycord1.append(yPt)
fr.close()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord0, ycord0, marker='s', s=90)
ax.scatter(xcord1, ycord1, marker='o', s=50, c='red')
plt.title('Support Vectors Circled')
circle = Circle((4.658191, 3.507396), 0.5, facecolor='none', edgecolor=(0, 0.8, 0.8), linewidth=3, alpha=0.5)
ax.add_patch(circle)
circle = Circle((3.457096, -0.082216), 0.5, facecolor='none', edgecolor=(0, 0.8, 0.8),linewidth=3, alpha=0.5)
ax.add_patch(circle)
circle = Circle((5.286862, -2.358286), 0.5, facecolor='none', edgecolor=(0, 0.8, 0.8), linewidth=3,alpha=0.5)
ax.add_patch(circle)
circle = Circle((6.080573, 0.418886), 0.5, facecolor='none', edgecolor=(0, 0.8, 0.8), linewidth=3,alpha=0.5)
ax.add_patch(circle)
# plt.plot([2.3,8.5], [-6,6]) #seperating hyperplane
b = -3.82407793
w0 = 0.81085367
w1 = -0.25395222
x = arange(-2.0, 12.0, 0.1)
y = (-w0 * x - b) / w1
ax.plot(x, y)
ax.axis([-2, 12, -8, 6])
plt.show()
运行结果4:
在几百个点 组成的小规模数据集上,简化版 S M O SMO SMO算法的运行是没有什么问题的,但是在更大的数据集上运行的速度就会很慢。
下面我们讨论完整版的 S M O SMO SMO算法。
在这两个版本中,实现 α \alpha α的更改和代数运算的优化环节一模一样。在优化过程中,唯一不同的就是选择 α \alpha α的方式。完整版 S M O SMO SMO算法应用了一些能够提速的启发式方法。
S M O SMO SMO算法是通过一个外循环来选择第一个 α \alpha α值的,并且其选择过程会在两种方式之间进行交替:
- 在所有数据集上进行单遍扫描
- 在非边界 α \alpha α中实现单遍扫描
所谓非边界 α \alpha α,即:指那些不等于边界0或C的 α \alpha α值。
对整个数据集的扫描相当容易,而实现非边界扫描,首先需要建立这些 α \alpha α值的列表,然后再对这个列表进行遍历,同时该步骤会跳过那些已知的不会改变的 α \alpha α值。
在选择第一个 α \alpha α值后,算法会通过一个内循环来选择第二个 α \alpha α值。在优化过程中通过最大化步长来获取第二个 α \alpha α值。
我们建立一个全局的缓存用于保存误差值,从中选择使得步长或者 E i − E j E_i - E_j Ei−Ej最大的 α \alpha α值。
利用完整版进行优化
以下为用于清理代码的数据结构和3个用于对 E E E进行缓存的辅助函数:
完整版 S M O SMO SMO的支持函数
python3 代码:
# 完整版Platt SMO的支持函数
# (不加核函数)构建一个仅包含init方法的optStruct类,实现其成员变量的填充
class optStruct:
def __init__(self, dataMatIn, classLabels, C, toler):
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)))
# 计算E值并返回
# 不加核函数
def calcEk(oS, k):
fXk = float(multiply(oS.alphas, oS.labelMat).T * (oS.X * oS.X[k, :].T)) + oS.b
Ek = fXk - float(oS.labelMat[k])
return Ek
# 内循环中的启发式方法,选择第二个alpha
def selectJ(i, oS, Ei):
maxK = -1
maxDeltaE = 0
Ej = 0
oS.eCache[i] = [1, Ei]
validEcacheList = nonzero(oS.eCache[:, 0].A)[0] # 构建出一个非零表,函数nonzero返回一个列表
# 在所有值上进行循环并选择其中使得该变量最大的那个值
if (len(validEcacheList)) > 1:
for k in validEcacheList:
if k == i:
continue
Ek = calcEk(oS, k)
deltaE = abs(Ei - Ek)
if (deltaE > maxDeltaE):
maxK = k
maxDeltaE = deltaE
Ej = Ek
return maxK, Ej
else:
j = selectJrand(i, oS.m)
Ej = calcEk(oS, j)
return j, Ej
# 计算误差值并存入缓存中,在对alpha值进行优化之后会用到这个值
def updateEk(oS, k):
Ek = calcEk(oS, k)
oS.eCache[k] = [1, Ek]
完整版$SMO#算法内外循环函数
# 完整Platt SMO算法中的优化例程(与smoSimple()函数一样,只是使用了自己的数据结构,在参数oS中传递)
# innerL()函数用来选择第二个alpha,并在可能是对其进行优化处理,如果存在任意一对alpha值发生变化,那么返回1
# 不加核函数
def innerL(i, oS):
Ei = calcEk(oS, i) # 计算E值
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)):
j, Ej = selectJ(i, oS, Ei) # 第二个alpha选择中的启发式方法
alphaIold = oS.alphas[i].copy()
alphaJold = oS.alphas[j].copy()
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
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
oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
oS.alphas[j] = clipAlpha(oS.alphas[j], H, L)
updateEk(oS, j) # 计算误差值,并存入缓存
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
print("j not moving enough")
return 0
oS.alphas[i] += oS.labelMat[j] * oS.labelMat[i] * (alphaJold - oS.alphas[j])
updateEk(oS, i)
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[j, :] * oS.X[j, :].T - oS.labelMat[j] * \
(oS.alphas[j] - alphaJold) * oS.X[j, :] * oS.X[j, :].T
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
# 完整版Platt SMO的外循环代码
# 不加核函数
def smoP(dataMatIn, classLabels, C, toler, maxIter): # 数据集、类别标签、常数C、容错率和退出前最大的循环次数
oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler) # 构建一个数据结构来容纳所有的数据
# 初始化一些控制变量
iter = 0
entireSet = True
alphaPairsChanged = 0
# 代码主体
# 退出循环的条件:
# 1、迭代次数超过指定的最大值;
# 2、历整个集合都没有对任意alpha值进行修改(即:alphaPairsChanged=0)
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
alphaPairsChanged = 0
if entireSet: # 遍历所有的值
for i in range(oS.m):
alphaPairsChanged += innerL(i, oS) # 调用innerL()函数
print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged))
iter += 1
else: # 遍历非边界值(非边界值指的是那些不等于边界0或C的alpha值)
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerL(i, oS)
print("non-bound, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged))
iter += 1
if entireSet:
entireSet = False
elif (alphaPairsChanged == 0):
entireSet = True
print("iteration number: %d" % iter)
return oS.b, oS.alphas
测试代码:
# 测试
dataArr, labelArr = loadDataSet('testSet.txt')
b, alphas = smoP(dataArr, labelArr, 0.6, 0.001, 40)
print(b)
print(alphas[alphas > 0]) # 观察元素大于0的
shape(alphas[alphas > 0]) # 得到支持向量的个数
for i in range(100):
if alphas[i] > 0.0:
print("完整版支持向量为:", dataArr[i], labelArr[i])
# 计算w的值
# 该程序最重要的是for循环,for循环中实现的仅仅是多个数的乘积,前面我们计算出的alpha值,大部分是为0
# 虽然遍历数据集中的所有数据,但是起作用的只有支持向量,其他对计算w毫无作用
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
ws = calcWs(alphas, dataArr, labelArr)
print(ws)
# 对数据进行分类处理
dataMat = mat(dataArr)
print(dataMat[0] * mat(ws) + b)
print(labelArr[0])
dataMat = mat(dataArr)
print(dataMat[1] * mat(ws) + b)
print(labelArr[1])
dataMat = mat(dataArr)
print(dataMat[2] * mat(ws) + b)
print(labelArr[2])
运行结果5:
将程序运行结果代入画图代码同样可以画出图:
图中我们可以看出,这两个类的数据点分布在一条直线的两侧,我们可以得到两类的分割线。但倘若两类数据集分布在一个圆的内部和外部呢?
之前博客理论部分我们以及说明了非线性支持向量机,使用核技巧
下面我们就来讨论在复杂数据上应用核函数
在复杂数据上应用核函数
代码如下:
import random
from numpy import *
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
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 calcEkK(oS, k):
fXk = float(multiply(oS.alphas, oS.labelMat).T * oS.K[:, k] + oS.b)
Ek = fXk - float(oS.labelMat[k])
return Ek
# 内循环中的启发式方法,选择第二个alpha
def selectJK(i, oS, Ei):
maxK = -1
maxDeltaE = 0
Ej = 0
oS.eCache[i] = [1, Ei]
validEcacheList = nonzero(oS.eCache[:, 0].A)[0] # 构建出一个非零表,函数nonzero返回一个列表
# 在所有值上进行循环并选择其中使得该变量最大的那个值
if (len(validEcacheList)) > 1:
for k in validEcacheList:
if k == i:
continue
Ek = calcEkK(oS, k)
deltaE = abs(Ei - Ek)
if (deltaE > maxDeltaE):
maxK = k
maxDeltaE = deltaE
Ej = Ek
return maxK, Ej
else:
j = selectJrand(i, oS.m)
Ej = calcEkK(oS, j)
return j, Ej
# 计算误差值并存入缓存中,在对alpha值进行优化之后会用到这个值
def updateEkK(oS, k):
Ek = calcEkK(oS, k)
oS.eCache[k] = [1, Ek]
# 核转换函数
def kernelTrans(X, A, kTup): # 2个数值型变量和一个元祖,kTup是一个包含核函数信息的元祖,该元祖的第一个参数描述的是所用核函数的类型的一个字符串
m, n = shape(X)
K = mat(zeros((m, 1)))
if kTup[0] == 'lin': # 线性核函数,内积计算在“所有数据集”和“数据集中的一行”这两个输入之间展开
K = X * A.T
elif kTup[0] == 'rbf': # 径向核函数,在for循环中对矩阵的每个元素计算高斯函数值
for j in range(m):
deltaRow = X[j, :] - A
K[j] = deltaRow * deltaRow.T
K = exp(K / (-1 * kTup[1] ** 2))
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):
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)))
self.K = mat(zeros((self.m, self.m)))
for i in range(self.m):
self.K[:, i] = kernelTrans(self.X, self.X[i, :], kTup)
# 加入核函数
def innerLK(i, oS):
Ei = calcEkK(oS, i) # 计算E值
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)):
j, Ej = selectJK(i, oS, Ei) # 第二个alpha选择中的启发式方法
alphaIold = oS.alphas[i].copy()
alphaJold = oS.alphas[j].copy()
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
eta = 2.0 * oS.K[i, j] - oS.K[i, i] - oS.K[j, j]
if eta >= 0:
print("eta>=0")
return 0
oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
oS.alphas[j] = clipAlpha(oS.alphas[j], H, L)
updateEkK(oS, j) # 计算误差值,并存入缓存
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
print("j not moving enough")
return 0
oS.alphas[i] += oS.labelMat[j] * oS.labelMat[i] * (alphaJold - oS.alphas[j])
updateEkK(oS, i)
b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, i] - oS.labelMat[j] * \
(oS.alphas[j] - alphaJold) * oS.K[i, j]
b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, j] - oS.labelMat[j] * \
(oS.alphas[j] - alphaJold) * oS.K[j, j]
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 smoPK(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)):
oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler, kTup)
iter = 0
entireSet = True
alphaPairsChanged = 0
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
alphaPairsChanged = 0
if entireSet:
for i in range(oS.m):
alphaPairsChanged += innerLK(i, oS)
print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged))
iter += 1
else:
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerLK(i, oS)
print("non-bound, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged))
iter += 1
if entireSet:
entireSet = False # toggle entire set loop
elif (alphaPairsChanged == 0):
entireSet = True
print("iteration number: %d" % iter)
return oS.b, oS.alphas
# 利用核函数进行分类的径向基测试函数
def testRbf(k1=1.3): # 参数为高斯径向基函数中的一个用户定义变量
dataArr, labelArr = loadDataSet('testSetRBF.txt') # 读入数据集
b, alphas = smoPK(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', k1)) # 调用函数smoP,核函数类型为"rbf"
datMat = mat(dataArr)
labelMat = mat(labelArr).transpose()
svInd = nonzero(alphas.A > 0)[0]
sVs = datMat[svInd] # 构建支持向量矩阵
labelSV = labelMat[svInd]
print("there are %d Support Vectors" % shape(sVs)[0])
m, n = shape(datMat)
errorCount = 0
for i in range(m):
# 整个代码最重要部分(for循环开始的这两行),给出如何利用核函数进行分类
kernelEval = kernelTrans(sVs, datMat[i, :], ('rbf', k1)) # 调用kernelTrans()核转换函数
predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b # 与其面的alpha及类别标签值求积
if sign(predict) != sign(labelArr[i]):
errorCount += 1
print("the training error rate is: %f" % (float(errorCount) / m))
dataArr, labelArr = loadDataSet('testSetRBF2.txt')
errorCount = 0
datMat = mat(dataArr)
labelMat = mat(labelArr).transpose()
m, n = shape(datMat)
for i in range(m): # 与前面一个for循环相比,只是数据集不同,使用的是测试数据集
kernelEval = kernelTrans(sVs, datMat[i, :], ('rbf', k1))
predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b
if sign(predict) != sign(labelArr[i]):
errorCount += 1
print("the test error rate is: %f" % (float(errorCount) / m))
# 测试
testRbf()
运行结果6:
你可以尝试更换不同的 k 1 k1 k1值以观察测试错位率、训练错误率,支持向量个数
- 参考书籍《机器学习实战》