支持向量机,是一种二类分类模型,其学习策略便是间隔最大化。
1,线性可分支持向量机(硬间隔支持向量机):当训练数据线性可分时,通过硬间隔最大化。
2,线性支持向量机(软间隔支持向量机):当训练数据近似线性可分时,通过软间隔最大化。
3,非线性支持向量机:当训练数据线性不可分时,通过使用核技巧
及软间隔最大化。
线性可分支持向量机与硬间隔最大化
函数间隔:如果成比例的改变w和b(如将它们改成2w和2b),则函数间隔的值f(x)却变成了原来的2倍(虽然此时超平面没有改变)。
几何间隔:函数间隔除以||w||,而且函数间隔y*(wx+b) = y*f(x)实际上就是|f(x)|,只是人为定义的一个间隔度量,而几何间隔|f(x)|/||w||才是直观上的点到超平面的距离。
最大间隔分类器(maximum margin classifier)的目标函数定义为
支持向通和间隔边界:在H1和H2上的点就是支持向量,支持向量的个数一般很少,所以支持向量机由很少的“重要的”训练样本确定。
线性可分支持向量机学习的最优化问题
构建拉格朗日函数
目标函数变成了
线性支持向量机与软间隔最大化
线性不可分意味着某些样本点(xi, Yi)不能满足函数间隔大于等于1的约束条件。C是一个事先确定好的常量。
用之前的方法将限制或约束条件加入到目标函数中,得到新的拉格朗日函数:
非线性支持向量机与核函数
核技巧示例
核函数定义
核技巧在支持向量机的应用
常用核函数
非线性支持向量机学习算法
SMO算法(序列最小最优化)
首先回到我们前面一直悬而未解的问题,对偶函数最后的优化问题:
算法实现
SMO原理:每次循环选择两个 alpha 进行优化处理,一旦找出一对合适的 alpha,那么就增大一个同时减少一个。这里指的合适必须要符合一定的条件,之所以要同时改变2个 alpha;原因是我们有一个约束条件。
1:这两个 alpha 必须要在间隔边界之外。
2:这两个 alpha 还没有进行过区间化处理或者不在边界上。
def selectJrand(i, m):
'''随机选择一个整数
:param i: 第一个alpha的下标
:param m: 所有alpha的数目
:return: 返回一个不为i的随机数,在0~m之间的整数值
'''
j = i
while j == i:
j = int(random.uniform(0, m))
return j
def clipAlpha(aj, H, L):
'''调整aj的值,使aj处于 L<=aj<=H
:param aj: 目标值
:param H: 最大值
:param L: 最小值
:return: aj 目标值
'''
if aj > H:
aj = H
if L > aj:
aj = L
return aj
简化版SMO算法
创建一个 alpha 向量并将其初始化为0向量
当迭代次数小于最大迭代次数时(外循环)
对数据集中的每个数据向量(内循环):
如果该数据向量可以被优化
随机选择另外一个数据向量
同时优化这两个向量
如果两个向量都不能被优化,退出内循环
如果所有向量都没被优化,增加迭代数目,继续下一次循环
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
'''
:param dataMatIn: 数据集
:param classLabels: 类别标签
:param C: 松弛变量(常量值),允许有些数据点可以处于分隔面的错误一侧。控制最大化间隔和保证大部分的函数间隔小于1.0这两个目标的权重。可以通过调节该参数达到不同的结果。
:param toler: 容错率(是指在某个体系中能减小一些因素或选择对某个系统产生不稳定的概率。)
:param maxIter: 退出前最大的循环次数
:return:
b:模型的常量值
alphas:拉格朗日乘子
'''
dataMatrix = mat(dataMatIn); labelMat = mat(classLabels).transpose()
m, n = shape(dataMatrix)
b = 0; alphas = mat(zeros((m, 1))) # 初始化 b和alphas(alpha有点类似权重值。)
iter = 0 # 没有任何alpha改变的情况下遍历数据的次数
while (iter < maxIter):
alphaPairsChanged = 0 # 记录alpha是否已经进行优化,每次循环时设为0,然后再对整个集合顺序遍历
for i in range(m):
fXi = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[i, :].T)) + b # 我们预测的类别 y = w^Tx[i]+b; 其中因为 w = Σ(1~n) a[n]*lable[n]*x[n]
Ei = fXi - float(labelMat[i]) # 预测结果与真实结果比对,计算误差Ei
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
j = selectJrand(i, m) # 如果满足优化的条件,我们就随机选取非i的一个点,进行优化比较
fXj = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[j, :].T)) + b # 预测j的结果
Ej = fXj - float(labelMat[j])
alphaIold = alphas[i].copy()
alphaJold = alphas[j].copy()
if (labelMat[i] != labelMat[j]): # 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: # L和H用于将alphas[j]调整到0-C之间。如果L==H,就不做任何改变,直接执行continue语句
print("L==H")
continue
eta = 2.0 * dataMatrix[i, :]*dataMatrix[j, :].T - dataMatrix[i, :]*dataMatrix[i, :].T - dataMatrix[j, :]*dataMatrix[j, :].T # eta是alphas[j]的最优修改量,如果eta==0,需要退出for循环的当前迭代过程
if eta >= 0:
print("eta>=0")
continue
alphas[j] -= labelMat[j]*(Ei - Ej)/eta # 计算出一个新的alphas[j]值
alphas[j] = clipAlpha(alphas[j], H, L) # 并使用辅助函数,以及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]) # 然后alphas[i]和alphas[j]同样进行改变,虽然改变的大小一样,但是改变的方向正好相反
# 在对alpha[i], alpha[j] 进行优化之后,给这两个alpha值设置一个常数b;w= Σ[1~n] ai*yi*xi => b = yj- Σ[1~n] ai*yi(xi*xj)
# 所以: b1 - b = (y1-y) - Σ[1~n] yi*(a1-a)*(xi*x1);为什么减2遍? 因为是 减去Σ[1~n],正好2个变量i和j,所以减2遍
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): # 在for循环外,检查alpha值是否做了更新,如果在更新则将iter设为0后继续运行程序;知道更新完毕后,iter次循环无变化,才推出循环。
iter += 1
else:
iter = 0
print("iteration number: %d" % iter)
return b, alphas