7&8. 支持向量机 SVM

SVM简介:

支持向量机(SVM,也称为支持向量网络),是机器学习中获得关注最多的算法没有之一。它源于统计学习理论, 是我们除了集成算法之外,接触的第一个强学习器。

 

从算法的功能来看:SVM囊括了分类和聚类功能:

从分类效力来讲:SVM在无论线性还是非线性分类中,都是明星般的存在,如此全能,宛如机器学习界的刘德华。

从学术的角度来看:SVM是最接近深度学习的机器学习算法。

 

 

这几个缩写的含义及关系,以免混淆:

  • SVM=Support Vector Machine 是支持向量
  • SVC=Support Vector Classification 就是支持向量机用于分类 
  • SVR=Support Vector Regression 就是支持向量机用于回归分析

SVM模型的几种:

  • svm.LinearSVC Linear Support Vector Classification.
  • svm.LinearSVR Linear Support Vector Regression.
  • svm.NuSVC Nu-Support Vector Classification.
  • svm.NuSVR Nu Support Vector Regression.
  • svm.OneClassSVM Unsupervised Outlier Detection.
  • svm.SVC C-Support Vector Classification.
  • svm.SVR Epsilon-Support Vector Regression.

 

 

支持向量机分类器是如何工作的:

支持向量机所作的事情其实非常容易理解。先来看看下面这一组数据的分布,这是一组两种标签的数据,两种标签分别由圆和方块代表。支持向量机的分类方法,是在这组分布中找出一个超平面作为决策边界,使模型在数据上的分类误差尽量接近于小,尤其是在未知数据集上的分类误差(泛化误差)尽量小。

另一种情况:升维。

 

关键概念:超平面

在几何中,超平面是一个空间的子空间,它是维度比所在空间小一维的空间。 如果数据空间本身是三维的, 则其超平面是二维平面,而如果数据空间本身是二维的,则其超平面是一维的直线。

在二分类问题中,如果一个超平面能够将数据划分为两个集合,其中每个集合中包含单独的一个类别,我们就说这个超平面是数据的“决策边界”。

 

选用不同的核函数,就可以解决不同数据分布下的寻找超平面问题。在sklearn的SVC中,这个功能由参数“kernel”和一系列与核函数相关的参数来进行控制。我们就在乳腺癌数据集上,来探索一下各种核函数的功能和选择。

 

除了选项"linear"之外,其他核函数都可以处理非线性问题。
多项式核函数有次数d,当d为1的时候它就是在处理线性问题,当d为更高次项的时候它就是在处理非线性问题。

 

 

那究竟什么时候选择哪一个核函数呢?

答:

线性核函数和多项式核函数在非线性数据上表现会浮动,如果数据相对线性可分,则表现不错;如果是像环形数据那样彻底不可分的,则表现糟糕。在线性数据集上,线性核函数和多项式核函数即便有扰动项也可以表现不错,可见多项式核函数是虽然也可以处理非线性情况,但更偏向于线性的功能。

Sigmoid核函数就比较尴尬了,它在非线性数据上强于两个线性核函数,但效果明显不如rbf,它在线性数据上完全比不上线性的核函数们,对扰动项的抵抗也比较弱,所以它功能比较弱小,很少被用到。

rbf,高斯径向基核函数基本在任何数据集上都表现不错,属于比较万能的核函数。

经验是,无论如何先试试看高斯径向基核函数,它适用于核转换到很高的空间的情况,在各种情况下往往效果都很不错,如果rbf效果不好,那我们再试试看其他的核函数。另外,多项式核函数多被用于图像处理之中。

 

 

实例: 乳腺癌数据集下探索kernel该如何选取

from sklearn.datasets import load_breast_cancer
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
from time import time
import datetime

data=load_breast_cancer()
x=data.data
y=data.target

x.shape
(569, 30)

# 查看y中有多少个不同的值,可知只有0和1
np.unique(y)
array([0, 1])

plt.scatter(x[:,0],x[:,1],c=y)
plt.show()

Xtrain, Xtest, Ytrain, Ytest = train_test_split(x,y,test_size=0.3,random_state=420)

Kernel = ["linear","poly","rbf","sigmoid"]

for kernel in Kernel:
    time0 = time()
    clf= SVC(kernel = kernel
              ,gamma="auto"
              , cache_size=5000 ).fit(Xtrain,Ytrain)
    print("The accuracy under kernel %s is %f" % (kernel,clf.score(Xtest,Ytest))) 
    print(datetime.datetime.fromtimestamp(time()-time0).strftime("%M:%S:%f"))

然后我们发现,怎么跑都跑不出来。模型一直停留在线性核函数之后,就没有再打印结果了。这证明,多项式核函数此时此刻要消耗大量的时间,运算非常的缓慢。因为默认多项式为三次方,而数据有30多个特征,运算量当然爆炸。

让我们在循环中去掉多项式核函数,再试试看能否跑出结果:

