支持向量机(Support Vetor Machine,SVM)由Vapnik等人于1995年首先提出,在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并推广到人脸识别、行人检测和文本分类等其他机器学习问题中。
SVM建立在统计学习理论的VC维理论和结构风险最小原理基础上,根据有限的样本信息在模型的复杂性和学习能力之间寻求最佳平衡,以求获得最好的推广能力。SVM可以用于数值预测和分类。
SVM从基础到复杂可以分成三种分别为线性可分支持向量机(也就是硬间隔支持向量机Hard-margin Support Vector Machine)、线性支持向量机(软间隔支持向量机Soft-margin Support Vector Machine)、非线性支持向量机(核函数支持向量机Non-Linear Support Vector Machine)
线性可分定义:
如果一个数据集是线性可分的,那么一定有无数多个超平面将各个类别分开,在这么多超平面中,哪一个是最好的呢?将这条直线分别向两侧移动,直到包含蓝色和红色的点,移动到蓝色、红色点位置的直线就叫做支撑向量,如果一个直线的两个支撑向量的间隔(margin)最大,那这条直线就是将红色、蓝色点分开的最优分类直线。
支持向量机寻找的最优分类直线应满足:
margin=2d,所以最大化margin也就是最大化d。
直线的Ax+By+C=0拓展到n维空间即theta*xb = 0,也可以写做wt是系数,b是截距,wt和b合在一起就是theta。
n维空间点到直线的距离为式1.1:
红色点到直线的距离大于等于d,蓝色点到直线距离小于等于d,将红色点定义为分类结果为1,蓝色点定义为分类结果为-1,然后不等式两边同时除以d:
将分母除开,用wT(d)表示wT除以d的结果,用bd表示b除以d的结果,则不等式可以转换为如下形式,且不等式表示的意思即左侧的直线方程:
将wT(d)重命名为wT,bd重命名为b,以上的式子就变成了:
注意,这时wT和b与刚开始公式中的wT和b不相同,但由于等式两边可以同时除以d,所以原wT * x + b的绝对值也等于1
两个不等式左右两边同时乘以y的真实值,将两个不等式合为一个不等式1.2:
在支撑向量上有wT * x + b的绝对值等于1,在非支撑向量上有wT * x + b的绝对值大于1,我们求的是最大化支撑向量上点到直线wT * x + b = 0的距离d,n维空间点到直线的距离的式子如右侧如图所示,而在支撑向量上wT * x + b = 1,所以最大化间隔就变成了最大化w模的倒数,也就是最小化w模的值。
这是一个有条件的最优化问题,s.t.表示限定条件,式1.3:
无约束条件求极值点只需要求导,梯度为零就是局部极小或极大值点,有条件的最优化问题较为复杂,需要用到拉格朗日乘子法和KKT条件。
在求取有约束条件的优化问题时,拉格朗日乘子法和KKT条件是非常重要的两个求取方法。
1)对于等式约束的优化问题,可以应用拉格朗日乘子法去求取最优值;
2)如果含有不等式约束,可以用于KKT条件去求取。
当然,这两个方法求得的结果只是必要条件,只有当是凸函数的情况下,才能保证是充分必要条件。
硬间隔支持向量机默认样本数据集是完全线性可分的,即存在一个超平面能将两个类别的数据完全分开,在数据近似可分的时候,就不能使用硬间隔支持向量机了。解决该问题的思路是:允许出现一些错误,并且要使得间隔最大的同时,错误最小化。
式1.3的限定条件左侧大于等于 1 是为了使得所有样本点都在正确的分类下,这也是为什么称为硬间隔的原因。而现在数据集无法用一个超平面完全分开,这时就需要允许部分数据集不满足上述约束条件。
可以看到只要每个Delta i足够大,上面的n个不等式一定可以成立,当然我们还要限制每个Delta无限制的扩大,让它在一个合理的范围内。
改造后的支持向量机优化版本式1.4:
其中C>0称为惩罚参数,C值大的时候对误分类的惩罚增大,C值小的时候对误分类的惩罚减小,上图中的式子也被称为是svm的L1正则和L2正则。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
iris = datasets.load_iris()
X = iris.data
y = iris.target
X = X[y<2,:2]
y = y[y<2]
# plt.scatter(X[y==0,0],X[y==0,1])
# plt.scatter(X[y==1,0],X[y==1,1])
# plt.show()
X_train,X_test,y_train,y_test = train_test_split(X, y)
standard = StandardScaler()
standard.fit(X_train)
X_train_standard = standard.transform(X_train)
X_test_standard = standard.transform(X_test)
# 线性支持向量机解决分类问题LinearSVC
# C就是公式中的惩罚参数,C越大越接近硬间隔支持向量机
svc = LinearSVC(C=1e8)
svc.fit(X_train_standard,y_train)
print(svc.coef_)
print(svc.intercept_)
# 当C过小,Delta可以超出合理范围的大,导致允许过多的预测错误出现,使模型预测准确度下降
print(svc.score(X_test_standard, y_test))
在如下图分类问题中,如果我们坚持分开两类的必须是直线,那么无论我们怎么取这条直线,预测的结果都不太准确。
这时候需要从低维映射到高维,使问题在高维度中重新变成线性可分问题。如下图左侧四个点在二维空间中线性不可分:
根据计算结果我们可以看到在五维空间,问题变的线性可分了。
定理:
这个定理告诉我们将训练样本由低维映射到高维,可以增大线性可分的概率。
如果我们将X映射为φ(X),式1.4转换成式1.5,这里有一个隐含的前提条件,在低维问题中wi与Xi维度相同,在高维问题中w与φ(X)维度相同,可以看到高维问题的解法与低维完全类似,都可以利用凸优化理论完成支持向量机的求解。
编写PolynomialSVC.py文件
from sklearn.preprocessing import PolynomialFeatures,StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.svm import LinearSVC
def PolynomialSVC(degree=2, C=1.):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_standar', StandardScaler()),
('svc', LinearSVC(C=C))
])
测试代码:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from common.PolynomialSVC import PolynomialSVC
from sklearn.preprocessing import PolynomialFeatures,StandardScaler
# 默认生成100条包含两个特征的数据,默认是规则的图形,添加noise噪音可以理解为标准差
X,y = datasets.make_moons(noise=0.1)
X_train,X_test,y_train,y_test = train_test_split(X, y)
# 使用Pipeline顺序执行PolynomialFeatures、StandardScaler、LinearSVC预测结果不好,
# 应该由于PolynomialSVC中LinearSVC接收到的X_train经过了PolynomialFeatures添加多项式,StandardScaler归一化
# 而X_test计算score时没有经过这两步,所以预测结果不准确
poly_svc = PolynomialSVC(degree=2, C=1e8)
poly_svc.fit(X_train,y_train)
print(poly_svc.score(X_test,y_test))
poly = PolynomialFeatures(degree=3)
poly.fit(X_train,y_train)
X_train = poly.transform(X_train)
X_test = poly.transform(X_test)
std = StandardScaler()
std.fit(X_train,y_train)
X_train = std.transform(X_train)
X_test = std.transform(X_test)
svc = PolynomialSVC(C=1e8)
svc.fit(X_train,y_train)
print(svc.score(X_test,y_test))
编写PolynomialKernelSVC.py文件
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
def PolynomialKernelSVC(degree=2,C=1.0):
return Pipeline([
('std_standar', StandardScaler()),
# 使用核函数的svm
('kernel_svc', SVC(degree=degree, C=C))
])
测试:
import numpy as np
from sklearn import datasets
from common.PolynomialKernelSVC import PolynomialKernelSVC
# 默认生成100条包含两个特征的数据,默认是规则的图形,添加noise噪音可以理解为标准差
X,y = datasets.make_moons(noise=0.1)
kernel_svc = PolynomialKernelSVC(degree=2, C=1e8)
kernel_svc.fit(X,y)
print(kernel_svc.score(X,y))
从结果的角度,LinearSVC和使用SVC且kernel传入linear,结果是一致的。但是由于LinearSVC只能计算线性核,而SVC可以计算任意核,所以,他们的底层计算方式不一样,这使得同样使用线性核的SVC,用LinearSVC的计算速度,要比用SVC且kernel传入linear参数,快很多。
所以,整体而言,如果你决定使用线性SVM,就使用LinearSVC,但如果你要是用其他核的SVM,就可以使用SVC。
我们将φ(X)的转置乘以φ(X)称为核函数(Kernel Function),核函数是一个实数。
已知核函数K求映射φ(X)的例子:
假设X是一个二维向量,X1=[x11, x12]的转置,X2=[x21, x22]的转置
如果φ(X)是如下五维向量的形式,核函数K就是上面的形式
核函数K和映射φ(X)是一 一对应的关系,知道一个,可以求出另一个。
Mercer定理:
正态分布就是一个高斯函数。高斯核函数有时也被称为RBF核(Radial Basis Function Kernel),有些文章中也会把1.0/(2 * sigma ** 2)替换成gamma,sklearn中的高斯核函数似乎也是用的gamma,gamma越大,正态分布越窄,模型越趋向过拟合,gamma越小,模型越趋向欠拟合。
高斯核函数代码演示:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-4, 5, 1)
print(x)
y = np.array((x > -2) & (x < 3), dtype=int)
print(y)
def gaussian(x,l):
sigma = 1.0
# 将高斯核函数中的x2固定为l,l是地标
return np.exp(-(1.0/(2 * sigma ** 2)) * (x - l) ** 2)
l1,l2 = -1,1
X_new = np.empty((len(x), 2))
for i,data in enumerate(x):
X_new[i,0] = gaussian(data, l1)
X_new[i,1] = gaussian(data, l2)
plt.scatter(X_new[y==0,0],X_new[y==0,1])
plt.scatter(X_new[y==1,0],X_new[y==1,1])
plt.show()
高斯核函数也是升维,是将m * n的数据映射为m * m的数据,对于每个数据点都是一个地标(landmark)。当样本数量m小于特征数n时,使用高斯核函数就非常划算,最典型的应用领域是自然语言处理。
编写RBFKernelSVC.py文件
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC
def RBFKernelSVC(gamma=1.0):
return Pipeline([
('std_standar', StandardScaler()),
('svc', SVC(kernel='rbf', gamma=gamma))
])
使用测试数据演示:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from common.RBFKernelSVC import RBFKernelSVC
X,y = datasets.make_moons(noise=0.15, random_state=666)
# plt.scatter(X[y==0,0],X[y==0,1])
# plt.scatter(X[y==1,0],X[y==1,1])
# plt.show()
svc = RBFKernelSVC(gamma=1.0)
svc.fit(X,y)
print(svc.score(X,y))
线性回归算法就是让预测的直线的MSE的值最小,对于SVM来说,拟合的定义是指定一个margin值,在这个margin范围里面,包含的数据点越多越好,包含的越多就代表这个范围能比较好的表达样本数据点,这种情况下取中间的直线作为真正的回归结果,用其来预测其他点的相应的值。
在训练的时候是要对margin的范围进行一个指定,这就要引入一个新的超参数epsilon,即上下两根直线到中间的直线的垂直距离。
这和前面SVM解决分类问题的思路相反,前面是margin中的点越少越好,硬间隔支持向量机要求margin中一个点都不能有,这里是越多越好。
编写StandardLinearSVR.py文件,这里使用LinearSVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.svm import SVR
from sklearn.svm import LinearSVR
def StandardLinearSVR(epsilon=0.1):
return Pipeline([
('std_scale', StandardScaler()),
('linear_svr', LinearSVR(epsilon=epsilon))
])
使用波士顿房价数据测试:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from common.StandardLinearSVR import StandardLinearSVR
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVR
boston = datasets.load_boston()
X = boston.data
y = boston.target
X_train,X_test,y_train,y_test = train_test_split(X,y)
linear_svr = StandardLinearSVR()
linear_svr.fit(X_train,y_train)
print(linear_svr.score(X_test,y_test))
std = StandardScaler()
std.fit(X_train,y_train)
X_train = std.transform(X_train)
X_test = std.transform(X_test)
linear_svr = LinearSVR()
linear_svr.fit(X_train,y_train)
print(linear_svr.score(X_test,y_test))