从软件工程的角度写机器学习5——SVM(支持向量机)实现

SVM实现

SVM在浅层学习时代是主流监督学习算法,在深度学习时代也往往作为最后一个预测层使用(说深度学习击败了SVM的纯属扯淡)。

SVM算法总体流程

本系列文章旨在讲解机器学习算法的工程实现方法,不在于推导数学原理。因此想深入了解原理的请移步去看《支持向量机通俗导论(理解SVM的三层境界)》:

http://www.cnblogs.com/v-July-v/archive/2012/06/01/2539022.html

对于急于求成的小伙伴,建议就看下面描述的基本过程,知其然,不必知其所以然。

按照上篇文章所规定的工程框架,所有操作尽量以矩阵为单元执行:

http://blog.csdn.net/jxt1234and2010/article/details/51926758

预测

SVM是一个监督学习算法,其训练时输入自变量矩阵X(N个m维向量)和因变量矩阵Y(这里要求Y中元素取值为两类之一:{A、B}),输出模型M。

如图所示,一个SVM模型包含如下要素:

1、支撑向量集SV

一个n行m列的矩阵,表示n个m维向量。

支撑向量集是在训练过程中,挑选出自变量矩阵的有效行形成的。

2、系数C与截距b

系数矩阵为n行1列的矩阵,它与支撑向量集一一对应。

截距为一个常数。

3、核函数K

核函数为实现了如下接口的一个类

class IKernel
{
public:
    virtual Matrix* vCompute(const Matrix* X1, const Matrix* X2) const = 0;
};

其中X1为 [m, n1]的矩阵,X2为[m, n2]的矩阵,输出结果为 [n2, n1] 的矩阵。

具体的核函数运算参考:

http://blog.csdn.net/xiaowei_cqu/article/details/35993729

预测时输入自变量矩阵X(待预测向量集)和模型M,输出预测值矩阵YP。

X为[m,p]的矩阵,表示p个待预测的m维向量。

YP为[1,p]的矩阵,表示p个预测值。

第一步,由核函数算出SV和X的核内积矩阵KX。KX(i, j)即第i个支撑向量和第j个待预测向量的核内积。

第二步,用KX和对应的系数矩阵相乘,加上截距,得到YP。

预值出来的YP只是一个实数,不表示具体类别,使用时需要对每个值作类别转化,大于0转为类别A,小于0转为类别B。

预测过程可以用一个公式简单表示:

YP=K((M.SV), X)?(M.C)+B

训练过程

在SVM模型中,核函数是事先指定的,支撑向量是从自变量向量集中抽取的,因此训练过程主要就是计算系数矩阵和截矩。

因变量矩阵Y假定其元素只有A、B两个取值

训练之前先要预处理,将因变量矩阵Y中A转为1,B转为-1。

如图所示,SVM模型的创建分三步:

1、创建二次规划(主要是计算核内积矩阵)

2、解二次规划,得到截矩b和原始系数矩阵A,原始系数矩阵为[1,N]的矩阵,其系数值与自变量矩阵中的向量一一对应。

3、把原始系数矩阵中非零系数及对应的自变量矩阵中的向量挑选出来,形成系数矩阵C及其对应的支撑向量集SV。

第3步只是一个简单的挑选过程,因此经常被一带而过,往往只有自己去实现,才能记得。

熟悉SVM的朋友可以看出,按上面这个流程这个方式写出来的SVM,并没有交叉验证、核函数选择及参数调优及多分类的功能。主要是考虑到这些特性并非SVM专属,应当在算法抽象层实现,以便给其他监督学习算法共享。

基础算法实现及相应的类

训练类

SVM是一种监督学习算法,对应的训练类定义为SVMLearner

class SVMLearner
{
public:
    virtual IPredictor* vLearn(Matrix* X, Matrix* Y) const;
private:
    double mG;//对应松驰因子G
    IKernel* mK;//核函数
    ISVMProgramSolver* mSolver;//二次规划求解器
};

二次规划生成

规划描述

mina12∑i∑jyiyjkijaiaj?∑iai

0<=ai<=G,∑iaiyi=0

ai 表示系数矩阵A的第i行的值

yi 表示因变量矩阵Y的第i行的值

kij 表示核内积矩阵第i行第j列的值,即第i个自变量向量和第j个自变量向量的核内积。

