机器学习中另一种重要的算法是支持向量机,简称SVM。下面我们就这个算法进行介绍。
首先支持向量机的整体思路是:
简单情况,线性可分,把问题转化为一个凸优化问题,可以用拉格朗日乘子法简化,
然后用既有的算法解决
复杂情况,线性不可分,用映射函数将样本投射到高维空间,使其变成线性可分的情
形。利用核函数来减少高维度计算量
这个思路的具体实现是什么呢,首先考虑样本集线性可分情况
简单情况,线性可分
如上图,如何选取最优的分离平面(决策边界)?
就好比所有数据点都是地雷,现在要找出一条最大边界,使得部队能够快速的通过。像下图中
这两个边界都可以让部队通过,但是右边的路线能是部队快速的通过,这个就是支持向量机(SVM)要做的事情。而通道上紧挨着的3个数据点,在这里叫做“支持向量”。。
好了,下面我们就要对SVM进行一步一步的算法分析,怎么能够得到这条最大的边界。
上图中我们假设最优的分离平面是w.x+b = 0
上边界定义为w.x+b = 1,数据点x1位于上边界上
下边界定义为w.x+b = -1,数据点x2位于下边界上。(注意,这里w.x是点乘,内积)
而我们要求解的是两个边界的最大距离d
用将上下边界上的点带入到边界上,并用上边界减去下边界可得
w.(x1-x2) = 2
而根据内积的几个意义可知
w.(x1-x2) = 2---------------->||w||*||x1-x2||*cosθ = 2 (θ为x1与x2向量和距离d的夹角)
所以。||w||*||x1-x2||*cosθ = 2 ---------------->||w||*d = 2由此可得 d = 2/||w||。。。。
到这里我们的目标是优化d = 2/||w||,使得d最大。而条件是
(因为是分类。)
而这样的“如果”是不方便我们进行数学计算的,并且按照通常的思路,求极大值问题的时候我们往往转换成求极小值问题。所以我们进行整理则有
到这里,似乎有些进行不下去了。这时候需要用到-------------拉格朗日乘子法。。。。
将上图中的式子进行转换
其中α是拉格朗日乘子。
根据拉格朗日乘子法我们要求解L(w,b,a)的最大值
对w和b分别求导可得
将上述带回到
最终呢,我们得到的一个带约束条件的式子:
到这里,还有一种情况,需要我们加入松弛变量和惩罚函数
如上图这种情况,有部分黑色的点在白色区域内,同样部分白色点在黑色区域内。由于这些异常点存在。我们不能完全分离。加入松弛变量和惩罚函数,就是使得边界到这些异常点越近越好。所以将原来的需求变成如下形式。
其中C越大惩罚力度越大,我们找到的一个分隔区域越窄。C越小惩罚力度越小,我们找到这个分隔区域越宽。
最后经过拉格朗日乘子法可得
由此看出,跟原来没有加惩罚函数和松弛变量的式子区别在于限制条件的变化。
到这里,最后要怎么计算这个式子,得出结果呢
就是采用SMO算法,这个算法有兴趣的朋友可以自行百度了解一下。
主要的思路是:空出两个乘子后,其余乘子随机赋值。通过上面的式子计算出这两个没有赋值的乘子。每次只更新两个乘子,最后反复迭代,知道空出的两个乘子不再变化。
复杂情况,线性不可分
而对于线性不可分的情况。思路就是将低维不可分的映射到高维可分。
公式中红字的地斱要使用映射后的样本向量代替做内积
但是高维映射会导致维度灾难
最初的特征是n维的,我们将其映射到n^2维,然后再计算,这样需要的时间从原先的O(n)变成O(n^2)
因此,我们引入核函数。
举个例子:
在一组房价的数据集中,特征是房子的面积x,这里的x是实数,结果y是房子的价格。假设我们从样本点的分布中看到x和y符合3次曲线,那么我们希望使用x的三次多项式来逼近这些样本点。那么首先需要将特征x扩展到三维,然后寻找特征和结果之间的模型。我们将这种特征变换称作特征映射(feature mapping)。映射函数称作
,在这个例子中
我们将得到的特征映射后的特征应用于SVM分类,而不是最初的特征。这样,我们需要将前面公式中的内积从
,映射到
。如果要实现该开头的式子,只需先计算
,然后计算
即可,然而这种计算方式是非常低效的。比如最初的特征是n维的,我们将其映射到
维,然后再计算,这样需要
的时间。
假设展开后
这个时候发现我们可以只计算原始特征x和z内积的平方(时间复杂度是O(n)),就等价与计算映射后特征的内积。也就是说我们不需要花时间了。
最后给出常用的核函数
有了核函数后可以将上面的拉格朗日函数简化
最后我们用代码来使用SVM
import numpy as np
import pylab as pl
from sklearn import svm
而使用的数据集是一个非常有名的用于分类问题的数据集:鸢尾花数据集。它是基于鸢尾花的花萼的长度和宽度进行分类的。我们只用其中两维特征,这能够方便可视化。
svc = svm.SVC(kernel='linear')
# 鸢尾花数据集是sklearn自带的。
from sklearn import datasets
iris = datasets.load_iris()
# 只提取前面两列数据作为特征
X = iris.data[:, :2]
y = iris.target
# 基于这些数据训练出一个支持向量分离器SVC
svc.fit(X, y)
数据结果可视化
from matplotlib.colors import ListedColormap
# 因为鸢尾花是3分类问题,我们要对样本和预测结果均用三种颜色区分开。
cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF'])
cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF'])
def plot_estimator(estimator, X, y):
'''
这个函数的作用是基于分类器,对预测结果与原始标签进行可视化。
'''
estimator.fit(X, y)
# 确定网格最大最小值作为边界
x_min, x_max = X[:, 0].min() - .1, X[:, 0].max() + .1
y_min, y_max = X[:, 1].min() - .1, X[:, 1].max() + .1
# 产生网格节点
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
np.linspace(y_min, y_max, 100))
# 基于分离器,对网格节点做预测
Z = estimator.predict(np.c_[xx.ravel(), yy.ravel()])
# 对预测结果上色
Z = Z.reshape(xx.shape)
pl.figure()
pl.pcolormesh(xx, yy, Z, cmap=cmap_light)
# 同时对原始训练样本上色
pl.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold)
pl.axis('tight')
pl.axis('off')
pl.tight_layout()
plot_estimator(svc, X, y)
而加入惩罚函数和松弛变量
svc = svm.SVC(kernel='linear', C=1e3)
plot_estimator(svc, X, y)
pl.scatter(svc.support_vectors_[:, 0], svc.support_vectors_[:, 1], s=80, facecolors='none', zorder=10)
pl.title('High C values: small number of support vectors')
svc = svm.SVC(kernel='linear', C=1e-3)
plot_estimator(svc, X, y)
pl.scatter(svc.support_vectors_[:, 0], svc.support_vectors_[:, 1], s=80, facecolors='none', zorder=10)
pl.title('Low C values: high number of support vectors')
在试试利用核函数
采用核方法,能够很方便地产生非线性分类边界。
- linear,线性核,会产生线性分类边界。一般来说它的计算效率最高,而且需要数据最少。
- poly ,多项式核,会产生多项式分类边界。
- rbf,径向基函数,也就是高斯核,是根据与每一个支持向量的距离来决定分类边界的。它的映射到无线维的。它是最灵活的方法,但是也需要最多的数据。
svc = svm.SVC(kernel='linear')
plot_estimator(svc, X, y)
pl.scatter(svc.support_vectors_[:, 0], svc.support_vectors_[:, 1], s=80, facecolors='none', zorder=10)
pl.title('Linear kernel')
svc = svm.SVC(kernel='poly', degree=4)
plot_estimator(svc, X, y)
pl.scatter(svc.support_vectors_[:, 0], svc.support_vectors_[:, 1], s=80, facecolors='none', zorder=10)
pl.title('Polynomial kernel')
svc = svm.SVC(kernel='rbf', gamma=1e2)
plot_estimator(svc, X, y)
pl.scatter(svc.support_vectors_[:, 0], svc.support_vectors_[:, 1], s=80, facecolors='none', zorder=10)
pl.title('RBF kernel')
我们可以看到,高斯核更灵活,而且对于训练数据效果是最好的。但是要担心过拟合。
最后,支持向量机SVM是一个在机器学习中常见的算法,虽然正逐渐被神经网络慢慢替代,但是其算法的思想是我们要掌握的。