机器学习(十四)Libsvm学习笔记

来源:互联网 发布:mac 修改host 编辑:程序博客网 时间:2024/05/24 01:51
Libsvm学习笔记
原文地址:http://blog.csdn.net/hjimce/article/details/46971039 
作者:hjimce
LIBSVM软件包是台湾大学林智仁(Chih-Jen Lin)博士等用C++实现的LIBSVM库,可以说是使用最方便的SVM训练工具。可以解决分类问题(包括C-SVC、n-SVC)、回归问题(包括e-SVR、n-SVR)以及分布估计(one-class-SVM )等问题,提供了线性、多项式、径向基和S形函数四种常用的核函数供选择,可以有效地解决多类问题、交叉验证选择参数、对不平衡样本加权、多类问题的概率估计等。
但是,在Windows环境下,此软件包只提供DOS工具集(主要包括:训练工具svmtrain.exe,预测工具svmpredict.exe,缩放数据工具svmscale.exe和二维演示工具svmtoy.exe),不过它的源码是开放的,因此我们可以直接调用源码。首先从网上下载libsvm工具包:


然后解压,


把svm.cpp和svm.h复制出来,因为对于我们做开发的,只需要这两个文件就可以了,然后把这两个文件放到vs的项目文件下,然后进行调用。

本篇博文主要讲,如何调用使用libsvm,我主要是通过两个简单的例子,演示如何调用libsvm。因为有了例子,学起来非常容易。

一、二分类测试案例

svm算法是学习训练算法,因此调用的步骤是:

1、数据归一化,数据归一化这一步libsvm好像没有提供函数,所以这一步要自己手动写一些代码,如果没有进行归一化,有的时候会出现问题。

2、数据训练

训练调用函数:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct svm_model *svm_train(const struct svm_problem *prob, const struct svm_parameter *param);  
只要学会这个函数的调用就可以实现训练了。

这个函数包含两个输入参数,第一个参数prob用于输入训练数据,包括输入特征X,训练数据标签y,训练样本的个数,具体svm_problem结构体的定义如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct svm_problem  
  2. {  
  3.     int l;//训练数据的个数  
  4.     double *y;//训练数据的标签,y[i]表示第i个样本的标签  
  5.     struct svm_node **x;//训练数据的特征,x[i][j]表示第i个样本的第j个特征值  
  6. };  
第二个参数,定义了svm算法的相关初始化参数,比如惩罚因子、核函数等,具体的结构体定义如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct svm_parameter  
  2. {  
  3.     int svm_type;//svm的类型,包括分类、预测拟合等,如果想用svm进行分类那么可以选择C_SVC,如果想用于拟合预测那么可以选择EPSILON_SVR  
  4.     int kernel_type;//核函数类型,包括线性核函数、高斯核函数、S激活函数、多项式  
  5.     int degree; /* for poly */  
  6.     double gamma;   /* for poly/rbf/sigmoid */  
  7.     double coef0;   /* for poly/sigmoid */  
  8.   
  9.     /* these are for training only */  
  10.     double cache_size; /* in MB */  
  11.     double eps; /* stopping criteria *///迭代误差终止条件  
  12.     double C;   /* for C_SVC, EPSILON_SVR and NU_SVR *///惩罚因子  
  13.     int nr_weight;      /* for C_SVC */  
  14.     int *weight_label;  /* for C_SVC */  
  15.     double* weight;     /* for C_SVC */  
  16.     double nu;  /* for NU_SVC, ONE_CLASS, and NU_SVR */  
  17.     double p;   /* for EPSILON_SVR */  
  18.     int shrinking;  /* use the shrinking heuristics */  
  19.     int probability; /* do probability estimates */  
  20. };  

具体每个参数的含义,在svm.h文件的定义中,都用相关的注释。

因此进行训练的时候,需要对这两个结构体的相关参数非常清晰。

