Opencv&SVM支持向量机参数与优化:
Opencv&SVM支持向量机
参考总结自:http://blog.****.net/liukun321
https://blog.****.net/computerme/article/details/38677599
依据机器学习算法如何学习数据可分为3类:有监督学习:从有标签的数据学习,得到模型参数,对测试数据正确分类;无监督学习:没有标签,计算机自己寻找输入数据可能的模型;强化学习(reinforcement learning):计算机与动态环境交互,学习错误反馈达到更优的目的。
依据机器学习期望结果来分类:分类:输入被分为N个类别的一种;回归:输出是连续值;如依据房子的大小,时间,位置来预测房子的价格;聚类:使用无监督学习将输入聚为N类;密度估计(density estimation):找到输入可能的分布;
下面介绍一种常用的一种机器学习方式支持向量机
支持向量机简介:
SVM是一种训练机器学习的算法,可以用于解决分类和回归问题,同时还使用了一种称之为kernel trick(支持向量机的核函数)的技术进行数据的转换,然后再根据这些转换信息,在可能的输出之中找到一个最优的边界(超平面)。简单来说,就是做一些非常复杂的数据转换工作,然后根据预定义的标签或者输出进而计算出如何分离用户的数据。
支持向量机方法是建立在统计学习理论的VC 维理论和结构风险最小原理基础上的,根据有限的样本信息在模型的复杂性(即对特定训练样本的学习精度,Accuracy)和学习能力(即无错误地识别任意样本的能力)之间寻求最佳折衷,以期获得最好的推广能力(或称泛化能力)。
较其他传统机器学习的优点:
1、小样本,并不是说样本的绝对数量少(实际上,对任何算法来说,更多的样本几乎总是能带来更好的效果),而是说与问题的复杂度比起来,SVM算法要求的样本数是相对比较少的。SVM解决问题的时候,和样本的维数是无关的(甚至样本是上万维的都可以,这使得SVM很适合用来解决文本分类的问题,当然,有这样的能力也因为引入了核函数)。
2、结构风险最小。(对问题真实模型的逼近与问题真实解之间的误差,就叫做风险,更严格的说,误差的累积叫做风险)。
3、非线性,是指SVM擅长应付样本数据线性不可分的情况,主要通过松弛变量(也有人叫惩罚变量)和核函数技术来实现,这一部分是SVM的精髓。(关于文本分类这个问题究竟是不是线性可分的,尚没有定论,因此不能简单的认为它是线性可分的而作简化处理,在水落石出之前,只好先当它是线性不可分的反正线性可分也不过是线性不可分的一种特例而已,我们向来不怕方法过于通用)。
核函数:
SVM的强大离不开这一个很重要的东西--核函数
1、为何需要核函数?
很多情况下低维空间向量集是难于划分的,解决办法是将它们映射到高维空间。但这个办法带来的艰苦就是策画错杂度的增长,而核函数正好奇妙地解决了这个问题。也就是说,只要选用恰当的核函数,就可以获得高维空间的分类函数(超平面)。在SVM理论中,采取不合的核函数将导致不合的SVM算法。在断定了核函数之后,因为断定核函数的已知数据也存在必然的误差,推敲到推广性题目,是以引入了败坏系数以及处罚系数两个参变量来加以校订。
其实核函数的本质作用可以简练概括为:将低维空间的线性不可分类问题,借助核函数转化为高维空间的线性可分,进而可以在高维空间找到分类的最优边界(超平面)。(下图引自July‘s 支持向量机通俗导论(理解SVM的三层境界))。若要要分类下图红色和蓝色样本点:
二维线性不可分 三维线性可分
2、核函数的分类
(1)线性核函数
(2)多项式核函数
(3)径向基(RBF)核函数(高斯核函数)
(4)Sigmoid核函数(二层神经收集核函数)
3、Opencv中的核函数定义:
CvSVM::LINEAR : 线性内核,没有任何向映射至高维空间,线性区分(或回归)在原始特点空间中被完成,这是最快的选择。
.
CvSVM::POLY : 多项式内核:
.
CvSVM::RBF : 基于径向的函数,对于大多半景象都是一个较好的选择:
.
CvSVM::SIGMOID : Sigmoid函数内核:
.
Opencv中SVM参数设置:
Opencv中SVM参数设置使用CvSVMParams方法定义如下:
CvSVMParams::CvSVMParams(int svm_type, int kernel_type, double degree, double gamma, double coef0, double Cvalue, double nu, double p, CvMat* class_weights, CvTermCriteria term_crit )
CvSVMParams方法如果不传自定义参数会按如下代码进行默认初始化:
CvSVMParams::CvSVMParams() : svm_type(CvSVM::C_SVC), kernel_type(CvSVM::RBF), degree(0), gamma(1), coef0(0), C(1), nu(0), p(0), class_weights(0) { term_crit = cvTermCriteria( CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 1000, FLT_EPSILON ); }
kernel_type:SVM的内核类型(4种):我们在上面已经介绍过了就不再多说了。
svm_type:指定SVM的类型(5种):
1、CvSVM::C_SVC : C类支撑向量分类机。 n类分组 (n≥2),容许用异常值处罚因子C进行不完全分类。
2、CvSVM::NU_SVC :
类支撑向量分类机。n类似然不完全分类的分类器。参数为
庖代C(其值在区间【0,1】中,nu越大,决定计划鸿沟越腻滑)。
3、CvSVM::ONE_CLASS : 单分类器,所有的练习数据提取自同一个类里,然后SVM建树了一个分界线以分别该类在特点空间中所占区域和其它类在特点空间中所占区域。
4、CvSVM::EPS_SVR :
类支撑向量回归机。练习集中的特点向量和拟合出来的超平面的间隔须要小于p。异常值处罚因子C被采取。
5、CvSVM::NU_SVR :
类支撑向量回归机。
庖代了 p。
degree: 内核函数(POLY)的参数degree。
gamma: 内核函数(POLY/ RBF/ SIGMOID)的参数。
coef0: 内核函数(POLY/ SIGMOID)的参数coef0。
Cvalue: SVM类型(C_SVC/ EPS_SVR/ NU_SVR)的参数C。
nu: SVM类型(NU_SVC/ ONE_CLASS/ NU_SVR)的参数 。
p: SVM类型(EPS_SVR)的参数。
SVM_params.degree:核函数中的参数degree,针对多项式核函数;
SVM_params.gama:核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params.coef0:核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params.c:SVM最优问题参数,设置C-SVC
,EPS_SVR
和NU_SVR
的参数;
SVM_params.nu:SVM最优问题参数,设置NU_SVC
, ONE_CLASS
和NU_SVR
的参数;
SVM_params.p:SVM最优问题参数,设置EPS_SVR
中损失函数p的值.
class_weights: C_SVC中的可选权重,赋给指定的类,乘以C今后变成 。所以这些权重影响不合类此外错误分类处罚项。权重越大,某一类此外误分类数据的处罚项就越大。
term_crit: SVM的迭代练习过程的中断前提,解决项目组受束缚二次最优题目。您可以指定的公差和/或最大迭代次数。
Opencv中SVM训练函数:
后面的优化部分所涉及的函数train_auto()可以自动帮你优化参数,建议选择后面的
CvSVM::train(const CvMat* trainData, const CvMat* responses, const CvMat* varIdx=0, const CvMat* sampleIdx=0, CvSVMParams params=CvSVMParams() )
1、trainData: 练习数据,必须是CV_32FC1 (32位浮点类型,单通道)。数据必须是CV_ROW_SAMPLE的,即特点向量以行来存储。
2、responses: 响应数据,凡是是1D向量存储在CV_32SC1 (仅仅用在分类题目上)或者CV_32FC1格局。
3、varIdx: 指定感爱好的特点。可所以整数(32sC1)向量,例如以0为开端的索引,或者8位(8uC1)的应用的特点或者样本的掩码。用户也可以传入NULL指针,用来默示练习中应用所有变量/样本。
4、sampleIdx: 指定感爱好的样本。描述同上。
5、params: SVM参数。
Opencv中的SVM识别(预测)函数:
Opencv的预言函数所有重载如下:
float CvSVM::predict(const Mat& sample, bool returnDFVal=false ) const float CvSVM::predict(const CvMat* sample, bool returnDFVal=false ) const float CvSVM::predict(const CvMat* samples, CvMat* results) const
1、sample: 须要猜测的输入样本。
2、samples: 须要猜测的输入样本们。
3、returnDFVal: 指定返回值类型。若是值是true,则是一个2类分类题目,该办法返回的决定计划函数值是边沿的符号间隔。
4、results: 响应的样本输出猜测的响应。
这个函数用来猜测一个新样本的响应数据(response)。
在分类题目中,这个函数返回类别编号;在回归题目中,返回函数值。
输入的样本必须与传给trainData的练习样本同样大小。
若是练习中应用了varIdx参数,必然记住在predict函数中应用跟练习特点一致的特点。
Opencv中SVM分类问题代码流程:
(1)获得练习样本及制作其类别标签(trainingDataMat,labelsMat)
(2)设置练习参数(CvSVMParams)
(3)对SVM进行训练(CvSVM::train)
(4)对新的输入样本进行猜测(CvSVM::predict),并输出结果类型(对应标签)
(5)获取支撑向量(CvSVM::get_support_vector_count,CvSVM::get_support_vector )
Opencv中SVM多类分类问题编程实例:
#include <cv.h> #include <highgui.h> #include <ml.h> #include <cxcore.h> #include <iostream> using namespace std; int main() { // step 1: //训练数据的分类标记,即4类 float labels[16] = {1.0, 1.0,1.0,1.0,2.0,2.0,2.0,2.0,3.0,3.0,3.0,3.0,4.0,4.0,4.0,4.0}; CvMat labelsMat = cvMat(16, 1, CV_32FC1, labels); //训练数据矩阵 float trainingData[16][2] = { {0, 0}, {4, 1}, {4, 5}, {-1, 6},{3,11},{-2,10},{4,30},{0,25},{10,13},{15,12},{25,40},{11,35},{8,1},{9,6},{15,5},{20,-1} }; CvMat trainingDataMat = cvMat(16, 2, CV_32FC1, trainingData); // step 2: //训练参数设定 CvSVMParams params; params.svm_type = CvSVM::C_SVC; //SVM类型 params.kernel_type = CvSVM::LINEAR; //核函数的类型 //SVM训练过程的终止条件, max_iter:最大迭代次数 epsilon:结果的精确性 params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, FLT_EPSILON ); // step 3: //启动训练过程 CvSVM SVM; SVM.train( &trainingDataMat, &labelsMat, NULL,NULL, params); // step 4: //使用训练所得模型对新样本进行分类测试 for (int i=-5; i<15; i++) { for (int j=-5; j<15; j++) { float a[] = {i,j}; CvMat sampleMat; cvInitMatHeader(&sampleMat,1,2,CV_32FC1,a); cvmSet(&sampleMat,0,0,i); // Set M(i,j) cvmSet(&sampleMat,0,1,j); // Set M(i,j) float response = SVM.predict(&sampleMat); cout<<response<<" "; } cout<<endl; } // step 5: //获取支持向量 int c = SVM.get_support_vector_count(); cout<<endl; for (int i=0; i<c; i++) { const float* v = SVM.get_support_vector(i); cout<<*v<<" "; } cout<<endl; system("pause"); return 0; }
Opencv自带优化参数值的功能:
注:原作者 https://blog.****.net/computerme/article/details/38677599
要让svm自动优化参数,那么训练时就不能再用train函数了,而应该用train_auto函数。下面是train_auto的函数原型
C++: bool CvSVM::train_auto(const Mat& trainData, const Mat& responses, const Mat& varIdx, const Mat& sampleIdx, CvSVMParams params, int k_fold=10, CvParamGrid Cgrid=CvSVM::get_default_grid(CvSVM::C), CvParamGrid gammaGrid=CvSVM::get_default_grid(CvSVM::GAMMA), CvParamGrid pGrid=CvSVM::get_default_grid(CvSVM::P), CvParamGrid nuGrid=CvSVM::get_default_grid(CvSVM::NU), CvParamGrid coeffGrid=CvSVM::get_default_grid(CvSVM::COEF), CvParamGrid degreeGrid=CvSVM::get_default_grid(CvSVM::DEGREE), bool balanced=false )
自动训练函数的参数注释(13个)
前5个参数参考构造函数的参数注释。
k_fold: 交叉验证参数。训练集被分成k_fold的自子集。其中一个子集是用来测试模型,其他子集则成为训练集。所以,SVM算法复杂度是执行k_fold的次数。
Grid: (6个)对应的SVM迭代网格参数。
balanced: 如果是true则这是一个2类分类问题。这将会创建更多的平衡交叉验证子集。
自动训练函数的使用说明
这个方法根据CvSVMParams中的最佳参数C, gamma, p, nu, coef0, degree自动训练SVM模型。
参数被认为是最佳的交叉验证,其测试集预估错误最小。
如果没有需要优化的参数,相应的网格步骤应该被设置为小于或等于1的值。例如,为了避免gamma的优化,设置gamma_grid.step = 0,gamma_grid.min_val, gamma_grid.max_val 为任意数值。所以params.gamma 由gamma得出。
最后,如果参数优化是必需的,但是相应的网格却不确定,你可能需要调用函数CvSVM::get_default_grid(),创建一个网格。例如,对于gamma,调用CvSVM::get_default_grid(CvSVM::GAMMA)。
该函数为分类运行 (params.svm_type=CvSVM::C_SVC 或者 params.svm_type=CvSVM::NU_SVC) 和为回归运行 (params.svm_type=CvSVM::EPS_SVR 或者 params.svm_type=CvSVM::NU_SVR)效果一样好。如果params.svm_type=CvSVM::ONE_CLASS,没有优化,并指定执行一般的SVM。
这里需要注意的是,对于需要的优化的参数虽然train_auto可以自动选择最优值,但在代码中也要先赋初始值,要不然编译能通过,但运行时会报错。
CvSVMParams param; param.svm_type = CvSVM::EPS_SVR; param.kernel_type = CvSVM::RBF; param.C = 1; //给参数赋初始值 param.p = 5e-3; //给参数赋初始值 param.gamma = 0.01; //给参数赋初始值 param.term_crit = cvTermCriteria(CV_TERMCRIT_EPS, 100, 5e-3); //对不用的参数step设为0 CvParamGrid nuGrid = CvParamGrid(1,1,0.0); CvParamGrid coeffGrid = CvParamGrid(1,1,0.0); CvParamGrid degreeGrid = CvParamGrid(1,1,0.0); CvSVM regressor; regressor.train_auto(PCA_training,tr_label,NULL,NULL,param, 10, regressor.get_default_grid(CvSVM::C), regressor.get_default_grid(CvSVM::GAMMA), regressor.get_default_grid(CvSVM::P), nuGrid, coeffGrid, degreeGrid);
用上面的代码的就可以自动训练并优化参数。最后,若想查看优化后的参数值,可以使用CvSVM::get_params()函数来获得优化后的CvSVMParams。下面是示例代码:
CvSVMParams params_re = regressor.get_params(); regressor.save("training_srv.xml"); float C = params_re.C; float P = params_re.p; float gamma = params_re.gamma; printf("\nParms: C = %f, P = %f,gamma = %f \n",C,P,gamma);