现在的位置: 首页 > 综合 > 正文

LibSVM学习

2018年04月11日 ⁄ 综合 ⁄ 共 9650字 ⁄ 字号 评论关闭

 LibSVM是台湾 林智仁(Chih-Jen Lin) 教授2001年开发的一套支持向量机的库,这套库运算速度还是挺快的,可以很方便的对数据做分类或回归。由于libSVM程序小,运用灵活,输入参数少,并且是开源的,易于扩展,因此成为目前国内应用最多的SVM的库。

    

       这套库可以从http://www.csie.ntu.edu.tw/~cjlin/免费获得,目前已经发展到2.89版。下载.zip格式的版本,解压后可以看到,主要有5个文件夹和一些c++源码文件。

   

       Java       —— 主要是应用于java平台;

       Python   —— 是用来参数优选的工具,稍后介绍;

       svm-toy —— 一个可视化的工具,用来展示训练数据和分类界面,里面是源码,其编译后的程序在windows文件夹下;

       tools       —— 主要包含四个python文件,用来数据集抽样(subset),参数优选(grid),集成测试(easy), 数据检查(checkdata);

       windows —— 包含libSVM四个exe程序包,我们所用的库就是他们,里面还有个heart_scale,是一 个样本文件,可以用记事本打开,用来测试用的。

       其他.h和.cpp文件都是程序的源码,可以编译出相应的.exe文件。其中,最重要的是svm.h和svm.cpp文件,svm-predict.c、svm-scale.c和svm-train.c(还有一个svm-toy.c在svm-toy文件夹中)都是调用的这个文件中的接口函数,编译后就是windows下相应的四个exe程序。另外,里面的 README 跟 FAQ 也是很好的文件,对于初学者如果E文过得去,可以看一下。

 

       下面以svm-train为例,简单的介绍下,怎么编译:(这步很简单,也没必要,对于仅仅使用libsvm库的人来说,windows下的4个exe包已经足够了,之所以加这步,是为了那些做深入研究的人,可以按照自己的思路改变一下svm.cpp,然后编译验证)

 

       我用的是VC 6.0,新建一个控制台(win32 console application)程序,程序名叫svm-train(这个可以随意),点击OK后,选择empty。

       进入程序框架后,里面什么都没有,然后找到你的程序目录,把svm-train.c、svm.h和svm.cpp拷贝过去(.c文件是c语言的,要是你习惯了c++,你尽可以改成.cpp),然后把这3个文件添加到工程,编译。。。如果没错误,到debug下面看看,是不是有个svm-train.exe。其实windows下的svm-train.exe就是这样编译出来的。

       1. 把LibSVM包解压到相应的目录(因为我只需要里面windows文件夹中的东东,我们也可以只把windows文件夹拷到相应的目录),比如D:/libsvm。

 

       2. 在电脑“开始”的“运行”中输入cmd,进入DOS环境。定位到d:/ libsvm下,具体命令如下:

          

           d: (回车)

         cd /libsvm/windows (回车)

         

           (上面第一行是先定位到盘符d,第二行cd 是定位到相应盘符下的目录)

 

       3. 进行libsvm训练,输入命令:(这里要注意文件的名字,2.89以前版本都是svmtrain.exe)

         

          svm-train heart_scale train.model

 

           heart_scale ——是目录下的已经存在的样本文件,要换成自己的文件,只需改成自己的文件名就可以了

         train.model ——是创建的结果文件,保存了训练后的结果

 

       

   可以看到结果:

     *

optimization finished, #iter = 162

nu = 0.431029

obj = -100.877288, rho = 0.424462