在上面输入训练数据后,我们一般是先调用下面这个函数:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. const char *svm_check_parameter(const struct svm_problem *prob, const struct svm_parameter *param);  
这个函数可以进行检验你输入的参数pro,para的格式是否正确,调用完后,才调用svm_train()函数,这样可以确保输入的数据和svm的参数设置没有错后在进行训练。

3、数据预测分类

通过调用svm_train()函数完成训练过程,将返回参数svm_model模型。接着如果要进行预测分类可以调用如下函数:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. double svm_predict(const struct svm_model *model, const struct svm_node *x);  
这个函数的第一个输入参数就是我们通过训练过程得到的svm_model,第二个参数就是*x就是输入特征,如x[i]表示第i维特征的数值。数据的返回值,如果你是二分类模型,那么将返回数据标签:-1.0,1.0。如果你是用于预测拟合,那么将返回预测到的数值。

开始写代码前,我觉得svm_node的格式需要说明一下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct svm_node //储存单一向量的单个特征  
  2. {  
  3.     int index; //索引  
  4.     double value; //值  
  5. };  

svm_node是定义特征向量的标准输入格式。假设有三维的特征向量X=(10,1,3),那么就需要使用语句:new svm_node*x=new svm_node[4],来定义一个特征向量,然后特征就可以使用3svm_node来保存:

index

   1    

      2       

      3     


 

  -1

value

  10

      1

      3


 

NULL


最后一个svm_node是结束的标志,其索引值为-1,数值随便。libsvm就是通过判断其索引是否为-1,来判别特征向量结束的。

OK,接着就用例子作为演示示例,下面是一个二分类的例子,通过输入一个人的身高和体重特征,判别其男性还是女性。下面的例子没有经过归一化,所以其实是不规范的,libsvm并没有进行内部归一化,要自己进行外部归一化好后,在调用libsvm,因为我选用了核函数为线性核函数,有没有归一化好像没啥影响,然而如果选择其他的核函数,那就会发现没有归一化会出现的问题了。

