该简易算法其实完成的任务很简单,就是违反KKT条件的alpha进行符合KKT条件,然后通过不在边界的点进行迭代,然后使其alpha稳定下来下面的代码基本每一句都进行了详细的解释,具体看代码,但是建议没搞懂参数是如何更新的同学,一旦把上一篇的参数更新方法彻底搞懂,下一篇将编写完整的SMO代码,然后在详细阐释其工作原理的来龙去脉,这个简单的smo算法主要集中在处理更新方面,并没有考虑优化计算速度,因此下一节将详细解释该算法的实现,本代码参考机器学习实战的代码,代码如下:
#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风
# File name: SMO_simplify.py
# Time:2018/10/22
# Email:[email protected]
import numpy as np
# 数据处理
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
# 选择j不等于i的数据
def selectJrand(i, m):
j = i #we want to select any J not equal to i
while (j==i):
j = int(np.random.uniform(0,m))
return j
# smo的约束条件,前面详解了即 0 ≤alphas≤C,即边界限制
def clipAlpha(aj, H, L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
# 简化版SMO算法
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
dataMatrix = np.mat(dataMatIn) # 把数据转换成numpy的矩阵形式
labelMat = np.mat(classLabels).transpose() # 同样处理,同时进行转置处理,此时的标签就是列向量了
b = 0 # 初始化b
m,n = np.shape(dataMatrix) # 获得数据的维度即m行n列,也可以看成是m个样本数据,每个样本有n特征
alphas = np.mat(np.zeros((m, 1))) # 初始化alphas,因为alphas是针对每一个样本数据,因此需要m行1列
iter = 0 # 迭代次数,即记录的是alpha没有任何改变的情况下遍历数据的次数
while (iter < maxIter): # 当遍历次数达到最大时退出循环
alphaPairsChanged = 0 # 大循环,对整个数据集遍历,该变量记录alpha是否已经被优化,循环结束才能知道
for i in range(m):
# 下面的式子其实就是f(x) = ∑alpha_i*y_i* + b,前面理论也详细讲了这一点,就是预测x的类别
fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
# 下面就是求在真实值和预测值的差即:E_i = f(x_i) - y_i,其中f是预测值,y是真实分类值
Ei = fXi - float(labelMat[i]) #if checks if an example violates KKT conditions
# 下面就是界定非边界的alpha,正负间隔都会被测试,筛选满足KKT条件的,在边界上的alpha等于0或者C,则不再优化
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
j = selectJrand(i,m) # 选择第二个alpha值
# 下面是求预测值f(x_j)同上解释
fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
# 下面是计算误差
Ej = fXj - float(labelMat[j])
# 存储更新前的alpha值,在更新时或者比较时使用
alphaIold = alphas[i].copy()
alphaJold = alphas[j].copy()
# 判断选择的i,j数据的标签是否异号,开始计算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 # 如果相等,直接跳出本次for循环,直接执行下一次for循环
# eta,大家还记得上篇的参数更新里的:K_11 + K_22 - 2K_12吗,其实就是这个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 # 满足KKT条件,跳出该循序
alphas[j] -= labelMat[j]*(Ei - Ej)/eta # 否则更新alpha
alphas[j] = clipAlpha(alphas[j],H,L) # 加入约束条件
if (abs(alphas[j] - alphaJold) < 0.00001):
# 查看更新后的alpha是否满足条件,即变化大不大,如果不大,说明该alpha已经趋于稳定了,不用继续更新了
print("j not moving enough")
continue
# 同理对i也进行更新,不同的是更新方向不同,这里是加,而j是减,注意细节。忘记的查看一下上篇的alpha1是如何更新的
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])#update i by the same amount as j
#the update is in the oppostie direction
# 更新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 # 当运行到这里说明alpha已经进行更新了,因此加一
print("iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
if (alphaPairsChanged == 0): iter += 1
else: iter = 0
print("iteration number: %d" % iter)
return b, alphas
下面给出测试代码和测试结果:
#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风
# File name: test.py
# Time:2018/10/23
# Email:[email protected]
import SMO_simplify
dataarr, labelarr = SMO_simplify.loadDataSet('testSet.txt')
print(labelarr)
b, alphas = SMO_simplify.smoSimple(dataarr, labelarr, 0.6, 0.0001, 40)
print('b 的值为:', b)
print('alphas[alphas>0]: ', alphas[alphas > 0])
for i in range(100):
if alphas[i] > 0.0:
print('dataarr[i],labelarr[i]为:', dataarr[i], labelarr[i])
测试结果:
......
........
........
iteration number: 38
j not moving enough
j not moving enough
iteration number: 39
j not moving enough
j not moving enough
iteration number: 40
b 的值为: [[-3.87921175]]
alphas[alphas>0]: [[0.08331274 0.27824106 0.04908614 0.31246766]]
dataarr[i],labelarr[i]为: [4.658191, 3.507396] -1.0
dataarr[i],labelarr[i]为: [3.457096, -0.082216] -1.0
dataarr[i],labelarr[i]为: [5.286862, -2.358286] 1.0
dataarr[i],labelarr[i]为: [6.080573, 0.418886] 1.0