SVM实现多分类常用的两种方法以及一对一法的代码(VS13+opencv3.4)
SVM实现多分类常用的两种方法以及一对一法的代码(VS13+opencv3.4)
SVM是一个二值分类器,处理多分类问题的时候需要构造合适的多类分类器。
(1)直接法,直接在目标函数上进行修改,将多个分类面的参数求解合并到一个最优化问题中,通过求解该最优化问题“一次性”实现多类分类。这种方法看似简单,但其计算复杂度比较高,实现起来比较困难,所以没有被广泛应用。
(2)间接法,将多类问题分解为一系列SVM可直接求解的二值分类问题,再根据一系列SVM求解结果得到最终判别结果。基于此种思想的多分类方法有一对余类法,一对一法,DAG法,决策树方法,纠错输出编码法,共五种,下面将说明比较常用的一对余类法,一对一法。
1.一类对余类法(One versus rest,OVR)是最早出现也是目前应用最为广泛的方法之一,其步骤是构造k个两类分类机(假设共有K个类别),训练时第i个分类机取训练集中第i类为正样本,其余类都划分为负样本,进行训练,实例可见下图。判别时,输入信号分别经过k个分类机共得到k个输出值fi(x)=sgn(gi(x)),若只有一个+1出现,则其对应类别为输入信号类别,此为理想情况 ;实际情况下构造的决策函数总是有误差的,若输出不只一个+1(不只一类声称它属于自己),或者没有一个输出为+1(即没有一个类声称它属于自己),则比较g(x)输出值,最大者对应类别为输入的类别。
图片里面有三类,因此训练三个分类器,三类中的一个类作为正样本,则其它两类都划分为负样本,进行训练,得到三个分类器。
评价:这种方法的优点是,对k类(K较大)问题,只需要训练k个两类分类支持向量机,故其所得到的分类函数的个数(k个)较少,其分类速度相对较快。缺点:样本不平衡问题:一对多方法在训练每个分类器时,其训练样本是1类对k-1类,正负样本的规模相差较大,如1个正样本和99个负样本,则在训练此分类器时很有可能最终的分类器是D(x)=-1,即不论输入什么都输出-1,这样他的错误率也很小只有0.01,达不到训练的效果。解决这个问题可以用一对一方法。详细见博客链接:https://www.geek-share.com/detail/2697709885.html
2.一对一法
分类器个数:假设有K类样本,其做法是在任意两类样本之间设计一个SVM,因此k个类别的样本就需要设计k(k-1)/2个SVM。
结果判定:训练好全部分类器后,当对一个测试样本进行分类时,此样本经过全部分类器,得到k(k-1)/2个分类结果,最后得票最多的类别即为该测试样本的类别。本次将实现基于这个方法的人,车,背景三分类,因为只有三类,所以分类器个数只需要三个。
评价:这种方法虽然好,但是当类别k很多的时候,分类器的个数是k*(k-1)/2,需要多次投票;此外还会存在误判(例如人在车与背景分类器中被划分为背景),拒分(票数相同不知道如何划分类),解决误判,拒分问题可采用DAG法。
3.DAG法
DAG-SvMS是由PIatt提出的决策导向的循环图DAG导出的,是针对“一对一"SvMS存在误分,拒分现象提出的。这种方法的训练过程类似于“一对一”方法,k类别问题需要求解k(k-1)/2个支持向量机分类器,这些分类器构成一个有向无环图。该有向无环图中含有k(k-1)/2个内部节点和k个叶结点,每个节点对应一个二类分类器。
DAG-SVMS简单易行,只需要使用k一1个决策函数即可得出结果,较“一对一"方法提高了测试速度,而且不存在误分、拒分区域;另外,由于其特殊的结构,故有一定的容错性,分类精度较一般的二叉树方法高。然而,由于存在自上而下的“误差积累”现象是层次结构固有弊端,故DAG-SVMS也逃脱不掉。即如果在某个结点上发生了分类错误,则会把分类错误延续到该结点的后续结点上.详见http://blog.sina.com.cn/s/blog_5eef0840010147pa.html
下面展示基于一对一法的三分类代码实现的两个部分
第一部分:首先我们需要分别训练出三个分类器,并且要测试每一个分类器的分类准确率,确保每一个SVM都有较高的正确分类率。下面展示人车分类器的训练以及测试分类代码,其余两个分类器也可同理求得。
#include <stdio.h> #include <iostream> #include <fstream> #include <opencv2/opencv.hpp> #include <string> #include <io.h> #include<stdlib.h> using namespace std; using namespace cv::ml; #define PosSamNO 1152 //正样本个数 #define NegSamNO 1152 //负样本个数 #define HardExampleNO 0 //难例个数 #define TestSamNO 300 //测试个数 /*-------------------------------------将正样本的图片名循环写入到txt文件内--------------------------------------------*/ void Pos_List() { cout << "Writingcar..." << endl; _finddata_t file; //_finddata_t是用来存储文件各种信息的结构体 ofstream outf; //声明输出流 outf.open("E:\\交通异常检测\\样本库\\人样本库-训练\\PositiveImageList.txt");//通过构造函数打开文件 int k; long long HANDLE; k = HANDLE = _findfirst("E:\\交通异常检测\\样本库\\人样本库-训练\\*.png", &file);//找到所有png文件 while (k != -1) { outf << file.name << endl; //输出文件的名字 k = _findnext(HANDLE, &file); } _findclose(HANDLE); outf.close(); cout << "Finish!" << endl; } /*-------------------------------------将负样本的图片名循环写入到txt文件内--------------------------------------*/ void Neg_List() { cout << "Writingcar..." << endl; _finddata_t file; //_finddata_t是用来存储文件各种信息的结构体 ofstream outf; outf.open("E:\\交通异常检测\\样本库\\车样本库-训练\\NegativeImageList.txt"); int k; long long HANDLE; k = HANDLE = _findfirst("E:\\交通异常检测\\样本库\\车样本库-训练\\*.png", &file); while (k != -1) { outf << file.name << endl; //输出文件的文件名 k = _findnext(HANDLE, &file); } _findclose(HANDLE); outf.close(); cout << "Finish!" << endl; //system("pause"); } /*-------------------------------------把测试文件夹里面的图片文件名循环写入到txt文件内--------------------------------------*/ void test_List() { cout << "Writingcar..." << endl; _finddata_t file; //_finddata_t是用来存储文件各种信息的结构体 ofstream outf; //声明输出流 outf.open("E:\\交通异常检测\\样本库\\人样本库-测试\\testList.txt");//通过构造函数打开文件 int k; long long HANDLE; k = HANDLE = _findfirst("E:\\交通异常检测\\样本库\\人样本库-测试\\*.png", &file);//找到所有png文件 while (k != -1) { outf << file.name << endl; //输出文件的名字 k = _findnext(HANDLE, &file); } _findclose(HANDLE); outf.close(); } /*-------------------------------------把测试文件夹的图片文件名循环写入到另一个txt文件内--------------------------------------*/ void train_svm() { //HOG检测器,用来计算HOG描述子的 //检测窗口(48,48),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9 cv::HOGDescriptor hog(cv::Size(48, 48), cv::Size(16, 16), cv::Size(8, 8), cv::Size(8, 8), 9); int DescriptorDim;//HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定 //设置SVM参数 cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create(); svm->setType(cv::ml::SVM::Types::C_SVC); svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR); svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 100, 1e-6)); std::string ImgName; //正样本图片的文件列表 std::ifstream finPos("E:\\交通异常检测\\样本库\\人样本库-训练\\PositiveImageList.txt"); //负样本图片的文件列表 std::ifstream finNeg("E:\\交通异常检测\\样本库\\车样本库-训练\\NegativeImageList.txt"); //所有训练样本的特征向量组成的特征矩阵,行数等于所有样本的个数(PosSamNO + NegSamNO),列数等于HOG描述子维数 cv::Mat sampleFeatureMat; //训练样本的类别向量,行数等于所有样本的个数,列数等于1;1表示正样本,-1表示负样本 cv::Mat sampleLabelMat; //依次读取txt中PosSamNO张正样本图片,并生成HOG描述子 for (int num = 0; num < PosSamNO && getline(finPos, ImgName); num++) { //给出图片绝对路径,将图片存入image矩阵 ImgName = "E:\\交通异常检测\\样本库\\人样本库-训练\\" + ImgName; cv::Mat image = cv::imread(ImgName); //灰度化 cv::Mat srcGray; cv::cvtColor(image, srcGray, CV_RGB2GRAY); std::cout << "Processing:" << ImgName << std::endl; //调整图片大小,令图片大小等于hog检测器窗口大小,运算速度将会很快 cv::resize(srcGray, srcGray, cv::Size(48, 48)); //HOG描述子向量 std::vector<float> descriptors; //计算HOG描述子,检测窗口移动步长(8,8) hog.compute(srcGray, descriptors, cv::Size(8, 8)); //处理第一个样本时初始化特征向量矩阵和类别矩阵,因为只有知道了特征向量的维数才能初始化特征向量矩阵 if (0 == num) { //HOG描述子的维数 DescriptorDim = descriptors.size(); //初始化所有训练样本的特征向量组成的矩阵,行数等于所有样本的个数,列数等于HOG描述子维数sampleFeatureMat sampleFeatureMat = cv::Mat::zeros(PosSamNO + NegSamNO, DescriptorDim, CV_32FC1); //初始化训练样本的类别向量,行数等于所有样本的个数,列数等于1 sampleLabelMat = cv::Mat::zeros(PosSamNO + NegSamNO, 1, CV_32SC1); } //将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat for (int i = 0; i < DescriptorDim; i++) { //第num个样本的特征向量中的第i个元素 sampleFeatureMat.at<float>(num, i) = descriptors[i]; } //正样本类别为1 sampleLabelMat.at<float>(num, 0) = 1; } //依次读取负样本图片,生成HOG描述子 for (int num = 0; num < NegSamNO && getline(finNeg, ImgName); num++) { ImgName = "E:\\交通异常检测\\样本库\\车样本库-训练\\" + ImgName; cv::Mat image = cv::imread(ImgName); cv::Mat srcGray; cv::cvtColor(image, srcGray, CV_RGB2GRAY); std::cout << "Processing:" << ImgName << std::endl; cv::resize(srcGray, srcGray, cv::Size(48, 48)); //HOG描述子向量 std::vector<float> descriptors; //计算HOG描述子,检测窗口移动步长(8,8) hog.compute(srcGray, descriptors, cv::Size(8, 8)); //将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat for (int i = 0; i < DescriptorDim; i++) { //第PosSamNO+num个样本的特征向量中的第i个元素 sampleFeatureMat.at<float>(num + PosSamNO, i) = descriptors[i]; } //负样本类别为-1 sampleLabelMat.at<float>(num + PosSamNO, 0) = -1; } //训练SVM分类器 std::cout << "开始训练SVM分类器" << std::endl; cv::Ptr<cv::ml::TrainData> td = cv::ml::TrainData::create(sampleFeatureMat, cv::ml::SampleTypes::ROW_SAMPLE, sampleLabelMat); svm->train(td); std::cout << "SVM分类器训练完成" << std::endl; //将训练好的SVM模型保存为xml文件 svm->save("E:\\交通异常检测\\样本库\\人车SVM_HOG.xml"); return; } void svm_hog_classification() { //HOG检测器,用来计算HOG描述子的 //检测窗口(48,48),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9 cv::HOGDescriptor hog(cv::Size(48, 48), cv::Size(16, 16), cv::Size(8, 8), cv::Size(8, 8), 9); //HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定 int DescriptorDim; //测试样本图片的文件列表 std::ifstream finTest("E:\\交通异常检测\\样本库\\人样本库-测试\\testList.txt"); std::string ImgName; //分类错误的样本总和,有一个错误就加一 float n = 0; for (int num = 0; num < TestSamNO && getline(finTest, ImgName); num++) { //从XML文件读取训练好的SVM模型 cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::loa 1b5d8 d("E:\\交通异常检测\\样本库\\人车SVM_HOG.xml"); if (svm->empty()) { std::cout << "load svm detector failed!!!" << std::endl; return; } //针对测试集进行识别 std::cout << "开始识别..." << std::endl; std::cout << "Processing:" << ImgName << std::endl; ImgName = "E:\\交通异常检测\\样本库\\人样本库-测试\\" + ImgName; //ImgName = "D:\\1.png"; cv::Mat test = cv::imread(ImgName); //也对测试图片进行灰度化 cv::Mat srcGray; cv::cvtColor(test, test, CV_RGB2GRAY); cv::resize(test, test, cv::Size(48, 48)); std::vector<float> descriptors; hog.compute(test, descriptors, cv::Size(8, 8)); cv::Mat testDescriptor = cv::Mat::zeros(1, descriptors.size(), CV_32FC1); for (size_t i = 0; i < descriptors.size(); i++) { testDescriptor.at<float>(0, i) = descriptors[i]; } float label = svm->predict(testDescriptor); std::cout << "这张图属于:" << label << std::endl; if (label < 0) { std::cout << "这张图属于:" << "background" << std::endl; n++; } } std::cout << "分类错误的图片数量:" << n << std::endl; std::cout << "测试图片总数量:" << TestSamNO << std::endl; float x = (TestSamNO - n) / TestSamNO; std::cout << "正确率:" << x << std::endl; return; } int main(int argc, char** argv) { //create txt Pos_List(); Neg_List(); test_List(); train_svm(); svm_hog_classification(); system("pause"); return 0; }
此SVM分类准确率经过调整输入正负样本的个数后,准确率有94%。
第二部分:再重复两次此过程得到三个分类器。通过调用分类器实现一对一法。
#include <stdio.h> #include <iostream> #include <fstream> #include <opencv2/opencv.hpp> #include <string> #include <io.h> #include<stdlib.h> using namespace std; using namespace cv::ml; int main(int argc, char** argv) { //HOG检测器,用来计算HOG描述子的 //检测窗口(48,48),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9 cv::HOGDescriptor hog(cv::Size(48, 48), cv::Size(16, 16), cv::Size(8, 8), cv::Size(8, 8), 9); //HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定 int DescriptorDim; //从XML文件读取训练好的SVM模型 cv::Ptr<cv::ml::SVM> svm1 = cv::ml::SVM::load("E:\\交通异常检测\\样本库\\人车SVM_HOG.xml"); cv::Ptr<cv::ml::SVM> svm2 = cv::ml::SVM::load("E:\\交通异常检测\\样本库\\人背景SVM_HOG.xml"); cv::Ptr<cv::ml::SVM> svm3 = cv::ml::SVM::load("E:\\交通异常检测\\样本库\\车背景SVM_HOG.xml"); if (svm1->empty()) { std::cout << "load svm1 detector failed!!!" << std::endl; //return 0; } if (svm2->empty()) { std::cout << "load svm2 detector failed!!!" << std::endl; //return 0; } if (svm3->empty()) { std::cout << "load svm3 detector failed!!!" << std::endl; //return 0; } //针对测试集进行识别 std::cout << "开始识别..." << std::endl; string ImgName = "E:\\交通异常检测\\样本库\\车样本库-测试\\1.png"; cv::Mat test = cv::imread(ImgName); //也对测试图片进行灰度化 cv::cvtColor(test, test, CV_RGB2GRAY); cv::resize(test, test, cv::Size(48, 48)); std::vector<float> descriptors; hog.compute(test, descriptors, cv::Size(8, 8)); cv::Mat testDescriptor = cv::Mat::zeros(1, descriptors.size(), CV_32FC1); for (int i = 0; i < descriptors.size(); i++) { testDescriptor.at<float>(0, i) = descriptors[i]; } //计算投票结果 int people = 0; int car = 0; int background = 0; float label1 = svm1->predict(testDescriptor); if (label1 < 0) { car++; } else { people++; } float label2 = svm2->predict(testDescriptor); if (label2 < 0) { people++; } else { background++; } float label3 = svm3->predict(testDescriptor); if (label3 < 0) { car++; } else { background++; } if (car == people && car == background) { std::cout << "胡歌0" << std::endl; system("pause"); return 0; } else if (car >= 2) { std::cout << "胡歌1" << std::endl; system("pause"); return 1; } else if (people >= 2) { std::cout << "胡歌2" << std::endl; system("pause"); return 2; } else if (background >=2 ) { std::cout << "胡歌3" << std::endl; system("pause"); return 3; } }
但是此种方法存在一个问题,如果分类器效果不好的话,会存在多次平票结果,难以判断出属于哪一个类,所以下一次我将结合DAG法,消去误判、拒分的情况。
- @V@ java代码笔记2010-06-12:java控制台输入各类型类实现;以及判断输入字符串里面是否有数字的两种方法:方法1:转换成字符数组;方法2:正则表达式。
- SVM原理以及Tensorflow 实现SVM分类(附代码)
- 用两种方法,实现常用的ThreadBase代码
- Jquery与JS两种方法仿twitter/新浪微博 高度自适应无缝滚动实现代码
- shader学习基础之十一实现纹理的缩放平移和旋转,以及用c#代码合并两种贴图并且控制位置
- Android常用控件-DatePicker以及对话框的两种使用方法
- OC基础——使用category(策略)实现分类,在不改变原类代码的前提下为原类增加方法
- # 数据挖掘算法 ## 18大经典DM算法 18大数据挖掘的经典算法以及代码实现,涉及到了决策分类,聚类,链接挖掘,关联挖掘,模式挖掘等等方面,后面都是相应算法的博文链接,希望能够帮助大家学。 目前
- javascript 方法实现千位分隔符以及代码解释
- 两种实现单件模式代码重用的方法
- JAVA用JNI方法调用C代码实现HelloWorld(含windows及ubuntu两种操作系统环境下的操作)
- 原始LBP纹理特征提取方法介绍以及代码实现
- SVM实现多分类的程序基础工作(一)——安装libsvm以及libsvm和matlab自带的svm的区别
- Eclipse中的快捷键快速生成常用代码(例如无参、带参构造,set、get方法),以及Java中重要的内存分析(栈、堆、方法区、常量池)
- 二分类模型评估指标的计算方法与代码实现
- 二分类问题打标签label以及求loss的选择——从accuracy底层实现代码理解
- C# ASP.NET 最常用的通用权限的3个方法例子展示(每个功能一行代码实现)
- 两种常用的javascript数组去重方法思路及代码
- android中通过contentprovider访问通讯录代码实现(两种方法实现)
- php递归方法实现无限分类实例代码