libSVM是台湾大学林智仁(Lin Chih-Jen)教授等开发的一个简单易用、快速有效的SVM模式识别与回归的第三方库。该库无需额外的第三方库
支持,只需要纯粹的C++编译运行环境,可以横跨Windows\Linux\Unix等平台。
libSVM是开源C++库,可从libSVM下载安装包。解压缩之后的文件夹,提供了跨平台的多个
MAKEFILE文件和源代码;一个Windows文件夹,里面是已经可以使用的程序和库文件。在Windows下,打开VS的命令行窗口,进入libSVM目录,输入:
nmake -f Makefile.win clean all
nmake -f Makefile.win
nmake -f Makefile.win lib
即可生成libsvm.lib和libsvm.dll文件。svm.h头文件在根目录下。将它们加入工程项目即可使用。libSVM使用主要需要构造两个结构:svm_param和svm_problem。
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 */
};
1、svm_type是SVM类型,包含C_SVC(传统SVM分类), NU_SVC(nu-SVM分类,和C_SVC差别不大), ONE_CLASS(单分类SVM,用于找到一个最小的边界包围一类数据), EPSILON_SVR(见SVR分类), NU_SVR
2、kernel_type表示用到的SVM核函数,有linear/poly/rbf/sigmoid,即线性(不加核)、多项式核、径向基核、sigmoid核。
3、degree,多项式核的参数;gamma核函数共同的参数;coef0多项式、sigmoid核的参数。具体可参考核方法。
4、cache_size,缓冲大小,自己设置。
5、eps,求解svm的方法是个迭代过程,eps是迭代收敛的终止条件。
6、C,SVM方法的惩罚参数。
7、nr_weight、weight_label、weight表示样本的权重。nr_weight是样本个数,weight_label是权重参数的数组,weight是缩放因子数组。如果样本都是平等的,则设置nr_weight=0
8、nu是 NU_SVC, ONE_CLASS, and NU_SVR的参数。
9、p是SVR回归中的松弛因子。
10、shrinking表示是否使用启发式算法,取值为1和0
11、probability表示是否计算概率信息,取值为1和0
struct svm_problem
{
int l;
double *y;
struct svm_node **x;
};
l是样本个数,y是样本分类数组,即标准答案;x是样本特征矩阵,用一个二维数组表示;svm_node*数组用于表示一个样本特征向量。
struct svm_node
{
int index;
double value;
};
传统的SVM分类
构建好svm_parameter和svm_problem,可以使用函数:struct svm_model *svm_train(const struct svm_problem *prob,const struct svm_parameter *param);
进行训练,返回的svm_model是训练结果,是一个黑匣子,用户不必理会。svm_model可以用svm_save_model函数存储,具体见libsvm的README文档。
使用训练好的结果svm_model,需要调用函数double svm_predict(const struct svm_model *model, const struct svm_node *x);
x是需要预测的样本的特征数组,返回的double表示分类结果。
下面来看一个球形区域聚类的例子!这个例子在[-100,100]x[-100,100]的平面中随机生成点,点到中心的距离小于80的话就是正样本点,否则就是负样本点。可以看出,正样本是一个圆形区域,圆外是负样本,圆内是正样本,是一个非线性分类问题,必须得用核函数。最为通用的和函数是RBF径向基核,我使用这个核函数。下面我写的网上广为流传的一个核函数聚类问题的解决代码。
#include <iostream>
#include <svm.h>
#include <ctime>
#include <stdlib.h>
int main()
{
//build svm_parameter
svm_parameter param;
param.svm_type = C_SVC;
param.kernel_type = RBF;
//param.degree = 3;
param.gamma = 0.5;
//param.coef0 = 0;
//param.nu = 0.5;
param.cache_size = 100;
param.C = 0.5;
param.eps = 1e-3;
//param.p = 0.1;
param.shrinking = 0;//默认为1,当样本量很大,设为0
param.probability = 0;
param.nr_weight = 0;
param.weight_label = NULL;
param.weight = NULL;
// build problem
svm_problem prob;
int dataSize = 10000;
prob.l = dataSize;
prob.y = new double[prob.l];
prob.x = new svm_node *[prob.l];
//build data
srand(time(0));
double radius2 = 80.0 * 80.0;
int posNum=0, negNum = 0;
for(int i = 0; i < dataSize; ++i){
prob.x[i] = new svm_node[3];
prob.x[i][0].index = 1;
prob.x[i][0].value = double(rand()%200 - 100);
prob.x[i][1].index = 2;
prob.x[i][1].value = double(rand()%200 - 100);
prob.x[i][2].index = -1;
prob.x[i][2].value = 0;
prob.y[i] = (prob.x[i][0].value * prob.x[i][0].value
+ prob.x[i][1].value * prob.x[i][1].value) > radius2 ? -1.0 : 1.0;
if (prob.y[i] > 0)
{
++posNum;
}else ++negNum;
if(i%500 == 0) std::cout << "x: "<<prob.x[i][0].value
<< " y: " << prob.x[i][1].value << " preclass : " << prob.y[i] << std::endl;
}
std::cout << "pos: " << posNum << " neg: " << negNum << std::endl;
//check
const char* err = svm_check_parameter(&prob, ¶m);
if(NULL != err){
std::cout << errno << std::endl;
return -1;
}
//build model and train
svm_model *model = svm_train(&prob, ¶m);
//predict
for(int i = 0; i < 20; ++i){
int n = std::max(0, rand() % dataSize - 1);
std:: cout << "x : " <<prob.x[i][0].value << " y: " << prob.x[i][1].value
<< " class : " << svm_predict(model, prob.x[i]) << " true class " << prob.y[i] << std::endl;
}
//svm_node x[3];
//x[0].index = 1;
//x[1].index = 2;
//x[2].index = -1;
//std::cout << "input x : " << std::endl;
//std::cin >> x[0].value;
//std::cout << "input y : " << std::endl;
//std::cin >> x[1].value;
//x[0].value = 0;
//x[1].value = 0;
//x[2].value = -1;
//double d = svm_predict(model, x);
//std::cout << "class is : " << d << std::endl;
svm_free_and_destroy_model(&model);
for (int i = 0; i < dataSize; ++i){
delete[] prob.x[i];
}
delete[] prob.x;
delete[] prob.y;
return 0;
}
输出为:
x: -74 y: 5 preclass : 1
x: 19 y: 11 preclass : 1
x: 1 y: 37 preclass : 1
x: -85 y: -21 preclass : -1
x: -36 y: 37 preclass : 1
x: -20 y: -53 preclass : 1
x: -34 y: -13 preclass : 1
x: -64 y: 34 preclass : 1
x: -36 y: -75 preclass : -1
x: 25 y: 57 preclass : 1
x: 65 y: -51 preclass : -1
x: -34 y: 68 preclass : 1
x: -57 y: 45 preclass : 1
x: -30 y: -62 preclass : 1
x: -41 y: 1 preclass : 1
x: -61 y: -13 preclass : 1
x: -48 y: -85 preclass : -1
x: -84 y: -71 preclass : -1
x: 35 y: -98 preclass : -1
x: -63 y: 40 preclass : 1
pos: 5066 neg: 4934
………..*
optimization finished, #iter = 11797
nu = 0.793672
obj = -2218.051986, rho = -0.010793
nSV = 9327, nBSV = 5399
Total nSV = 9327
x : -74 y: 5 class : 1 true class 1
x : 71 y: 78 class : -1 true class -1
x : -16 y: -67 class : 1 true class 1
x : -72 y: -36 class : -1 true class -1
x : -16 y: 89 class : -1 true class -1
x : 78 y: 61 class : -1 true class -1
x : 12 y: 83 class : -1 true class -1
x : 3 y: -68 class : 1 true class 1
x : 73 y: -53 class : -1 true class -1
x : 60 y: -13 class : 1 true class 1
x : 52 y: -3 class : 1 true class 1
x : 30 y: 21 class : 1 true class 1
x : 87 y: 41 class : -1 true class -1
x : -99 y: -60 class : -1 true class -1
x : -22 y: 37 class : 1 true class 1
x : -1 y: 10 class : 1 true class 1
x : -4 y: -29 class : 1 true class 1
x : 26 y: -71 class : 1 true class 1
x : 9 y: -5 class : 1 true class 1
x : -43 y: -63 class : 1 true class 1
可以看出,我们用RBF SVM解决了圆形区域的分类问题,虽然有点过拟合,但是分类效果还是不错的。