我是靠谱客的博主 聪慧月光,这篇文章主要介绍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://blog.csdn.net/henghane/article/details/54970544

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都有较高的正确分类率。下面展示人车分类器的训练以及测试分类代码,其余两个分类器也可同理求得。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#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::load("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%。
在这里插入图片描述
第二部分:再重复两次此过程得到三个分类器。通过调用分类器实现一对一法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#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法,消去误判、拒分的情况。

最后

以上就是聪慧月光最近收集整理的关于SVM实现多分类常用的两种方法以及一对一法的代码(VS13+opencv3.4)的全部内容,更多相关SVM实现多分类常用内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部