nSV = 132, nBSV = 107

      Total nSV = 132

 

       其中,#iter为迭代次数,nu 是你选择的核函数类型的参数,obj为SVM文件转换为的二次规划求解得到的最小值,rho为判决函数的偏置项b,nSV 为标准支持向量个数(0<a[i]<c),nBSV为边界上的支持向量个数(a[i]=c),Total nSV为支持向量总个数(对于两类来说,因为只有一个分类模型Total nSV = nSV,但是对于多类,这个是各个分类模型的nSV之和)。

 

 

    在目录下,还可以看到产生了一个train.model文件,可以用记事本打开,记录了训练后的结果。

          svm_type c_svc                     //所选择的svm类型,默认为c_svc

          kernel_type rbf                       //训练采用的核函数类型,此处为RBF核

          gamma 0.0769231                   //RBF核的参数γ

          nr_class 2                               //类别数,此处为两分类问题

          total_sv 132                           //支持向量总个数

          rho 0.424462                          //判决函数的偏置项b

          label 1 -1                                 //原始文件中的类别标识

          nr_sv 64 68                           //每个类的支持向量机的个数

          SV                                          //以下为各个类的权系数及相应的支持向量

   1 1:0.166667 2:1 3:-0.333333 … 10:-0.903226 11:-1 12:-1 13:1

   0.5104832128985164 1:0.125 2:1 3:0.333333 … 10:-0.806452 12:-0.333333 13:0.5

   ………..

   -1 1:-0.375 2:1 3:-0.333333…. 10:-1 11:-1 12:-1 13:1

   -1 1:0.166667 2:1 3:1 …. 10:-0.870968 12:-1 13:0.5

 
    到现在,第一次体验libsvm到这就基本结束了,其他的两个(svm-predict、svm-scale)的使用过程类似。对于个别参数你还不理解,下面我们会具体讲到。

其实,这部分写也是多余,google一下“libsvm使用”,就会N多的资源,但是,为了让你少费点心,在这里就简单的介绍一下,有不清楚的只有动动你的mouse了。需要说明的是,2.89版本以前,都是svmscale、svmtrain和svmpredict,最新的是svm-scale、svm-train和svm-predict,要是用不习惯,只需要把那四个exe文件名去掉中间的短横线,改成svmscale、svmtrain和svmpredict就可以了,我们还是按原来函数名的讲。

 

1. libSVM的数据格式

Label 1:value 2:value ….

 

Label:是类别的标识,比如上节train.model中提到的1 -1,你可以自己随意定,比如-10,0,15。当然,如果是回归,这是目标值,就要实事求是了。

Value:就是要训练的数据,从分类的角度来说就是特征值,数据之间用空格隔开

 

比如: -15 1:0.708 2:1056 3:-0.3333

 

需要注意的是,如果特征值为0,特征冒号前面的(姑且称做序号)可以不连续。如:

       -15 1:0.708 3:-0.3333

表明第2个特征值为0,从编程的角度来说,这样做可以减少内存的使用,并提高做矩阵内积时的运算速度。我们平时在matlab中产生的数据都是没有序号的常规矩阵,所以为了方便最好编一个程序进行转化。

 

 

 

2. svmscale的用法

 

    svmscale是用来对原始样本进行缩放的,范围可以自己定,一般是[0,1]或[-1,1]。缩放的目的主要是

1)防止某个特征过大或过小,从而在训练中起的作用不平衡;

2)为了计算速度。因为在核计算中,会用到内积运算或exp运算,不平衡的数据可能造成计算困难。

 

用法:svmscale [-l lower] [-u upper]

                         [-y y_lower y_upper]

                         [-s save_filename]

                         [-r restore_filename] filename

 

 

其中,[]中都是可选项:

         -l:设定数据下限;lower:设定的数据下限值,缺省为-1

         -u:设定数据上限;upper:设定的数据上限值,缺省为 1

         -y:是否对目标值同时进行缩放;y_lower为下限值,y_upper为上限值;

         -s save_filename:表示将缩放的规则保存为文件save_filename;

         -r restore_filename:表示将按照已经存在的规则文件restore_filename进行缩放;

         filename:待缩放的数据文件,文件格式按照libsvm格式。

 

 