G为松驰因子,事先设定。

截距b值由如下公式推断:

b=∑iyiaikik?yk,ak>0

至于,为什么是这个二次规划公式,请移步相关的原理文章看推导过程。

看完上面的结构图,我们定义如下的结构体去描述SVM创建的二次规划:

struct SVMProblem
{
    Matrix* K;
    Matrix* Y;
    double G;

    /*解的描述:系数矩阵及截矩*/
    struct Answer{
        Matrix* C;
        double b;
    };
};

这里并不定义一个通用的二次规划问题结构体,主要考虑到仅SVM算法涉及二次规划的求解,不是太有必要基于普通二次规划求解。

这个结构体中,松驰变量G需要事先设定,Y直接来源于因变量,因此,创建二次规划的过程就等同于核函数运算的过程,即求出核内积矩阵:K=K(X,X)

核函数运算

构造二次规划问题过程中,最核心的是核函数运算,即求出所有kij。

核函数本身是向量间的一种内积,但为了后续优化方便,我们将核函数的接口定义为对矩阵的运算。

考虑到核函数有多种,定义相应的类:

比如线性核函数:

class LinearKernel:public IKernel
{
public:
    virtual Matrix* vCompute(const Matrix* X1, const Matrix* X2) const;
};

径向基核函数:

class RBFKernel:public IKernel
{
public:
    virtual Matrix* vCompute(const Matrix* X1, const Matrix* X2) const;

private:
    double mGamma;
};

注意到核内积矩阵K是[N,N]的规模,当训练数据量很大时,这个矩阵是非常大的。

核函数计算还是相当好并行化实现的,不过对于不同的核函数,似乎没有一个统一的方案,只好一个个来优化了。

二次规划求解

SVM的二次规划的求解是一种凸优化问题,能求出导数表达式,因此可以用通用的优化算法梯度下降法求解。此外,由于它本身的特殊性,可以用一种更快的优化方法SMO算法。

考虑到有多种解法,在这定义一个接口:

class ISVMProgramSolver {
public:
    virtual SVMProblem::Answer* vSolve(Problem*) const = 0;
};

梯度下降

定义梯度下降法解SVMProblem的类为

GradientDecentSVMProgramSolver

class GradientDecentSVMProgramSolver:public ISVMProgramSolver {
public:
    virtual SVMProblem::Answer* vSolve(Problem*) const;
};

梯度下降是一种通用性质的优化算法了,有必要定义梯度下降这一优化算法抽象出来。

梯度下降简单来说就是一个公式:

θ=θ?α?θJ

θ为待确定的系数向量,α为一个系数,?θJ为目标函数对系数向量的偏导。

求偏导这种工作涉及符号代数,需要加公式解析和公式树运算的模块,本工程就不做这个事情了,而是直接将梯度函数定义为一个接口,每个上层算法自行实现:

class DerivativeFunction {
public:
    /*输入:初始的系数向量 coefficient,用于更新系数的样本集矩阵X
      输出:系数梯度向量,与 coefficient 同长
    */
    virtual Matrix* vCompute(Matrix* coefficient, Matrix* X) const = 0;
};

梯度下降算法定义为如下的接口:

其中系数矩阵 coefficient 既是输入(需要提供初始值),也是输出,X为样本集,alpha为更新速度,iteration为最高迭代次数,DerivativeFunction为梯度计算函数。

class IGradientDecent {
public:
    virtual void vOptimize(Matrix* coefficient, Matrix* X, const DerivativeFunction* delta, double alpha, int iteration) const = 0;
};

其中可分两种实现方式:

/*普通梯度下降,每次用全部样本来更新梯度*/
class GradientDecent :public IGradientDecent{
public:
    virtual void vOptimize(Matrix* coefficient, Matrix* X, const DerivativeFunction* delta, double alpha, int iteration) const;
};

/*随机梯度下降,每次随机抽取样本来更新梯度*/
class StochasticGradientDecent : public IGradientDecent{
public:
    virtual void vOptimize(Matrix* coefficient, Matrix* X, const DerivativeFunction* delta, double alpha, int iteration) const;
};

在GradientDecentSVMProgramSolver的实现中,

定义一个 DerivativeFunction,初始化一个全为零的coefficient矩阵,然后由通用的梯度下降算法去确定系数即可。

