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.h==和==svm.cpp==两个文件,将其放入自己的工程下,在使用时我将有关函数封装为类ClassificationSVM 。
在LIBSVM中,与读取特征文件相关的类型为svm_problem ,其主要在训练和预测过程中记录导入的数据。这个类中有三个元素,如下所示:
1 2 3 4 5 6 struct svm_problem { int n; double *y; struct svm_node **x; };
其中svm_node 类型的定义如下:
1 2 3 4 5 struct svm_node { int index; double value; };
这次记录过程中我使用的是官方的vowel 数据集,数据集中包含11个分类,每个分类包含48个数据,一共528个数据进行模型训练。官方的数据格式为:
分类号 1:数据1 2:数据2 3:数据3…
从txt文件读取到数组中的函数如下:
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 void ClassificationSVM::readTxt2 (const std::string& featureFileName) { dataVec.clear (); labels.clear (); 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) { 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 ) ∗ m a x − m i n y − m i n
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 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中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 <= log~2~C <= 15,-15 <= log~2~G <= 3,步长均为2,在C++中我们需要调用svm.cpp中的svm_cross_validation 函数遍历参数来进行交叉验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct svm_parameter { int svm_type; int kernel_type; int degree; double gamma; double coef0; double cache_size; double eps; double C; int nr_weight; int *weight_label; double * weight; double nu; double p; int shrinking; int probability; };
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 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, ¶m, 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 函数。
1 2 3 4 5 6 std::cout << "start training" << std::endl; svm_model *svmModel = svm_train (&prob, ¶m); 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型数据)。
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 void ClassificationSVM::predict (const std::string& featureFileName, const std::string& modelFileName) { svm_model *model = svm_load_model (modelFileName.c_str ()); readTxt2 (featureFileName); svmScale (false ); 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 resultLabel2 = svm_predict (model, sample); if (resultLabel - labels[i] < 1e-5 ) count++; } double possibility = 1.0 * count / dataVec.size (); }
7. 参考及链接
一个入门的DEMO
主要参考 ,但这个里面没有交叉验证
我的完整程序