opencv7-ml之svm
这部分要特别说明:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html#introductiontosvms或者是《opencv即使是3.0tutorials》中都还是使用的CvSVM::train这样的函数,但是在对应的《opencv2refman,3.0》中并没有这个,而是改成了SVM类了,在2.4.10中还是保留的CvSVM的,也就是这里3.0之前和之后得区别对待一下。
这里还是先介绍CvSVM的版本,毕竟手头上有2.4.10的,当然也在装3.0版本的,看着到时候在直接上3.0beta的SVM版本的。下面的都是照搬的,http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html#introductiontosvms。先照搬,然后理解他们 ,接着在加上自己的一些想法
一、svm的介绍支持向量机 (SVM) 是一个类分类器,正式的定义是一个能够将不同类样本在样本空间分隔的超平面。 换句话说,给定一些标记(label)好的训练样本 (监督式学习), SVM算法输出一个最优化的分隔超平面。
如何来界定一个超平面是不是最优的呢? 考虑如下问题:假设给定一些分属于两类的2维点,这些点可以通过直线分割, 我们要找到一条最优的分割线.
在上面的图中, 你可以直觉的观察到有多种可能的直线可以将样本分开。 那是不是某条直线比其他的更加合适呢? 我们可以凭直觉来定义一条评价直线好坏的标准:距离样本太近的直线不是最优的,因为这样的直线对噪声敏感度高,泛化性较差。 因此我们的目标是找到一条直线,离所有点的距离最远。
由此,SVM算法的实质是找出一个能够将某个值最大化的超平面(关于超平面的更加详细的说明可以参考T. Hastie, R. Tibshirani 和 J. H. Friedman的书籍 Elements of Statistical Learning , section 4.5 (Seperating Hyperplanes)。),这个值就是超平面离所有训练样本的最小距离。这个最小距离用SVM术语来说叫做 间隔(margin) 。 概括一下,最优分割超平面 最大化 训练数据的间隔。
下面的公式定义了超平面的表达式:
叫做权重向量, 叫做偏置。最优超平面可以有无数种表达方式,即通过任意的缩放和,习惯上我们使用以下方式来表达最优超平面:
式中 x 表示离超平面最近的那些点。 这些点被称为 支持向量**。 该超平面也称为 **canonical 超平面.通过几何学的知识,我们知道点 x 到超平面(,) 的距离为:
特别的,对于 canonical 超平面, 表达式中的分子为1,因此支持向量到canonical 超平面的距离是:
刚才我们介绍了间隔(margin),这里表示为 M ,
它的取值是最近距离的2倍:
最后最大化
M 转化为在附加限制条件下最小化函数L() 。
限制条件隐含超平面将所有训练样本 xi 正确分类的条件,
式中 yi 表示样本的类别标记。这是一个拉格朗日优化问题,可以通过拉格朗日乘数法得到最优超平面的权重向量 和偏置 。
上面的是例子程序,从注释中就能够看出,在opencv中训练一个svm是相当容易和简单的,不过这是否与封装的好,还是所支持的内容有限有关就不知道了,不过它是基于libsvm的,不会差,至于libsvm的实现算法,好像听说新版和旧版的不一样,不过这都是核心的地方,不了解也没关系。
CvSVMParams结构体(在3.0版本的refman中说的是类,虽然在cpp中两者只有默认权限的差别,不过还是可以看出3.0更加往着cpp方向实现了)
是svm的训练参数。该结构必须被初始化并传递给CVSVM的训练方法
构造函数原型:CvSVMParams::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);
参数:
svm_type:svm公式的类型,可能的值为:
– CvSVM::C_SVC :C-支持向量分类,n-类 分类(n>=2),允许使用惩罚乘数C对离群点进行不完美分离的惩罚。
– CvSVM::NU_SVC :--支持向量分类,有着可能的不完美分离的n-类分类。参数(范围为0..1,更大的值表示决策面更加的平滑)用来代替上面的C。
– CvSVM::ONE_CLASS :分布估计(单类svm)。所有的训练数据都来自于同一个类别,svm构建一个边界用来将这个类与剩下的其他所有训练数据在特征空间中分隔开。
– CvSVM::EPS_SVR :支持向量回归。介于来自训练集的特征向量与所拟合的超平面之间的距离必须小于 p 。对于离群点来说,会使用惩罚乘数C。
– CvSVM::NU_SVR :支持向量回归,用来代替 p。
见 【libsvm】参考更详细的说明
kernel_type :svm核函数类型。可能的值为:
– CvSVM::LINEAR 线性核函数。没有映射操作,只是在最初的特征空间中使用线性决策(或者是回归)。这是最快的选择。(当然结果。。)
– CvSVM::POLY 多项式核函数:
– CvSVM::RBF 径向基函数,在大多数情况下都是一个很好的选择,
– CvSVM::SIGMOID sigmoid核函数,
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)的参数;
class_weights :在C_SVC问题中可选的权重,指定给具体的类别。这样得到的结果就是class#i 变成了.所以这些权重是影响着不同类别的错误分类惩罚的程度的。更大的权值表示在相对的类的错误分类数据的更大的惩罚。
term_crit :解决受限二次优化问题的部分情况下迭代svm训练过程的终止条件。你可以指定tolerance 和/或者 最大迭代次数。
该默认构造函数的初始化内容是:
CvSVMParams::CvSVMParams() : <span style="white-space:pre"> </span>svm_type(CvSVM::C_SVC), kernel_type(CvSVM::RBF), degree(0), <span style="white-space:pre"> </span>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 ); }终止条件结构函数:
CV_INLINE CvTermCriteria cvTermCriteria( int type, int max_iter, double epsilon ) { CvTermCriteria t; t.type = type; t.max_iter = max_iter; t.epsilon = (float)epsilon; return t; }该函数是内联函数,返回的值为CvTermCriteria结构体。看得出该函数还是c接口想使用c语言来模拟面向对象的结构,其中的参数为:
type:
- CV_TERMCRIT_ITER 在当算法迭代次数超过max_iter的时候终止。
- CV_TERMCRIT_EPS 在当算法得到的精度低于epsolon时终止;
-CV_TERMCRIT_ITER+CV_TERMCRIT_EPS 当算法迭代超过max_iter或者当获得的精度低于epsilon的时候,哪个先满足就停止。
max_iter:
迭代的最大次数;
epsilon:
要求的精度
svm训练的函数原型:bool CvSVM::train(const Mat& trainData, const Mat& responses, const Mat& varIdx=Mat(), const Mat& sampleIdx=Mat(), CvSVMParams params=CvSVMParams() );
bool CvSVM::train(const CvMat* trainData, const CvMat* responses, const CvMat* varIdx=0, const CvMat* sampleIdx=0, CvSVMParams params=CvSVMParams() );
该方法是用来训练SVM模型的,它与通常的CvStatModel::train()(该函数为统计模型部分的一个函数,后面的不同分类器训练函数都与该函数类似,这里并没有仔细介绍该函数,所以下面的参数暂时没介绍,下面的约束条件帮助有限,留待我更新统计模型部分的介绍,然后引过来)有着一样便利的约束条件:
a、只有CV_ROW_SAMPLE的数据分布才被支持;
b、输入的变量都是有序的
c、输出的变量可以是分类的((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)。
参数列表:
d、不支持缺省的测量方法;
所有的其他参数都在CvSVMParams结构体中。
svm预测的函数原型: 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;
参数列表:
sample:为了预测的输入样本;
samples:为了预测的输入的一群样本;
returnDFVal :指定返回值的类型。如果为true,而且问题是2分类,那么该方法返回的与边缘之间有符号距离的决策函数的值;不然该函数返回一个类别标签(分类)或者估计函数值(回归)。
return :输出有关样本群的预测响应值。(就是针对许多样本得到的预测结果)
如果你传递一个样本那么就得到一个预测结果。如果你想传递几个样本那么急救该传递给results一个矩阵用来保存预测的结果。
得到支持向量的个数和某个支持向量的函数原型:int CvSVM::get_support_vector_count() const
const float* CvSVM::get_support_vector(int i) const
参数列表: i 用来索引某个具体的支持向量。
第一个函数返回支持向量的个数;第二个可以返回某个支持向量。
赠送一个,返回变量个数的函数原型:int CvSVM::get_var_count() const
该函数返回特征的数量(也就是变量的个数)
二、支持向量机对线性不可分数据的处理
为什么需要将支持向量机优化问题扩展到线性不可分的情形? 在多数计算机视觉运用中,我们需要的不仅仅是一个简单的SVM线性分类器, 我们需要更加强大的工具来解决 训练数据无法用一个超平面分割 的情形。我们以人脸识别来做一个例子,训练数据包含一组人脸图像和一组非人脸图像(除了人脸之外的任何物体)。 这些训练数据超级复杂,以至于为每个样本找到一个合适的表达 (特征向量) 以让它们能够线性分割是非常困难的。
还记得我们用支持向量机来找到一个最优超平面。 既然现在训练数据线性不可分,我们必须承认这个最优超平面会将一些样本划分到错误的类别中。 在这种情形下的优化问题,需要将 错分类(misclassification) 当作一个变量来考虑。新的模型需要包含原来线性可分情形下的最优化条件,即最大间隔(margin), 以及在线性不可分时分类错误最小化。
还是从最大化 间隔 这一条件来推导我们的最优化问题的模型(这在上面 已经讨论了):
在这个模型中加入错分类变量有多种方法。比如,我们可以最小化一个函数,该函数定义为在原来模型的基础上再加上一个常量乘以样本被错误分类的次数:
然而,这并不是一个好的解决方案,其中一个原因是它没有考虑错分类的样本距离同类样本所属区域的大小。
因此一个更好的方法是考虑 错分类样本离同类区域的距离:
这里为每一个样本定义一个新的参数 ,
这个参数包含对应样本离同类区域的距离。 下图显示了两类线性不可分的样本,以及一个分割超平面和错分类样本距离同类区域的距离。
红色和蓝色直线表示各自区域的边际间隔, 每个 表示从错分类样本到同类区域边际间隔的距离。
最后我们得到最优问题的最终模型:
关于参数C的选择, 明显的取决于训练样本的分布情况。 尽管并不存在一个普遍的答案,但是记住下面几点规则还是有用的:
a、C比较大时分类错误率较小,但是间隔也较小。 在这种情形下, 错分类对模型函数产生较大的影响,既然优化的目的是为了最小化这个模型函数,那么错分类的情形必然会受到抑制。
b、C比较小时,间隔较大,但是分类错误率也较大。在这种情形下,模型函数中错分类之和这一项对优化过程的影响变小,优化过程将更加关注于寻找到一个能产生较大间隔的超平面。
(这里的两个例子我都直接复制了完整的代码,而没有拆分成重要的函数说说,就是为了能更加的好理解,拆分成简单的几个函数是为了方便记忆,这里主要还是为了理解。后续修改3.0版本的时候有可能会删减!!)
用随机数来填充数组的函数原型:void RNG::fill(InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange= false );参数列表:输入输出矩阵、分布类型、第一个分布的参数、第二个分布参数、饱和范围;
第一个参数:2D或者N维矩阵;当前的不支持超过4通道的矩阵,使用Mat::reshape()作为一个可能的解决方案;
第二个参数:分布蕾西,RNG::UNIFORM或者RNG::NORMAL.(即是均匀分布还是正太分布)
第三个参数:第一个分布参数;在均匀分布中,这是一个包含的下限值;在正太分布中,这是一个均值;
第四个参数:第二个分布参数:在均匀分布中,这是一个不包含的上限值;在正太分布中,这是一个标准差(标准差矩阵的对角线或者 全标准差矩阵,此处疑问,是两个矩阵的对角线还是前面那个矩阵的对角线???)。
第五个参数:预饱和标识;只对均匀分布有效;如果该值为true,该方法会首先将a和b转换成可接受的值的范围(按照mat的数据类 型)然后用范围【saturate(a),saturate(b))来生成均匀分布的随机值;如果该值为false,该方法会首先按照原 始的范围【a,b)生成均匀分布随机值,然后进行饱和操作,也就是说,例如RNG().fill(mat_8u, RNG::UNIFORM, -DBL_MAX, DBL_MAX),将会生成一个数组差不多用0和255填满的数组,因为范围(0,255)明显小于 【-DBL_MAX, DBL_MAX)。
每个使用随机值来填充矩阵的方法都来自特定的分布。RNG的状态也是按照新生成的值更新的。在多通道图像的情况中,每个通道都是独立填充的,也就是说RNG不会直接从非对焦协方差矩阵的多维度高斯分布中采样。所以该方法是从有着0均值和单一协方差矩阵的多维度标准高斯分布中进行采样的,然后接着使用ransform()来转换生成具体的高斯分布的样本值。
对一个矩阵头生成具体的行范围的函数原型:Mat Mat::rowRange(int startrow, int endrow) const
Mat Mat::rowRange(const Range& r) const;
参数列表:
startrow: 一个基于0索引的行范围的开始,包含
endrow :一个基于0索引的行范围的结束,不包含
r :Range结构,用来包含开始和结束索引。
对一个矩阵头生成具体的列范围的函数原型:Mat Mat::colRange(int startcol, int endcol) const
Mat Mat::colRange(const Range& r) const;
参数列表:
startrow: 一个基于0索引的列范围的开始,包含
endrow :一个基于0索引的列范围的结束,不包含
r :Range结构,用来包含开始和结束索引。
上面两个方法都是用来对一个新的矩阵头指定需要生成的行列区域,相似于Mat::row()和Mat::col(),而且复杂度是O(1).
相对于最上面的终止方法,的类class TermCriteria:
class CV_EXPORTS TermCriteria{ public: enum{ <span style="white-space:pre"> </span>COUNT=1, //!< the maximum number of iterations or elements to compute <span style="white-space:pre"> </span>MAX_ITER=COUNT, //!< ditto <span style="white-space:pre"> </span>EPS=2 //!< the desired accuracy or change in parameters at which the iterative algorithm stops }; //! default constructor <span style="white-space:pre"> </span>TermCriteria(); //! full constructor <span style="white-space:pre"> </span>TermCriteria(int type, int maxCount, double epsilon); //! conversion from CvTermCriteria <span style="white-space:pre"> </span>TermCriteria(const CvTermCriteria& criteria); //! conversion to CvTermCriteria <span style="white-space:pre"> </span>operator CvTermCriteria() const;
int type; //!< the type of termination criteria: COUNT, EPS or COUNT + EPS int maxCount; // the maximum number of iterations/elements double epsilon; // the desired accuracy };该类用来为迭代算法定义终止标准。你可以通过默认构造函数进行初始化然后重载任何参数,或者这个结构可以完全使用构造函数的高级变量来全部的初始化。
TermCriteria类的构造函数:TermCriteria::TermCriteria()
TermCriteria::TermCriteria(int type, int maxCount, double epsilon)
TermCriteria::TermCriteria(const CvTermCriteria& criteria)
参数列表:
type :终止标准的类型:TermCriteria::COUNT, TermCriteria::EPS 或者 TermCriteria::COUNT + TermCriteria::EPS;
maxCount:迭代或者计算元素的最大次数;
epsilon:在迭代算法停止是参数改变所需要达到的精度要求;
criteria:在弃用的CvTermCriteria格式中的终止条件。
转换到弃用的CvTermCriteria格式的函数原型:TermCriteria::operator CvTermCriteria() const (在3.0版本中已经移除该函数)