默认情况下,只需要输入要缩放的文件名就可以了:比如(已经存在的文件为test.txt)

        

                         svmscale test.txt

 

    这时,test.txt中的数据已经变成[-1,1]之间的数据了。但是,这样原来的数据就被覆盖了,为了让规划好的数据另存为其他的文件,我们用一个dos的重定向符 > 来另存为(假设为out.txt):

        

                        svmscale test.txt > out.txt

 

   运行后,我们就可以看到目录下多了一个out.txt文件,那就是规范后的数据。假如,我们想设定数据范围[0,1],并把规则保存为test.range文件:

                         svmscale –l 0 –u 1 –s test.range test.txt > out.txt

 

这时,目录下又多了一个test.range文件,可以用记事本打开,下次就可以用-r test.range来载入了。

 

 

3. svmtrain的用法

 

      svmtrain我们在前面已经接触过,他主要实现对训练数据集的训练,并可以获得SVM模型。

 

        用法: svmtrain [options] training_set_file [model_file]

 

其中,options为操作参数,可用的选项即表示的涵义如下所示:

-s 设置svm类型:

         0 – C-SVC

         1 – v-SVC

         2 – one-class-SVM

         3 – ε-SVR

         4 – n - SVR

-t 设置核函数类型,默认值为2

         0 -- 线性核:u'*v

         1 -- 多项式核: (g*u'*v+ coef 0)degree

         2 -- RBF 核:exp(-γ*||u-v||2)

         3 -- sigmoid 核:tanh(γ*u'*v+ coef 0)

-d degree: 设置多项式核中degree的值,默认为3

-gγ: 设置核函数中γ的值,默认为1/k,k为特征(或者说是属性)数;

         -r coef 0:设置核函数中的coef 0,默认值为0;

         -c cost:设置C-SVC、ε-SVR、n - SVR中从惩罚系数C,默认值为1;

         -n v :设置v-SVC、one-class-SVM 与n - SVR 中参数n ,默认值0.5;

         -p ε :设置v-SVR的损失函数中的e ,默认值为0.1;

         -m cachesize:设置cache内存大小,以MB为单位,默认值为40;

         -e ε :设置终止准则中的可容忍偏差,默认值为0.001;

         -h shrinking:是否使用启发式,可选值为0 或1,默认值为1;

         -b 概率估计:是否计算SVC或SVR的概率估计,可选值0 或1,默认0;

         -wi weight:对各类样本的惩罚系数C加权,默认值为1;

         -v n:n折交叉验证模式;

         model_file:可选项,为要保存的结果文件,称为模型文件,以便在预测时使用。

    

    默认情况下,只需要给函数提供一个样本文件名就可以了,但为了能保存结果,还是要提供一个结果文件名,比如:test.model,则命令为:

                                         svmtrain test.txt test.model

 

结果说明见LibSVM学习(二)。

 

4. svmpredict 的用法

 

    svmpredict 是根据训练获得的模型,对数据集合进行预测。

 

   用法:svmpredict [options] test_file model_file output_file

 

   其中,options为操作参数,可用的选项即表示的涵义如下所示:

-b probability_estimates——是否需要进行概率估计预测,可选值为0 或者1,默认值为0。

 

model_file ——是由svmtrain 产生的模型文件;

test_file—— 是要进行预测的数据文件,格式也要符合libsvm格式,即使不知道label的值,也要任意填一个,svmpredict会在output_file中给出正确的label结果,如果知道label的值,就会输出正确率;

output_file ——是svmpredict 的输出文件,表示预测的结果值。

   至此,主要的几个接口已经讲完了,满足一般的应用不成问题。对于要做研究的,还需要深入到svm.cpp文件内部,看看都做了什么。

 

其实,在之前上海交大模式分析与机器智能实验室对2.6版本的svm.cpp做了部分注解,(在哪里?google一下你就知道)。但是,这个注释只是针对代码而注释,整篇看下来,你会发现除了理解几个参数的含义,还是会对libsvm一头雾水。当然作为理解程序的辅助材料,还是有很大用处的。特别是,对几个结构体的说明,比较清楚。但是要清楚程序具体做了什么,还是要追踪程序中去。

       由于svm涉及的数学知识比较多,我们这篇只是讲一些基本的思路,所以就从最基本的C-SVC型svm,核函数采用常用的RBF函数。LibSVM就采用2.6版本的好了,因为后续的版本作者又加了很多内容,不易理解作者最初的思路。我是做模式识别,主要从分类的角度来解析函数的调用过程,我们从svmtrain.c看起,其调用的函数过程如下:   

 

上图是整个C-SVC的计算过程,下面对一些重要的内容进行具体说明:

 

1. svm_group_class

      2.6版中没有此函数的,其功能直接在svm_train实现,为了增强可读性,2.89版中设置了这个函数,其实所作的工作都是一样的。需要说明的是其重新排列后perm中只存储的是各个样本在原始位置的序号,而非数据。这样做的好处有两个:

       1)不必破坏原始数据(也就是读进来的x的数据);

       2)检索起来方便,只需要L维的数据检索,得到序号后,然后定位到原始数据中相应的位置就可以。 

