1 概述
支持向量机(SVM,也称为支持向量网络),是机器学习中获得关注最多的算法没有之一。从实际应用来看,在生物科学的尖端研究中,人们使用支持向量机来识别用于模型预测的各种特征,以找出各种基因表现结果的影响因素。
支持向量机是如何工作的
支持向量机的分类方法,是在这组分布中找出一个超平面作为决策边界,使模型在数据上的分类误差尽量接近于小,尤其是在未知数据集上的分类误差(泛化误差)尽量小。超平面是一个空间的子空间,它是维度比所在空间小一维的空间。在二分类问题中,超平面就是决策边界。 在b11和b12中间的距离,叫做这条决策边界B1的边际(margin),通常记作d。
支持向量机,就是通过找出边际最大的决策边界,来对数据进行分类的分类器。 image.png
2 sklearn.svm.SVC 线性支持向量分类
class sklearn.svm.SVC (C=1.0, kernel=’rbf’, degree=3, gamma=’auto_deprecated’, coef0=0.0, shrinking=True,
probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1,
decision_function_shape=’ovr’, random_state=None)
线性SVM用于分类的原理过于复杂。两类数据中距离我们的决策边界最近的点,这些点就被称为“支持向量”。
2.1 线性决策过程的可视化
使用sklearn中的式子来为可视化我们的决策边界,支持向量,以及决策边界平行的两个超平面。
1. 导入库和模块
from sklearn.datasets import make_blobs
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np
2. 实例化和可视化数据集
X,y = make_blobs(n_samples=50, centers=2, random_state=0,cluster_std=0.6) #cluster_std表示簇的方差是0.6
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow") #x1是横坐标,x2是纵坐标,c颜色是y,s是尺寸,cmap是所采用的点的颜色
plt.xticks([])
plt.yticks([])
plt.show()
3. 画决策边界:理解函数contour
3.1 理解meshgrid和vstack的作用
a = np.array([1,2,3])
b = np.array([7,8])
# meshgrid就是将一维的v1和v2分别进行复制,元素多的直接复制,元素少的是每一个数生成一个list,list的个数是元素多的v1的个数
v1,v2 = np.meshgrid(a,b)
print(v1.ravel()) #ravel是准换为一维
print(v2.ravel())
np.vstack([v1.ravel(), v2.ravel()]) #vstack能够将多个结构一致的一维数组按行堆叠起来
v = np.vstack([v1.ravel(), v2.ravel()]).T
v
image.png
3.2 理解函数contour
matplotlib.axes.Axes.contour([X, Y,] Z, [levels], kwargs),Contour是我们专门用来绘制等高线的函数。Contour就是将由X和Y构成平面上的所有点中,高度一致的点连接成线段的函数,在同一条等高线上的点一定具有相同的Z值。**
其实,我们的样本构成的平面上,把所有到决策边界的距离为0的点相连,就是我们的决策边界,而把所有到决策边界的相对距离为1的点相连,就是我们的两个平行于决策边界的超平面了。
image.png
首先需要获取样本构成的平面,作为一个对象
#首先要有散点图
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
ax = plt.gca() #获取当前的子图,如果不存在,则创建新的子图
image.png
4. 画决策边界:制作网格,理解函数meshgrid
# 有了这个平面,我们需要在平面上制作一个足够细的网格,来代表我们“平面上的所有点”。
#获取平面上两条坐标轴的最大值和最小值
xlim = ax.get_xlim()
ylim = ax.get_ylim()
#在最大值和最小值之间形成30个规律的数据
#就是把axisx和axisy分别当成横纵坐标。
axisx = np.linspace(xlim[0],xlim[1],30) #linspace(起始点,终止点,取值的个数)
axisy = np.linspace(ylim[0],ylim[1],30) #在y的最小值和最大值之间取30个点
#我们将使用这里形成的二维数组作为我们contour函数中的X和Y
#使用meshgrid函数将两个一维向量转换为特征矩阵
#核心是将两个特征向量广播,以便获取y.shape * x.shape这么多个坐标点的横坐标和纵坐标
axisy,axisx = np.meshgrid(axisy,axisx)
xy = np.vstack([axisx.ravel(), axisy.ravel()]).T
#其中ravel()是降维函数,vstack能够将多个结构一致的一维数组按行堆叠起来
#xy就是已经形成的网格,它是遍布在整个画布上的密集的点
xy.shape
plt.scatter(xy[:,0],xy[:,1],s=1,cmap="rainbow")
image.png
5. 建模,计算决策边界并找出网格上每个点到决策边界的距离
重要接口decision_function,返回每个输入的样本所对应的到决策边界的距离
#建模,通过fit计算出对应的决策边界
clf = SVC(kernel = "linear").fit(X,y)
#重要接口decision_function,返回每个输入的样本所对应的到决策边界的距离
#然后再将这个距离转换为axisx的结构,这是由于画图的函数contour要求Z的结构必须与X和Y保持一致
Z = clf.decision_function(xy).reshape(axisx.shape) #生成的也是(30,30)
#画决策边界和平行于决策边界的超平面
#首先要有散点图
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
ax = plt.gca() #获取当前的子图,如果不存在,则创建新的子图
ax.contour(axisx,axisy,Z
,colors="k"
,levels=[-1,0,1] #画三条等高线,分别是Z为-1,Z为0和Z为1的三条线
,alpha=0.5
,linestyles=["--","-","--"]) #虚,实,虚
ax.set_xlim(xlim) #x取值就用xlim,就是x的最大和最小值
ax.set_ylim(ylim)
image.png
对Z的本质就是输入样本到决策边界的距离
#记得Z的本质么?是输入的样本到决策边界的距离,而contour函数中的level其实是输入了这个距离
#让我们用一个点来试试看
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plt.scatter(X[10,0],X[10,1],c="black",s=50,cmap="rainbow")
clf.decision_function(X[10].reshape(1,2)) #decision_function查看第10行的点的距离
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
ax = plt.gca()
ax.contour(axisx,axisy,Z
,colors="k"
,levels=[-3.33917354] #高度就是第10行数据到决策边界的高度
,alpha=0.5
,linestyles=["--"])
画出一个点的等高线
6 将绘图过程包装成函数
#将上述过程包装成函数:
def plot_svc_decision_function(model,ax=None):
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x = np.linspace(xlim[0],xlim[1],30)
y = np.linspace(ylim[0],ylim[1],30)
Y,X = np.meshgrid(y,x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
P = model.decision_function(xy).reshape(X.shape)
ax.contour(X, Y, P,colors="k",levels=[-1,0,1],alpha=0.5,linestyles=["--","-","--"]) #就是画出决策边界和超平面
ax.set_xlim(xlim)
ax.set_ylim(ylim)
#则整个绘图过程可以写作:
clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
image.png
7 探索建好的模型,接口探索
clf.predict(X)
#根据决策边界,对X中的样本进行分类,返回的结构为n_samples
clf.score(X,y) #因为这里没有分测试集恶化训练集,所以返回的结果是1
#返回给定测试数据和标签的平均准确度
clf.support_vectors_
#返回支持向量,返回的就是穿过超平面的三个点的坐标
clf.n_support_
#返回每个类中支持向量的个数
#第一类中有2个支持向量,第二类中有一个
image.png
8 推广到非线性情况,比如环形数据
from sklearn.datasets import make_circles #画环
X,y = make_circles(100, factor=0.1, noise=.1) #100个样本,画成两个环
X.shape
y.shape
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plt.show()
image.png
# 用定义好的函数画决策边界
clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
clf.score(X,y) #可见训练集的效果就很差了
#0.68
9. 为非线性数据增加维度并绘制3D图像
如果我们能够在原本的X和y的基础上,添加一个维度r,变成三维,我们可视这个数据,来看看添加维度让我们的数据如何变化。
#定义一个由x计算出来的新维度r
import numpy as np
r = np.exp(-(X**2).sum(1)) #e为底x的平方求和 就是高斯径向基核函数所对应的功能
r.shape
rlim = np.linspace(min(r),max(r),100) #rlim轴的取值
rlim.shape
from mpl_toolkits import mplot3d #画三维图像
#定义一个绘制三维图像的函数
#elev表示上下旋转的角度
#azim表示平行旋转的角度
def plot_3D(elev=30,azim=30,X=X,y=y):
ax = plt.subplot(projection="3d") #画基于3d的子图
ax.scatter3D(X[:,0],X[:,1],r,c=y,s=50,cmap='rainbow') #scatter3D画3d点图,r作为第三个轴
ax.view_init(elev=elev,azim=azim) #调整旋转角度
ax.set_xlabel("x") #轴的标题
ax.set_ylabel("y")
ax.set_zlabel("r")
plt.show()
plot_3D()
所以在三维空间中,超平面就是一个二维平面
如果核函数kernel改成rbf,会怎么样呢?
clf = SVC(kernel = "rbf").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
我们刚才做的,计算r,并将r作为数据的第三维度来将数据升维的过程,被称为“核变换”,
2.2 非线性SVM和核函数
重要参数kernel
参数“kernel"在sklearn中可选以下几种选项:
image.png
2.2.1 探索核函数在不同数据中的表现
1. 导入库和模块
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap #引入画图颜色
from sklearn.svm import SVC
from sklearn.datasets import make_circles, make_moons, make_blobs,make_classification #圆形,月亮,簇,对半分
2. 创建数据集,定义核函数的选择
n_samples = 100
#实例化四个图形
datasets = [
make_moons(n_samples=n_samples, noise=0.2, random_state=0),
make_circles(n_samples=n_samples, noise=0.2, factor=0.5, random_state=1),
make_blobs(n_samples=n_samples, centers=2, random_state=5), #两个簇
#分类需要指定特定个数,n_informative是两个特征都是带信息的,不带信息的是0个。
make_classification(n_samples=n_samples,n_features =2,n_informative=2,n_redundant=0, random_state=5)
]
datasets
Kernel = ["linear","poly","rbf","sigmoid"] #定义四个核函数
#四个数据集分别是什么样子呢?
for X,Y in datasets:
plt.figure(figsize=(5,4))
plt.scatter(X[:,0],X[:,1],c=Y,s=50,cmap="rainbow")
3. 构建子图,开始子图循环
[*enumerate]代码理解
[*enumerate(datasets)] #enumerate,map.zip [*enumerate]是打开惰性对象,并添加索引
#[(索引,array([特征矩阵X],[标签Y]))] 打开后是这样
#所以index,(X,Y) = [(索引,array([特征矩阵X],[标签Y]))] 就是循环在做的事
print(Kernel)
[*enumerate(Kernel)] #就是给kernel加了索引
image.png
# 3 构建子图
nrows=len(datasets)
ncols=len(Kernel) + 1
fig, axes = plt.subplots(nrows, ncols,figsize=(20,16)) #4行5列
#4 开始进行子图循环
#第一层循环:在不同的数据集中循环
for ds_cnt, (X,Y) in enumerate(datasets): #分别取出每个数据集对应的索引,特征和标签
#在图像中的第一列,放置原数据的分布
ax = axes[ds_cnt, 0] #将0123行的第一列都放入原始图片
if ds_cnt == 0:
ax.set_title("Input data") #只在[0,1]的上面加标题
##zorder=10,就是把散点图显示在上面,cmap=plt.cm.Paired是一种对比颜色,edgecolors是点的边缘的颜色 k黑色
ax.scatter(X[:, 0], X[:, 1], c=Y, zorder=10, cmap=plt.cm.Paired,edgecolors='k')
ax.set_xticks(()) #不写,空的元组和列表一样
ax.set_yticks(())
#第二层循环:在不同的核函数中循环
#从图像的第二列开始,一个个填充分类结果
for est_idx, kernel in enumerate(Kernel): #这里是循环kernel
#定义子图位置
ax = axes[ds_cnt, est_idx + 1]
#建模
clf = SVC(kernel=kernel, gamma=2).fit(X, Y) #实例化训练
score = clf.score(X, Y) #打分,看在训练集上的效果,没有测试数据,看究竟对数据能拟合到什么程度
#绘制图像本身分布的散点图
ax.scatter(X[:, 0], X[:, 1], c=Y
,zorder=10
,cmap=plt.cm.Paired,edgecolors='k')
#绘制支持向量 support_vectors_调出支持向量坐标点
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=50,
facecolors='none', zorder=10, edgecolors='k') #facecolors是将点显示为透明
#绘制决策边界
#先组成网格
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
#np.mgrid,合并了我们之前使用的np.linspace和np.meshgrid的用法
#一次性使用最大值和最小值来生成网格
#表示为[起始值:结束值:步长] j是复数的意思
#如果步长是复数,则其整数部分就是起始值和结束值之间创建的点的数量,并且结束值被包含在内
XX, YY = np.mgrid[x_min:x_max:200j, y_min:y_max:200j] #形成网格
#np.c_,类似于np.vstack的功能 np.c_将降维后的XX,YY按行结合在一起
#decision_function计算所有点到决策边界的距离,reshape(XX.shape)是为了Z的形状能够放到contour里面
Z = clf.decision_function(np.c_[XX.ravel(), YY.ravel()]).reshape(XX.shape)
#pcolormesh填充等高线不同区域的颜色,Z > 0填充两类颜色
ax.pcolormesh(XX, YY, Z > 0, cmap=plt.cm.Paired)
#绘制等高线
ax.contour(XX, YY, Z, colors=['k', 'k', 'k'], linestyles=['--', '-', '--'],
levels=[-1, 0, 1])
#设定坐标轴为不显示
ax.set_xticks(())
ax.set_yticks(())
#将标题放在第一行的顶上
if ds_cnt == 0:
ax.set_title(kernel)
#为每张图添加分类的分数
ax.text(0.95, 0.06, ('%.2f' % score).lstrip('0') #0.95,0.06分别是横纵坐标的位置,保留两位小数,不要写成0.9,而是.9这种
, size=15
#为分数添加一个白色的格子作为底色
, bbox=dict(boxstyle='round', alpha=0.8, facecolor='white')
, transform=ax.transAxes #确定文字所对应的坐标轴,就是ax子图的坐标轴本身
, horizontalalignment='right' #位于坐标轴的什么方向
)
plt.tight_layout()#图像之间的间隔紧
plt.show()
kernel在不同数据中的表现
rbf,高斯径向基核函数基本在任何数据集上都表现不错,属于比较万能的核函数。所以无论如何先试试看高斯径向基核函数,它适用于核转换到很高的空间的情况,在各种情况下往往效果都很不错。
2.2.2 核函数的优缺点
以乳腺癌数据为例,进行探索
1 导入库
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
2 数据探索
data = load_breast_cancer()
data#是字典
X = data.data
y = data.target
X.shape
np.unique(y) #去重,看下y里面有几个值
#用前两个特征先画一下,看下数据什么样子
plt.scatter(X[:,0],X[:,1],c=y)
plt.show()
可以看到,数据是混杂在一起的
2 用PCA降维试一下
from sklearn.decomposition import PCA
X_dr = PCA(2).fit_transform(X) #降到2维
X_dr.shape
plt.scatter(X_dr[:,0],X_dr[:,1],c=y)
plt.show()
可以看到,其实线性还是可以分的
3 运行四种核函数
从结果可以看到,乳腺癌数据集是一个线性数据集,线性核函数跑出来的效果很好,但是时间较长。
#那如果我们把degree参数调整为1,多项式核函数应该也可以得到不错的结果:
Kernel = ["linear","poly","rbf","sigmoid"]
for kernel in Kernel:
time0 = time()
clf= SVC(kernel = kernel
, gamma="auto"
, degree = 1
, 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"))
#多项式核函数的运行速度立刻加快了,并且精度也提升到了接近线性核函数的水平
输出结果
The accuracy under kernel linear is 0.929825
00:01:510632
The accuracy under kernel poly is 0.923977
00:00:303329
The accuracy under kernel rbf is 0.596491
00:00:138133
The accuracy under kernel sigmoid is 0.596491
00:00:022278
4 探索一下乳腺癌数据集的量纲
import pandas as pd
data = pd.DataFrame(X)
#describe观察数据的描述性分布,在%1,5,10,25到99
data.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.99]).T #转置
#可以看到,数据方差差异大,量纲不统一。
#数据存在偏态问题,就是说数据在1%和99%中数据的值差异很大
数据量纲存在问题
对数据进行标准化
#数据预处理中的标准化的类,对数据进行标准
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,这时的X已经是去量纲化后的
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"
, degree = 1
, 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"))。
数据结果
The accuracy under kernel linear is 0.976608
00:00:028113
The accuracy under kernel poly is 0.964912
00:00:012613
The accuracy under kernel rbf is 0.970760
00:00:023112
The accuracy under kernel sigmoid is 0.953216
00:00:012823
量纲统一之后,可以观察到,所有核函数的运算时间都大大地减少了。尤其是对于线性核来说,而多项式核函数居然变成了计算最快的。其次,rbf表现出了非常优秀的结果。
因此得到以下结论:
1. 线性核,尤其是多项式核函数在高次项时计算非常缓慢
2. rbf和多项式核函数都不擅长处理量纲不统一的数据集
3. SVM执行之前,非常推荐先进行数据的无量纲化
2.2.3 选取与核函数相关的参数
image.png对于高斯径向基核函数,调整gamma的方式就是画学习曲线
score = []
gamma_range = np.logspace(-10, 1, 50) #logspace返回在对数刻度上均匀间隔的数字
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))]) # gamma_range[score.index(max(score))]取出最大score索引对应的gamma_range
plt.plot(gamma_range,score)
plt.show()
#我们可以多次调整gamma_range来观察结果,可以发现97.6608应该是rbf核函数的极限了。
image.png
对于多项式核,进行网格搜索
#需要使用网格搜索来共同调整三个对多项式核函数有影响的参数
from sklearn.model_selection import StratifiedShuffleSplit #规定交叉验证的模式
from sklearn.model_selection import GridSearchCV #带交叉验证的网格搜索
time0 = time()
gamma_range = np.logspace(-10,1,20) #logspace返回在对数刻度上均匀间隔的数字
coef0_range = np.linspace(0,5,10) #不能为复数,用linsapce取10个数
param_grid = dict(gamma = gamma_range
,coef0 = coef0_range) #字典
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.3, random_state=420)#分5份,cv就是交叉验证的模式
#网格搜索的实例化
grid = GridSearchCV(SVC(kernel = "poly",degree=1,cache_size=5000),
param_grid=param_grid, #一一进行枚举匹配
cv=cv)
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"))
数据结果
The best parameters are {'coef0': 0.0, 'gamma': 0.18329807108324375} with a score of 0.96959
00:21:503182
2.3 硬间隔核软间隔,重要参数C
在实际应用中,会碰到不完全线性可分的数据集。
当两组数据是完全线性可分,我们可以找出一个决策边界使得训练集上的分类误差为0,这两种数据就被称为是存在”硬间隔“的。当两组数据几乎是完全线性可分的,但决策边界在训练集上存在较小的训练误差,这两种数据就被称为是存在”软间隔“。
对于软间隔地数据来说,边际越大被分错的样本也就会越多,因此我们需要找出一个”最大边际“与”被分错的样本数量“之间的平衡。
参数C:就是用于权衡”训练样本的正确分类“与”决策函数的边际最大化“两个不可同时完成的目标。默认情况下C为1,通常来说这都是一个合理的参数。
调线性核函数
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.show()
image.png
2. 换brf
score = []
C_range = np.linspace(0.01,30,50)
for i in C_range:
clf = SVC(kernel="rbf",C=i,gamma =
0.012742749857031322,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()
#可以看到 C越大,精确度越好
image.png
#进一步细化
score = []
C_range = np.linspace(5,7,50)
for i in C_range:
clf = SVC(kernel="rbf",C=i,gamma =
0.012742749857031322,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%的准确率
网友评论