基于spark的svm算法实现
2017-03-08 11:50
302 查看
支持向量机SVM(Support Vector Machine)是一种有监督的学习模型,它的核心有两个:一、核函数(kernel trick);二、序列最小优化算法SMO(Sequential minimal optimization)是John Platt在1996年发布的用于训练SVM的有效算法。本文不打算细化SVM支持向量机的详细推倒算法,只涉及以上两点的内容做一个说明,最后给出算法实现和一个实验对比图。
多项式核:
高斯核(径向基函数):
线性核:
即是两个矩阵空间的内积。
1、选择需要更新的一对
和
,采取启发式的方式进行选择,以使目标函数最大程度的接近其全局最优值;
2、将目标函数对
和
进行优化,以保持其它所有α不变。
以上是两个基本步骤,实现具体推到公式如下:
所需要收到的约束条件为:
同时更新
和
,要求满足如下条件,就可以保证为0的约束
消去
可得
其中
的表达式为:
为第i个特征因素的真实标签值
之后考虑约束条件
则
约束条件的线性表示
依据
和
同号或是异号,可得出上下两个边界为
对于
有
对于
首先可以通过
求得j,之后计算方式可为
而b的更新为
其中
每次更新完和都需要重新计算b以及对应的和
有了以上的公式,代码实现就比较简单了。
在以上算法里面重点关注是j的选择,
J的选择
首选采取启发式选择j,通过计算deltaE的最大值来逼近j的选择,如果选择不到就随机选择一个j值,在j选择里面有一个Ek的计算方式
下面再介绍一下核函数计算方式,本文主要采取径向基函数(RBF)实现,如下
最后看下测试代码实现
测试代码是首先找出所有的支持向量,并提取支持向量下的特征向量和标签向量,采取核函数进行隐式映射,最后计算预测值。
通过径向基函数映射后采取支持向量预测计算得到的可分平面如下
本算法100个数据训练准确率可达98%。
注:本文算法均来自Peter Harrington的《Machine Learning in action》
核函数
核函数在处理复杂数据时效果显著,它的做法是将某一个维度的线性不可分数据采取核函数进行特征空间的隐式映射到高维空间,从而在高维空间将数据转化为线性可分,最后回归到原始维度空间实施分类的过程,常见的几个核函数如下:多项式核:
高斯核(径向基函数):
线性核:
即是两个矩阵空间的内积。
SMO算法流程
SMO的主要两个步骤就是:1、选择需要更新的一对
和
,采取启发式的方式进行选择,以使目标函数最大程度的接近其全局最优值;
2、将目标函数对
和
进行优化,以保持其它所有α不变。
以上是两个基本步骤,实现具体推到公式如下:
所需要收到的约束条件为:
同时更新
和
,要求满足如下条件,就可以保证为0的约束
消去
可得
其中
的表达式为:
为第i个特征因素的真实标签值
之后考虑约束条件
则
约束条件的线性表示
依据
和
同号或是异号,可得出上下两个边界为
对于
有
对于
首先可以通过
求得j,之后计算方式可为
而b的更新为
其中
每次更新完和都需要重新计算b以及对应的和
有了以上的公式,代码实现就比较简单了。
算法实现
完整的Platt-smo算法实现入口public SvmResult plattSmo(final SvmResult svmResult) { double b = svmResult.getB(); double[] alphas = svmResult.getAlphas(); for(int i=0;i<featuresArray.length;i++){ double ei = this.calcEk(i, alphas, b); if (((lablesArray[i] * ei < -tolerFactor) && (alphas[i] < penaltyFactor)) || ((lablesArray[i] * ei > tolerFactor) && (alphas[i] > 0))) { double[] jSelected = this.selectJ(i, ei, alphas, b); //启发式实现j的选择 int j = (int) jSelected[0]; double ej = jSelected[1]; double alphaIold = alphas[i]; double alphaJold = alphas[j]; double L = 0; double H = 0; //边界计算 if (lablesArray[i] != lablesArray[j]) { L = Math.max(0, alphas[j] - alphas[i]); H = Math.min(penaltyFactor, penaltyFactor + alphas[j] - alphas[i]); } else { L = Math.max(0, alphas[j] + alphas[i] - penaltyFactor); H = Math.min(penaltyFactor, alphas[j] + alphas[i]); } if (L == H) { logger.info("L==H"); } else { double eta = (2.0 * this.kernelArray[i][j] - this.kernelArray[i][i] - this.kernelArray[j][j]); if (eta >= 0) { logger.info("eta>=0"); } else { //双向调整alphas[j]递减 alphas[j] -= lablesArray[j] * (ei - ej) / eta; if (alphas[j] > H) { alphas[j] = H; } if (L > alphas[j]) { alphas[j] = L; } //更新ej this.updateEk(j, alphas, b); if (Math.abs(alphas[j] - alphaJold) < 0.00001) { logger.info("j not moving enough"); } else { //双向调整alphas[i]递减 alphas[i] += lablesArray[j] * lablesArray[i] * (alphaJold - alphas[j]); //更新ei this.updateEk(i, alphas, b); //计算b double b1 = b - ei- lablesArray[i]*(alphas[i]-alphaIold)*this.kernelArray[i][i] - lablesArray[j]*(alphas[j]-alphaJold)*this.kernelArray[i][j]; double b2 = b - ej- lablesArray[i]*(alphas[i]-alphaIold)*this.kernelArray[i][j] - lablesArray[j]*(alphas[j]-alphaJold)*this.kernelArray[j][j]; if ((0 < alphas[i]) && (penaltyFactor > alphas[i])){ b = b1; }else if ((0 < alphas[j]) && (penaltyFactor > alphas[j])){ b = b2; }else{ b = (b1 + b2)/2.0; } } } } } } return new SvmResult(b, alphas); }
在以上算法里面重点关注是j的选择,
J的选择
private double[] selectJ(int i,double ei,double[] alphas,double b){ int maxK = -1; double maxDeltaE = 0; double ej = 0; int j = -1; double[] eiArray= new double[2]; eiArray[0] = 1d; eiArray[1] = ei; this.eCache[i] = eiArray; boolean hasValidEcacheList = false; for(int k=0;k<this.eCache.length;k++){ if(this.eCache[k][0] > 0){ if(k == i){ continue; } hasValidEcacheList = true; if(k == this.m){ k = m-1; } double ek = this.calcEk(k, alphas, b); double deltaE = Math.abs(ei - ek); if (deltaE > maxDeltaE){ maxK = k; maxDeltaE = deltaE; ej = ek; } } } j = maxK; if(!hasValidEcacheList || j == -1){ j = this.selectJRandom(i); ej = this.calcEk(j, alphas, b); } if(j == this.m){ j = m-1; } return new double[]{j,ej}; }
首选采取启发式选择j,通过计算deltaE的最大值来逼近j的选择,如果选择不到就随机选择一个j值,在j选择里面有一个Ek的计算方式
private double calcEk(int k,double[] alphas,double b){ Matrix alphasMatrix = new Matrix(alphas); Matrix lablesMatrix = new Matrix(lablesArray); Matrix kMatrix = new Matrix(this.kernelArray[k]); double fXk = alphasMatrix.multiply(lablesMatrix).dotMultiply(kMatrix.transpose()).dotValue() + b; double ek = fXk - (float)this.lablesArray[k]; return ek; }
下面再介绍一下核函数计算方式,本文主要采取径向基函数(RBF)实现,如下
public double[] kernelTrans(double[][] featuresArray,double[] featuresIArray){ int mCount = featuresArray.length; double[] kernelTransI = new double[mCount]; Matrix featuresMatrix = new Matrix(featuresArray); Matrix featuresIMatrix = new Matrix(featuresIArray); if(trainFactorMap.get("KT").equals("lin")){ Matrix result = featuresMatrix.dotMultiply(featuresIMatrix.transpose()); kernelTransI = result.transpose().values()[0]; }else if(trainFactorMap.get("KT").equals("rbf")){ double rbfDelta = (double)trainFactorMap.get("rbfDelta"); for(int j=0;j<mCount;j++){ Matrix xj = new Matrix(featuresArray[j]); Matrix delta = xj.reduce(featuresIMatrix); double deltaValue = delta.dotMultiply(delta.transpose()).dotValue(); kernelTransI[j] = Math.exp((-1.0*deltaValue)/(2*Math.pow(rbfDelta, 2))); } } return kernelTransI; }
最后看下测试代码实现
double[][] datasvs = new double[m][d[0].length]; double[] labelsvs = new double[m]; double[] alphassvs = new double[m]; int n = 0; for(int i=0;i<alphas.length;i++){ if(alphas[i] != 0){ datasvs = d[i]; labelsvs = l[i]; alphassvs = alphas[i]; n++; } } //model test int errorCount = 0; for(int i=0;i<d.length;i++){ double[] kernelTransI = learner.kernelTrans(datasvs, d[i]); Matrix kernelTransIM = new Matrix(kernelTransI); Matrix labelsvsM = new Matrix(labelsvs); Matrix alphassvsM = new Matrix(alphassvs); double predict = kernelTransIM.dotMultiply(labelsvsM.multiply(alphassvsM).transpose()).dotValue() + b; System.out.println(i+"\t"+predict+"\t"+l[i]); if(AdaBoost.sigmoid(predict) != l[i]){ errorCount++; } }
测试代码是首先找出所有的支持向量,并提取支持向量下的特征向量和标签向量,采取核函数进行隐式映射,最后计算预测值。
训练结果
本文采取100个二维平面无法线性可分的数据集合,如下通过径向基函数映射后采取支持向量预测计算得到的可分平面如下
本算法100个数据训练准确率可达98%。
注:本文算法均来自Peter Harrington的《Machine Learning in action》
相关文章推荐
- 基于Spark的移动用户主要活动地点的挖掘算法实现以及JavaEE技术整合
- 基于OpenCV的 SVM算法实现数字识别(三)---SMO求解
- 基于Spark的移动用户主要活动地点的挖掘算法实现以及JavaEE技术整合
- 基于OpenCV的 SVM算法实现数字识别(一)---理论基础
- 机器学习经典算法详解及Python实现--基于SMO的SVM分类器
- 基于OpenCV的 SVM算法实现数字识别(二)---SVM原理
- 基于OpenCV的 SVM算法实现数字识别(四)---代码实现
- 基于Spark上的中文分词算法的实现
- 基于Andrew ng课后作业6,matlab实现svm算法的垃圾邮件分类器(spam classifier)
- SVM实现多分类的程序基础工作(三)——基于纠错编码的SVM多类分类算法和基于二叉树的多类SVM算法的基本思想
- 基于python的分词算法的实现(1) - 算法
- 体绘制(Volume Rendering)概述之4:光线投射算法(Ray Casting)实现流程和代码(基于CPU的实现)
- 基于python的分词算法的实现(3) - 建立字典
- 基于python的分词算法的实现(2) - 字典的选择
- Java基于双向链表实现列表结构(算法源码)
- 基于随机游走的社团划分算法hadoop MR实现
- 基于AR模型谱估计算法(Yule-Walker方法与Burg方法)的C++实现
- 最短路径算法,VC6源码,基于STL的实现
- 基于.NET的分词软件设计与实现V1.0--总体思路及算法实现
- 基于GPU实现的经典光照模型算法:漫反射模型(使用cg语言实现)