libsvm在vs2017下使用c++实例详解(含c++代码)

最近有项目需要利用svm进行光谱数据分类,所以使用了libsvm实现了该分类器,并且效果不错

环境搭建

首先下载libSVM最新的版本
libsvm在vs2017下使用c++实例详解(含c++代码)_第1张图片
使用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; //该特征的值
};

libsvm在vs2017下使用c++实例详解(含c++代码)_第2张图片
数据读取的核心思想就是将数据的每个特征的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转换为svm_node数组,因为我的数据需要预处理,所以先将数据读入vector中进行预处理计算,如果不需要这一步的可以直接将数据读入svm_node数组。
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, &param);
	
	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函数,对我的光谱数据分类效果不理想,所以利用了多项式和函数,分类效果就很理想了,根据不同的数据类型可以自己选择调整参数。

相关参考

你可能感兴趣的:(笔记)