perm是中各类的排列顺序是按照原始样本中各类出现的先后顺序排列的,不一定是按照你原始样本的label序号排列,假如原始样本的label是{-1,0,1},而最先出现的label为1的样本,那么perm中就把label为1的作为类0最先排列。而start中记录的是各类的起始序号,而这个序号是在perm中的序号。

 

2. 多类判别的one-against-one

      svm做判别是用的分界线(面),两类之间只有一个分界线(面),因此分类器也只有1种,要么是1类要么是2类。但是对于多类,分类方式就有多种。目前,存在的方法主要有:

       1)1-V-R方式

       对于k类问题,把其中某一类的n个训练样本视为一类,所有其他类别归为另一类,因此共有k个分类器。最后预测时,判别式使用竞争方式,也就是哪个类得票多就属于那个类。

       2)1-V-1方式

       也就是我们所说的one-against-one方式。这种方法把其中的任意两类构造一个分类器,共有(k-1)×k/2个分类器。最后预测也采用竞争方式。

       3)有向无环图(DAG-SVM)

       该方法在训练阶段采用1-V-1方式,而判别阶段采用一种两向有向无环图的方式。

       LibSVM采用的是1-V-1方式,因为这种方式思路简单,并且许多实践证实效果比1-V-R方式要好。

 

 

   上图是一个5类1-V-1组合的示意图,红色是0类和其他类的组合,紫色是1类和剩余类的组合,绿色是2类与右端两类的组合,蓝色只有3和4的组合。因此,对于nr_class个类的组合方式为:

                                                        for(i = 0; i < nr_class; i ++)

                                                        {

                                                               for(j = i+1; i < nr_class; j ++)     

                                                               { 类 i –V – 类 j }

                                                        }

 

3. hessian矩阵的内存处理        

      因为svm是基于结构风险最小的,因此在分类识别方式具有较传统的基于经验风险最小的方式有优势。但是svm也有一个致命的缺陷,因为要计算hessian矩阵Qij所耗的内存巨大,不利于实践中应用。目前,怎么减小内存的使用依旧是SVM的研究的课题。LibSVM对hessian矩阵处理的策略是定义了一个内存处理类Cache类,预先认为分配一定的内存,存储计算好的Qij,其序号的检索采用双向链表的方式,加快了检索速度。其最重要的函数为:

       int Cache::get_data(const int index, Qfloat **data, int len)

       //len 是 data 的长度,data为返回的内存首地址,index为Qij的行。

       每次都要查找链表中行为index的Qi,假如已经计算过了,就返回计算过的内存地址,并把储存首地址的链表节点插入到链表尾部。假如没计算过,就分配内存并进行计算,当剩余的内存不够时,就要回收链表头指向的内存。这里,可能有人会问,难道以前计算的就没有用了吗??其实,是因为Qij是稀疏矩阵,在训练过程中只要其对应的alpha[i]不再变动(这时alpha[i]=0或者alpha[i]=C),其对应的Qi就不会被选到来训练,因此原来计算的Qi就没有用了。其实,链表的顺序代表了别选到的频率,最头部的是最不可能被选到,因为这时alpha[i]=0或者alpha[i]=C,而最尾部的最容易被选到。

 

