博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)
阅读量:5877 次
发布时间:2019-06-19

本文共 4548 字,大约阅读时间需要 15 分钟。

支持向量机SVM(Support Vector Machine)是一种有监督的学习模型,它的核心有两个:一、核函数(kernel trick);二、序列最小优化算法SMO(Sequential minimal optimization)是John Platt在1996年发布的用于训练SVM的有效算法。本文不打算细化SVM支持向量机的详细推倒算法,只涉及以上两点的内容做一个说明,最后给出算法实现和一个实验对比图。

  核函数

核函数在处理复杂数据时效果显著,它的做法是将某一个维度的线性不可分数据采取核函数进行特征空间的隐式映射到高维空间,从而在高维空间将数据转化为线性可分,最后回归到原始维度空间实施分类的过程,常见的几个核函数如下:

多项式核:

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

高斯核(径向基函数):

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

线性核:

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

即是两个矩阵空间的内积。

  SMO算法流程

SMO的主要两个步骤就是:

1、选择需要更新的一对α,采取启发式的方式进行选择,以使目标函数最大程度的接近其全局最优值;

2、将目标函数对α进行优化,以保持其它所有α不变。

以上是两个基本步骤,实现具体推到公式如下:

所需要收到的约束条件为:

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

同时更新α,要求满足如下条件,就可以保证为0的约束

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

消去α可得

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

其中

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

u 的表达式为:

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

y为第i个特征因素的真实标签值

之后考虑约束条件 0<α<c 则

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

约束条件的线性表示

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

依据 y 同号或是异号,可得出上下两个边界为

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

对于α有

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

对于α首先可以通过E求得j,之后计算方式可为:

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

而b的更新为

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

其中

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

每次更新完和都需要重新计算b以及对应的和

有了以上的公式,代码实现就比较简单了。

  算法实现

完整的Platt-smo算法实现入口:

public SvmResult plattSmo(final SvmResult svmResult) {double b = svmResult.getB();double[] alphas = svmResult.getAlphas();for(int i=0;i
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;}//更新ejthis.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]);//更新eithis.updateEk(i, alphas, b);//计算bdouble 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
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

最后看下测试代码实现:

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

测试代码是首先找出所有的支持向量,并提取支持向量下的特征向量和标签向量,采取核函数进行隐式映射,最后计算预测值。

  训练结果

本文采取100个二维平面无法线性可分的数据集合,如下:

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

通过径向基函数映射后采取支持向量预测计算得到的可分平面如下

基于Spark如何实现SVM算法?这里有一份详尽的开发教程(含代码)

本算法100个数据训练准确率可达98%。

注:本文算法均来自Peter Harrington的《Machine Learning in action》

====================================分割线================================
本文作者:AI研习社
本文转自雷锋网禁止二次转载,
你可能感兴趣的文章
lintcode:Remove Nth Node From End of Lis 删除链表中倒数第n个节点
查看>>
POJ 1915-Knight Moves (单向BFS &amp;&amp; 双向BFS 比)
查看>>
java中在linux下利用jstack检测死锁
查看>>
linux编译安装LAMP
查看>>
php中的continue用法
查看>>
Android小游戏应用---撕破美女衣服游戏
查看>>
TextKit简单示例
查看>>
网格最短路径算法(Dijkstra & Fast Marching)(转)
查看>>
最短路径算法-Dijkstra算法的应用之单词转换(词梯问题)
查看>>
软链接和硬链接详解
查看>>
HTML5 video 视频标签 常用属性
查看>>
深入理解javascript对象系列第一篇——初识对象
查看>>
Redis_master-slave模式
查看>>
qemu安装
查看>>
多媒体开发之rtmp---rtmp client 端的实现
查看>>
3.使用Maven构建Web项目
查看>>
iView实现自定义Modal
查看>>
如何在云帮上配置https
查看>>
JQuery干货篇之插入元素
查看>>
Imperva开源域目录控制器,简化活动目录集成
查看>>