1、定义:
支持向量机 (SVM) 是一个类分类器,正式的定义是一个能够将不同类样本在样本空间分隔的超平面。 换句话说,给定一些标记(label)好的训练样本 (监督式学习), SVM算法输出一个最优化的分隔超平面。
如何来界定一个超平面是不是最优的呢? 考虑如下问题:
假设给定一些分属于两类的2维点,这些点可以通过直线分割, 我们要找到一条最优的分割线.
在这个示例中,我们考虑卡迪尔平面内的点与线,而不是高维的向量与超平面。 这一简化是为了让我们以更加直觉的方式建立起对SVM概念的理解, 但是其基本的原理同样适用于更高维的样本分类情形。
在上面的图中, 你可以直觉的观察到有多种可能的直线可以将样本分开。 那是不是某条直线比其他的更加合适呢? 我们可以凭直觉来定义一条评价直线好坏的标准:
距离样本太近的直线不是最优的,因为这样的直线对噪声敏感度高,泛化性较差。 因此我们的目标是找到一条直线,离所有点的距离最远。
由此, SVM算法的实质是找出一个能够将某个值最大化的超平面,这个值就是超平面离所有训练样本的最小距离。这个最小距离用SVM术语来说叫做 间隔(margin) 。 概括一下,最优分割超平面 最大化 训练数据的间隔。
2、超平面的定义:
下面的公式定义了超平面的表达式:
叫做 权重向量 , 叫做 偏置(bias) 。
最优超平面可以有无数种表达方式,即通过任意的缩放 和 。 习惯上我们使用以下方式来表达最优超平面
式中 表示离超平面最近的那些点。 这些点被称为 支持向量**。 该超平面也称为 **canonical 超平面.
通过几何学的知识,我们知道点 到超平面 的距离为:
特别的,对于 canonical 超平面, 表达式中的分子为1,因此支持向量到canonical 超平面的距离是:
刚才我们介绍了间隔(margin),这里表示为 , 它的取值是最近距离的2倍:
最后最大化 转化为在附加限制条件下最小化函数 。 限制条件隐含超平面将所有训练样本 正确分类的条件,
式中 表示样本的类别标记。
这是一个拉格朗日优化问题,可以通过拉格朗日乘数法得到最优超平面的权重向量 和偏置 。
3、参数含义: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)
<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的迭代训练过程的中止条件,解决部分受约束二次最优问题。您可以指定的公差和/或最大迭代次数。
4、训练SVM
调用CvSVM::train函数建立SVM模型,第一个参数为训练数据,第二个参数为分类结果,最后一个参数即CvSVMParams
5、SVM进行分类
调用函数CvSVM::predict实现分类 (注意数据矩阵的数据类型,在《学习Opencv》第514页中有介绍)
6、获得支持向量
除了分类,也可以得到SVM的支持向量,调用函数CvSVM::get_support_vector_count获得支持向量的个数,CvSVM::get_support_vector获得对应的索引编号的支持向量(离超平面最近的点)。
7、例子
1 #include "stdafx.h" 2 #include "opencv2/opencv.hpp" 3 using namespace std; 4 5 int main(int argc, char **argv) 6 { 7 int size = 400; //图像的长度和宽度 8 const int s = 1000; //试验点个数(可更改!!) 9 int i, j, sv_num; 10 IplImage *img; 11 CvSVM svm = CvSVM(); 12 CvSVMParams param; 13 CvTermCriteria criteria;//停止迭代的标准 14 CvRNG rng = cvRNG(); 15 CvPoint pts[s]; //定义1000个点 16 float data[s*2]; //点的坐标 17 int res[s]; //点的所属类 18 CvMat data_mat, res_mat; 19 CvScalar rcolor; 20 const float *support; // (1)图像区域的确保和初始化 21 img= cvCreateImage(cvSize(size, size), IPL_DEPTH_8U, 3); 22 cvZero(img); //确保画像区域,并清0(用黑色作初始化处理)。 23 // (2)学习数据的生成 24 for (i= 0; i< s; i++) 25 { 26 pts[i].x= cvRandInt(&rng) % size; //用随机整数赋值 27 pts[i].y= cvRandInt(&rng) % size; 28 if (pts[i].y> 50 * cos(pts[i].x* CV_PI/ 100) + 200) //注意运算符的优先级 29 { 30 cvLine(img, cvPoint(pts[i].x- 2, pts[i].y- 2), cvPoint(pts[i].x+ 2, pts[i].y+ 2), CV_RGB(255, 0, 0)); 31 cvLine(img, cvPoint(pts[i].x+ 2, pts[i].y- 2), cvPoint(pts[i].x- 2, pts[i].y+ 2), CV_RGB(255, 0, 0)); 32 res[i] = 1; 33 } 34 else 35 { 36 if (pts[i].x> 200) 37 { 38 cvLine(img, cvPoint(pts[i].x- 2, pts[i].y- 2), cvPoint(pts[i].x+ 2, pts[i].y+ 2), CV_RGB(0, 255, 0)); 39 cvLine(img, cvPoint(pts[i].x+ 2, pts[i].y- 2), cvPoint(pts[i].x- 2, pts[i].y+ 2), CV_RGB(0, 255, 0)); 40 res[i] = 2; 41 } 42 else 43 { 44 cvLine(img, cvPoint(pts[i].x- 2, pts[i].y- 2), cvPoint(pts[i].x+ 2, pts[i].y+ 2), CV_RGB(0, 0, 255)); 45 cvLine(img, cvPoint(pts[i].x+ 2, pts[i].y- 2), cvPoint(pts[i].x- 2, pts[i].y+ 2), CV_RGB(0, 0, 255)); 46 res[i] = 3; 47 } 48 } 49 } //生成2维随机训练数据,并将其值放在CvPoint数据类型的数组pts[ ]中。 50 // (3)学习数据的显示 51 cvNamedWindow("SVM", CV_WINDOW_AUTOSIZE); 52 cvShowImage("SVM", img); 53 cvWaitKey(0); 54 // (4)学习参数的生成,归一化处理 55 for (i= 0; i< s; i++) 56 { 57 data[i* 2] = (float) pts[i].x / size; //注意强制数据类型转化,i与float结合成为float型,再进行除操作 58 data[i* 2 + 1] = (float) pts[i].y / size; 59 } 60 cvInitMatHeader(&data_mat, s, 2, CV_32FC1, data); 61 cvInitMatHeader(&res_mat, s, 1, CV_32SC1, res); //标签 62 criteria= cvTermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON); 63 param= CvSVMParams (CvSVM::C_SVC, CvSVM::RBF, 10.0, 8.0, 1.0, 10.0, 0.5, 0.1, NULL, criteria); 64 /* 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型的数组里。 */ 65 //(5)SVM学习 66 svm.train(&data_mat, &res_mat, NULL, NULL, param); 67 //利用训练数据和确定的学习参数,进行SVM学习 68 //(6)学习结果的绘图 69 for (i= 0; i< size; i++) 70 { 71 for (j= 0; j< size; j++) 72 { 73 CvMat m; 74 float ret = 0.0; 75 float a[] = { (float) i / size, (float) j / size }; //获取坐标值 76 cvInitMatHeader(&m, 1, 2, CV_32FC1, a); 77 ret= svm.predict(&m); 78 switch ((int) ret) 79 { 80 case 1: rcolor= CV_RGB(100, 0, 0); 81 break; 82 case 2: rcolor= CV_RGB(0, 100, 0); 83 break; 84 case 3: rcolor= CV_RGB(0, 0, 100); 85 break; 86 } 87 cvSet2D(img,j,i,rcolor); 88 } 89 } //为了显示学习结果,通过输入图像区域的所有像素(特征向量)并进行分类。然后对输入像素用所属等级的颜色绘图。 90 cvNamedWindow("SVM", CV_WINDOW_AUTOSIZE); 91 cvShowImage("SVM", img); 92 cvWaitKey(0); 93 // (7)训练数据的再绘制 94 for (i= 0; i< s; i++) 95 { 96 CvScalar rcolor; 97 switch (res[i]) 98 { 99 case 1: rcolor= CV_RGB(255, 0, 0); 100 break; 101 case 2: rcolor= CV_RGB(0, 255, 0); 102 break; 103 case 3: rcolor= CV_RGB(0, 0, 255); 104 break; 105 } 106 cvLine(img, cvPoint(pts[i].x- 2, pts[i].y- 2), cvPoint(pts[i].x+ 2, pts[i].y+ 2), rcolor); 107 cvLine(img, cvPoint(pts[i].x+ 2, pts[i].y- 2), cvPoint(pts[i].x- 2, pts[i].y+ 2), rcolor); 108 } //将训练数据在结果图像上重复的绘制出来。 109 // (8)支持向量的绘制 110 sv_num= svm.get_support_vector_count(); 111 for (i= 0; i< sv_num; i++) 112 { 113 support = svm.get_support_vector(i); 114 cvCircle(img, cvPoint((int) (support[0] * size), (int) (support[1] * size)), 5, CV_RGB(200, 200, 200)); 115 } //用白色的圆圈对支持向量作标记。 116 // (9)图像的显示 117 cvNamedWindow("SVM", CV_WINDOW_AUTOSIZE); 118 cvShowImage("SVM", img); 119 cvWaitKey(0); 120 cvDestroyWindow("SVM"); 121 cvReleaseImage(&img); 122 return 0; //显示实际处理结果的图像,直到某个键被按下为止。 123 }