1、支持向量机算法原理

支持向量机(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)

1.1硬间隔支持向量机理论推导

线性可分定义:

支持向量机算法公式 支持向量机算法介绍_支持向量机

如果一个数据集是线性可分的,那么一定有无数多个超平面将各个类别分开,在这么多超平面中,哪一个是最好的呢?将这条直线分别向两侧移动,直到包含蓝色和红色的点,移动到蓝色、红色点位置的直线就叫做支撑向量,如果一个直线的两个支撑向量的间隔(margin)最大,那这条直线就是将红色、蓝色点分开的最优分类直线。

支持向量机算法公式 支持向量机算法介绍_人工智能_02

支持向量机寻找的最优分类直线应满足:

  • 该直线分来了两类
  • 该直线最大化margin
  • 该直线处于间隔的中间,到所有支持向量距离相等

margin=2d,所以最大化margin也就是最大化d。

直线的Ax+By+C=0拓展到n维空间即theta*xb = 0,也可以写做wt是系数,b是截距,wt和b合在一起就是theta。

支持向量机算法公式 支持向量机算法介绍_支持向量机算法公式_03

n维空间点到直线的距离为式1.1:

支持向量机算法公式 支持向量机算法介绍_数据_04

红色点到直线的距离大于等于d,蓝色点到直线距离小于等于d,将红色点定义为分类结果为1,蓝色点定义为分类结果为-1,然后不等式两边同时除以d:

支持向量机算法公式 支持向量机算法介绍_核函数_05

将分母除开,用wT(d)表示wT除以d的结果,用bd表示b除以d的结果,则不等式可以转换为如下形式,且不等式表示的意思即左侧的直线方程:

支持向量机算法公式 支持向量机算法介绍_核函数_06

将wT(d)重命名为wT,bd重命名为b,以上的式子就变成了:

支持向量机算法公式 支持向量机算法介绍_数据_07

注意,这时wT和b与刚开始公式中的wT和b不相同,但由于等式两边可以同时除以d,所以原wT * x + b的绝对值也等于1

两个不等式左右两边同时乘以y的真实值,将两个不等式合为一个不等式1.2:

支持向量机算法公式 支持向量机算法介绍_人工智能_08

在支撑向量上有wT * x + b的绝对值等于1,在非支撑向量上有wT * x + b的绝对值大于1,我们求的是最大化支撑向量上点到直线wT * x + b = 0的距离d,n维空间点到直线的距离的式子如右侧如图所示,而在支撑向量上wT * x + b = 1,所以最大化间隔就变成了最大化w模的倒数,也就是最小化w模的值。

支持向量机算法公式 支持向量机算法介绍_人工智能_09

这是一个有条件的最优化问题,s.t.表示限定条件,式1.3:

支持向量机算法公式 支持向量机算法介绍_人工智能_10

无约束条件求极值点只需要求导,梯度为零就是局部极小或极大值点,有条件的最优化问题较为复杂,需要用到拉格朗日乘子法和KKT条件。

在求取有约束条件的优化问题时,拉格朗日乘子法和KKT条件是非常重要的两个求取方法。

1)对于等式约束的优化问题,可以应用拉格朗日乘子法去求取最优值;

2)如果含有不等式约束,可以用于KKT条件去求取。

当然,这两个方法求得的结果只是必要条件,只有当是凸函数的情况下,才能保证是充分必要条件。

1.2 软间隔支持向量机理论推导

硬间隔支持向量机默认样本数据集是完全线性可分的,即存在一个超平面能将两个类别的数据完全分开,在数据近似可分的时候,就不能使用硬间隔支持向量机了。解决该问题的思路是:允许出现一些错误,并且要使得间隔最大的同时,错误最小化。

支持向量机算法公式 支持向量机算法介绍_数据_11

式1.3的限定条件左侧大于等于 1 是为了使得所有样本点都在正确的分类下,这也是为什么称为硬间隔的原因。而现在数据集无法用一个超平面完全分开,这时就需要允许部分数据集不满足上述约束条件。

支持向量机算法公式 支持向量机算法介绍_支持向量机算法公式_12

可以看到只要每个Delta i足够大,上面的n个不等式一定可以成立,当然我们还要限制每个Delta无限制的扩大,让它在一个合理的范围内。

改造后的支持向量机优化版本式1.4:

支持向量机算法公式 支持向量机算法介绍_核函数_13

其中C>0称为惩罚参数,C值大的时候对误分类的惩罚增大,C值小的时候对误分类的惩罚减小,上图中的式子也被称为是svm的L1正则和L2正则。

2、sklearn中的svm

2.1 sklearn中的线性SVM

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))

2.2 线性svm多项式特征理论推导

在如下图分类问题中,如果我们坚持分开两类的必须是直线,那么无论我们怎么取这条直线,预测的结果都不太准确。

支持向量机算法公式 支持向量机算法介绍_数据_14

这时候需要从低维映射到高维,使问题在高维度中重新变成线性可分问题。如下图左侧四个点在二维空间中线性不可分:

支持向量机算法公式 支持向量机算法介绍_核函数_15

支持向量机算法公式 支持向量机算法介绍_数据_16

支持向量机算法公式 支持向量机算法介绍_核函数_17

支持向量机算法公式 支持向量机算法介绍_数据_18

根据计算结果我们可以看到在五维空间,问题变的线性可分了。

定理:

支持向量机算法公式 支持向量机算法介绍_支持向量机算法公式_19

这个定理告诉我们将训练样本由低维映射到高维,可以增大线性可分的概率。

如果我们将X映射为φ(X),式1.4转换成式1.5,这里有一个隐含的前提条件,在低维问题中wi与Xi维度相同,在高维问题中w与φ(X)维度相同,可以看到高维问题的解法与低维完全类似,都可以利用凸优化理论完成支持向量机的求解。

支持向量机算法公式 支持向量机算法介绍_人工智能_20

2.3 LinearSVC使用多项式特征

编写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))

2.4 使用SVC

编写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。

3、核函数

3.1 核函数理论

我们将φ(X)的转置乘以φ(X)称为核函数(Kernel Function),核函数是一个实数。

支持向量机算法公式 支持向量机算法介绍_核函数_21

支持向量机算法公式 支持向量机算法介绍_支持向量机算法公式_22

已知核函数K求映射φ(X)的例子:

假设X是一个二维向量,X1=[x11, x12]的转置,X2=[x21, x22]的转置

支持向量机算法公式 支持向量机算法介绍_支持向量机_23

如果φ(X)是如下五维向量的形式,核函数K就是上面的形式

支持向量机算法公式 支持向量机算法介绍_人工智能_24

核函数K和映射φ(X)是一 一对应的关系,知道一个,可以求出另一个。

Mercer定理:

支持向量机算法公式 支持向量机算法介绍_数据_25

支持向量机算法公式 支持向量机算法介绍_支持向量机_26

正态分布就是一个高斯函数。高斯核函数有时也被称为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时,使用高斯核函数就非常划算,最典型的应用领域是自然语言处理

3.2 sklearn中的高斯核函数

编写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))

4、使用svm解决回归问题

线性回归算法就是让预测的直线的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))