下面我先通过win32控制台,进行测试的一个例子:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include "stdafx.h"  
  2. #include "svm/svm.h"  
  3. #include <iostream>  
  4. svm_parameter Initialize_svm_parameter()  
  5. {  
  6.     svm_parameter svmpara;//svm的相关参数  
  7.     svmpara.svm_type = C_SVC;  
  8.     svmpara.kernel_type = LINEAR;  
  9.     svmpara.degree = 3;  
  10.     svmpara.gamma = 0;  // 默认大小可选择特征的倒数 1/num_features,核函数中的gamma函数设置(只针对多项式/rbf/sigmoid核函数)  
  11.     svmpara.coef0 = 0;  
  12.     svmpara.nu = 0.5;  
  13.     svmpara.cache_size = 1;//缓存块大小  
  14.     svmpara.C = 1;  
  15.     svmpara.eps = 1e-3;  
  16.     svmpara.p = 0.1;  
  17.     svmpara.shrinking = 1;  
  18.     svmpara.probability = 0;  
  19.     svmpara.nr_weight = 0;  
  20.     svmpara.weight_label = NULL;  
  21.     svmpara.weight = NULL;  
  22.     return svmpara;  
  23. }  
  24. //二分类测试  
  25. //下面是通过人体身高和体重,进行性别的判别  
  26. int _tmain(int argc, _TCHAR* argv[])  
  27. {  
  28.       
  29.   
  30.     /*训练样本选取是学生的身高和体重: 
  31.         男1:身高:190cm,体重:70kg; 
  32.         男2:身高:180cm,体重:80kg; 
  33.         女1:身高:161cm,体重:80kg; 
  34.         女2:身高:161cm,体重:47kg;*/  
  35.   
  36.     int sample_num=4;//训练样本个数为4  
  37.     int feature_dimn=2;//样本的特征维数为2  
  38.     double *y=new double[sample_num];  
  39.     double **x=new double *[sample_num];  
  40.     for (int i=0;i<sample_num;i++)  
  41.     {  
  42.         x[i]=new double[feature_dimn];  
  43.     }  
  44.     x[0][0]=190;x[0][1]=70;y[0]=-1;//训练样本1  
  45.     x[1][0]=180;x[1][1]=80;y[1]=-1;//训练样本2  
  46.     x[2][0]=161;x[2][1]=80;y[2]=1;//训练样本3  
  47.     x[3][0]=161;x[3][1]=47;y[3]=1;//训练样本4  
  48.   
  49.   
  50.   
  51.   
  52. //训练数据输入  
  53.     svm_parameter svmpara=Initialize_svm_parameter();//svm参数初始化  
  54.     svm_problem svmpro;//svm训练数据  
  55.     svmpro.l=sample_num;  
  56.     svmpro.y=y;//训练数据标签  
  57.     svmpro.x=new svm_node *[sample_num];//训练数据的特征向量  
  58.     for (int i=0;i<sample_num;i++)  
  59.     {  
  60.         svmpro.x[i]=new svm_node[feature_dimn+1];  
  61.         for (int j=0;j<feature_dimn;j++)  
  62.         {  
  63.             svm_node node_ij;  
  64.             node_ij.index=j+1;//需要注意的是svm_node的第一个数据的索引为1,数值为第一位特征值,我一开始这里搞错了,把索引搞成从0开始  
  65.             node_ij.value=x[i][j];  
  66.             svmpro.x[i][j]=node_ij;  
  67.         }  
  68.         svm_node node_last;//需要添加最后一维特征的索引为-1  
  69.         node_last.index=-1;  
  70.         svmpro.x[i][feature_dimn]=node_last;  
  71.     }  
  72.   
  73.   
  74.   
  75.   
  76.   
  77.   
  78. //验证输入的训练数据、初始化的参数是否有误  
  79.     const char *error_msg;  
  80.     error_msg = svm_check_parameter(&svmpro,&svmpara);  
  81.     if(error_msg)  
  82.     {  
  83.         std::cout<<error_msg;  
  84.         return 0;  
  85.     }  
  86. //数据训练  
  87.     svm_model *svmmodel=svm_train(&svmpro,&svmpara);  
  88.   
  89.       
  90. /*预测数据1:身高180cm,体重85kg; 
  91. 预测数据2:身高161cm,体重50kg;*/  
  92.     svm_node *testX1=new svm_node[feature_dimn+1];  
  93.     testX1[0].index=1;  
  94.     testX1[0].value=180;  
  95.     testX1[1].index=2;  
  96.     testX1[1].value=85;  
  97.     testX1[2].index=-1;  
  98.     double testY1=svm_predict(svmmodel,testX1);  
  99.     std::cout<<"测试预测1:"<<testY1<<std::endl;  
  100.   
  101.   
  102.     svm_node *testX2=new svm_node[feature_dimn+1];  
  103.     testX2[0].index=1;  
  104.     testX2[0].value=161;  
  105.     testX2[1].index=2;  
  106.     testX2[1].value=50;  
  107.     testX2[2].index=-1;  
  108.     double testY2=svm_predict(svmmodel,testX2);//分类预测函数  
  109.     std::cout<<"测试预测2:"<<testY2<<std::endl;  
  110.   
  111.     return 0;  
  112. }  

最后的运行正确结果:


二、数据预测拟合案例

接着这个例子是要演示,使用libsvm进行如下图所示的数据拟合,通过数据输入二维的数据点,然后拟合出曲线,也就是相当于输入特征x,然后预测y值:


因此这个特征向量X是一维特征向量。

