SVM简介:
支持向量机(Support Vector Machine)简称SVM,它是一种二类分类的模型,其基本模型为特征空间上间隔最大的线性分类器,其学习策略就是要使间隔最大化,最终可转化为一个凸二次规划问题的求解。
最底层的SVM是一个二分类器,为了实现多类分类,出现了很多SVM多类分类的策略,常见的有:(1) 一对其余法;(2)一对一法;(3) DAG方法(有向无环图);(4) 决策树方法;(5) 纠错输出编码法(ECOC)
Libsvm简介:
下面是百度百科上的介绍:
“LIBSVM是台湾大学林智仁(Lin Chih-Jen)教授等开发设计的一个简单、易于使用和快速有效的SVM模式识别与回归的软件包,他不但提供了编译好的可在Windows系列系统的执行文件,还提供了源代码,方便改进、修改以及在其它操作系统上应用;该软件对SVM所涉及的参数调节相对比较少,提供了很多的默认参数,利用这些默认参数可以解决很多问题;并提供了交互检验(Cross Validation)的功能。该软件可以解决C-SVM、ν-SVM、ε-SVR和ν-SVR等问题,包括基于一对一算法的多类模式识别问题。”【百度百科】
Libsvm数据结构和参数说明:
LibSVM:
(1) svm_node结构
struct svm_node
{
int index;//节点的序号
double value;//节点的值
};
一个SVM样本数据,通常用一个n维向量表示,把它转化成libsvm的样本数据结构,就是一个svm_node数组,一个svm_node就表示n维向量中的一维,其中index是维度序号表示第几维,value标志这一维的值。
例如有下面的样本要转换成svm_node数组:
可以看出,这里有5个样本,每个样本维度为5,标签为(1,2,1,2,1)于是就可以表示为下面的形式。需要注意的是每个样本的后面还要加一维数据,维度序号位-1,作为样本结束的标志。
(1, 0)(2, 0.1)(3, 0.2)(4, 0)(5, 0)(-1,?)
(1, 0)(2, 0.1)(3, 0.3)(4,-1.2)(5, 0)(-1,?)
(1, 0.4)(2, 0)(3, 0)(4, 0)(5, 0)(-1,?)
(1, 0)(2, 0.1)(3, 0)(4, 1.4)(5,0.5)(-1,?)
(1,-0.1)(2,-0.2)(3, 0.1)(4, 1.1)(5,0.1)(-1,?)
(2). svm_problem结构
用来做SVM训练的输入数据有样本以及样本的类别标签。在libsvm中把这两种数据放在一个结构体里面,这个结构体就是svm_problem。
struct svm_problem
{
int l;//样本的个数
double *y;//指向标签数组
struct svm_node **x;//样本维度序号和内容,指向svm_node数组
};
在上面的样本中,l=5;y->{1,2,1,2,1};x->{{ svm_node11,… },{ svm_node22,… }}
(3) svm_parameter参数说明:
在进行训练前,需要设置svm的参数,libsvm把所有训练相关的参数封装在了svm_parameter结构体中。
struct svm_parameter
{
//核函数相关
int svm_type;//SVM类型
int kernel_type;//核函数类型
double degree;//多项式参数
double gamma;//核函数poly/rbf/sigmoid的参数
double coef0;//核函数poly/sigmoid的参数
//训练相关
doublecache_size;//训练用的内存(MB)
doubleeps;//训练停止的标准
double C;//惩罚因子,越大训练时间越长,泛华性能越好
int nr_weight;//权重的数目,目前只有两个值,默认为0
int *weight_label;//权重,元素个数由nr_weight决定
double* weight;//C_SVC权重
double nu;//设置nu - SVC、one-class-SVM 与nu - SVR 中参数nu ,默认值0.5;
double p;// 核宽,设置e- SVR的损失函数中的e ,默认值为0.1
intshrinking;//训练过程是否使用压缩
int probability;//是否做概率估计
};
SVM类型
C_SVC :
C类支持向量分类机。n类分组(n≥2),允许用异常值惩罚因子C进行不完全分类。
NU_SVC :
v类支持向量分类机。n类似然不完全分类的分类器。参数为v取代C(其值在区间[0,1]中,nu越大,决策边界越平滑)。
ONE_CLASS :
单分类器,所有的训练数据提取自同一个类里,然后SVM建立了一个分界线以分割该类在特征空间中所占区域和其它类在特征空间中所占区域。
EPS_SVR:
E类支持向量回归机。训练集中的特征向量和拟合出来的超平面的距离需要小于p。异常值惩罚因子C被采用。
NU_SVR :
v类支持向量回归机。v代替了 p。
SVM核函数类型
LINEAR:线性核,相当于没有核。主要用于线性可分的数据。参数少,速度快。
POLY:多项式核。
RBF:高斯径向基函数。可以把非线性可分的数据变成线性可分,参数比较多,参数设置对分类效果影响很大。可以通过训练数据的交叉验证来得到最佳参数,不过时间较长,一般得到最佳参数后高斯核的分类效果最好。
SIGMOID: Sigmoid函数内核。
PRECOMPUTED:自定义核函数
补充:
svm_predict();和svm_predict_probability();分类结果比较:
下面帖子中说明了情况。svm_predict();//这个函数的结果总是正确的
svm_predict_probability();//这个函数正确的前提是训练样本足够多
实验代码:
实验程序完成的主要供就是通过svm对三个样本{{255,0,0},{0,255,0},{0,0,255}}进行训练,然后用训练好的svm分类器对像素点颜色进行分类。
Qt完成工程下载地址:http://download.csdn.net/detail/u013752202/9253103
有两个主程序,main.cpp中样本数据格式为opencv的Mat矩阵。main1.c中样本为float型的二维数组。编译的时候把不同的主程序添加进工程即可。main1.cpp可以不要opencv的库。
(1)样本为opencv的Mat类型时的代码
/*******************************
//湖南·长沙 2015-11-8
********************************/
#include <QtCore/QCoreApplication>
#include "svm.h"
#include "opencv2\opencv.hpp"
using namespace cv;
#define SAMPLEDIM 3
#define MODELNAME "model.txt"
//把单行Mat转换为svm_node格式,按行存储
void array2svm_node(float *srcData,svm_node *node,int dimentions)
{
int i;
for(i = 0; i < dimentions; i++){
(*node).index = i + 1;//维度
(*node).value = *(srcData+i);//数据
cout<<(*node).value<<" ";
node++;
}
cout<<endl;
(*node).index=-1;
}
//获取Svm_problem格式的训练样本数据
void getSvm_problem(double *labels,Mat &srcData,svm_problem *prob,int size,int dimentions)
{
prob->l=size;
prob->y=labels;//指向标签地址
prob->x=new svm_node *[prob->l]; //指向svm_node **地址
svm_node *node = new svm_node[(dimentions+1) * prob->l];//样本维度要比实际温度高一维,索引以-1结尾
for(int i = 0; i < prob->l; i++){
array2svm_node(srcData.ptr<float>(i),&node[(dimentions + 1) * i],dimentions);
node[(dimentions + 1) * i + dimentions].index = -1;//每个样本索引要以-1结尾
prob->x[i] = &node[(dimentions + 1) * i]; //相当与移动指针(PROBLEM_DIMENSION + 1)
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//用于保存可视化数据的矩阵
Mat image = Mat::zeros(512, 512, CV_8UC3);
for(int i=0;i<image.rows;i++){
for(int j=0;j<image.cols;j++){
if(i<image.rows/3)
image.ptr<Vec3b>(i)[j]=Vec3b(255,0,0);
else if(i<2*image.rows/3)
image.ptr<Vec3b>(i)[j]=Vec3b(0,255,0);
else
image.ptr<Vec3b>(i)[j]=Vec3b(0,0,255);
}
}
cout<<"svm start :"<<endl;
//填充svm_problem数据机构
svm_problem *prob=new svm_problem;//初始化
double labels[3] = {1.0,2.0,3.0};
float trainingData[3][3] = {{255,0,0},{0,255,0},{0,0,255}};
Mat trainingMat(3,3,CV_32FC1,trainingData);
getSvm_problem(labels,trainingMat,prob,sizeof(labels)/sizeof(double),SAMPLEDIM);
//参数设置
svm_parameter para;
para.svm_type = C_SVC;//SVM的类型
para.kernel_type = RBF;//核函数类型
para.degree = 3;//核函数为多项式的参数
para.gamma = 0.0001;//核函数为poly/rbf/sigmoid的参数
para.coef0 = 0;//核函数为poly/sigmoid的参数
para.nu = 0.5;//设置nu - SVC、one-class-SVM 与nu - SVR 中参数nu ,默认值0.5;
para.cache_size =200;//训练所需的内存单位(MB)
para.C = 10;//惩罚因子,越大训练时间越长
para.eps = 1e-5;//训练停止的标准
para.p = 0.1;//核宽,设置e - SVR的损失函数中的e ,默认值为0.1
para.shrinking = 1;//训练过程是否使用压缩
para.probability = 1;//是否做概率估计
para.nr_weight = 0;//权重的数目,目前只有两个值,默认为0
para.weight_label = NULL;//权重,元素个数由nr_weight决定
para.weight = NULL;//C_SVC的权重
//训练模型
svm_model *model;
model = svm_train(prob, ¶);//训练
svm_save_model(MODELNAME,model);//保存模型
//分类
cout<<"SVM test "<<endl;
svm_node *node1 = new svm_node[SAMPLEDIM+1]; //样本特征存储空间
string str1="blue",str2="green",str3="red";
for (int i = 100; i < image.rows; i+=image.rows/3){
int j=10;
int c0=image.ptr<Vec3b>(i)[j][0];
int c1=image.ptr<Vec3b>(i)[j][1];
int c2=image.ptr<Vec3b>(i)[j][2];
float ta[3]={c0,c1,c2};
Mat sampleMat = Mat(1,3,CV_32FC1,ta);
array2svm_node(sampleMat.ptr<float>(0),node1,SAMPLEDIM);//转化带分类样本数据
double response = svm_predict(model,node1);//进行分类
cout<<"response="<<response<<endl;
//分类结果处理
if (response == 1.0)
putText(image,str1,Point(j,i),FONT_HERSHEY_SIMPLEX,2,Scalar(255,255,255),2,8);
else if (response == 2.0)
putText(image,str2,Point(j,i),FONT_HERSHEY_SIMPLEX,2,Scalar(255,255,255),2,8);
else if(response == 3.0)
putText(image,str3,Point(j,i),FONT_HERSHEY_SIMPLEX,2,Scalar(255,255,255),2,8);
}
imshow("SVM分类", image);
waitKey(0);
return a.exec();
}
(2)样本用为二维数组时的代码
/*******************************
//湖南·长沙 2015-11-8
********************************/
#include <QtCore/QCoreApplication>
#include <iostream>
#include "svm.h"
using namespace std;
#define SAMPLEDIM 3
#define MODELNAME "model.txt"
//把数组转换为svm_node格式
void array2svm_node(float *srcData,svm_node *node,int dimentions)
{
int i;
for(i = 0; i < dimentions; i++){
(*node).index = i + 1;//维度
(*node).value = *(srcData+i);//数据
node++;
}
(*node).index=-1;
}
//获取Svm_problem格式的训练样本数据
void getSvm_problem(double *labels,void **srcData,svm_problem *prob,int size,int dimentions)
{
prob->l=size;
prob->y=labels;//指向标签地址
prob->x=new svm_node *[prob->l]; //指向svm_node **地址
svm_node *node = new svm_node[(dimentions+1) * prob->l];//样本维度要比实际温度高一维,索引以-1结尾
for(int i = 0; i < prob->l; i++){
array2svm_node((float *)srcData+i*dimentions,&node[(dimentions + 1) * i],dimentions);
node[(dimentions + 1) * i + dimentions].index = -1;//每个样本索引要以-1结尾
prob->x[i] = &node[(dimentions + 1) * i]; //相当与移动指针(PROBLEM_DIMENSION + 1)
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout<<"====SVM Start===="<<endl;
//填充svm_problem数据机构
svm_problem *prob=new svm_problem;//初始化
double labels[3] = {1.0,2.0,3.0};
float trainingData[3][3] = {{255,0,0}, {0,255,0}, {0,0,255}};
getSvm_problem(labels,(void **)trainingData,prob,sizeof(labels)/sizeof(double),SAMPLEDIM);
//参数设置
svm_parameter para;
para.svm_type = C_SVC;//SVM的类型
para.kernel_type = RBF;//核函数类型
para.degree = 3;//核函数为多项式的参数
para.gamma = 0.0001;//核函数为poly/rbf/sigmoid的参数
para.coef0 = 0;//核函数为poly/sigmoid的参数
para.nu = 0.5;
para.cache_size = 100;//训练所需的内存单位(MB)
para.C = 10;//惩罚因子,越大训练时间越长
para.eps = 1e-5;//训练停止的标准
para.p = 0.1;
para.shrinking = 1;//训练过程是否使用压缩
para.probability = 0;//是否做概率估计
para.nr_weight = 0;//权重的数目,目前只有两个值,默认为0
para.weight_label = NULL;//权重,元素个数由nr_weight决定
para.weight = NULL;//C_SVC的权重
//训练模型
svm_model *model;
model = svm_train(prob, ¶);//训练
svm_save_model(MODELNAME,model);//保存模型
//分类
svm_node *node1 = new svm_node[SAMPLEDIM+1]; //样本特征存储空间
float testData[3]={0,255,0};
array2svm_node(testData,node1,SAMPLEDIM);//转化待分类样本数据
double result = svm_predict(model,node1);//进行分类
cout<<"result="<<result<<endl;
return a.exec();
}
运行结果分析:
下图是样本为Mat矩阵时的运行结果,可以看出程序中建立的图像颜色被成功的分为三类。
下图为样本是float型数组时的运行结果:
程序对{0,255,0}进行分类,得到的标签为2,绿色。分类正确!