opencv3.1 svm(支持向量机)使用心得
SVM是opencv中一个内置的机器学习类,在使用该类时,我们需要接触到的是train(训练数据)/trainAuto(自动训练数据)/predict(对未知数据进行预测)等三个函数。本文以这三个函数为中心记录一些SVM(支持向量机)的使用心得。
1. SVM训练参数的设定范围
在使用svm函数时,我们需要先新建一个变量:cv::Ptr<ml::SVM> svm = cv::ml::SVM::create(); 并为该变量设定训练参数,需要设定的训练参数有kernelType/svmType/gamma/coef0/degree/k_fold/C/nu/p/termCrit,其中kernelType和svmType的设定值比较重要,这两个参数决定其他参数的设置走向,如下表所示。表格中为固定值(0和1)的项,表示在相应的kernelType和svmType设置下该参数的唯一值,train和trainAuto函数均是如此。
参数k_fold仅在使用trainAuto函数时需要设定,在介绍trainAuto函数时会进行说明。
参数termCrit需要通过CvTermCriteria来设定,用于决定SVM训练的终止条件,该结构体定义如下:
typedef struct CvTermCriteria
{
//【1】int type—迭代算法终止条件的类型,是下面之一:
//【1】CV_TERMCRIT_ITER—在完成最大的迭代次数之后,停止算法
//【2】CV_TERMCRIT_EPS—-当算法的精确度小于参数double epsilon指定的精确度时,停止算法
//【3】CV_TERMCRIT_ITER+CV_TERMCRIT_EPS–无论是哪一个条件先达到,都停止算法
int type; //可以两者结合
CV_TERMCRIT_ITER
CV_TERMCRIT_EPS
//【2】最大的迭代次数
int max_iter;
//【3】所要求的精确度
double epsilon;
}
kernelType的默认值为SVM::RBF,svmType的默认值为SVM::C_SVC在此设定下,如果要使用train函数手动调参数,那么只需要调整参数gamma/C/termCrit;使用trainAuto函数时,termCrit需要提前设定,自动调整的参数只会包含gamma和C。
2. SVM的类型(svmType)和核函数(kernelType)
svmType在A Library for Support Vector Machines一文的第二章节中有详细介绍,我个人目前还没有完全理解,因此这里就不献丑了。想了解的同学可以去找论文研读。
关于svm的核函数,我觉得EasyPR开发详解中介绍的非常详细且易懂,如下所示(关于SVM核函数的介绍来自于EasyPR开发详解,感谢原作者用如此通俗的语言讲述了核函数,这一段我只是一个搬运工。)
SVM 中最关键的技巧是核技巧。“核”其实是一个函数,通过一些转换规则把低维的数据映射为高维的数据。在机器学习里,数据跟向量是等同的意思。例如,一个 [174, 72]表示人的身高与体重的数据就是一个两维的向量。在这里,维度代表的是向量的长度。(务必要区分“维度”这个词在不同语境下的含义,有的时候我们会说向量是一维的,矩阵是二维的,这种说法针对的是数据展开的层次。机器学习里讲的维度代表的是向量的长度,与前者不同)。简单来说,低维空间到高维空间映射带来的好处就是可以利用高维空间的线型切割模拟低维空间的非线性分类效果。也就是说,SVM 模型其实只能做线型分类,但是在线型分类前,它可以通过核技巧把数据映射到高维,然后在高维空间进行线型切割。高维空间的线型切割完后在低维空间中最后看到的效果就是划出了一条复杂的分线型分类界限。从这点来看,SVM 并没有完成真正的非线性分类,而是通过其它方式达到了类似目的,可谓“曲径通幽”。
SVM 模型总共可以支持多少种核呢。根据官方文档,支持的核类型有以下几种:
1. liner 核,也就是无核,也就是没有使用核函数对数据进行转换。当数据量不多,但是每个数据量的维数都很大时,适合用线型核。
2. rbf 核,使用的是高斯函数作为核函数,当你的数据量很大,但是每个数据量的维度一般时,才适合用 rbf 核。
3. poly 核,使用多项式函数作为核函数。
4. sigmoid 核,使用 sigmoid 函数作为核函数。
liner 核和 rbf 核是所有核中应用最广泛的。
3. 函数train/trainAtuo/predict简述
函数train定义有两种形式:
1 2 3 4 5 6 7 8 |
//data: 可通过TrainData: create 或 TrainData::loadFromCSV.构造 //flags: 取决于模型,有的模型会根据新的训练样本更新,不会被覆盖,默认为0 CV_WRAP virtual bool train( const Ptr<TrainData>& trainData, int flags=0 ); //samples:训练样本 //layout:样本类型 //responses:与训练样本相对应的标签值(二值化分类时即0或1) CV_WRAP virtual bool train( InputArray samples, int layout, InputArray responses ); |
上述train函数的第二种定义的函数体如下所示:
1 2 3 4 |
bool StatModel::train( InputArray samples, int layout, InputArray responses ) { return train(TrainData::create(samples, layout, responses)); } |
trainAuto函数是opencv中提供了一个自动训练的方法,该函数可以指定需要自动调整的参数,该参数的调整范围,每一次调整的步长。设定完成后,该函数会根据设定,不断改变指定参数,训练模型,测试模型,最后选择效果最好的参数。
那么trainAuto函数如何验证哪些参数效果最好呢?trainAuto函数会将数据样本分成训练数据(train data)和测试数据 validate data,用train data进行训练,用 validate data进行测试,这种做法也成为交叉验证cross-validation。每一次调节参数后,都会记录测试时记录预测结果与标签数据不一致的数量count,所有参数调节完成后,count最小时对应的参数即效果最好的参数。参数k_fold就是用来设置数据样本分为训练数据(train data)和测试数据 validate data数据比例的参数。
trainAuto函数的定义如下,其中包含了一些简单的注释。
1 2 3 4 5 6 7 8 9 10 11 |
//data: 可通过TrainData: create 或 TrainData::loadFromCSV.构造 //kFold: 交叉验证参数,设定自动训练时数据样本被划分为训练样本和测试样本的比例 virtual bool trainAuto( const Ptr<TrainData>& data, int kFold = 10, //设置需要自动调整的参数的最大值,最小值和每一次的调整步长 ParamGrid Cgrid = SVM::getDefaultGrid(SVM::C), ParamGrid gammaGrid = SVM::getDefaultGrid(SVM::GAMMA), ParamGrid pGrid = SVM::getDefaultGrid(SVM::P), ParamGrid nuGrid = SVM::getDefaultGrid(SVM::NU), ParamGrid coeffGrid = SVM::getDefaultGrid(SVM::COEF), ParamGrid degreeGrid = SVM::getDefaultGrid(SVM::DEGREE), bool balanced=false) = 0;//balanced为true时表示当前是一个二分法的问题,那么自动训练会根据此特征优化交叉训练集 |
train与trainAuto函数均需要TrainData,也就是我们使用SVM时准备的样本数据。该数据中需要包含训练样本的特征矩阵和相应矩阵的标签矩阵。其中特征矩阵的行数是样本的数量,矩阵的列则是样本的特征向量。通常我们获取的特征都是一个矩阵,但在开始训练之前,我们都要将该矩阵转换为向量。
svm 的 perdict 方法的输入是待预测数据的特征,也称之为 features,其函数定义如下:
1 2 3 |
//samples: 待预测样本的特征向量,要求浮点数 //results:输出矩阵,可选, CV_WRAP virtual float predict( InputArray samples, OutputArray results=noArray(), int flags=0 ) const = 0; |
train/trainAuto/predict输入的数据类型均为CV_32F。
关于SVM的使用简单介绍到这里,下一篇文章将记录LBP/HOG等特征点在SVM中的使用方法和结果对比,敬请期待,欢迎指点和探讨。
呼叫博主呼叫博主~我在进行测试的时候,predict的返回值为负,不能理解为什么?
很抱歉,我也不是很清楚。如果可以的话,你可以把你的测试程序发给我调试一下,看看是否能找到答案。
请问能告知你的邮箱吗?我可以把代码发邮箱~谢谢啦~