下面是通过svm进行数据拟合预测的类,为了方便我先把它它的调用封装成类,.cpp文件如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. CLibsvm::CLibsvm(float C,float gamma,float epsilon)  
  2. {  
  3.   
  4.     m_svmpara=Initialize_svm_parameter(C,gamma,epsilon);  
  5. }  
  6.   
  7.   
  8. CLibsvm::~CLibsvm(void)  
  9. {  
  10. }  
  11. svm_parameter CLibsvm::Initialize_svm_parameter(float C,float gamma,float epsilon)  
  12. {  
  13.     svm_parameter svmpara;//svm的相关参数  
  14.     svmpara.svm_type = EPSILON_SVR;  
  15.     svmpara.kernel_type =RBF;  
  16.     svmpara.degree = 3;  
  17.     svmpara.gamma = gamma;  // 默认大小可选择特征的倒数 1/num_features,核函数中的gamma函数设置(只针对多项式/rbf/sigmoid核函数)  
  18.     svmpara.coef0 = 0;  
  19.     svmpara.nu = 0.5;  
  20.     svmpara.cache_size = 1;//缓存块大小  
  21.     svmpara.C = C;  
  22.     svmpara.eps = 1e-3;  
  23.     svmpara.p = epsilon;  
  24.     svmpara.shrinking = 1;  
  25.     svmpara.probability = 0;  
  26.     svmpara.nr_weight = 0;  
  27.     svmpara.weight_label = NULL;  
  28.     svmpara.weight = NULL;  
  29.     return svmpara;  
  30. }  
  31. //拟合数据输入  
  32. void CLibsvm::TrainModel(vector<vec2>traindata)  
  33. {  
  34.     //Normalizedata(traindata,m_minpt,m_maxpt);  
  35.     int sample_num=traindata.size();//训练样本个数  
  36.     int feature_dimn=1;//样本的特征维数为1  
  37.     double *y=new double[sample_num];  
  38.     double **x=new double *[sample_num];  
  39.     for (int i=0;i<sample_num;i++)  
  40.     {  
  41.         x[i]=new double[feature_dimn];  
  42.     }  
  43.   
  44.     for (int i=0;i<sample_num;i++)  
  45.     {  
  46.         y[i]=traindata[i][feature_dimn];  
  47.         for (int j=0;j<feature_dimn;j++)  
  48.         {  
  49.             x[i][j]=traindata[i][j];  
  50.         }  
  51.     }  
  52.   
  53. //训练数据归一化  
  54.     //获取最大最小值  
  55.     GetMax_Min(y,sample_num,m_miny,m_maxy);//训练数据y的最大最小值获取  
  56.     m_minx=new double[feature_dimn];//特征最大最小值获取  
  57.     m_maxx=new double[feature_dimn];  
  58.     double *pdata=new double[sample_num];  
  59.     for (int j=0;j<feature_dimn;j++)  
  60.     {  
  61.         for (int i=0;i<sample_num;i++)  
  62.         {  
  63.             pdata[i]=x[i][j];     
  64.         }  
  65.         GetMax_Min(pdata,sample_num,m_minx[j],m_maxx[j]);  
  66.     }  
  67.     //训练数据归一化  
  68.     for (int i=0;i<sample_num;i++)  
  69.     {  
  70.         y[i]=2*(y[i]-m_miny)/(m_maxy-m_miny)-1;  
  71.         for (int j=0;j<feature_dimn;j++)  
  72.         {  
  73.             x[i][j]=2*(x[i][j]-m_minx[j])/(m_maxx[j]-m_minx[j])-1;  
  74.         }  
  75.     }  
  76.   
  77.   
  78.   
  79. //训练数据输入  
  80.     svm_problem svmpro;//svm训练数据  
  81.     svmpro.l=sample_num;  
  82.     svmpro.y=new double[sample_num];  
  83.     svmpro.x=new svm_node *[sample_num];//训练数据的特征向量  
  84.     for (int i=0;i<sample_num;i++)  
  85.     {  
  86.         svmpro.y[i]=y[i];//用训练数据的y,作为输入标签  
  87.         svmpro.x[i]=new svm_node[feature_dimn+1];  
  88.         for (int j=0;j<feature_dimn;j++)  
  89.         {  
  90.             svm_node node_ij;  
  91.             node_ij.index=j+1;//需要注意的是svm_node的第一个数据的索引为1,数值为第一位特征值,我一开始这里搞错了,把索引搞成从0开始  
  92.             node_ij.value=x[i][j];  
  93.             svmpro.x[i][j]=node_ij;  
  94.         }  
  95.         svm_node node_last;//需要添加最后一维特征的索引为-1  
  96.         node_last.index=-1;  
  97.         svmpro.x[i][feature_dimn]=node_last;  
  98.     }  
  99. //验证输入的训练数据、初始化的参数是否有误  
  100.     const char *error_msg;  
  101.     error_msg = svm_check_parameter(&svmpro,&m_svmpara);  
  102.     if(error_msg)  
  103.     {  
  104.         AfxMessageBox(error_msg);  
  105.     }  
  106. //数据训练  
  107.     m_svmmodel=svm_train(&svmpro,&m_svmpara);  
  108. }  
  109. void CLibsvm::Predict(float x,float &y)  
  110. {  
  111.   
  112.     x=2*(x-m_minx[0])/(m_maxx[0]-m_minx[0])-1;  
  113.     svm_node *testX1=new svm_node[1+1];  
  114.     testX1[0].index=1;  
  115.     testX1[0].value=x;  
  116.     testX1[1].index=-1;  
  117.     y=(svm_predict(m_svmmodel,testX1)+1)*(m_maxy-m_miny)*0.5+m_miny;  
  118.   
  119.   
  120. }  
  121. //数据归一化  
  122. void CLibsvm::GetMax_Min(double*pdata,int data_num,double &minpt,double&maxpt)  
  123. {  
  124.   
  125.     double minx=1e10;  
  126.     double maxx=-1e10;  
  127.     for (int i=0;i<data_num;i++)  
  128.     {  
  129.         if (pdata[i]<minx)  
  130.         {  
  131.             minx=pdata[i];  
  132.         }  
  133.         if (pdata[i]>maxx)  
  134.         {  
  135.             maxx=pdata[i];  
  136.         }  
  137.     }  
  138.     minpt=minx;  
  139.     maxpt=maxx;  
  140. }  

