主要来自OpenCV教程
1、什么是支持向量机SVM
支持向量机是一个分类器,正式的定义是一个能够将不同类样本在样本空间分隔的超平面,换句话说,给定一些标记(label)好的训练样本,SVM算法输出一个最优化的分隔超平面。
如何来界定一个超平面是不是最优的:
假设给定一些分别属于两类的2维点,这些点可以通过直线分隔,我们要找到一条最优的分割线。
在上面的图中,你可以直觉的观察到有多重可能的直线可以将样本分开,也可以凭直觉来定义一条评价直线好坏的标准:
距离样本太近的直线不是最优的,因为这样的直线对噪声敏感度高,泛化性差。因此要找到一条直线,离所有的点的距离最远。由此,SVM算法的实质是找出一个能够将某个值最大化的超平面,这个值就是超平面离所有训练样本的最小距离。这个最小距离用SVM术语来说叫做间隔。概括说就是最优分割超平面,最大化训练数据的间隔。
2、如何计算最优超平面
公式一定义了超平面的表达式:
叫做权重向量 , 叫做偏置(bias)。
最优超平面可以有无数种表达方式,即通过任意的缩放和。习惯上使用公式二来表达最优超平面
式中x表示离超平面最近的那些点。这些点被称为支持向量。
通过几何学的知识,我们知道点x到超平面(,)的距离为:
特别的是,对于该超平面,表达式中的分子为1,因此支持向量到该超平面的距离是:
刚才介绍的间隔,这里表示为M,它的取值是最近距离的2倍:
最后最大化M转化为附加限制条件下最小化寒素。限制条件隐含超平面将所有训练样本正确分类的条件
式中表示样本的类别标记。
这是一个拉格朗日优化问题,可以通过拉格朗日乘数法得到最优超平面的权重向量 和偏置。
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
//数据可视化表示
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
//设置训练数据
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);
//设置参数
CvSVMParams params;
params.svm_type = CvSVM::C_SVC; //n(n>2)的分类器,其中参数C是异常惩罚因子,可以进行不完全分类
//params.svm_type = CvSVM::NU_SVC; //n(n>2)类似不完全分类的分类器,参数nu(nu属于[0,1]),取代了C_SVC类型的异常惩罚因子C;
//params.svm_type = CvSVM::ONE_CLASS; //单分类器,用一个分界线对特征空间进行分割。
params.kernel_type = CvSVM::LINEAR; //线性核函数,此核函数在分类是速度最快,分类将在原始空间中完成;
//params.kernel_type = CvSVM::POLY; //多项式核
//params.kernel_type = CvSVM::RBF; //径向基核,对于大部分情况都是选择此类型
//params.kernel_type = CvSVM::SIGMOID; //sigmoid核函数
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6); //SVM迭代终止条件,CV_TERMCRIT_ITER为终止条件类型,100为最大迭代次数,1e-6为结果的准确率
//训练SVM
CvSVM SVM;
SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
Vec3b green(0,255,0), blue(255,0,0);
//显示SVM划分出的决测区域
for(int i = 0; i<image.rows; i++)
{
for(int j=0; j<image.cols; j++)
{
Mat sampleMat = (Mat_<float>(1,2) << i, j); //待预测的图像
float response = SVM.predict(sampleMat);
if(response == 1)
image.at<Vec3b>(j,i) = green;
else if(response == -1)
image.at<Vec3b>(j,i) = blue;
}
}
//显示训练数据,即那四个点
int thickness = -1;
int lineType = 8;
cv::circle(image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness, lineType);
cv::circle(image, Point(255, 10), 5, Scalar(255,255,255), thickness, lineType);
cv::circle(image, Point(501, 255), 5, Scalar(255,255,255), thickness, lineType);
cv::circle(image, Point(10, 501), 5, Scalar(255,255,255), thickness, lineType);
//显示支持的向量
thickness = 2;
lineType = 8;
int c = SVM.get_support_vector_count();
for(int i=0; i<c; i++)
{
const float* v = SVM.get_support_vector(i);
circle(image, Point( (int)v[0], (int)v[1] ), 6, Scalar(128,128,128), thickness, lineType);
}
imwrite("result.png", image);
imshow("SVM Simple Example", image);
cv::waitKey(0);
}
程序创建了一张图像,在其中显示了训练样本,即图中的四个点,分成了两类,其中一个类显示为白色圆圈,另一个类显示为黑色圆圈。
训练得到SVM,并将图像的每一个像素分类,分类的结果将图像分为蓝绿两部分,中间线就是最优分割超平面。
最后将支持向量通过灰色边框加重显示,即那四个训练样本点。