Xtrain,Xtest,Ytrain,Ytest=train_test_split(x,y,test_size=0.3,
                                            random_state=420)
Kernel = ["linear","rbf","sigmoid"]
for kernel in Kernel:
    time0 = time()
    clf= SVC(kernel = kernel
             , gamma="auto"
             , cache_size=5000).fit(Xtrain,Ytrain)
   
    print("The accuracy under kernel %s is %f" % (kernel,clf.score(Xtest,Ytest))) 
    print(datetime.datetime.fromtimestamp(time()-time0).strftime("%M:%S:%f"))

结果: 

我们可以有两个发现。
首先,乳腺癌数据集是一个线性数据集,线性核函数跑出来的效果很好;rbf和sigmoid两个擅长非线性的数据从效果上来看完全不可用;

其次,线性核函数的运行速度远远不如非线性的两个核函数。

如果数据是线性的(实际上乳腺癌数据集就是线性的),那如果我们把degree参数调整为1,多项式核函数应该也可以得到不错的结果:

Xtrain, Xtest, Ytrain, Ytest = train_test_split(x,y,test_size=0.3,random_state=420)

Kernel = ["linear","poly","rbf","sigmoid"]

for kernel in Kernel:
    time0 = time()
    clf= SVC(kernel = kernel
              ,degree=1
              ,gamma="auto"
              , cache_size=5000 ).fit(Xtrain,Ytrain)
    print("The accuracy under kernel %s is %f" % (kernel,clf.score(Xtest,Ytest))) 
    print(datetime.datetime.fromtimestamp(time()-time0).strftime("%M:%S:%f"))

多项式核函数的运行速度立刻加快了,并且精度也提升到了接近线性核函数的水平。

但是,我们之前说rbf是全能的,堪称刘德华,rbf在线性数据上也可以表现得非常好,可是在这里,为什么跑出来的结果如此糟糕呢?

其实,这里真正的问题是数据的量纲问题。
回忆一下我们如何求解决策边界,如何判断点是否在决策边界的一边?  是靠计算”距离“,虽然我们不能说SVM是完全的距离类模型,但是它严重受到数据量纲的影响。

让我们来探索一下乳腺癌数据集的量纲:


x.shape
(569, 30)

import pandas as pd
data=pd.DataFrame(x)
data.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.99]).T
# 对这个数据做描述性统计,显示数据在1%,5%,......90%,99%时候的分布

一眼望去,果然数据存在严重的量纲不一的问题:均值mean最小有0.09,最大有654.88;其他也是;而在99%数据时占了全数据信息的24.37%,可知数据的分布不是标准正态分布,而是严重的右倾。

 

所以,我们来使用数据预处理中的标准化的类,对数据进行标准化:(所用的知识在《3.数据预处理 Preprocessing & Impute》中)

from sklearn.preprocessing import StandardScaler
x=StandardScaler().fit_transform(x)
data=pd.DataFrame(x)

# 看看标准化后的数据:
data.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.99]).T

一看,果然好多了。

 

标准化完毕后,再次让SVC在核函数中遍历。
此时我们把degree的数值设定为1,观察各个核函数在去量纲后的数据上的表现:

Xtrain, Xtest, Ytrain, Ytest = train_test_split(x,y,test_size=0.3,random_state=420)

Kernel = ["linear","poly","rbf","sigmoid"]

for kernel in Kernel:
    time0 = time()
    clf= SVC(kernel = kernel
              ,degree=1
              ,gamma="auto"
              , cache_size=5000 ).fit(Xtrain,Ytrain)
    print("The accuracy under kernel %s is %f" % (kernel,clf.score(Xtest,Ytest))) 
    print(datetime.datetime.fromtimestamp(time()-time0).strftime("%M:%S:%f"))


量纲统一之后,可以观察到,所有核函数的运算时间都大大地减少了,尤其是对于线性核来说,而多项式核函数居然变成了计算最快的;其次,rbf表现出了非常优秀的结果。


经过我们的探索,我们可以得到的结论是:

1. 线性核,尤其是多项式核函数在高次项时计算非常缓慢
2. rbf和多项式核函数都不擅长处理量纲不统一的数据集

幸运的是,这两个缺点都可以由数据无量纲化来解决。
因此,SVM执行之前,非常推荐先进行数据的无量纲化!到了这一步,我们是否已经完成建模了呢?虽然线性核函数的效果是最好的,但它是没有核函数相关参数可以调整的,rbf和多项式却还有着可以调整的相关参数,接下来我们就来看看这些参数。

 

 

通过调参数来进一步提高预测结果与运行时间:

对于线性核函数,"kernel"是唯一能够影响它的参数(即这三个参数是固定的,参数不可调整),对于其他三种非线性核函数,他们受到参数gamma,degree以及coef0的影响。 

