LIBSVM的VS2015使用记录

LIBSVM工具箱是台湾大学林智仁(C.JLin)等人开发的一套简单的、易于使用的SVM模式识别与回归机软件包,该软件包利用收敛性证明的成果改进算法,取得了很好的结果。下面对LIBSVM(Version 3.25)VS2015中的分类使用进行记录。

1. 使用流程

(1). LIBSVM所要求的格式准备数据集(也可以自行准备数据格式,需自己写获取数据的函数,记录中以LIBSVM官方数据的格式读取为例);

(2). 对数据进行简单的缩放操作;

(3). 考虑选用RBF(radial basis function)核参数;

(4). 如果选用RBF,通过采用交叉验证获取最佳参数C与gamma;

(5). 采用最佳参数C与g对整个训练集进行训练获取支持向量机模型

(6). 利用获取的模型进行测试与预测

2. 数据格式介绍

将LIBSVM官网的文件包进行下载,我们在VS中主要用到svm.hsvm.cpp两个文件,将其放入自己的工程下,在使用时我将有关函数封装为类ClassificationSVM

在LIBSVM中,与读取特征文件相关的类型为svm_problem,其主要在训练和预测过程中记录导入的数据。这个类中有三个元素,如下所示:

struct svm_problem 
{
	int n; //记录样本总数
	double *y; //记录样本所属类别
	struct svm_node **x; //存储所有样本的特征,二维数组,一行存一个样本的所有特征
};

其中svm_node类型的定义如下:

struct svm_node //用来存储输入空间中的单个特征
{
	int index; //该特征在特征空间中的维度编号
	double value; //该特征的值
};

这次记录过程中我使用的是官方的vowel数据集,数据集中包含11个分类,每个分类包含48个数据,一共528个数据进行模型训练。官方的数据格式为:

分类号 1:数据1 2:数据2 3:数据3…

从txt文件读取到数组中的函数如下:

//从官方文件中读取数据
void ClassificationSVM::readTxt2(const std::string& featureFileName)
{
	dataVec.clear();//dataVec为二维数组,对应svm_problem的x中的数据
	labels.clear();//labels记录每个数据对应的分类,整型数组
	featureDim = -1;//特征数量记录
	sampleNum = 0;//样本数

	//官方标准样式
	std::ifstream fin;
	std::string rowData;//一行内容
	std::istringstream iss;
	fin.open(featureFileName);

	//保存特征数据
	std::string dataVal;
	while (std::getline(fin, rowData))
	{
		iss.clear();
		iss.str(rowData);
		bool first = true;
		std::vector<double>rowDataVec;
		// 逐词读取,遍历每一行中的每个词
		while (iss >> dataVal)
		{
			//第一个数据是label分类标识
			if (first) {
				first = false;
				labels.push_back(atof(dataVal.c_str()));
				sampleNum++;
			}
			else {
				//分割字符串得到冒号后数据
				for (int k = 0;k < dataVal.size();k++)
				{
					if (dataVal[k] == ':') {
						dataVal = dataVal.substr(k+1);
						break;
					}
				}
				rowDataVec.push_back(atof(dataVal.c_str()));
			}
		}
		dataVec.push_back(rowDataVec);
	}
	featureDim = dataVec[0].size();
}

3. 数据缩放

缩放输入数据,原始数据范围可能过大或过小,该过程可将数据重新缩放到适当范围使训练与预测速度更快,一般缩放到[0, 1]或[-1, 1],这里我缩放到[-1, 1],缩放公式如下,
y ′ =  lower  + (  upper  −  lower  ) ∗ y − min ⁡ max ⁡ − min ⁡ y^{\prime}=\text { lower }+(\text { upper }-\text { lower }) * \frac{y-\min }{\max -\min } y= lower +( upper  lower )maxminymin

//归一化到[-1, 1],分为训练时缩放,要写一个缩放的文件;预测时读取这个缩放文件
void ClassificationSVM::svmScale(bool train_model)
{
	double *minVals = new double[featureDim];
	double *maxVals = new double[featureDim];

	if (train_model) {
		for (int i = 0;i < featureDim;i++)
		{
			minVals[i] = dataVec[0][i];
			maxVals[i] = dataVec[0][i];
		}

		for (int i = 0;i < dataVec.size();i++)
		{
			for (int j = 0;j < dataVec[i].size();j++)
			{
				if (dataVec[i][j] < minVals[j])
					minVals[j] = dataVec[i][j];
				if (dataVec[i][j] > maxVals[j])
					maxVals[j] = dataVec[i][j];
			}
		}

        //缩放文件存放每个特征的最大最小值
		std::ofstream out("scale_params.txt");
		for (int i = 0;i < featureDim;i++)
		{
			out << minVals[i] << " ";
		}
		out << std::endl;
		for (int i = 0;i < featureDim;i++)
		{
			out << maxVals[i] << " ";
		}
	}
	else {
		std::ifstream fin;
		std::string rowData;//一行内容
		std::istringstream iss;
		fin.open("scale_params.txt");
		std::getline(fin, rowData);
		iss.clear();
		iss.str(rowData);
		double dataVal;
		int count = 0;
		// 逐词读取,遍历每一行中的每个词
		while (iss >> dataVal)
		{
			minVals[count] = dataVal;
			count++;
		}
		count = 0;
		std::getline(fin, rowData);
		iss.clear();
		iss.str(rowData);
		while (iss >> dataVal)
		{
			maxVals[count] = dataVal;
			count++;
		}
	}

	for (int i = 0;i < dataVec.size();i++)
	{
		for (int j = 0;j < dataVec[i].size();j++)
		{
			dataVec[i][j] = -1 + 2 * (dataVec[i][j] - minVals[j]) / (maxVals[j] - minVals[j]);
		}
	}

	delete minVals;
	delete maxVals;
}

