作者 群号 C语言交流中心 240137450 微信
15013593099
OpenCV开发SVM算法是基于LibSVM软件包开发的,LibSVM是台湾大学林智仁(LinChih-Jen)等开发设计的一个简单、易于使用和快速有效的SVM模式识别与回归的软件包。用OpenCV使用SVM算法的大概流程是
1)设置训练样本集
需要两组数据,一组是数据的类别,一组是数据的向量信息。
2)设置SVM参数
利用CvSVMParams类实现类内的成员变量svm_type表示SVM类型:
CvSVM::C_SVC C-SVC
CvSVM::NU_SVC v-SVC
CvSVM::ONE_CLASS 一类SVM
CvSVM::EPS_SVR e-SVR
CvSVM::NU_SVR v-SVR
成员变量kernel_type表示核函数的类型:
CvSVM::LINEAR 线性:u‘v
CvSVM::POLY 多项式:(r*u‘v +coef0)^degree
CvSVM::RBF RBF函数:exp(-r|u-v|^2)
CvSVM::SIGMOID sigmoid函数:tanh(r*u‘v +coef0)
成员变量degree针对多项式核函数degree的设置,gamma针对多项式/rbf/sigmoid核函数的设置,coef0针对多项式/sigmoid核函数的设置,Cvalue为损失函数,在C-SVC、e-SVR、v-SVR中有效,nu设置v-SVC、一类SVM和v-SVR参数,p为设置e-SVR中损失函数的值,class_weightsC_SVC的权重,term_crit为SVM训练过程的终止条件。其中默认值degree= 0,gamma = 1,coef0
= 0,Cvalue = 1,nu = 0,p = 0,class_weights =0
3)训练SVM
调用CvSVM::train函数建立SVM模型,第一个参数为训练数据,第二个参数为分类结果,最后一个参数即CvSVMParams
4)用这个SVM进行分类
调用函数CvSVM::predict实现分类
5)获得支持向量
除了分类,也可以得到SVM的支持向量,调用函数CvSVM::get_support_vector_count获得支持向量的个数,CvSVM::get_support_vector获得对应的索引编号的支持向量。
实现代码如下:
[cpp] view plaincopy
- // step 1:
- float labels[4] = {1.0, -1.0, -1.0, -1.0};
- Mat labelsMat(3, 1, CV_32FC1, labels);
- float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
- Mat trainingDataMat(3, 2, CV_32FC1, trainingData);
- // step 2:
- CvSVMParams params;
- params.svm_type = CvSVM::C_SVC;
- params.kernel_type = CvSVM::LINEAR;
- params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
- // step 3:
- CvSVM SVM;
- SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
- // step 4:
- Vec3b green(0, 255, 0), blue(255, 0, 0);
- for (int i=0; i
- {
- for (int j=0; j
- {
- Mat sampleMat = (Mat_<<spanclass="datatypes" style="margin: 0px; padding: 0px; border: none;color: rgb(46,
139, 87); background-color: inherit; font-weight:bold;">float>(1,2) << i,j); - float response = SVM.predict(sampleMat);
- if (fabs(response-1.0) < 0.0001)
- {
- image.at(j, i) = green;
- }
- else if (fabs(response+1.0) < 0.001)
- {
- image.at(j, i) = blue;
- }
- }
- }
- // step 5:
- int c = SVM.get_support_vector_count();
- for (int i=0; i
- {
- const float* v = SVM.get_support_vector(i);
- }
总结:
1、SVM是一个分类器(Classifier) ,也可以做回归 (Regression) 。
2、 SVM的主要思想可以概括为两点 :
(1)它是针对线性可分情况进行分析,对于线性不可分的情况,通过使用非线性映射算法将低维输入空间线性不可分的样本转化为高维特征空间使其线性可分,从而 使得高维特征空间采用线性算法对样本的非线性特征进行线性分析成为可能;
(2)它基于结构风险最小化理论之上在特征空间中建构最优分割超平面,使得学习器得到全局最优化,并且在整个样本空间的期望风险以某个概率满足一定上界。
3、 最优超平面 :使得每一类数据与超平面距离最近的向量与超平面之间的距离最大的这样的平面。
4、 支持向量 :那些在间隔区边缘的训练样本点。
5、 核函数 :SVM的关键在于核函数。简单说就是将低维空间线性不可分的样本转化为高维空间线性可分的。低维空间向量集通常难于划分,解决的方法是将它们映射到高维空间。但这个办法带来的困难就是计算复杂度的增加,而核函数正好巧妙地解决了这个问题。也就是说,只要选用适当的核函数,就可以得到高维空间的分类函数。在SVM理论中,采用不同的核函数将导致不同的SVM算法。在确定了核函数之后,由于确定核函数的已知数据也存在一定的误差,考虑到推广性问题,因此引入了松弛系数
以及 惩罚系数 两个参变量来加以校正。在确定了核函数基础上,再经过大量对比实验等将这两个系数取定,该项研究就基本完成,适合相关学科或业务内应用,且有一定能力的推广性。当然误差是绝对的,不同学科、不同专业的要求不一。
常用的核函数有以下4种:
⑴ 线性核函数
⑵ 多项式核函数
⑶ 径向基(RBF)核函数(高斯核函数)
⑷ Sigmoid 核函数(二层神经网络核函数)
<5-1> 径向基(RBF)核函数(高斯核函数) 的说明
这个核函数可以将原始空间映射到无穷维空间。对于参数 ,如果选的很大,高次特征上的权重实际上衰减得非常快,所以实际上(数值上近似一下)相当于一个低维的子空间;反过来,如果选得很小,则可以将任意的数据映射为线性可分——当然,这并不一定是好事,因为随之而来的可能是非常严重的过拟合问题。不过,总的来说,通过调控参数
,高斯核实际上具有相当高的灵活性,也是使用最广泛的核函数 之一。
<5-2> 径向基(RBF)核函数的参数选取
径向基( RBF )核函数主要确定 惩罚因子
C 和 参数 。其中C 控制着使间隔margin 最大且错误率最小的折中,就是在确定的特征空间中调节学习机器的置信范围和经验风险的比例;而是RBF
核函数参数,主要影响样本数据在高维特征空间中分布的复杂程度。因此分类器的好坏取决于参数C 、
的确定。参数选择的好坏直接影响到分类器性能的好坏,但这方面目前缺乏理论指导,没有合适的方法,传统的参数选取都是通过反复的试验,人工选取令人满意的解。这种方法需要人的经验指导,并且需要付出较高的时间代价。常用的参数选择方法有:
I、网格法【OpenCV中SVM用到】
选取 U 个 C 和
V 个 ,就会有的组合状态,每种组合状态对应一种SVM
分类器,通过测试对比,找出推广识别率最高的 C 和 组合。一般取U=V=15 ,C 取值分别为,取值分别为共255
个 C 、 组合。网格法实质上是一种穷举法,随着排列组合的可能情况越多,其运算量将急剧增加。
II、双线性法
利用 RBF 核 SVM 的性能,首先对线性
SVM 求解最佳参数,使之为参数的线性 SVM 推广识别率最高,称为
;然后固定,对满足
的 ,训练SVM ,根据对其推广识别率的估算,得到最优参数。虽然这种方法对有非常明确的公式,但首先要求解C
,而很难确定最优的C 。
III、梯度下降搜索法
设泛化误差为
核函数为 ,是待定的核参数,基本过程为:
a 将 置一个初始值
b 用一个标准的 SVM 解法(如
SMO ),求出 SVM 的解—— Lagrange 乘子c
d 跳转到 b 直至
T 最小
其中 是足够小且最终收敛到零的数列。步骤c 是一个标准的梯度下降算法。由分类函数公式可以求解,的求解较麻烦,需要通过求解一个二次规划问题得到。
IV、遗传算法
基本步骤为:
a t=0
b 随机选择初始种群 P(t)
c 计算个体适应度函数值 F(t)
d 若种群中最优个体所对应的适应度函数值足够大或者算法已经连续运行多代,且个体的最佳适应度无明显改进则转到第h 步
e t=t+1
f 应用选择算子法从 P(t-1) 中选择P(t)
g 对 P(t) 进行交叉、变异操作,转到第c 步
h 给出最佳的核函数参合和惩罚因子 C ,并用其训练数据集以获得全局最优分类面。
遗传算法的缺点是收敛很慢,容易受局部极小值干扰。
<5-3>验证核函数性能的方法(3种)(衡量泛化能力)
I、单一验证估计
将大数量的样本分为两部分:训练样本和测试样本,此时测试集的错误率为:
式中, p 为样本数, 为样本实际所属的类别,为对训练样本预测出的类别。这种方法直观简单。可以通过理论证明,当样本数量趋近于无穷大时,该估计为无偏估计,但现实中处理的总是数量有限的样本问题,所以此方法的应用范围在一定程度上受到了限制。
II、K 折交叉验证【OpenCV中SVM用到】
K 折交叉验证是一种迭代方式,一共迭代 K 次,每次将所有训练样本分为K 份相等的子集样本,训练样本是选择其中K-1 份样本,测试样本是剩余的一个样本。通过K 次迭代后,可以利用平均值来评估期望泛化误差,根据期望泛化误差选择一组性能最佳的参数。K
折交叉验证由 K 折交叉验证误差决定, K 折交叉验证误差是算法错误率的估计,其计算方式为:假设为错分类的样本个数,经过K
次迭代后,得到 ,那么算法的错误率可以近似为错误分类数和总样本点数之比。该方法具有操作简单的优点,成为目前应用最广泛
的方法,但是这种方法容易受样本划分方式的影响。
III、留一法
留一法是 K 折交叉验证的特例,其基本思想是当可用样本数为N 时,训练集由其中N-1 个样本构成,测试样本为剩余的一个样本,经N 次重复,使所有的样本都参加过测试。通过理论证明,这种估计是无偏估计。因此,从实现原理来说,留一法的效果是最佳的;但是,在参数固定的情况下,确定其错误率对样本要训练N-1
次,运算量很大。为了解决留一法计算量大的缺陷,目前该方法确定核函数及其参数的常用方法是估计经验风险的上界,只要上界小,分类器的推广能力就强。
二、OpenCV中SVM的参数和函数说明
1、训练参数结构体 CvSVMParams (可参考 【OpenCV2.4】SVM的参数和函数介绍 )
(1)注意: 该结构必须被初始化后,传给CvSVM
(2)构造函数的原型:
C++: CvSVMParams:: CvSVMParams ()
C++: 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)
(3)注释
A. 默认的构造函数初始化有以下值:
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 ); }
B. 构造函数的参数(一共10个):
<1> svm_type: 指定SVM的类型(5种):
- CvSVM::C_SVC : C类支持向量分类机。 n类分组 (n 2),允许用异常值惩罚因子C进行不完全分类。
- CvSVM::NU_SVC : 类支持向量分类机。n类似然不完全分类的分类器。参数为
取代C(其值在区间【0,1】中,nu越大,决策边界越平滑)。 - CvSVM::ONE_CLASS : 单分类器,所有的训练数据提取自同一个类里,然后SVM建立了一个分界线以分割该类在特征空间中所占区域和其它类在特征空间中所占区域。
- CvSVM::EPS_SVR : 类支持向量回归机。训练集中的特征向量和拟合出来的超平面的距离需要小于p。异常值惩罚因子C被采用。
- CvSVM::NU_SVR : 类支持向量回归机。
代替了 p。
<2> kernel_type: SVM的内核类型(4种):
- CvSVM::LINEAR : 线性内核,没有任何向映射至高维空间,线性区分(或回归)在原始特征空间中被完成,这是最快的选择。
.
- CvSVM::POLY : 多项式内核:
.
- CvSVM::RBF : 基于径向的函数,对于大多数情况都是一个较好的选择:
.
- CvSVM::SIGMOID : Sigmoid函数内核:
.
<3> degree: 内核函数(POLY)的参数degree。
<4> gamma: 内核函数(POLY/ RBF/ SIGMOID)的参数 。
<5> coef0: 内核函数(POLY/ SIGMOID)的参数coef0。
<6> Cvalue: SVM类型(C_SVC/ EPS_SVR/ NU_SVR)的参数C。
<7> nu: SVM类型(NU_SVC/ ONE_CLASS/ NU_SVR)的参数 。
<8> p: SVM类型(EPS_SVR)的参数 。
<9> class_weights: C_SVC中的可选权重,赋给指定的类,乘以C以后变成 。所以这些权重影响不同类别的错误分类惩罚项。权重越大,某一类别的误分类数据的惩罚项就越大。
<10> term_crit: SVM的迭代训练过程的中止条件,解决部分受约束二次最优问题。您可以指定的公差和/或最大迭代次数。
2、支持向量机 CvSVM 类(8个函数)
(1)构造函数
<1>构造函数的原型
C++: CvSVM:: CvSVM ()
C++: CvSVM:: CvSVM (const Mat& trainData ,
const Mat& responses ,const Mat& varIdx=Mat() ,const Mat& sampleIdx=Mat() ,CvSVMParams params=CvSVMParams())
C++: CvSVM:: CvSVM (const CvMat* trainData ,
const CvMat* responses ,const CvMat* varIdx=0 ,const CvMat* sampleIdx=0 ,CvSVMParams params=CvSVMParams())
<2>构造函数的参数注释(5个)
- trainData : 训练数据,必须是CV_32FC1 (32位浮点类型,单通道)。数据必须是CV_ROW_SAMPLE的,即特征向量以行来存储。
- responses : 响应数据,通常是1D向量存储在CV_32SC1 (仅仅用在分类问题上)或者CV_32FC1格式。
- varIdx : 指定感兴趣的特征。可以是整数(32sC1)向量,例如以0为开始的索引,或者8位(8uC1)的使用的特征或者样本的掩码。用户也可以传入NULL指针,用来表示训练中使用所有变量/样本。
- sampleIdx : 指定感兴趣的样本。描述同上。
- params : SVM参数。
(2)训练函数
<1>作用:训练一个SVM
<2>训练函数的原型
C++: bool CvSVM:: train (const Mat& trainData ,
const Mat& responses ,const Mat& varIdx=Mat() ,const Mat& sampleIdx=Mat() ,CvSVMParams params=CvSVMParams())
C++: bool CvSVM:: train (const CvMat* trainData ,
const CvMat* responses ,const CvMat* varIdx=0 ,const CvMat* sampleIdx=0 ,CvSVMParams params=CvSVMParams())
<3>训练函数的参数注释(5个)
和构造函数的参数是一样的,请参考构造函数的参数注释。
(3)自动训练函数
<1>作用: 根据可选参数训练一个SVM。
<2>自动训练函数原型
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)
C++: bool CvSVM:: train_auto (const CvMat * trainData ,
const CvMat * responses ,const CvMat * varIdx ,const CvMat * sampleIdx ,CvSVMParams params ,int kfold=10 ,CvParamGrid Cgrid=get_default_grid(CvSVM::C) ,CvParamGrid gammaGrid=get_default_grid(CvSVM::GAMMA) ,CvParamGrid pGrid=get_default_grid(CvSVM::P) ,CvParamGrid nuGrid=get_default_grid(CvSVM::NU) ,CvParamGrid coeffGrid=get_default_grid(CvSVM::COEF) ,CvParamGrid degreeGrid=get_default_grid(CvSVM::DEGREE) ,bool balanced=false)
<3>自动训练函数的参数注释(13个)
- 前5个参数参考 构造函数的参数注释。
- k_fold: 交叉验证参数。训练集被分成k_fold的自子集。其中一个子集是用来测试模型,其他子集则成为训练集。所以,SVM算法复杂度是执行k_fold的次数。
- *Grid: (6个) 对应的SVM迭代网格参数。
- balanced: 如果是true则这是一个2类分类问题。这将会创建更多的平衡交叉验证子集。
<4>自动训练函数的使用说明
- 这个方法根据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。
<5>网格搜索法+K交叉验证
上述使用说明是OpenCV使用文档中的,这里再加其他一些补充:
A、可参考文章《 SVM分类核函数和参数选择比较 》《 基于改进的网格搜索法的SVM参数优化 》《LibSVM分类的实用指南》《 libsvm交叉验证与网格搜索(参数选择) 》,讲到了K交叉验证和网格搜索法。
B、优化参数的方式一般是用网格搜索法取值,然后对这组参数进行K交叉验证,计算精确值(交叉验证的准确率等于能够被正确分类的数量百分比),寻求最优参数。
(4)预测函数
<1>作用 :对输入样本做预测响应。
<2>预测函数的函数原型
C++: float CvSVM:: predict (const Mat& sample , bool returnDFVal=false ) const
C++: float CvSVM:: predict (const CvMat* sample , bool returnDFVal=false ) const
C++: float CvSVM:: predict (const CvMat* samples , CvMat* results ) const
<3>预测函数的参数注释
- sample: 需要预测的输入样本。
- samples: 需要预测的输入样本们。
- returnDFVal: 指定返回值类型。如果值是true,则是一个2类分类问题,该方法返回的决策函数值是边缘的符号距离。
- results: 相应的样本输出预测的响应。
<4>预测函数的使用说明
- 这个函数用来预测一个新样本的响应数据(response)。
- 在分类问题中,这个函数返回类别编号;在回归问题中,返回函数值。
- 输入的样本必须与传给trainData的训练样本同样大小。
- 如果训练中使用了varIdx参数,一定记住在predict函数中使用跟训练特征一致的特征。
- 后缀const是说预测不会影响模型的内部状态,所以这个函数可以很安全地从不同的线程调用。
(5)生成SVM网格参数的函数
<1>作用 : 生成一个SVM网格参数。
<2>函数原型
C++: CvParamGrid CvSVM:: get_default_grid (int param_id )
<3>函数的参数注释
- param_id: SVM参数的IDs必须是下列中的一个:(网格参数将根据这个ID生成 )
- CvSVM::C
- CvSVM::GAMMA
- CvSVM::P
- CvSVM::NU
- CvSVM::COEF
- CvSVM::DEGREE
<4>函数的使用说明
该函数生成一个指定的SVM网格参数,主要用于传递给自动训练函数
CvSVM::train_auto()。
(6)获取当前SVM参数的函数
<1>作用:获取当前SVM参数
<2>函数原型:
C++: CvSVMParams CvSVM:: get_params() const
<3>函数的使用说明
这个函数主要是在使用CvSVM::train_auto()时去获得最佳参数。
(7)获取支持向量及其数量的函数
<1>作用 :获取支持向量及其数量
<2>函数原型 :
C++: int CvSVM:: get_support_vector_count () const //获取支持向量的数量
C++: const float* CvSVM:: get_support_vector (int i ) const //获取支持向量
参数: i – 指定支持向量的索引。
(8)获取所用特征的数量的函数
<1>作用 :获取所用特征的数量
<2>函数原型:
C++: int CvSVM:: get_var_count () const
三、OpenCV的简单的程序例子
1、 Introduction to Support Vector Machines (可参考 【OpenCV2.4】SVM的参数和函数介绍 )
上述讲述了处理一个 线性可分情况 的例子,包含了SVM使用的几个步骤:
(1)准备训练样本及其类别标签( trainingDataMat,labelsMat )
(2)设置训练参数(CvSVMParams)
(3)对SVM进行训练( CvSVM:: train)
(4)对新的输入样本进行预测( CvSVM:: predict)
(5)获取支持向量( CvSVM:: get_support_vector_count ,CvSVM::get_support_vector )
2、 Support Vector Machines for Non-Linearly Separable Data (可参考 【OpenCV2.4】SVM处理线性不可分的例子 )
上述讲述了处理一个 线性不可分情况 的例子,着重讲述了 惩罚因子C 的作用:
- C比较大时:分类错误率较小,但是间隔也较小。 在这种情形下, 错分类对模型函数产生较大的影响,既然优化的目的是为了最小化这个模型函数,那么错分类的情形必然会受到抑制。
- C比较小时:间隔较大,但是分类错误率也较大。 在这种情形下,模型函数中错分类之和这一项对优化过程的影响变小,优化过程将更加关注于寻找到一个能产生较大间隔的超平面。
换而言之,C越大,优化时越关注错分问题;C越小,越关注能否产生一个较大间隔的超平面。
由于样本非线性可分, 自然就有一些被错分类的样本。
3、多分类的简单例子 (可参考 利用SVM解决2维空间向量的3级分类问题 )
上述讲述了一个三分类的例子,核函数用了RBF,并用到了其参数gamma,以及惩罚因子C,训练与预测和二分类一样,只要对样本赋予第三类的类别标签。
4、文字识别的简单例子 (可参考 SVM对文字识别的简单使用 和 使用OPENCV训练手写数字识别分类器 )
训练与预测的使用方法和上述一样,主要看下对图像数据的处理(简单的特征提取)。
5、HOG+SVM的例子 (可参考 OpenCV中的HOG+SVM物体分类 和 利用HOG+SVM训练自己的XML文件 )
训练与预测的使用方法还是和上述一样,主要看下Hog特征的使用( HOGDescriptor::compute )。
四、SVM处理流程总结:
1、收集数据 , 相关性分析 (比如p卡方检验), 特征选择 (比如主成份分析PCA)。
2、归一化数据 :就是根据实际要求,将数据的取值范围转化为统一的区间如[a,b],a,b为整数。(参考 缩放训练和测试数据时的常见错误 [附录B])
3、分训练集和测试集 :利用抽样技术将数据集分为训练集和测试集。抽样技术有分层抽样,简单抽样(等概率抽样)。
一般训练集数量大于测试集数量 ,就是要保证足够的训练样例。
4、将数据转化为软件(接口)所支持的格式 。
5、选择核函数 ,可以优先考虑RBF。
6、使用交叉验证(cross-validation)寻找最佳参数C和Υ: 对训练集利用交叉验证法选择最好的参数C和r(西格玛)(RBF核函数中的参数gama)。可以通过网格法寻找出最优的参数,注意一次交叉验证得到一个参数对所对应的模型精度,网格法目的就是找到使得模型精度达到对高的参数对(这里的参数对可能不止两个,有可能也有其他的),可以使用一些启发式的搜索来降低复杂度,虽然这个方法笨了点,但是它能得到很稳定的搜索结果。需要提到的这里在对训练集进行分割的时候涉及到抽样,一个较好的方法就是分层抽样。从这步可以看出其实
Cross-Validation是一种评估算法的方法。
a. 训练的目的得到参数和支持向量(存储在xml文件中),得到参数就能得到支持向量,带进算式计算SVM分类的准确度,以准确度最高的一组参数作为最终的结果,没有绝对线性可分的,都有一个误差,参数就是把那个误差降到最低。
b. 这里的准确性是指将训练集的每个样本的向量与支持向量做运算,将运算结果与标记值比较,判断是否属于这个类,统计这个类的正确的样本数,最高的那一组参数准确性最高。
c. 最终训练得到分类器。SVM只能分两类,所以这里的分类器是两个类组成一个分类器,如果有K类,就有k(k-1)/2个分类器。
7、使用最佳参数C和Υ来训练整个训练集: 用6中得到的参数对在整个训练集合上进行训练,从而得出模型。
8、测试: 利用测试集测试模型,得到精度。这个精度可以认为是模型最终的精度。当然有人会担心3步中抽样会有一定的误差,导致8得到的精度不一定是最好的,因此可以重复3-8得到多个模型的精度,然后选择最好的一个精度最为模型的精度(或者求所有精度的均值做为模型精度)。(需要多次选择训练集和测试集,然后每一次得到一个精度的模型,选择最好的一个精度作为模型,也就是我们项目里面要多次训练的原因)。
9. 识别分类: 两个类超平面的形成,意味着目标函数的形成,然后代入待识别样本,识别时对应的组代入对应的参数,得出结果进行投票,判定属于那个类。
SVM样本训练步骤
1、引言
近期在做飞形体目标识别的研究,需要做SVM训练来生成识别的分类器。从网上找了大量的参考文章,但是发现很多文章都讲的比较零散。鉴于此原因,本文对SVM训练过程做一个较为系统的总结,希望对广大初学者有所帮助。
2、步骤
(1)生成SVM描述文件;
将需要训练的样本文件的路径和对应的分类类别号写入txt文档,如:
plane/飞机训练正样本Normalize/0.jpg 1 plane/飞机训练正样本Normalize/1.jpg 命名为:SVM_DATA.txt 1
(2)将描述文件读入容器中;
定义两个容器,用于保存样本路径和分类标号,如: vector<string> img_path; vector<int> img_catg; 读入数据: int nLine = 0; string buf; ifstream svm_data( "SVM_DATA.txt" ); while( svm_data ) { if( getline( svm_data, buf) ) /*原型 istream& getline ( istream &is , string &str , char delim ); istream& getline ( istream& , string& ); 参数 is 进行读入操作的输入流 str 存储读入的内容 delim 终结符 返回值 与参数is是一样的 功能 将输入流is中读到的字符存入str中,直到遇到终结符delim才结束。 对于第一个函数delim是可以由用户自己定义的终结符;对于第二个函数delim默认为 '\n'(换行符)。 函数在输入流is中遇到文件结束符(EOF)或者在读入字符的过程中遇到错误都会结束。 在遇到终结符delim后,delim会被丢弃,不存入str中。在下次读入操作时,将在delim的下个字符开始读入。*/ { nLine ++; if( nLine % 2 == 0 ) { img_catg.push_back( atoi( buf.c_str() ) );//atoi将字符串转换成整型,值为0或1 用0,1区分正负样本 //功 能: 把字符串转换成整型数。 名字来源:array to integer 的缩写。 //原型: int atoi(const char *nptr); //函数说明: 参数nptr字符串,如果第一个非空格字符不存在或者不是数字也不是正负号则返回零,否则开始做类型转换, //之后检测到非数字(包括结束符 \0) 字符时停止转换,返回整型数。 // 函数声明:const char *c_str(); c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同. } else { img_path.push_back( buf );//图像路径 } } } svm_data.close();//关闭文件
(3)读入样本数量,生成样本矩阵和类型矩阵
CvMat *data_mat, *res_mat; int nImgNum = nLine / 2; //读入样本数量 ////样本矩阵,nImgNum:横坐标是样本数量, WIDTH * HEIGHT:样本特征向量,即图像大小 data_mat = cvCreateMat( nImgNum, 144, CV_32FC1 ); cvSetZero( data_mat ); //类型矩阵,存储每个样本的类型标志 res_mat = cvCreateMat( nImgNum, 1, CV_32FC1 ); cvSetZero( res_mat );
(4)读入样本图像
IplImage* src; IplImage* trainImg=cvCreateImage(cvSize(64,64),8,3);//需要分析的图片 for( string::size_type z = 0; z != img_path.size(); z++ ) //整体循环为z { src=cvLoadImage(img_path[z].c_str(),1); // 函数声明:const char *c_str();c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同. if( src == NULL ) { cout<<" can not load the image: "<<img_path[z].c_str()<<endl; continue; } cout<<" processing "<<img_path[z].c_str()<<endl;
(5)提取HOG特征
//以下为提取Hog特征 cvResize(src,trainImg); //读取图片,归一化大小 HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(16,16),cvSize(16,16),9); vector<float>descriptors;//结果数组 hog->compute(trainImg, descriptors,Size(8,8), Size(0,0)); //调用计算函数开始计算 cout<<"HOG dims: "<<descriptors.size()<<endl; //CvMat* SVMtrainMat=cvCreateMat(descriptors.size(),1,CV_32FC1); n=0; for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++) //迭代器 { cvmSet(data_mat,z,n,*iter); //将HOG特征 存入data_mat矩阵中 x=cvmGet(data_mat,z,n); cout<<"hog"<<x<<endl; n++; }
(6)将HOG特征写入txt文件
FILE *fp1; int i,j; if((fp1=fopen("Hog.txt","ab"))==NULL)// 读写打开一个二进制文件,允许读或在文件末追加数据。 { printf("can not open the hu file\n"); exit(0);//正常退出程序 } for (i = 0; i <144; ++i) { fprintf(fp1,"%lf ",descriptors[i]); } //fprintf(fp1,"\r\n"); fclose(fp1); cvmSet( res_mat, z, 0, img_catg[z] ); //将正负样本标记存入矩阵res_mat中 cout<<" end processing "<<img_path[z].c_str()<<" "<<img_catg[z]<<endl; }
(7)进行SVM训练
CvSVM svm = CvSVM(); CvSVMParams param; CvTermCriteria criteria; criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON ); param = CvSVMParams( CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria ); /* SVM种类:CvSVM::C_SVC Kernel的种类:CvSVM::RBF degree:10.0(此次不使用) gamma:8.0 coef0:1.0(此次不使用) C:10.0 nu:0.5(此次不使用) p:0.1(此次不使用) 然后对训练数据正规化处理,并放在CvMat型的数组里。 */ //SVM学习 svm.train( data_mat, res_mat, NULL, NULL, param ); //利用训练数据和确定的学习参数,进行SVM学习 svm.save( "SVM_DATA1.xml" ); cvReleaseImage(&src); cvReleaseMat( &data_mat ); cvReleaseMat( &res_mat ); return 0;
在以上训练过程中,要特别注意的是在创建样本矩阵的时候,其矩阵大小由样本数量和样本提取的特征维数决定的。比如上面创建的样本矩阵大小为:
int nImgNum = nLine / 2; 行,144列; 144是由提取HOG特征时,由窗口大小、块大小、胞元大小和每个抱怨大小中的特征数共同决定的。