简要介绍
1.SVM - Support Vector Machine ,俗称支持向量机,是一种 supervised learning (监督学习)算法,属于 classification (分类)的范畴。广泛应用于 Machine Learning (机器学习),Computer Vision (计算机视觉,装逼一点说,就是 cv)和 Data Mining (数据挖掘)当中。
2.支持向量:通俗理解:support vector (支持向量)的意思就是 数据集中的某些点,位置比较特殊。比如 x+y-2=0 这条直线,直线上面区域 x+y-2>0 的全是 A 类,下面的 x+y-2<0 的全是 B 类,我们找这条直线的时候,一般就看聚集在一起的两类数据,他们各自的 最边缘 位置的点,也就是最靠近划分直线的那几个点,而其他点对这条直线的最终位置的确定起不了作用,所以我姑且叫这些点叫 “支持点”(意思就是有用的点),但是在数学上,没这种说法,数学里的点,又可以叫向量,比如 二维点 (x,y) 就是二维向量,三维度的就是三维向量 (x,y,z)。所以 “支持点” 改叫 “支持向量” 。所以支持向量(Support Vector)就是离分隔超平面最近的那些点。
支持向量机举例
明显发现:选择D会比B、C分隔的效果要好很多。
支持向量机原理
SVM工作原理
- 寻找最大分类间距
- 转而通过拉格朗日函数求优化的问题
- 数据可以通过画一条直线就可以将它们完全分开,这组数据叫线性可分(linearly separable)数据,而这条分隔直线称为分隔超平面(separating hyperplane)。
- 如果数据集上升到1024维呢?那么需要1023维来分隔数据集,也就说需要N-1维的对象来分隔,这个对象叫做超平面(hyperlane),也就是分类的决策边界。
寻找最大间隔
为什么
- 直觉上是最安全的
- 如果我们在边界的位置发生了一个小错误(它在垂直方向上被颠倒),这给我们最小的可能导致错误分类。
- CV(cross validation 交叉验证)很容易,因为该模型对任何非支持向量数据点的去除是免疫的。
怎么寻找最大间隔
点到超平面的距离
- 分隔超平面函数间距: y(x)=w^Tx+b
- 分类的结果: f(x)=sign(w^Tx+b) (sign表示>0为1,<0为-1,=0为0)
- 点到超平面的几何间距: d(x)=(w^T x+b)/||w|| (||w||表示w矩阵的二范数=> (sqrt{w^T*w}), 点到超平面的距离也是类似的)
拉格朗日乘子
1.类别标签用-1、1,是为了后期方便 (label*(w^Tx+b)) 的标识和距离计算;如果 (label*(w^Tx+b)>0) 表示预测正确,否则预测错误。
2.现在目标很明确,就是要找到w和b,因此我们必须要找到最小间隔的数据点,也就是前面所说的支持向量。
- 也就说,让最小的距离取最大.(最小的距离:就是最小间隔的数据点;最大:就是最大间距,为了找出最优超平面–最终就是支持向量)
- 目标函数:
- 如果表示预测正确,也称函数间隔,||w||可以理解为归一化,也称几何间隔。
- 令 , 因为0~1之间,得到的点是存在误判的可能性,所以要保障 ,才能更好降低噪音数据影响。
- 所以本质上是求 ;也就说,我们约束(前提)条件是:
松弛变量
-
我们知道几乎所有的数据都不那么干净, 通过引入松弛变量来 允许数据点可以处于分隔面错误的一侧。
-
约束条件:
-
常量C是 惩罚因子, 表示离群点的权重(用于控制“最大化间隔”和“保证大部分点的函数间隔小于1.0” ) -
-
- (label*(w^Tx+b) > 1) and alpha = 0 (在边界外,就是非支持向量)
-
- (label*(w^Tx+b) = 1) and 0< alpha < C (在分割超平面上,就支持向量)
-
- (label*(w^Tx+b) < 1) and alpha = C (在分割超平面内,是误差点 -> C表示它该受到的惩罚因子程度)
参考地址:https://www.zhihu.com/question/48351234/answer/110486455
- (label*(w^Tx+b) < 1) and alpha = C (在分割超平面内,是误差点 -> C表示它该受到的惩罚因子程度)
-
C值越大,表示离群点影响越大,就越容易过度拟合;反之有可能欠拟合。
-
我们看到,目标函数控制了离群点的数目和程度,使大部分样本点仍然遵守限制条件。
-
例如:正类有10000个样本,而负类只给了100个(C越大表示100个负样本的影响越大,就会出现过度拟合,所以C决定了负样本对模型拟合程度的影响!,C就是一个非常关键的优化点!)
-
这一结论十分直接,SVM中的主要工作就是要求解 alpha.
SMO 高效优化算法
- SVM有很多种实现,最流行的一种实现是: 序列最小优化(Sequential Minimal Optimization, SMO)算法
- 下面还会介绍一种称为 核函数(kernel) 的方式将SVM扩展到更多数据集上。
- SVM几何含义比较直观,但其算法实现较复杂,牵扯大量数学公式的推导。
序列最小优化(Sequential Minimal Optimization, SMO)
- SMO目标:求出一系列 alpha 和 b,一旦求出 alpha,就很容易计算出权重向量 w 并得到分隔超平面。
- SMO思想:是将大优化问题分解为多个小优化问题来求解的。
- SMO原理:每次循环选择两个 alpha 进行优化处理,一旦找出一对合适的 alpha,那么就增大一个同时减少一个。
-
- 这里指的合适必须要符合一定的条件:这两个 alpha 必须要在间隔边界之外并且还没有进行过区间化处理或者不在边界上。
SMO 伪代码大致如下:
创建一个 alpha 向量并将其初始化为0向量
当迭代次数小于最大迭代次数时(外循环)
对数据集中的每个数据向量(内循环):
如果该数据向量可以被优化
随机选择另外一个数据向量
同时优化这两个向量
如果两个向量都不能被优化,退出内循环
如果所有向量都没被优化,增加迭代数目,继续下一次循环
简化版
#!/usr/bin/python# coding:utf8
"""Created on Nov 4, 2010Update on 2017-05-18Chapter 5 source file for Machine Learing in ActionAuthor: Peter/geekidentity/片刻GitHub: https://github.com/apachecn/AiLearning"""from __future__ import print_functionfrom numpy import *import matplotlib.pyplot as plt
def loadDataSet(fileName): """ 对文件进行逐行解析,从而得到第行的类标签和整个特征矩阵 Args: fileName 文件名 Returns: dataMat 特征矩阵 labelMat 类标签 """ 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): """ 随机选择一个整数 Args: i 第一个alpha的下标 m 所有alpha的数目 Returns: j 返回一个不为i的随机数,在0~m之间的整数值 """ j = i while j == i: j = int(random.uniform(0, m)) return j
def clipAlpha(aj, H, L): """clipAlpha(调整aj的值,使aj处于 L<=aj<=H) Args: aj 目标值 H 最大值 L 最小值 Returns: aj 目标值 """ if aj > H: aj = H if L > aj: aj = L return aj
def smoSimple(dataMatIn, classLabels, C, toler, maxIter): """smoSimple Args: dataMatIn 数据集 classLabels 类别标签 C 松弛变量(常量值),允许有些数据点可以处于分隔面的错误一侧。 控制最大化间隔和保证大部分的函数间隔小于1.0这两个目标的权重。 可以通过调节该参数达到不同的结果。 toler 容错率(是指在某个体系中能减小一些因素或选择对某个系统产生不稳定的概率。) maxIter 退出前最大的循环次数 Returns: b 模型的常量值 alphas 拉格朗日乘子 """ dataMatrix = mat(dataMatIn) # 矩阵转置 和 .T 一样的功能 labelMat = mat(classLabels).transpose() m, n = shape(dataMatrix)
# 初始化 b和alphas(alpha有点类似权重值。) b = 0 alphas = mat(zeros((m, 1)))
# 没有任何alpha改变的情况下遍历数据的次数 iter = 0 while (iter < maxIter): # w = calcWs(alphas, dataMatIn, classLabels) # print("w:", w)
# 记录alpha是否已经进行优化,每次循环时设为0,然后再对整个集合顺序遍历 alphaPairsChanged = 0 for i in range(m): # print 'alphas=', alphas # print 'labelMat=', labelMat # print 'multiply(alphas, labelMat)=', multiply(alphas, labelMat) # 我们预测的类别 y = w^Tx[i]+b; 其中因为 w = Σ(1~n) a[n]*lable[n]*x[n] fXi = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[i, :].T)) + b # 预测结果与真实结果比对,计算误差Ei Ei = fXi - float(labelMat[i])
# 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值) # 0<=alphas[i]<=C,但由于0和C是边界值,我们无法进行优化,因为需要增加一个alphas和降低一个alphas。 # 表示发生错误的概率:labelMat[i]*Ei 如果超出了 toler, 才需要优化。至于正负号,我们考虑绝对值就对了。 ''' # 检验训练样本(xi, yi)是否满足KKT条件 yi*f(i) >= 1 and alpha = 0 (outside the boundary) yi*f(i) == 1 and 0<alpha< C (on the boundary) yi*f(i) <= 1 and alpha = C (between the boundary) ''' if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
# 如果满足优化的条件,我们就随机选取非i的一个点,进行优化比较 j = selectJrand(i, m) # 预测j的结果 fXj = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[j, :].T)) + b Ej = fXj - float(labelMat[j]) alphaIold = alphas[i].copy() alphaJold = alphas[j].copy()
# L和H用于将alphas[j]调整到0-C之间。如果L==H,就不做任何改变,直接执行continue语句 # labelMat[i] != labelMat[j] 表示异侧,就相减,否则是同侧,就相加。 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是alphas[j]的最优修改量,如果eta==0,需要退出for循环的当前迭代过程 # 参考《统计学习方法》李航-P125~P128<序列最小最优化算法> 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]值 alphas[j] -= labelMat[j]*(Ei - Ej)/eta # 并使用辅助函数,以及L和H对其进行调整 alphas[j] = clipAlpha(alphas[j], H, L) # 检查alpha[j]是否只是轻微的改变,如果是的话,就退出for循环。 if (abs(alphas[j] - alphaJold) < 0.00001): print("j not moving enough") continue # 然后alphas[i]和alphas[j]同样进行改变,虽然改变的大小一样,但是改变的方向正好相反 alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - 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)) # 在for循环外,检查alpha值是否做了更新,如果在更新则将iter设为0后继续运行程序 # 知道更新完毕后,iter次循环无变化,才推出循环。 if (alphaPairsChanged == 0): iter += 1 else: iter = 0 print("iteration number: %d" % iter) return b, alphas
def calcWs(alphas, dataArr, classLabels): """ 基于alpha计算w值 Args: alphas 拉格朗日乘子 dataArr feature数据集 classLabels 目标变量数据集 Returns: wc 回归系数 """ 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
def plotfig_SVM(xMat, yMat, ws, b, alphas): """ 参考地址: http://blog.csdn.net/maoersong/article/details/24315633 http://www.cnblogs.com/JustForCS/p/5283489.html http://blog.csdn.net/kkxgx/article/details/6951959 """
xMat = mat(xMat) yMat = mat(yMat)
# b原来是矩阵,先转为数组类型后其数组大小为(1,1),所以后面加[0],变为(1,) b = array(b)[0] fig = plt.figure() ax = fig.add_subplot(111)
# 注意flatten的用法 ax.scatter(xMat[:, 0].flatten().A[0], xMat[:, 1].flatten().A[0])
# x最大值,最小值根据原数据集dataArr[:, 0]的大小而定 x = arange(-1.0, 10.0, 0.1)
# 根据x.w + b = 0 得到,其式子展开为w0.x1 + w1.x2 + b = 0, x2就是y值 y = (-b-ws[0, 0]*x)/ws[1, 0] ax.plot(x, y)
for i in range(shape(yMat[0, :])[1]): if yMat[0, i] > 0: ax.plot(xMat[i, 0], xMat[i, 1], 'cx') else: ax.plot(xMat[i, 0], xMat[i, 1], 'kp')
# 找到支持向量,并在图中标红 for i in range(100): if alphas[i] > 0.0: ax.plot(xMat[i, 0], xMat[i, 1], 'ro') plt.show()
if __name__ == "__main__": # 获取特征和目标变量 dataArr, labelArr = loadDataSet('data/6.SVM/testSet.txt') # print labelArr
# b是常量值, alphas是拉格朗日乘子 b, alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40) print('/n/n/n') print('b=', b) print('alphas[alphas>0]=', alphas[alphas > 0]) print('shape(alphas[alphas > 0])=', shape(alphas[alphas > 0])) for i in range(100): if alphas[i] > 0: print(dataArr[i], labelArr[i]) # 画图 ws = calcWs(alphas, dataArr, labelArr) plotfig_SVM(dataArr, labelArr, ws, b, alphas)