缩放之后我们就可以将导入的参数dataVec和labels构造到官方的结构svm_problem中。

	//设置prob,prob的定义为:svm_problem prob;
	prob.l = sampleNum;   //训练样本数
	prob.x = new svm_node*[sampleNum];  //特征矩阵
	prob.y = new double[sampleNum];     //标签矩阵
	for (int i = 0; i < sampleNum; ++i)
	{
		prob.x[i] = new svm_node[featureDim + 1]; //
		for (int j = 0; j < featureDim; ++j)
		{
			prob.x[i][j].index = j + 1;
			prob.x[i][j].value = dataVec[i][j];
		}
		prob.x[i][featureDim].index = -1;
		prob.y[i] = labels[i];
	}

4. 交叉验证

交叉验证(Cross Validation)是用来验证分类器的性能一种统计分析方法,基本思想是把在某种意义下将原始数据(dataset)进行分组,一部分做为训练集(train set),另一部分做为验证集(validation set),首先用训练集对分类器进行训练,在利用验证集来测试训练得到的模型(model),以此来做为评价分类器的性能指标。

这里主要使用K折交叉验证(一般选择5折)去得到最合理的模型中的C和gamma(模型的参数列表如下),官方的tools文件夹下grid.py就是在求解最优化的参数,定义的参数范围时-5 <= log2C <= 15,-15 <= log2G <= 3,步长均为2,在C++中我们需要调用svm.cpp中的svm_cross_validation函数遍历参数来进行交叉验证。

struct svm_parameter
{
	int svm_type;
	int kernel_type;
	int degree;	/* for poly */
	double gamma;	/* for poly/rbf/sigmoid */
	double coef0;	/* for poly/sigmoid */

	/* these are for training only */
	double cache_size; /* in MB */
	double eps;	/* stopping criteria */
	double C;	/* for C_SVC, EPSILON_SVR and NU_SVR */
	int nr_weight;		/* for C_SVC */
	int *weight_label;	/* for C_SVC */
	double* weight;		/* for C_SVC */
	double nu;	/* for NU_SVC, ONE_CLASS, and NU_SVR */
	double p;	/* for EPSILON_SVR */
	int shrinking;	/* use the shrinking heuristics */
	int probability; /* do probability estimates */
};
	//交叉验证最优化参数求解
	double* target = new double[prob.l];
	int logG, logC;
	int bestG, bestC;//记录最好的参数值
	int minCount = prob.l;//记录错误的数量
	std::vector<double>rates;//记录每次组合对应的正确率
	for (logC = -5;logC <= 15;logC += 2)
	{
		for (logG = -15;logG <= 3;logG += 2)
		{
			double c = pow(2, logC);
			double g = pow(2, logG);
			setParam(c, g);//对模型参数进行修改
			svm_cross_validation(&prob, &param, 5, target);
			int count = 0;
			for (int i = 0;i < prob.l;i++)
			{
				if (target[i] != i % 11)
					count++;
			}
			if (count < minCount) {
				minCount = count;
				bestC = c;
				bestG = g;
			}
			rates.push_back(1.0*(prob.l-count) / prob.l*100);
		}
	}
	//输出每对参数及对应概率
	std::ofstream out("rates.txt");
	int count1 = 0;
	for (logC = -5;logC <= 15;logC += 2)
	{
		for (logG = -15;logG <= 3;logG += 2)
		{
			std::string s1 = "log2c=";
			s1 += std::to_string(logC);
			std::string s2 = "log2g=";
			s2 += std::to_string(logG);
			std::string s3 = "rate=";
			s3 += std::to_string(rates[count1]);
			count1++;

			out << s1 << " " << s2 << " " << s3 << std::endl;
		}
	}

5. 模型训练

模型训练主要调用官方的svm_train函数。

	std::cout << "start training" << std::endl;
	svm_model *svmModel = svm_train(&prob, &param);

	std::cout << "save model" << std::endl;
	svm_save_model(modelFileName.c_str(), svmModel);
	std::cout << "done!" << std::endl;

训练完成后将在指定的位置modelFileName生成对应的模型文件。

6. 模型测试(预测)

模型的测试使用svm_predict函数(或svm_predict_probability函数),流程与上面类似,将文件导入并缩放后,无需交叉验证直接使用导入的模型进行预测得到预测的结果,返回值类型为double是因为该函数包含分类和回归两个方面,我们在回归时返回的其实就是我们输入的labels中的一个分类的分类号(int型数据)。

void ClassificationSVM::predict(const std::string& featureFileName, const std::string& modelFileName)
{
	//读取特征文件中的特征及保存模型
	svm_model *model = svm_load_model(modelFileName.c_str());
	readTxt2(featureFileName);
	svmScale(false);

	//从vector中构造prob
	int count = 0;//正确预测计数
	for (int i = 0;i < dataVec.size();i++)
	{		
		svm_node *sample = new svm_node[featureDim + 1];
		for (int j = 0; j < featureDim; ++j)
		{
			sample[j].index = j + 1;
			sample[j].value = dataVec[i][j];
		}
		sample[featureDim].index = -1;

		//double *probresut = new double[11];
		//double resultLabel = svm_predict_probability(model, sample, probresut);
		double resultLabel2 = svm_predict(model, sample);
		if (resultLabel - labels[i] < 1e-5)
			count++;
		//std::cout << resultLabel2 << std::endl;
	}
	double possibility = 1.0*  count / dataVec.size();//正确率
}

7. 参考及链接

一个入门的DEMO

主要参考,但这个里面没有交叉验证

我的完整程序

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值