SMO

http://www.cnblogs.com/jerrylead/archive/2011/03/18/1988419.html

SMO是一个相对专一的优化算法,因此直接写为一个继承ISVMProgramSolver的类。

class SMOSVMProgramSolver:public ISVMProgramSolver {
public:
    virtual SVMProblem::Answer* vSolve(Problem*) const;
};

预测类

定义SVM的预测类如下:

/*预测模型*/
class SVMPredictor:public IPredictor
{
public:
    //获得类别值
    virtual Matrix* vPredict(Matrix* X) const;

    //获得各个类别的概率([0,1]区间)
    virtual Matrix* vPredictProb(Matrix* X) const;

    //获得各个类型的值
    virtual Matrix* vGetValues() const;

    //保存为树结点(后续可将树结点存成json)
    virtual Node* vDump() const;
private:
    double mTypeA;//A类别的值
    double mTypeB;//B类别的值
    std::string mKernelDescription;//核函数描述,根据此字段可以重建核函数
    IKernel* mKernel;//核函数
    Matrix* mSV;//支持向量
    double mB;//截矩
};

抽象特性实现

讲SVM的文章,实现SVM的代码都把这些特性内嵌到SVM算法本身去了。但这个原应在一个更高的抽象层实现。现在,让我们仅仅将SVM视为一个普通的监督学习算法,实现这些特性。

交叉验证

关于交叉验证的说明参考如下文章:

http://blog.csdn.net/xywlpo/article/details/6531128

在工程中,交叉验证是一个监督学习的评价类:

/*CRV Means Cross Validate*/
class ICRVEvaluate {
public:
    //ICompare为对比函数,依据预测值和真实值的差距评分,一般取为n阶矩离
    class ICompare {
    public:
        virtual double vCompare(Matrix* Y, Matrix* YP) const;
    };
    virtual double vEvaluate(ILearner* learner, Matrix* X, Matrix* Y, ICompare* compare) const = 0;
};

交叉验证有若干种方式,但统一的流程都是拆分——训练——预测与结果累加,以k-folder cross-validation为例:

class KFolderCrossValidation : public ICRVEvaluate {
public:
    virtual double vEvaluate(ILearner* learner, Matrix* X, Matrix* Y, ICompare* compare) const {
        double sumValue = 0.0;
        for (int i=0; i<mK; ++i) {
            Matrix* trainSetX = splitForTrain(X, i);
            Matrix* trainSetY = splitForTrain(X, i);
            IPredictor* p = learner->vLearn(subSetX, subSet, Y);
            delete trainSetX;
            delete trainSetY;
            Matrix* testSetX = splitForTest(X, i);
            Matrix* predictY = p->vPredict(testSetX);
            delete testSetX;
            delete p;

            Matrix* testSetY = splitForTest(Y, i);
            sumValue += compare->vCompare(predictY, testSetY);
            delete testSetY;
            delete predictY;
        }
        return sumValue;
    }
private:
    int mK;//交叉验证的划分数,一般取10
};

选择了交叉验证函数之后,可以此去判定一个监督学习算法的好坏,这个就是后续参数调优中的目标函数。

核函数选择与参数调优

SVM的核函数选择与参数调优无法基于数理统计找到适合的优化方法,只能使用启发式优化算法或穷举。这种优化算法理应独自为一通用模块:

定义一个通用的启发式优化算法:

class IOptimizer{
public:
    /*评价函数*/
    class IEvaluate {
    public:
        //parameters 为[n,1]的矩阵,即一个向量,其中每个数都为[0,1]的实数
        virtual double vEvaluate(Matrix* parameters) const = 0;
    };

    /*parameters既是输入也是输出,evaluate为评价函数*/
    virtual void vOptimize(Matrix* parameters, IEvaluate* evaluate) const = 0;
};

定义一个SVMFactory,基于parameters构建SVM训练器,此处类似于抽象工厂的设计:

class ILearnerFactory{
public:
    virtual ILearner* vCreate(Matrix* parameters) const = 0;
};

class SVMFactory : public ILearnerFactory{
public:
    virtual ILearner* vCreate(Matrix* parameters) const;
};

这个构建过程将挑选SVMLearner中的Kernel函数和二次规划求解函数并确定G值。

针对监督学习算法的评价函数便可如此编写:

/*评价函数*/
class LearnerEvaluate:public IOptimizer::IEvaluate {
public:
    virtual double vEvaluate(Matrix* parameters) const {
        ILearner* l = mFactory->vCreate(parameters);
        double p = mEvaluate->vEvaluate(l, mX, mY, mCompare);
        delete l;
        return p;
    }
private:
    ILearnerFactory* mFactory;
    ICRVEvaluate* mEvaluate;
    ICRVEvaluate::ICompare* mCompare;
    Matrix* mX;
    Matrix* mY;
};

实际的优化算法可使用遗传算法、粒子群等。

//遗传算法
class GAOptimizer : public IOptimizer{
public:
    virtual void vOptimize(Matrix* parameters, IEvaluate* evaluate) const;
};

//粒子群算法
class PSOOptimizer : public IOptimizer{
public:
    virtual void vOptimize(Matrix* parameters, IEvaluate* evaluate) const;
};

定义引入参数调优的监督学习算法类:

class OptimizedLearner:public ILearner {
public:
    virtual IPredictor* vLearn(Matrix* X, Matrix* Y) const;
private:
    ILearnerFactory* mFactory;
    IOptimizer* mOptimizer;
    int mParameterSize;
};

该类将先用参数寻优算法构造一个实际的训练器ILearner,再用该训练器得到模型。

多分类实现

SVM原理上只能进行二分类,为了支持多分类,需要将其扩展,如何扩展参考这篇文章:

http://www.cnblogs.com/CheeseZH/p/5265959.html

如上面的文章所述,有多种扩展方式,我们定义如下几个装饰类:

/*一对多法*/
class OVRLearner : public ILearner{
public:
    OVRLearner(ILearner* l);
    virtual IPredictor* vLearn(Matrix* X, Matrix* Y) const;
private:
    ILearner* mOrigin;
};

/*一对一法*/
class OVOLearner : public ILearner{
public:
    OVOLearner(ILearner* l);
    virtual IPredictor* vLearn(Matrix* X, Matrix* Y) const;
private:
    ILearner* mOrigin;
};

创建多分类器时,基于二分类器包裹一下即可,比如:

ILearner* origin = factory->vCreate(parameters);
ILearner* l = new OVOLearner(origin);

抽象层的各种Learner对应地需要定义各自的Predictor,不详述。

代码框架

比起上一节的决策树,SVM的代码框架明显复杂了许多,主要其中很大一部分是可复用的,独立出来以便其他算法调用:

时间: 2024-11-03 05:40:44

从软件工程的角度写机器学习5——SVM(支持向量机)实现的相关文章

从软件工程的角度写机器学习6——深度学习之卷积神经网络(CNN)实现

卷积神经网络(CNN)实现 背景 卷积神经网络广泛用于图像检测,它的实现原理与传统神经网络基本上是一样的,因此将普遍意义的神经网络和卷积神经网络的实现合成一篇. 神经网络实现思路 "扔掉神经元" 尽管所有教程在介绍神经网络时都会把一大堆神经元画出来,并且以输入节点--神经元--输出结点连线,但是,在编程实现时,基于神经元去编程是低效的.典型如这篇经典文章里面的代码: http://blog.csdn.net/zzwu/article/details/575125. 比较合适的方法是将神

从软件工程的角度写机器学习4——-C4.5决策树的工程实现

C4.5决策树的工程实现 这篇文章开始,将讲述一系列机器学习算法的工程实现方案.出于常用且简单的考虑,选择了C4.5决策树作为第一个算法. 工程框架 鉴于本篇是第一个算法实现,应此需要把整个工程框架介绍一下. 出于最优性能考虑,本框架是为C/C++语言设计的.不过即使用其他语言,也可以按这个框架实现,模块还可以再精简. 本工程定位: 1.无脑版机器学习算法库,使用者基本不需要了解任何算法细节,也不需要了解配置的算法参数含义. 2.可分离的算法库,算法库输出的模型文件可以方便地被其他工程解析使用.

从软件工程的角度写机器学习3——主要监督学习算法的工程性分析