然后是头文件:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #pragma once  
  2. #include "svm/svm.h"  
  3. #include "Vec.h"  
  4. #include <vector>  
  5. class CLibsvm  
  6. {  
  7. public:  
  8.     CLibsvm(float C=1,float gamma=1,float epsilon=0.1);  
  9.     ~CLibsvm(void);  
  10.     void TrainModel(vector<vec2>traindata);//数据训练  
  11.     void Predict(float x,float &y);//拟合预测函数  
  12.   
  13. private:  
  14.     svm_parameter Initialize_svm_parameter(float C=1,float gamma=1,float epsilon=0.1);//参数初始化函数  
  15.     svm_parameter m_svmpara;  
  16.     svm_problem m_svmprob;  
  17.     std::vector<vec2>m_traindata;//二维拟合数据  
  18.     svm_model *m_svmmodel;  
  19.     void GetMax_Min(double*pdata,int data_num,double &minpt,double&maxpt);//获取数据归一化的最大最小值  
  20.     double *m_maxx;//归一化用的参数  
  21.     double *m_minx;  
  22.     double m_miny;  
  23.     double m_maxy;  
  24.   
  25. };  

OK,接着是封装好的这个类的调用:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1.     //m_controlpoint为训练数据,也就是鼠标输入蓝色的点  
  2.     CLibsvm csvm(1);  
  3.     csvm.TrainModel(m_controlpoint);//二维数据点  
  4. /* 
  5.     CLibsvm csvm2(1,1); 
  6.     csvm2.TrainModel(m_controlpoint);*/  
  7.     float minx=1e10;  
  8.     float maxx=0;  
  9.     for (int i=0;i<m_controlpoint.size();i++)  
  10.     {  
  11.         if (m_controlpoint[i][0]<minx)  
  12.         {  
  13.             minx=m_controlpoint[i][0];  
  14.         }  
  15.         if (m_controlpoint[i][0]>maxx)  
  16.         {  
  17.             maxx=m_controlpoint[i][0];  
  18.         }  
  19.     }  
  20.     m_resultcurve.clear();  
  21.     for (int i=minx;i<maxx;i++)  
  22.     {  
  23.         float y=0;  
  24.         csvm.Predict(i,y);  
  25.         m_resultcurve.push_back(vec2(i,y));//绘制拟合曲线  
  26.   
  27.         float y2=0;  
  28. /* 
  29.         csvm2.Predict(i,y2); 
  30.         m_resultcurve2.push_back(vec2(i,y2));*/  
  31.     }  

