支持向量机(Support Vector Machine,简称SVM)是一种常见的监督学习方法,用于分类和回归分析。SVM的主要思想是找到一个最优的超平面,将不同类别的样本分隔开来。
在二分类情况下,SVM试图找到一个能够将两个不同类别的样本分隔开的最优超平面。最优超平面被定义为距离两个类别最近的样本点到该超平面的距离最大化。这些最近的样本点被称为支持向量。
SVM可以处理线性可分和线性不可分的数据集。对于线性不可分的情况,SVM使用“核技巧”将数据映射到更高维度的特征空间,使得数据在高维空间中变得线性可分。
SMO高效优化算法
SMO(Sequential Minimal Optimization)是一种用于求解支持向量机(SVM)的优化算法。SMO算法的基本思想是将大规模的二次规划问题拆分为一系列的子问题,并通过迭代求解这些子问题来逐步逼近原始问题的解。在每一次迭代中,SMO算法选择两个变量作为优化变量,并固定其他变量,通过解决一个简化的二次优化问题来更新这两个变量。这样不断迭代直到满足停止条件,从而得到整个优化问题的解。
工作原理
SMO算法的工作原理是:每次循环中选择两个alpha进行优化处理。一旦找到一对合适的alpha,那么就增大其中一个同时减小另一个。这里所谓的“合适”就是指两个alpha必须要符合一定的条件,条件之一就是这两个alpha必须要在间隔边界之外,而其第二个条件则是这两个alpha还没有进行过区间化处理或者不在边界上。
需要注意的是,对于非支持向量(其对应的α值为0),它们对最终的权重向量w没有贡献。
具体代码实现
数据集,标签为1和-1
数据集的载入,以及SMO算法中所需的函数
# SMO算法中的辅助函数
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
import random
# i输入下标,m为总数
def selectJrand(i,m):
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
数据的二维分布可视化展示
# 画出待分类数据的二维分布
import numpy as np
def plotBestFit(dataMat,labelMat):
import matplotlib.pyplot as plt
# getA()函数将矩阵转化为数组
# weights=wei.getA()
dataArr=np.array(dataMat)
n=np.shape(dataArr)[0]
xcord1=[]
ycord1=[]
xcord2=[]
ycord2=[]
for i in range(n):
if int(labelMat[i])==1:
xcord1.append(dataArr[i][0])
ycord1.append(dataArr[i][1])
else:
xcord2.append(dataArr[i][0])
ycord2.append(dataArr[i][1])
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(xcord1,ycord1,s=30,c='red',marker='s')
ax.scatter(xcord2,ycord2,s=30,c='green')
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
简化版的SMO算法,该函数有5个输入参数, 分别为数据集,类别标签,常数C,容错率,退出前最大的循环次数。
该SMO函数的伪代码大致如下:
创建一个alpha向量并将其初始化为0向量
当迭代次数小于最大迭代次数时:(外循环)
对数据集中的每个数据向量:(内循环)
如果该数据向量可以被优化:
随机选择另外一个数据向量
同时优化这两个向量
如果两个向量都不能被优化,推出内循环
如果所有向量都没有被优化,增加迭代次数,继续下一次循环
# 简化版的smo算法
from numpy import *
# 输入参数:数据集,类别标签,常数C,容错率,取消最大的循环次数
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
# 将数列转化为矩阵
dataMatrix = mat(dataMatIn)
# .transpose()函数映射坐标轴
labelMat = mat(classLabels).transpose()
b = 0;
# 得到矩阵的长和宽
m,n = shape(dataMatrix)
# 得到一个m行1列全部是0的矩阵
alphas = mat(zeros((m,1)))
iter = 0
while (iter < maxIter):
alphaPairsChanged = 0
for i in range(m):
fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
Ei = fXi - float(labelMat[i])
# 如果alpha可以更改则进入优化过程
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
j = selectJrand(i,m)
fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
Ej = fXj - float(labelMat[j])
alphaIold = alphas[i].copy()
alphaJold = alphas[j].copy()
# 保证alpha在0和C之间
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
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
alphas[j] -= labelMat[j]*(Ei - Ej)/eta
alphas[j] = clipAlpha(alphas[j],H,L)
if (abs(alphas[j] - alphaJold) < 0.00001): print ("j not moving enough"); continue
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
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
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
参数设置,以及运算结果如图所示
最后根据alpha值计算出权重w,并圈出支持向量以及画出分隔超平面
def get_w(dataMat, labelMat, alphas):
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()
def showClassifer(dataMat, w, b):
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
#绘制样本点
data_plus = []
data_minus = []
for i in range(len(dataMat)):
if labelMat[i] > 0:
data_plus.append(dataMat[i])
else:
data_minus.append(dataMat[i])
data_plus_np = np.array(data_plus)
data_minus_np = np.array(data_minus)
fig=plt.figure()
ax=fig.add_subplot(111)
ymajorLocator = MultipleLocator(2)
ax.yaxis.set_major_locator(ymajorLocator)
ax.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7,c='red',marker='s')
ax.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7,c='green')
plt.ylim(-8,6)
x1 = max(dataMat)[0]
x2 = min(dataMat)[0]
a1, a2 = w
b = float(b)
a1 = float(a1[0])
a2 = float(a2[0])
y1, y2 = (-b- a1*x1)/a2, (-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()
结果如图所示