对于高斯径向基核函数rbf,调整gamma的方式其实比较容易,那就是画学习曲线。
我们来试试看高斯径向基核函数 rbf的参数gamma在乳腺癌数据集上的表现:

score=[]
gamma_range=np.logspace(-10,1,50)
# 返回在对数刻度上均匀间隔的数字

for i in gamma_range:
    clf=SVC(kernel="rbf"
            ,gamma=i
            ,cache_size=5000).fit(Xtrain,Ytrain)
    score.append(clf.score(Xtest,Ytest))

print(max(score),gamma_range[score.index(max(score))])
plt.plot(gamma_range,score)
plt.show()

通过学习曲线,很容就找出了rbf的最佳gamma值。但我们观察到,这其实与线性核函数的准确率一模一样之前的准确率。我们可以多次调整gamma_range来观察结果,可以发现97.6608应该是rbf核函数的极限了。

 

但对于多项式核函数来说,一切就没有那么容易了,因为三个参数共同作用在一个数学公式上影响它的效果。

因此我们往往使用网格搜索,来共同调整三个对多项式核函数有影响的参数。
依然使用乳腺癌数据集:

# 探索多个参数共同影响一个函数的效率:用带交叉验证的网格搜索

from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.model_selection import GridSearchCV
# GridSearchCV:带交叉验证的网格搜索。要自己设置交叉验证的模式,
# StratifiedShuffleSplit是专门用来设置交叉验证的模式的

time0=time()

gamma_range=np.logspace(-10,1,20)
coef0_range=np.linspace(0,5,10)

param_grid=dict(gamma=gamma_range,coef0=coef0_range)
# 设置了参数的字典 

cv=StratifiedShuffleSplit(n_splits=5,test_size=0.3,random_state=420)
# 数据分几份,测试集大小,随机程度
  
grid=GridSearchCV(SVC(kernel="poly",degree=1,cache_size=5000)
                  ,param_grid=param_grid
                  ,cv=cv)
# 第一个参数: 实例化的一个类
# 第二个参数 param_grid:参数设置
# 第三个参数 cv:这里的cv就是之前用StratifiedShuffleSplit设置的cv模式。(cross validation )

grid.fit(x,y)

print("the best parameters are %s with a score of %0.5f"%(grid.best_params_,grid.best_score_))

print(datetime.datetime.fromtimestamp(time()-time0).strftime("%M:%S:%f"))

可以发现,网格搜索为我们返回了参数coef0=0,gamma=0.18329807108324375,但整体的分数是0.96959,虽然比调参前略有提高,但依然没有超过线性核函数核rbf的结果。

可见,如果最初选择核函数的时候,你就发现多项式的结果不如rbf和线性核函数,那就不要挣扎了,试试看调整rbf或者直接使用线性。

 

其实,还有一个重要参数C,可以影响线性核函数,也可以影响高丝径向基核函数。

在实际使用中,C和核函数的相关参数(gamma,degree等等)们搭配,往往是SVM调参的重点。

与gamma不同,C没有在函数中出现,并且是明确了调参目标的,所以我们可以明确我们究竟是否需要训练集上的高精确度来调整C的方向。

默认情况下C为1,通常来说这都是一个合理的参数。 

如果我们的数据很嘈杂,那我们往往减小C。
当然,我们也可以使用网格搜索或者学习曲线来调整C的值。

# 调整线性核函数中的C:

score=[]
C_range=np.linspace(0.01,30,50)
for i in C_range:
    clf=SVC(kernel="linear",C=i,cache_size=5000).fit(Xtrain,Ytrain)
    score.append(clf.score(Xtest,Ytest))

print(max(score),C_range[score.index(max(score))])
plt.plot(C_range,score)
plt.xlabel("C_range")
plt.ylabel("C")
plt.show()

# 调整rbf核函数中的C:

score=[]
C_range=np.linspace(0.01,30,50)
for i in C_range:
    clf=SVC(kernel="rbf",C=i,gamma=0.012,cache_size=5000).fit(Xtrain,Ytrain)
    score.append(clf.score(Xtest,Ytest))

print(max(score),C_range[score.index(max(score))])
plt.plot(C_range,score)
plt.xlabel("C_range")
plt.ylabel("C")
plt.show()

至此,我们知道,应该选用rbf,因为在上面所用的各种kernel与参数调整中,rbf的上限更高。

现在我们进一步细化C点取值:

# 进一步细化rbf:

score = []
C_range = np.linspace(5,7,50) 
for i in C_range:
    clf = SVC(kernel="rbf",C=i,gamma = 0.012,cache_size=5000).fit(Xtrain,Ytrain)
    score.append(clf.score(Xtest,Ytest))
    
print(max(score), C_range[score.index(max(score))]) 
plt.plot(C_range,score)
plt.show()

此时,我们找到了乳腺癌数据集上的最优解:rbf核函数下的98.24%的准确率。参数取值:gamma=0.012,C=6.26。

 

 

 

 

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值