主要机器学习算法的工程适用性分析 前段时间AlphaGo跟李世石的大战及相关的深度学习的新闻刷了一遍又一遍的朋友圈.不过这件事情,也只是在机器学习的深度上进一步拓展,而机器学习的广度(也即工程化实践)上,仍然没有什么突破性的理论或实践,用的领域继续用,不用的领域依然不用. 工程性分析的作用 工程上的琐事 机器学习的使命是使计算机强大的运算能力和存储能力转化为推演能力,能转化是一方面,转化的效率则是另一方面.科研性质的AlphaGo,拥有近乎无限的计算资源,不用太考虑这方面的问题,但在我们实际的工

从软件project的角度写机器学习3——主要监督学习算法的project性分析

主要机器学习算法的project适用性分析 前段时间AlphaGo跟李世石的大战及相关的深度学习的新闻刷了一遍又一遍的朋友圈.只是这件事情,也仅仅是在机器学习的深度上进一步拓展,而机器学习的广度(也即project化实践)上,仍然没有什么突破性的理论或实践,用的领域继续用,不用的领域依旧不用. project性分析的作用 project上的琐事 机器学习的使命是使计算机强大的运算能力和存储能力转化为推演能力.能转化是一方面.转化的效率则是还有一方面.科研性质的AlphaGo,拥有近乎无限的计算资

机器学习之&amp;&amp;SVM支持向量机入门:Maximum Margin Classifier

概率论只不过是把常识用数学公式表达了出来. --拉普拉斯 0. 前言 这是一篇SVM的入门笔记,来自我对PlusKid.JerryLead.July等大神文章的拜读心得,说是心得还不如说是读文笔记,希望在自己理解的层面上给予SVM这个伟大的机器学习算法概要介绍,让更多的热爱机器学习的伙伴们进入到SVM的世界.PS:文章会以问答的形式为主要结构. 1.概念 1.1.什么是SVM? 支持向量机即 Support Vector Machine,简称 SVM .(第一次接触SVM是在阿里大数据竞赛的时候

机器学习——svm支持向量机的原理

前言 动笔写这个支持向量机(support vector machine)是费了不少劲和困难的,原因很简单,一者这个东西本身就并不好懂,要深入学习和研究下去需花费不少时间和精力,二者这个东西也不好讲清楚,尽管网上已经有朋友写得不错了(见文末参考链接),但在描述数学公式的时候还是显得不够.得益于同学白石的数学证明,我还是想尝试写一下,希望本文在兼顾通俗易懂的基础上,真真正正能足以成为一篇完整概括和介绍支持向量机的导论性的文章. 本文在写的过程中,参考了不少资料,包括<支持向量机导论>.<统

【机器学习算法-python实现】svm支持向量机(2)—简化版SMO算法

(转载请注明出处:http://blog.csdn.net/buptgshengod) 1.背景知识 通过上一节我们通过引入拉格朗日乗子得到支持向量机变形公式.详细变法可以参考这位大神的博客--地址 参照拉格朗日公式F(x1,x2,...λ)=f(x1,x2,...)-λg(x1,x2...).我们把上面的式子变型为: 约束条件就变成了: 下面就根据最小优化算法SMO(Sequential Minimal Optimization).找出距离分隔面最近的点,也就是支持向量集.如下图的蓝色点所示.

机器学习与数据挖掘-支持向量机(SVM)(一)

最近在看斯坦福大学的机器学习的公开课,学习了支持向量机,再结合网上各位大神的学习经验总结了自己的一些关于支持向量机知识. 一.什么是支持向量机(SVM)? 1.支持向量机(Support Vector Machine,常简称为SVM)是一种监督式学习的方法,可广泛地应用于统计分类以及回归分析.支持向量机属于一般化线性分类器,这族分类器的特点是他们能够同时最小化经验误差与最大化几何边缘区,因此支持向量机也被称为最大边缘区分类器. 2.支持向量机将向量映射到一个更高维的空间里,在这个空间里建立有一个

机器学习笔记——SVM之一

SVM(Support Vector Machine),中文名为 支持向量机,就像自动机一样,听起来异常神气,最初总是纠结于不是机器怎么能叫"机",后来才知道其实此处的"机"实际上是算法的意思. 支持向量机一般用于分类,基本上,在我的理解范围内,所有的机器学习问题都是分类问题.而据说,SVM是效果最好而成本最低的分类算法. SVM是从线性可分的情况下最优分类面发展而来的,其基本思想可以用下图表示: (最优分类面示意图) 图中空心点和实心点代表两类数据样本,H为分类线