接着分析一下CLibsvm构造函数的三个参数好如何选择:

首先第一个参数是惩罚因子,这个参数越大,拟合出来的曲线自然而然越精确,测试一下,测试代码如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //m_controlpoint为训练数据,也就是鼠标输入蓝色的点  
  2.     CLibsvm csvm(1);  
  3. csvm.TrainModel(m_controlpoint);//二维数据点  
  4.   
  5. CLibsvm csvm2(100);  
  6. csvm2.TrainModel(m_controlpoint);  
  7. float minx=1e10;  
  8. float maxx=0;  
  9. for (int i=0;i<m_controlpoint.size();i++)  
  10. {  
  11.     if (m_controlpoint[i][0]<minx)  
  12.     {  
  13.         minx=m_controlpoint[i][0];  
  14.     }  
  15.     if (m_controlpoint[i][0]>maxx)  
  16.     {  
  17.         maxx=m_controlpoint[i][0];  
  18.     }  
  19. }  
  20. m_resultcurve.clear();  
  21. for (int i=minx;i<maxx;i++)  
  22. {  
  23.     float y=0;  
  24.     csvm.Predict(i,y);  
  25.     m_resultcurve.push_back(vec2(i,y));//绘制拟合曲线  
  26.   
  27.     float y2=0;  
  28.   
  29.     csvm2.Predict(i,y2);  
  30.     m_resultcurve2.push_back(vec2(i,y2));  
  31. }  
通过分别选用C=1 和C=100的参数,得到如下绿色和红色结果曲线,红色的曲线为C=100得到的结果:


OK,接着测试一下,参数sigma对结果的影响,测试代码如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1.     CLibsvm csvm(1,1);  
  2. csvm.TrainModel(m_controlpoint);//二维数据点  
  3.   
  4. CLibsvm csvm2(1,100);  
  5. csvm2.TrainModel(m_controlpoint);  
  6. float minx=1e10;  
  7. float maxx=0;  
  8. for (int i=0;i<m_controlpoint.size();i++)  
  9. {  
  10.     if (m_controlpoint[i][0]<minx)  
  11.     {  
  12.         minx=m_controlpoint[i][0];  
  13.     }  
  14.     if (m_controlpoint[i][0]>maxx)  
  15.     {  
  16.         maxx=m_controlpoint[i][0];  
  17.     }  
  18. }  
  19. m_resultcurve.clear();  
  20. for (int i=minx;i<maxx;i++)  
  21. {  
  22.     float y=0;  
  23.     csvm.Predict(i,y);  
  24.     m_resultcurve.push_back(vec2(i,y));//绘制拟合曲线  
  25.   
  26.     float y2=0;  
  27.   
  28.     csvm2.Predict(i,y2);  
  29.     m_resultcurve2.push_back(vec2(i,y2));  
  30. }  
上面的代码中,我分别选择sigma=1,sigma=100进行比较,结果如下:


可以看到与C一样,参数越大,对于训练数据,其误差越小。当然我们需要知道,对于机器学习算法来说,训练数据后的误差越小并不是越好,上面的结果其实是过拟合的。

**********作者:hjimce     联系qq:1393852684   更多资源请关注我的博客:http://blog.csdn.net/hjimce                原创文章,转载请保留本行信息。******************

参考文献:

1、http://blog.csdn.NET/liulina603/article/details/8532837

0 0
原创粉丝点击