SVM简介
目标
在本教程中,您将学习如何:
- 使用OpenCV函数cv :: ml :: SVM :: train构建基于SVMs和cv :: ml :: SVM :: predict的分类器来测试其性能。
什么是SVM?
Support Vector Machines(SVM)是由分离超平面正式定义的鉴别分类器。换句话说,给定的训练数据(监督学习),算法输出了一个最优的超平面,对新的例子进行了分类。
在哪个意义上是超平面获得最优?让我们考虑以下简单问题:
对于属于两类之一的线性可分离的2D点组,找到分离直线。
在上图中,您可以看到有多条线路可以解决问题。他们中的任何一个比别人好吗?我们可以直观地定义一个标准来估计线条的价值:如果线路通过太靠近点,则它是坏的,因为它将会对噪声敏感,并且不会正确地推广。所以我们的目标应该是尽可能地找出所有的路线。
然后,SVM算法的操作基于找到给出训练样本最大最小距离的超平面。两次,这个距离在SVM的理论中得到了边缘的重要名称。因此,最优分离超平面使训练数据的余量达到最大。
如何计算最佳超平面?
我们来介绍用于定义超平面的符号:
其中β被称为权重向量,而β0称为偏差。
通过缩放β和可以以无限数量的不同方式表示最优超平面β0。作为惯例,在超平面的所有可能的表示中,选择的是
其中X表示最接近超平面的训练样本。通常,最接近超平面的训练样本称为支持向量。这种表示被称为规范超平面。
现在,我们使用几何的结果给出点X和超平面之间的距离(β,β0):
特别地,对于规范超平面,分子等于1,并且到支持向量的距离为
回想一下,上一节中介绍的the margin(这里表示为M)是距离最接近的例子的两倍:
最后,m最大化问题等价于一个函数L(β)在某些约束条件下的极小化问题。约束模型对超平面的要求正确分类所有训练样本。从形式上看,
其中yi 表示训练样本的每个标签。
这是拉格朗日优化的问题,可以使用拉格朗日乘数来求解最优超平面的权重向量β和偏置β0。
源代码
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/ml.hpp>
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
// Data for visual representation
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
// Set up training data
int labels[4] = {1, -1, -1, -1};
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
// Train the SVM
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
// Show the decision regions given by the SVM
Vec3b green(0,255,0), blue (255,0,0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1,2) << j,i);
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i,j) = green;
else if (response == -1)
image.at<Vec3b>(i,j) = blue;
}
// Show the training data
int thickness = -1;
int lineType = 8;
circle( image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness, lineType );
circle( image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType );
circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType );
circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness, lineType );
// Show support vectors
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);
circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType);
}
imwrite("result.png", image); // save the image
imshow("SVM Simple Example", image); // show it to the user
waitKey(0);
}
说明
- 设置训练数据该练习的训练数据由属于两个不同类之一的一组标记的2D点形成; 其中一个课程包括一个点和另一个三点。
int labels [4] = {1,-1,-1,-1};
float trainingData [4] [2] = {{501,10},{255,10},{501,255},{10,501}};
将要使用的函数cv :: ml :: SVM :: train需要将训练数据存储为cv :: Mat对象的浮点数。因此,我们从上面定义的数组创建这些对象:
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
- 设置SVM的参数
在本教程中,我们在最简单的情况下介绍了SVM的理论,当训练示例分为两类可以线性分离时。然而,SVM可以用于各种各样的问题(例如,非线性可分离数据的问题,使用内核函数的SVM来提高示例的维度等)。因此,我们必须在训练SVM之前定义一些参数。这些参数存储在类cv :: ml :: SVM的对象中。
ptr <SVM> svm = SVM :: create();
svm->的setType(SVM :: C_SVC);
svm-> setKernel(SVM :: LINEAR);
svm-> setTermCriteria(TermCriteria(TermCriteria :: MAX_ITER,100,1e-6));
这里:
- SVM类型。我们在这里选择可用于n类分类的类型C_SVC(n 2)。这种类型的重要特征是处理类的不完全分离(即训练数据是非线性分离的)。这个功能在这里并不重要,因为数据是线性可分的,我们选择这个SVM类型是最常用的。≥
- SVM内核的类型。我们没有谈到内核函数,因为它们对我们正在处理的培训数据不是有趣的。不过,现在让我们简单介绍一下内核函数的主要思路。它是对训练数据进行的映射,以改进其与线性可分离数据集的相似度。该映射包括增加数据的维度,并使用内核函数高效地完成。我们在这里选择类型LINEAR,这意味着没有映射完成。此参数使用cv :: ml :: SVM :: setKernel定义。
- 算法的终止标准。SVM训练程序以迭代方式解决约束二次优化问题。这里我们指定最大迭代次数和公差误差,因此即使最佳超平面尚未计算,我们允许算法以较少的步数完成。此参数在结构cv :: TermCriteria中定义。
- 训练SVM我们称之为cv :: ml :: SVM :: train方法构建SVM模型。
svm-> train(trainingDataMat,ROW_SAMPLE,labelsMat);
- 由SVM分类的区域
方法cv :: ml :: SVM :: predict用于使用经过训练的SVM对输入样本进行分类。在这个例子中,我们使用这种方法来根据SVM所做的预测来对空间进行着色。换句话说,遍历图像将其像素解释为笛卡尔平面的点。每个点根据SVM预测的类别着色; 如果是带有标签1的类,则为绿色,如果为带有标签-1的类,则为蓝色
支持向量
我们在这里使用几种方法来获取有关支持向量的信息。方法cv :: ml :: SVM :: getSupportVectors获取所有的支持向量。我们在这里使用这种方法来找到支持向量的训练示例并突出显示。
Vec3b green(0,255,0), blue (255,0,0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1,2) << j,i);
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i,j) = green;
else if (response == -1)
image.at<Vec3b>(i,j) = blue;
}
- 支持向量
我们在这里使用几种方法来获取有关支持向量的信息。方法cv :: ml :: SVM :: getSupportVectors获取所有的支持向量。我们在这里使用这种方法来找到支持向量的训练示例并突出显示。
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);
circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType);
}
结果
- 该代码打开一个图像并显示两个类的训练示例。一个等级的点用白色圆圈表示,黑色的点用于另一个类。
- SVM被训练并用于对图像的所有像素进行分类。这导致在蓝色区域和绿色区域中的图像的划分。两个区域之间的边界是最优分离超平面。
- 最后,训练示例周围使用灰色戒指显示支持向量。