最近有项目需要利用svm进行光谱数据分类,所以使用了libsvm实现了该分类器,并且效果不错
首先下载libSVM最新的版本
使用VS2017创建一个新的空工程,把上图目录中的svm.cpp和svm.h复制到工程目录下,把这两个文件添加到工程中去。
注意:VS2017中使用fopen会出现一个错误,原因是VS2017自身兼容性不好,认为fopen不安全,可以通过 工程右键——Properties——C++——Preprocesser——Preprocesser Definitions中添加_CRT_SECURE_NO_WARNINGS解决该问题。
同时VS2017中编译会出现strdup函数编译不过去,同样根据提示,把该函数改为_strdup即可。
网上对于libsvm有一种误导,就是你的特征文件必须要按照一定的格式来,才能够被读取训练,其实这只是对于使用dos命令行调用libsvm时的规定,因为libsvm自定义的特征文件格式是与其读取相匹配的。如果我们使用自己的读取文件函数,则完全不用拘束于这种格式,只要我们在读取函数之中与我们自己的特征文件格式相匹配即可。
在libsvm中,与读取特征文件相关的类型为svm_problem。这个类中有三个元素,如下所示:
struct svm_problem
{
int n; //记录样本总数
double *y; //记录样本所属类别
struct svm_node **x; //存储所有样本的特征,二维数组,一行存一个样本的所有特征
};
struct svm_node //用来存储输入空间中的单个特征
{
int index; //该特征在特征空间中的维度编号
double value; //该特征的值
};
数据读取的核心思想就是将数据的每个特征的index与value对应填入svm_node中去,最后建立一个svm_problem类,就可以调用libsvm内部的函数了
我的svm类如下所示
#include "svm.h"
#include "readdata.h"
#include "data_standard.h"
#include
#include
#include
#include
using namespace std;
class ClassificationSVM
{
public:
ClassificationSVM() :SampleNum(0) {};
~ClassificationSVM() = default;
void trainSVM(); //训练数据
bool predictSVM(vector<double> &datav, const std::string &ModelFileName); //单个数据预测
void multi_predictSVM(vector<bool> &v); //一组数据预测
private:
svm_node* vector2svmnode(const vector<double> &v); //将vector转换为svm_node[]
void setParam();
void readTrainData(std::string type, bool tag, int num, int st = 1); //从 path\typename 第st个数据开始读取num个数据,并记录对应标签为tag(正样本为true,负样本为false)
private:
svm_parameter param;
svm_problem prob; //all the data to train.
std::deque<svm_node*> datas; //fatures of all the sample
std::deque<double> tags; //type of all the sample
int SampleNum;
};
应项目需求该类对外提供了三个函数接口分别是训练数据trainSVM,单个数据预测predictSVM,一组数据预测multi_predictSVM(调用单个预测)
内部三个私有函数分别是
vector2svmnode:将vector
setParam():设置参数
readTrainData():读取训练数据
datas和tags用来存放读取的数据和数据对应的标签,读取完数据以后将其存入prob。
函数如下:
void ClassificationSVM::setParam()
{
param.svm_type = C_SVC;
param.kernel_type = POLY;
param.degree = 3;
param.gamma = 0.5;
param.coef0 = 0;
param.nu = 0.5;
param.cache_size = 50;
param.C = 500;
param.eps = 1e-3;
param.p = 0.1;
param.shrinking = 1;
param.nr_weight = 0;
param.weight = NULL;
param.weight_label = NULL;
}
//将path\type内第st个数据开始读取num个数据到datas中,并将tags设置为tag
void ClassificationSVM::readTrainData(std::string type, bool tag, int num, int st)
{
vector<vector<double>> datav;
readdatas(datav, type, num, st);
data_standard(datav);
int dim = datav[0].size(); //number of features
//将vector内数据按格式读入datas和tag
for (int i = 0; i < num; i++) //处理num个数据
{
svm_node* sample = new svm_node[dim+1];
for (int j = 0; j < dim; j++) //处理每个数据的dim个特征
{
sample[j].index = j+1; //index从1开始
sample[j].value = datav[i][j];
}
sample[dim].index = -1; //标记结束
double dtag = tag ? 1 : 0; //将tag转换为double
datas.push_back(sample);
tags.push_back(dtag);
SampleNum++;
}
}
其中readdatas是将文本数据读入一个二维vector中,不同格式的文本文件有不同的实现方法就不展示了,data_standard是我的预处理函数
//将vector转换为svm_node[]
svm_node* ClassificationSVM::vector2svmnode(const vector<double> &v)
{
int l = v.size();
svm_node* sn = new svm_node[l+1];
int i = 0;
for (auto d : v)
{
sn[i].index = i + 1; //index从1开始
sn[i].value = d;
i++;
}
sn[l].index = -1; //标志结束
return sn;
}
//训练模型并存为ModelFileName
void ClassificationSVM::trainSVM()
{
setParam();
string type;
int st, num;
//reading positive datas
cout << "input positive datas(type startindex number):" << endl;
cin >> type;
cin >> st;
cin >> num;
if (st < 1 || num < 1)//输入检验
{
cout << "error parameter." << endl;
return;
}
readTrainData(type, true, num, st);
//reading negative datas
cout << "input negative datas (type startindex number):" << endl;
cin >> type;
cin >> st;
cin >> num;
if (st < 1 || num < 1)//输入检验
{
cout << "error parameter." << endl;
return;
}
readTrainData(type, false, num, st);
if (datas.size() != SampleNum || tags.size() != SampleNum)//长度检验
{
cout << "datas or tags length error." << endl;
return;
}
prob.l = SampleNum;
prob.x = new svm_node *[prob.l];
prob.y = new double[prob.l];
//将数据读入svm_problem
int index = 0;
while (!datas.empty())
{
prob.x[index] = datas.front();
prob.y[index] = tags.front();
datas.pop_front();
tags.pop_front();
index++;
}
cout << "start training..." << endl;
svm_model *SVMmodel= svm_train(&prob, ¶m);
string ModelFileName;
cout << "input name of svm_model:" << endl;
cin >> ModelFileName;
cout << "save model..." << endl;
svm_save_model(ModelFileName.c_str(), SVMmodel);
cout << "done!" << endl;
}
//单个数据预测
bool ClassificationSVM::predictSVM(vector<double> &datav, const std::string &ModelFileName)
{
svm_node *sample =vector2svmnode(datav);
svm_model *SVMmodel = svm_load_model(ModelFileName.c_str());
double predictvalue = svm_predict(SVMmodel, sample); //返回值为1或0
return predictvalue ? true : false;
}
//一组数据预测
void ClassificationSVM::multi_predictSVM(vector<bool> &v)
{
string modelname, type;
int st, num;
cout << "start predicting..."<<endl;
cout << "input name of svm_model" << endl;
cin >> modelname;
cout << "input predict datas (type startindex number):" << endl;
cin >> type;
cin >> st;
cin >> num;
cout << "predicting..." << endl;
vector<vector<double>> datav;
readdatas(datav, type, num, st); //读取预测数据到datav中
vector<bool> judge;
for (auto v : datav)
{
data_standard1(v);
judge.push_back(predictSVM(v, modelname.c_str()));
}
cout << "classification resluts:" << endl;
cout << boolalpha; //控制输出true和false而不是1和0
for (auto b : judge)
{
cout << b << " ";
}
cout << noboolalpha<<endl;
}
注意:参数设置方面默认核函数是RBF函数,对我的光谱数据分类效果不理想,所以利用了多项式和函数,分类效果就很理想了,根据不同的数据类型可以自己选择调整参数。
相关参考