4. 数据选择select_working_set(i,j)     

     对于样本数量比较多的时候(几千个),SVM所需要的内存是计算机所不能承受的。目前,对于这个问题的解决方法主要有两种:块算法和分解算法。这里,libSVM采用的是分解算法中的SMO(串行最小化)方法,其每次训练都只选择两个样本。我们不对SMO做具体的讨论,要想深入了解可以查阅相关的资料,这里只谈谈和程序有关的知识。

      一般SVM的对偶问题为:                      

 

 

 

 

 

SVM收敛的充分必要条件是KKT条件,其表现为:

 

 4.1式求导可得:

 

进一步推导可知:

 

 

也就是说,只要所有的样本都满足4.4式,那么得到解就是最优值。因此,在每轮训练中,每次只要选择两个样本(序号为ij),是最违反KKT条件(也就是4.4式)的样本,就能保证其他样本也满足KKT条件。序号ij的选择方式如下:

5. 停止准则

    LibSVM程序中,停止准则蕴含在了函数select_working_set(i,j)返回值中。也就是,当找不到符合4.5式的样本时,那么理论上就达到了最优解。但是,实际编程时,由于KKT条件还是蛮苛刻的,要进行适当的放松。令:

 

4.4式可知,当所有样本都满足KKT条件时,gi ≤ -gj

4.8式中α1α2 表示,即:

结合上图由解析几何可得α2的取值范围:

经过一系列变换,可以得到α2更新值α2new

结合4.94.10式得到α2new最终表达式:

7. 数据缩放do_shrinking()

     上面说到SVM用到的内存巨大,另一个缺陷就是计算速度,因为数据大了,计算量也就大,很显然计算速度就会下降。因此,一个好的方式就是在计算过程中逐步去掉不参与计算的数据。因为,实践证明,在训练过程中,alpha[i]一旦达到边界(alpha[i]=0或者alpha[i]=C),alpha[i]值就不会变,随着训练的进行,参与运算的样本会越来越少,SVM最终结果的支持向量(0<alpha[i]<C)往往占很少部分。

       LibSVM采用的策略是在计算过程中,检测active_size中的alpha[i]值,如果alpha[i]到了边界,那么就应该把相应的样本去掉(变成inactived),并放到栈的尾部,从而逐步缩小active_size的大小。

 

8. 截距b的计算

    b计算的基本公式为:

理论上,b的值是不定的。当程序达到最优后,只要用任意一个标准支持向量机(0<alpha[i]<C)的样本带入4.12式,得到的b值都是可以的。目前,求b的方法也有很多种。在libSVM中,分别对y=+1y=-1的两类所有支持向量求b,然后取平均值:

 至此,libSVM的整个思路我们简单的过了一遍,里面涉及到很到理论知识,许多细节需要查看相关的SVM的书籍.

 

 

 

 

 

 

 

       加一个适当的宽松范围ε,也就是程序中的eps,默认为0.001,那么最终的停止准则为:

gi ≤ -gj +ε  →    gi + gj ≤ε

6. 因子α的更新

 

    由于SMO每次都只选择2个样本,那么4.1式的等式约束可以转化为直线约束: 

4.12                                             (4.8)

   转化为图形表示为: 

 

抱歉!评论已关闭.