支持向量机(support vector machines, SVM)是一种二类分类模型。它的基本模型是定义在特征空间上的间隔最大的线性分类器;支持向量机还包括核技巧,这使它成为实质上的非线性分类器。支持向量机的学习策略就是间隔最大化,可形式化为一个求解凸二次规划(convex quadratic programming)的问题,也等价于正则化的合页损失函数的最小化问。支持向量机的学习算法是求解凸二次规划的最优化算法。
支持向量机学习模型:线性可分支持向量机(linear support vector machine in linearly separable case )、线性支持向量机(linear support vector machine)及非线性支持向量机(non-linear support vector machine)。
学习方法包括:硬间隔最大化(hud margin maximization)、软间隔最大化(soft margin maximization)、核技巧(kernel trick)。通过使用核函数可以学习非线性支持向量机,等价于隐式地在高维的特征空间中学习线性支持向量机。这样的方法称为核技巧(kernel trick)。
一、凸优化与对偶问题
1、凸优化问题
在优化问题中,凸优化问题由于具有优良的性质(局部最优解即是全局最优解),受到广泛研究。
以上便是凸优化问题的一般形式。常见的线性规划、二次规划、二次约束二次规划等优化问题都是凸优化问题。关于凸优化问题的相关知识在之前的博客中有提到:
2、对偶问题
如果原问题是强对偶问题(凸二次规划问题是强对偶)或者可行解满足KKT条件,则原始问题的解等价于对偶问题的解。
相关知识在之前的博客中有提到:
二、线性可分支持向量机与硬间隔最大化
1、线性可分支持向量机
给定线性可分训练数据集,通过间隔最大化或等价地求解相应的凸二次规划问题学习得到的分离超平面为:
以及相应的分类决策函数:
称为线性可分支持向量机。
2、函数间隔和几何间隔
(1)函数间隔
对于给定的训练数据集T和超平面(w, b),定义超平面关于样本点(xi, yi)的函数间隔为:
定义超平面(w,b)关于训练数据集T的函数间隔为超平面(w,b)关于T中所有样本点(xi, yi)的函数间隔之最小值,即:
函数间隔可以表示分类预测的正确性及确信度。但是成比例地改变w和b,例如将它们改为2w和2b,超平面并没有改变,但函数间隔却成为原来的2倍。
(2)几何间隔
点到平面的公式为:
对于给定的训练数据集T和超平面(w, b),定义超平面关于样本点(xi, yi)的几何间隔为:
定义超平面(w,b)关于训练数据集T的函数间隔为超平面(w,b)关于T中所有样本点(xi, yi)的几何间隔之最小值,即:
函数间隔和几何间隔的关系:
观察上述规划问题可知,将w, b同时放大或者缩小相同的倍数,不影响结果,即w, b存在多组解。为了得到唯一的一组w, b, 不妨令“函数间隔”为1。
3、硬间隔最大化
支持向量机学习的基本想法是求解能够正确划分训练数据集并且几何间隔最大的分离超平面。对线性可分的训练数据集而言,线性可分分离超平面有无穷多个(等价于感知机),但是几何间隔最大的分离超平面是唯一的。这里的间隔最大化又称为硬间隔最大化。
间隔最大化的直观解释是:对训练数据集找到几何间隔最大的超平面意味着以充分大的确信度对训练数据进行分类,也就是说,不仅将正负实例点分开,而且对最难分的实例点(离超平面最近的点)也有足够大的确信度将它们分开。
由于函数间隔为1,所以硬间隔最大化可以描述为:
我们在优化时喜欢求最小值,将上式转化正等价的求最小值如下:
4、原问题的算法流程
5、算法求解
原问题是一个凸二次规划问题,我们可以使用拉格朗日乘数法进行优化:
根据拉格朗日对偶性,原始问题的对偶问题是拉格朗日函数的极大极小问题:
设a*是对偶最优化问题的解,则存在下标j使得aj* >0,别求L对w,b的导数,并令其为0,求得原始最优化问题的解:
将最优化问题的解w*和b*代入到对偶问题中(因为极值在导数为零的点处取到):
6、对偶问题的算法流程
7、支持向量
在间隔边界上的点称为支持向量。
三、线性可分支持向量机与软间隔最大化
线性可分支持向量机必须满足线性可分,但当且仅当个别数据不满足线性不可分条件时,线性可分支持向量机变得不能够适用显然条件有点苛刻。因此,我们引入了软间隔最大化,让它扩展到线性不可分的问题。
1、软间隔
2、算法流程
3、支持向量
四、非线性支持向量机与核函数
对解线性分类问题,线性分类支持向量机是一种非常有效的方法。但是,有时分类问题是非线性的,这时可以使用非线性支持向量机。本节叙述非线性支持向量机,其主要特点是利用核技巧。为此,先要介绍核技巧。核技巧不仅应用于支持向量机,而且应用于其他统计学习问题。
1、核技巧
非线性分类问题的本质在于,把低维不可分数据映射到高维可分,从而能够使用线性支持向量机来求解问题。而这一映射函数,便是我们所谓的核技巧。
如上图所示,针对在二维平面无法用超平面去分离两类数据,但通过某种映射表达式,我们便有了三维的数据点,而此时此刻,便可以应用线性支持向量机来求解这分离超平面了。如何从二维变成三维呢?往往我们有两种解决方案:对数据增加新的特征,尝试着是否能够根据新加入的特征,在n+1维上对数据进行分离;当寻找特征相对困难时,我们可以利用核技巧,寻找一枚映射函数,即利用原有的特征生成新的特征。
2、核函数的定义及常用的核函数
3、算法流程
从非线性分类训练集,通过核函数与软间隔最大化,或凸二次规划,学习得到的分类决策函数:
称为非线性支持向量,K(x,z)是正定核函数:
五、序列最小最优化算法(sequential minimal opfimization,SMO)
1、SMO算法的思想
SMO算法要解如下凸二次规划的对偶问题:
SMO算法是一种启发式算法,其基本思路是:如果所有变量的解都满足此最优化问题的KKT条件(Karush-Kuhn-Tucker conditions),那么这个最优化问题的解就得到了。因为KKT条件是该最优化问题的充分必要条件。否则,选择两个变量,固定其他变量,针对这两个变量构建一个二次规划问题。这个二次规划问题关于这两个变量的解应该更接近原始二次规划问题的解,因为这会使得原始二次规划问题的目标函数值变得更小。重要的是,这时子问题可以通过解析方法求解,这样就可以大大提高整个算法的计算速度。子问题有两个变量,一个是违反KKT条件最严重的那一个,另一个由约束条件自动确定。如此,SMO算法将原问题不断分解为子问题并对子问题求解,进而达到求解原问题的目的。
2、SMO算法的流程
六、libSVM库
1、几个重要的数据结构
struct svm_problem
{
int l; // 记录样本的总数
double *y; // 样本所属的标签(+1, -1)
struct svm_node **x; // 指向样本数据的二维数组(即一个矩阵,行数是样本数,列数是特征向量维度)
};
svm_node是用来存储单个样本数据的,打个比方说,svm_problem是一群羊,那么svm_node就是这一群羊中的一只。需要注意的是,svm_node的存储空间应该比特征数大一位,最后一位index值必须以-1结束。比如:
struct svm_node
{
int index;
double value;
};
svm_node* node = new svm_node[1 + feature_size];
for (int j = 0; j < feature_size; j++)
{
node[j].index = j + 1;
node[j].value = xdata[j];
}
node[feature_size].index = -1;
return node;
//核心参数
struct svm_parameter
{
int svm_type;// SVM的类型
int kernel_type;// 核函数
double degree;// 多项式参数
double gamma;// 核函数为poly/rbf/sigmoid的参数
double coef0;// 核函数为poly/sigmoid的参数
//下面是训练所需的参数
double cache_size;// 训练所需的内存MB为单位
double eps;// 训练停止的标准(误差小于eps停止)
double C;// 惩罚因子,越大训练时间越长
int nr_weight;// 权重的数目,目前只有两个值,默认为0
int *weight_label;// 权重,元素个数由nr_weight决定
double* weight;// C_SVC权重
double nu;
double p;
int shrinking;// 训练过程是否使用压缩
int probability;// 是否做概率估计
};
2、核心demo
#include "Svm_Test.h"
//加载数据
void Svm_Test::loadDataset(const string &filename)
{
ifstream file(filename);
string line;
while (getline(file, line))
{
istringstream record(line);
vector<double> data;
double temp;
while (record >> temp)
data.push_back(temp);
if (filename.find("train") != string::npos)
{
train_y.push_back(int(temp));
data.pop_back();
train_x.push_back(data);
}
else
{
test_y.push_back(int(temp));
data.pop_back();
test_x.push_back(data);
}
}
}
//初始化训练集
void Svm_Test::init_svm_problem()
{
int train_size = train_y.size();// 训练样本数
int feature_size = train_x[0].size();//样本特征维数
prob.l = train_size;
prob.y = new double[train_size];
prob.x = new svm_node*[train_size];//每一个X指向一个样本
svm_node *node = new svm_node[train_size*(1 + feature_size)];//样本特征存储空间
// 按照格式打包
for (int i = 0; i < train_size; i++)
{
for (int j = 0; j < feature_size; j++)
{
node[(feature_size + 1) * i + j].index = j + 1;
node[(feature_size + 1) * i + j].value = train_x[i][j];
}
node[(feature_size + 1) * i + feature_size].index = -1;
prob.x[i] = &node[(feature_size + 1) * i];
prob.y[i] = train_y[i];
}
}
//初始化SVM的相关参数
void Svm_Test::init_svm_parameter()
{
param.svm_type = C_SVC; // 即普通的二类分类
param.kernel_type = RBF; // 径向基核函数
param.degree = 3; //多项式核函数的参数degree
param.gamma = 0.01; //1/num_features,rbf核函数参数
param.coef0 = 0; //多项式核函数的参数coef0
param.nu = 0.5; //nu-svc的参数
param.cache_size = 1000; //求解的内存缓冲 100MB
param.C = 0.09; //正则项的惩罚系数
param.eps = 1e-5; //收敛精度
param.p = 0.1;
param.shrinking = 1;
param.probability = 0; //1表示训练时生成概率模型,0表示训练时不生成概率模型,用于预测样本的所属类别的概率
param.weight_label = NULL;//类别权重
param.weight = NULL; //样本权重
}
//初始化测试集
svm_node * Svm_Test::init_test_data(vector<double> &input)
{
int sz = input.size();
svm_node *node = new svm_node[sz + 1];
for (int k = 0; k < sz; ++k)
{
node[k].index = k + 1;
node[k].value = input[k];
}
node[sz].index = -1;
return node;
}
void Svm_Test::acc_result()
{
init_svm_problem(); // 打包训练样本
init_svm_parameter(); // 初始化训练参数
svm_model* model = svm_train(&prob, ¶m);
svm_save_model("model", model); // 保存训练好的模型,下次使用时就可直接导入
int acc_num = 0; // 分类正确数
//svm_model* model = svm_load_model("model");
int test_size = test_y.size();
for (int i = 0; i < test_size; i++)
{
double pred = svm_predict(model, init_test_data(test_x[i]));
if (int(pred) == test_y[i])
{
acc_num++;
}
}
cout << "accuracy: " << acc_num*100.0 / test_size << "%" << endl;
cout << "classification: " << acc_num << " / " << test_size << endl;
}
七、SVM算法总结
1、优点
1. 可以解决小样本下机器学习的问题;
2. 提高泛化性能;
3. 可以解决高维、非线性问题。超高维文本分类仍受欢迎;
4. 避免神经网络结构选择和局部极小的问题。
2、缺点
1. 缺失数据敏感;
2. 内存消耗大,难以解释;
3. 运行和调参略麻烦。